package org.hibernate.auction.persistence;

import net.sf.hibernate.*;
import net.sf.hibernate.cfg.Configuration;
import org.apache.commons.logging.*;
import org.hibernate.auction.exceptions.InfrastructureException;

import javax.naming.*;

/**
 * Podstawowa klasa pomocnicza dla Hibernate, obsługuje klasy SessionFactory, Session i Transaction.
 * <p>
 * Używa statycznego inicjalizatora dla klasy SessionFactory i przechowuje obiekty
 * Session i Transactions w zmiennych lokalnowątkowych. Wszystkie wyjątki zostały oroczone
 * nieweryfikowalnym wyjątkiem InfrastructureException.
 *
 * @author christian@hibernate.org
 */
public class HibernateUtil {

	private static Log log = LogFactory.getLog(HibernateUtil.class);

	private static Configuration configuration;
	private static SessionFactory sessionFactory;
	private static final ThreadLocal threadSession = new ThreadLocal();
	private static final ThreadLocal threadTransaction = new ThreadLocal();
	private static final ThreadLocal threadInterceptor = new ThreadLocal();

	// Utwórz początkowy SessionFactory z domyślnych plików konfiguracyjnych.
	static {
		try {
			configuration = new Configuration();
			sessionFactory = configuration.configure().buildSessionFactory();
			// Możemy też dołączyć się do JNDI:
			// configuration.configure().buildSessionFactory()
		} catch (Throwable ex) {
			// Musimy wyłapywać Throwable, bo w przeciwnym razie nie złapalibyśmy
			// NoClassDefFoundError ai innych podklas Error.
			log.error("Nieudane utworzenie SessionFactory.", ex);
			throw new ExceptionInInitializerError(ex);
		}
	}

	/**
	 * Zwraca SessionFactory używaną w metodach statycznych.
	 *
	 * @return SessionFactory
	 */
	public static SessionFactory getSessionFactory() {
		/* Zamiast zmiennej statycznej użyj JNDI:
		SessionFactory sessions = null;
		try {
			Context ctx = new InitialContext();
			String jndiName = "java:hibernate/HibernateFactory";
			sessions = (SessionFactory)ctx.lookup(jndiName);
		} catch (NamingException ex) {
			throw new InfrastructureException(ex);
		}
		return sessions;
		*/
		return sessionFactory;
	}

	/**
	 * Zwraca oryginalną konfigurację Hibernate.
	 *
	 * @return Configuration
	 */
	public static Configuration getConfiguration() {
		return configuration;
	}

	/**
	 * Ponownie tworzy SessionFactory na podstawie statycznej Configuration.
	 *
	 */
	 public static void rebuildSessionFactory()
		throws InfrastructureException {
		synchronized(sessionFactory) {
			try {
				sessionFactory = getConfiguration().buildSessionFactory();
			} catch (Exception ex) {
				throw new InfrastructureException(ex);
			}
		}
	 }

	/**
	 * Ponownie tworzy SessionFactory na podstawie wskazanej konfiguracji.
	 *
	 * @param cfg
	 */
	 public static void rebuildSessionFactory(Configuration cfg)
		throws InfrastructureException {
		synchronized(sessionFactory) {
			try {
				sessionFactory = cfg.buildSessionFactory();
				configuration = cfg;
			} catch (Exception ex) {
				throw new InfrastructureException(ex);
			}
		}
	 }

	/**
	 * Pobiera sesję lokalnowątkową..
	 * <p/>
	 * Jeśli sesja nie istnieje, otwiera nową.
	 *
	 * @return Session
	 */
	public static Session getSession()
		throws InfrastructureException {
		Session s = (Session) threadSession.get();
		try {
			if (s == null) {
				log.debug("Potwieram nową sesję dla wątku.");
				if (getInterceptor() != null) {
					log.debug("Używam przechwytywania: " + getInterceptor().getClass());
					s = getSessionFactory().openSession(getInterceptor());
				} else {
					s = getSessionFactory().openSession();
				}
				threadSession.set(s);
			}
		} catch (HibernateException ex) {
			throw new InfrastructureException(ex);
		}
		return s;
	}

	/**
	 * Zamyka sesję lokalnowątkową.
	 */
	public static void closeSession()
		throws InfrastructureException {
		try {
			Session s = (Session) threadSession.get();
			threadSession.set(null);
			if (s != null && s.isOpen()) {
				log.debug("Zamyka sesję wątku.");
				s.close();
			}
		} catch (HibernateException ex) {
			throw new InfrastructureException(ex);
		}
	}

	/**
	 * Uruchamia transakcję bazodanową.
	 */
	public static void beginTransaction()
		throws InfrastructureException {
		Transaction tx = (Transaction) threadTransaction.get();
		try {
			if (tx == null) {
				log.debug("Uruchamia transakcję bazodanową dla wątku.");
				tx = getSession().beginTransaction();
				threadTransaction.set(tx);
			}
		} catch (HibernateException ex) {
			throw new InfrastructureException(ex);
		}
	}

	/**
	 * Zatwierdza transakcję bazodanową.
	 */
	public static void commitTransaction()
		throws InfrastructureException {
		Transaction tx = (Transaction) threadTransaction.get();
		try {
			if ( tx != null && !tx.wasCommitted()
							&& !tx.wasRolledBack() ) {
				log.debug("Zatwierdza transakcję bazodanową dla wątku.");
				tx.commit();
			}
			threadTransaction.set(null);
		} catch (HibernateException ex) {
			rollbackTransaction();
			throw new InfrastructureException(ex);
		}
	}

	/**
	 * Wycofuje transakcję bazodanową.
	 */
	public static void rollbackTransaction()
		throws InfrastructureException {
		Transaction tx = (Transaction) threadTransaction.get();
		try {
			threadTransaction.set(null);
			if ( tx != null && !tx.wasCommitted() && !tx.wasRolledBack() ) {
				log.debug("Próbuję wycofać transakcję bazodanową dla wątku.");
				tx.rollback();
			}
		} catch (HibernateException ex) {
			throw new InfrastructureException(ex);
		} finally {
			closeSession();
		}
	}

	/**
	 * Ponownie dołącza sesję do aktualnego wątku.
	 *
	 * @param session Sesja do podłączenia.
	 */
	public static void reconnect(Session session)
		throws InfrastructureException {
		try {
			session.reconnect();
			threadSession.set(session);
		} catch (HibernateException ex) {
			throw new InfrastructureException(ex);
		}
	}

	/**
	 * Odłącza i zwraca sesję aktualnego wątku.
	 *
	 * @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;
	}

	/**
	 * Rejestruje przechytywanie dla aktualnej sesji.
	 * <p>
	 * Każda nowa sesja zostaje otwarta z przechwytywaniem od momentu rejestracji.
	 * Nie ma wpływu na sesję otwartą przed rejestracją.
	 */
	public static void registerInterceptor(Interceptor interceptor) {
		threadInterceptor.set(interceptor);
	}

	private static Interceptor getInterceptor() {
		Interceptor interceptor =
			(Interceptor) threadInterceptor.get();
		return interceptor;
	}

}

