/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.hibernate.jmx;

import org.jboss.system.ServiceMBeanSupport;
import org.jboss.hibernate.cache.DeployedTreeCacheProvider;

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

import javax.management.Notification;

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;

/**
 * A Hibernate service.  Configures a {@link net.sf.hibernate.SessionFactory}
 * instance as an 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.11 $</tt>
 * @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a>
 * @author <a href="mailto:gavin@hibernate.org">Gavin King</a>
 * @author <a href="mailto:steve@hibernate.org">Steve Ebersole</a>
 * @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>
 */
public class Hibernate
      extends ServiceMBeanSupport
      implements HibernateMBean
{
   // Constants -----------------------------------------------------
   
   /** notification type produced when the session factory gets created */
   public static final String SESSION_FACTORY_CREATE = "hibernate.sessionfactory.create";
   
   /** notification type produced when the session factory gets destroyed */
   public static final String SESSION_FACTORY_DESTROY = "hibernate.sessionfactory.destroy";
   
   // Private -------------------------------------------------------
   
   private URL harUrl;

   private String datasourceName;
   private String dialect;
   private String defaultSchema;
   private String maxFetchDepth;
   private String jdbcBatchSize;
   private String batchVersionedDataEnabled;
   private String jdbcFetchSize;
   private String jdbcScrollableResultSetEnabled;
   private String getGeneratedKeysEnabled;
   private String streamsForBinaryEnabled;

   private String reflectionOptimizationEnabled;
   private String hbm2ddlAuto;
   private String querySubstitutions;
   private String showSqlEnabled;

   private String username;
   private String password;

   private String queryCacheEnabled;
   private String cacheProviderClass;
   private String deployedTreeCacheJndiName;
   private String minimalPutsEnabled;
   private String cacheRegionPrefix;

   private String sessionFactoryInterceptor;

   private boolean dirty = false;

   private String sessionFactoryName;
   private SessionFactory sessionFactory;

   /**
    * 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;
   }

   /**
    * @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;
   }

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

   /**
    * @jmx.managed-attribute
    */
   public void setHbm2ddlAuto(String hbm2ddlAuto)
   {
      this.hbm2ddlAuto = hbm2ddlAuto;
      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;
   }

   /**
    * 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 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;
   }

   /**
    * @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 getMinimalPutsEnabled()
   {
      return minimalPutsEnabled;
   }

   /**
    * @jmx.managed-attribute
    */
   public void setMinimalPutsEnabled(String minimalPutsEnabled)
   {
      this.minimalPutsEnabled = minimalPutsEnabled;
      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 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-attribute
    */
   public String getDeployedTreeCacheJndiName()
   {
      return deployedTreeCacheJndiName;
   }

   /**
    * @jmx.managed-attribute
    */
   public void setDeployedTreeCacheJndiName(String deployedTreeCacheJndiName)
   {
      this.deployedTreeCacheJndiName = deployedTreeCacheJndiName;
   }

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

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

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

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

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

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

   /**
    * @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 Notification(SESSION_FACTORY_CREATE, getServiceName(), getNextNotificationSequenceNumber())
      );      
   }

   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 Notification(SESSION_FACTORY_DESTROY, getServiceName(), getNextNotificationSequenceNumber())
         );           
      }
   }

   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, DeployedTreeCacheProvider.NAMESPACE_PROP_NAME, deployedTreeCacheJndiName);
      setUnlessNull(props, Environment.USE_MINIMAL_PUTS, minimalPutsEnabled);
      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());
      setUnlessNull(props, Environment.BATCH_VERSIONED_DATA, batchVersionedDataEnabled);
      setUnlessNull(props, Environment.USE_STREAMS_FOR_BINARY, streamsForBinaryEnabled);
      setUnlessNull(props, Environment.USE_REFLECTION_OPTIMIZER, reflectionOptimizationEnabled);

      return props;
   }

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