package org.infinispan.persistence.jdbc.connectionfactory;

import java.sql.Connection;
import java.sql.SQLException;

import org.infinispan.persistence.jdbc.JdbcUtil;
import org.infinispan.persistence.jdbc.configuration.ConnectionFactoryConfiguration;
import org.infinispan.persistence.jdbc.configuration.PooledConnectionFactoryConfiguration;
import org.infinispan.persistence.jdbc.logging.Log;
import org.infinispan.persistence.spi.PersistenceException;
import org.infinispan.util.logging.LogFactory;

/**
 * Pooled connection factory that uses HikariCP by default. In order to utilise the legacy connection pool, C3P0, users
 * must pass the system property <tt>infinispan.jdbc.c3p0.force</tt> with the value true.
 *
 * HikariCP property files can be specified by explicitly stating its path or name (if the file is on the classpath) via
 * PooledConnectionFactoryConfiguration.propertyFile field.  Or by ensuring that a <tt>hikari.properties</tt> file is
 * on the classpath. Note, that the file specified by <tt>propertyField</tt> takes precedence over <tt>hikari.properties</tt>.
 *
 * For a complete configuration reference for C3P0 look <a href="http://www.mchange.com/projects/c3p0/index.html#configuration">here</a>.
 * The connection pool can be configured n various ways, as described
 * <a href="http://www.mchange.com/projects/c3p0/index.html#configuration_files">here</a>. The simplest way is by having
 * an <tt>c3p0.properties</tt> file in the classpath.
 *
 * If no properties files are found for either HikariCP or C3PO then the default values of these connection pools are
 * utilised.
 *
 * @author Mircea.Markus@jboss.com
 * @author Tristan Tarrant
 * @author Ryan Emerson
 */
public class PooledConnectionFactory extends ConnectionFactory {

   private static final Log log = LogFactory.getLog(PooledConnectionFactory.class, Log.class);
   private static boolean trace = log.isTraceEnabled();

   private ConnectionPool connectionPool;

   @Override
   public void start(ConnectionFactoryConfiguration config, ClassLoader classLoader) throws PersistenceException {
      PooledConnectionFactoryConfiguration poolConfig;
      if (config instanceof PooledConnectionFactoryConfiguration) {
         poolConfig = (PooledConnectionFactoryConfiguration) config;
      } else {
         throw new PersistenceException("ConnectionFactoryConfiguration passed in must be an instance of " +
                                              "PooledConnectionFactoryConfiguration");
      }

      connectionPool = C3P0ConnectionPool.forceC3P0() ? new C3P0ConnectionPool(classLoader, poolConfig) :
            new HikariConnectionPool(classLoader, poolConfig);
      if (trace) log.tracef("Started connection factory with config: %s", config);
   }

   @Override
   public void stop() {
      if (connectionPool != null) {
         connectionPool.close();
         if (trace) log.debug("Successfully stopped PooledConnectionFactory.");
      }
   }

   @Override
   public Connection getConnection() throws PersistenceException {
      try {
         logBefore(true);
         Connection connection = connectionPool.getConnection();
         logAfter(connection, true);
         return connection;
      } catch (SQLException e) {
         throw new PersistenceException("Failed obtaining connection from PooledDataSource", e);
      }
   }

   @Override
   public void releaseConnection(Connection conn) {
      logBefore(false);
      JdbcUtil.safeClose(conn);
      logAfter(conn, false);
   }

   public int getMaxPoolSize() {
      return connectionPool.getMaxPoolSize();
   }

   public int getNumConnectionsAllUsers() throws SQLException {
      return connectionPool.getNumConnectionsAllUsers();
   }

   public int getNumBusyConnectionsAllUsers() throws SQLException {
      return connectionPool.getNumBusyConnectionsAllUsers();
   }

   private void logBefore(boolean checkout) {
      log(null, checkout, true);
   }

   private void logAfter(Connection connection, boolean checkout) {
      log(connection, checkout, false);
   }

   private void log(Connection connection, boolean checkout, boolean before)  {
      if (trace) {
         String stage = before ? "before" : "after";
         String operation = checkout ? "checkout" : "release";
         try {
            log.tracef("DataSource %s %s (NumBusyConnectionsAllUsers) : %d, (NumConnectionsAllUsers) : %d",
                       stage, operation, getNumBusyConnectionsAllUsers(), getNumConnectionsAllUsers());
         } catch (SQLException e) {
            log.sqlFailureUnexpected(e);
         }

         if (connection != null)
            log.tracef("Connection %s : %s", operation, connection);
      }
   }
}
