/*
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.hibernate.jmx;

import org.jboss.system.ServiceMBeanSupport;

import java.net.URL;
import java.io.File;
import java.util.Properties;

import net.sf.hibernate.cfg.Environment;
import net.sf.hibernate.cfg.Configuration;
import net.sf.hibernate.transaction.JBossTransactionManagerLookup;
import net.sf.hibernate.transaction.JTATransactionFactory;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.Interceptor;

import javax.management.NotificationEmitter;


/**
 * A Hibernate service.  Configures a {@link net.sf.hibernate.SessionFactory}
 * instance as a MBean and exposes it through JNDI.  Meant to work in
 * conjuction with the {@link org.jboss.hibernate.har.HARDeployer}.
 *
 * @jmx:mbean
 *    name="jboss.har:service=Hibernate"
 *    extends="org.jboss.system.ServiceMBean"
 *
 * @version <tt>$Revision: 1.1.2.7 $</tt>
 * @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a>
 * @author <a href="mailto:gavin@hibernate.org">Gavin King</a>
 */
public class Hibernate
   extends ServiceMBeanSupport
   implements HibernateMBean, NotificationEmitter
{
   private URL harUrl;
   private String datasourceName;
   private String defaultSchema;
   private String dialect;
   private String hbm2ddlAuto;
   private String maxFetchDepth;
   private String sessionFactoryName;
   private String jdbcBatchSize;
   private String jdbcFetchSize;
   private String jdbcScrollableResultSetEnabled;
   private String querySubstitutions;
   private String queryCacheEnabled;
   private String cacheProviderClass;
   private String cacheRegionPrefix;
   private String showSqlEnabled;
   private String username;
   private String password;
   private String getGeneratedKeysEnabled;
   private String sessionFactoryInterceptor;

   private boolean dirty = false;
   private SessionFactory sessionFactory;

   /**
    * @jmx.managed-attribute
    */
   public URL getHarUrl()
   {
      return harUrl;
   }

   /**
    * @jmx.managed-attribute
    */
   public void setHarUrl(URL harUrl)
   {
      this.harUrl = harUrl;
      dirty = true;
   }

   /**
    * Get the JNDI datasource name.
    * @jmx.managed-attribute
    */
   public String getDatasourceName()
   {
      return datasourceName;
   }

   /**
    * Set the JNDI datasource name.
    * @jmx.managed-attribute
    */
   public void setDatasourceName(String datasourceName)
   {
      this.datasourceName = datasourceName;
      dirty = true;
   }

   /**
    * Get the default database schema.
    * @jmx.managed-attribute
    */
   public String getDefaultSchema()
   {
      return defaultSchema;
   }

   /**
    * Set the default database schema.
    * @jmx.managed-attribute
    */
   public void setDefaultSchema(String defaultSchema)
   {
      this.defaultSchema = defaultSchema;
      dirty = true;
   }

   /**
    * Get the Hibernate SQL dialect.
    * @jmx.managed-attribute
    */
   public String getDialect()
   {
      return dialect;
   }

   /**
    * Set the Hibernate SQL dialect.
    * @jmx.managed-attribute
    */
   public void setDialect(String dialect)
   {
      this.dialect = dialect;
      dirty = true;
   }

   /**
    * @jmx.managed-attribute
    */
   public String getHbm2ddlAuto()
   {
      return hbm2ddlAuto;
   }

   /**
    * @jmx.managed-attribute
    */
   public void setHbm2ddlAuto(String hbm2ddlAuto)
   {
      this.hbm2ddlAuto = hbm2ddlAuto;
      dirty = true;
   }

   /**
    * Get the maximum outer join fetch depth.
    * @jmx.managed-attribute
    */
   public String getMaxFetchDepth()
   {
      return maxFetchDepth;
   }

   /**
    * Set the maximum outer join fetch depth.
    * @jmx.managed-attribute
    */
   public void setMaxFetchDepth(String maxFetchDepth)
   {
      this.maxFetchDepth = maxFetchDepth;
      dirty = true;
   }

   /**
    * Get the <tt>SessionFactory</tt> JNDI name.
    * @jmx.managed-attribute
    */
   public String getSessionFactoryName()
   {
      return sessionFactoryName;
   }

   /**
    * Set the <tt>SessionFactory</tt> JNDI name.
    * @jmx.managed-attribute
    */
   public void setSessionFactoryName(String sessionFactoryName)
   {
      this.sessionFactoryName = sessionFactoryName;
      dirty = true;
   }

   /**
    * Get the JDBC batch update batch size.
    * @jmx.managed-attribute
    */
   public String getJdbcBatchSize()
   {
      return jdbcBatchSize;
   }

   /**
    * Set the JDBC batch update batch size.
    * @jmx.managed-attribute
    */
   public void setJdbcBatchSize(String jdbcBatchSize)
   {
      this.jdbcBatchSize = jdbcBatchSize;
      dirty = true;
   }

   /**
    * Get the JDBC fetch size.
    * @jmx.managed-attribute
    */
   public String getJdbcFetchSize()
   {
      return jdbcFetchSize;
   }

   /**
    * Set the JDBC fetch size.
    * @jmx.managed-attribute
    */
   public void setJdbcFetchSize(String jdbcFetchSize)
   {
      this.jdbcFetchSize = jdbcFetchSize;
      dirty = true;
   }

   /**
    * Are scrollable result sets enabled?
    * @jmx.managed-attribute
    */
   public String getJdbcScrollableResultSetEnabled()
   {
      return jdbcScrollableResultSetEnabled;
   }

   /**
    * @jmx.managed-attribute
    */
   public void setJdbcScrollableResultSetEnabled(String jdbcScrollableResultSetEnabled)
   {
      this.jdbcScrollableResultSetEnabled = jdbcScrollableResultSetEnabled;
      dirty = true;
   }

   /**
    * Is the use of JDBC3 <tt>getGeneratedKeys()</tt> enabled?
    * @jmx.managed-attribute
    */
   public String getGetGeneratedKeysEnabled()
   {
   	  return getGeneratedKeysEnabled;
   }

   /**
    * @jmx.managed-attribute
    */
   public void setGetGeneratedKeysEnabled(String getGeneratedKeysEnabled)
   {
      this.getGeneratedKeysEnabled = getGeneratedKeysEnabled;
      dirty = true;
   }

   /**
    * Get the query substitutions.
    * @jmx.managed-attribute
    */
   public String getQuerySubstitutions()
   {
      return querySubstitutions;
   }

   /**
    * Set the query substitutions.
    * @jmx.managed-attribute
    */
   public void setQuerySubstitutions(String querySubstitutions)
   {
      this.querySubstitutions = querySubstitutions;
      dirty = true;
   }

   /**
    * Is the query cache enabled?
    * @jmx.managed-attribute
    */
   public String getQueryCacheEnabled()
   {
      return queryCacheEnabled;
   }

   /**
    * @jmx.managed-attribute
    */
   public void setQueryCacheEnabled(String queryCacheEnabled)
   {
      this.queryCacheEnabled = queryCacheEnabled;
      dirty = true;
   }

   /**
    * Get the cache provider class.
    * @jmx.managed-attribute
    */
   public String getCacheProviderClass()
   {
      return cacheProviderClass;
   }

   /**
    * Set the cache provider class.
    * @jmx.managed-attribute
    */
   public void setCacheProviderClass(String cacheProviderClass)
   {
      this.cacheProviderClass = cacheProviderClass;
      dirty = true;
   }

   /**
    * Is SQL being logged to the console?
    * @jmx.managed-attribute
    */
   public String getShowSqlEnabled()
   {
      return showSqlEnabled;
   }

   /**
    * @jmx.managed-attribute
    */
   public void setShowSqlEnabled(String showSqlEnabled)
   {
      this.showSqlEnabled = showSqlEnabled;
      dirty = true;
   }

   /**
    * Get the database username.
    * @jmx.managed-attribute
    */
   public String getUsername()
   {
      return username;
   }

   /**
    * Set the database username.
    * @jmx.managed-attribute
    */
   public void setUsername(String username)
   {
      this.username = username;
      dirty = true;
   }

   /**
    * Get the database password.
    * @jmx.managed-attribute
    */
   public String getPassword()
   {
      return password;
   }

   /**
    * Set the database password.
    * @jmx.managed-attribute
    */
   public void setPassword(String password)
   {
      this.password = password;
      dirty = true;
   }

   /**
    * @jmx.managed-attribute
    */
   public String getCacheRegionPrefix()
   {
      return cacheRegionPrefix;
   }

   /**
    * @jmx.managed-attribute
    */
   public void setCacheRegionPrefix(String cacheRegionPrefix)
   {
      this.cacheRegionPrefix = cacheRegionPrefix;
      dirty = true;
   }

   /**
    * @jmx.managed-attribute
    */
   public String getSessionFactoryInterceptor()
   {
      return sessionFactoryInterceptor;
   }

   /**
    * @jmx.managed-attribute
    */
   public void setSessionFactoryInterceptor(String sessionFactoryInterceptor)
   {
      this.sessionFactoryInterceptor = sessionFactoryInterceptor;
      dirty = true;
   }

   /**
    * @jmx.managed-attribute
    */
   public boolean isDirty()
   {
      return dirty;
   }

   /**
    * @jmx.managed-attribute
    */
   public boolean isSessionFactoryRunning()
   {
      return sessionFactory != null;
   }

   /**
    * @jmx.managed-operation
    */
   public void rebuildSessionFactory() throws Exception
   {
      destroySessionFactory();
      buildSessionFactory();
   }

   /**
    * Configure Hibernate and bind the <tt>SessionFactory</tt> to JNDI.
    */
   public void startService() throws Exception
   {
      buildSessionFactory();
   }

   /**
    * Close the <tt>SessionFactory</tt>.
    */
   public void stopService() throws Exception
   {
      destroySessionFactory();
   }

   private void buildSessionFactory() throws Exception
   {
      Configuration cfg = new Configuration();
      cfg.addProperties( getProperties() );

      final File file = new File(harUrl.getFile());
      if(file.isDirectory())
      {
         cfg.addDirectory(file);
      }
      else
      {
         cfg.addJar(file);
      }

      Interceptor interceptorInstance = generateInterceptorInstance();
      if (interceptorInstance != null)
      {
         cfg.setInterceptor(interceptorInstance);
      }

      sessionFactory = cfg.buildSessionFactory();
      dirty = false;

      sendNotification( new SessionFactoryBuiltEvent( getServiceName() ) );
   }

   private Interceptor generateInterceptorInstance()
   {
      if (sessionFactoryInterceptor == null)
      {
         return null;
      }

      log.info("Generating session factory interceptor instance [" + sessionFactoryInterceptor + "]");
      try
      {
         return (Interceptor) Class.forName(sessionFactoryInterceptor).newInstance();
      }
      catch(Throwable t)
      {
         log.warn("Unable to generate session factory interceptor instance", t);
      }

      return null;
   }

   private void destroySessionFactory() throws Exception
   {
      if (sessionFactory != null)
      {
         sessionFactory.close();
         sessionFactory = null;
         sendNotification( new SessionFactoryDestroyedEvent( getServiceName() ) );
      }
   }

   private Properties getProperties()
   {
      Properties props = new Properties();

      setUnlessNull(props, Environment.DATASOURCE, datasourceName);
      setUnlessNull(props, Environment.DIALECT, dialect);
      setUnlessNull(props, Environment.SESSION_FACTORY_NAME, sessionFactoryName);
      setUnlessNull(props, Environment.CACHE_PROVIDER, cacheProviderClass);
      setUnlessNull(props, Environment.CACHE_REGION_PREFIX, cacheRegionPrefix);
      setUnlessNull(props, Environment.HBM2DDL_AUTO, hbm2ddlAuto);
      setUnlessNull(props, Environment.DEFAULT_SCHEMA, defaultSchema);
      setUnlessNull(props, Environment.STATEMENT_BATCH_SIZE, jdbcBatchSize);
      setUnlessNull(props, Environment.STATEMENT_FETCH_SIZE, jdbcFetchSize);
      setUnlessNull(props, Environment.USE_SCROLLABLE_RESULTSET, jdbcScrollableResultSetEnabled);
      setUnlessNull(props, Environment.USE_QUERY_CACHE, queryCacheEnabled);
      setUnlessNull(props, Environment.QUERY_SUBSTITUTIONS, querySubstitutions);
      setUnlessNull(props, Environment.MAX_FETCH_DEPTH, maxFetchDepth);
      setUnlessNull(props, Environment.SHOW_SQL, showSqlEnabled);
      setUnlessNull(props, Environment.USE_GET_GENERATED_KEYS, getGeneratedKeysEnabled);
      setUnlessNull(props, Environment.USER, username);
      setUnlessNull(props, Environment.PASS, password);
      setUnlessNull(props, Environment.TRANSACTION_MANAGER_STRATEGY, JBossTransactionManagerLookup.class.getName());
      setUnlessNull(props, Environment.TRANSACTION_STRATEGY, JTATransactionFactory.class.getName());

      return props;
   }

   private void setUnlessNull(Properties props, String key, String value)
   {
      if(value != null)
      {
         props.setProperty(key, value);
      }
   }
}
