All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.zaxxer.hikari.AbstractHikariConfig Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2013, 2014 Brett Wooldridge
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.zaxxer.hikari;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.health.HealthCheckRegistry;
import com.zaxxer.hikari.util.PropertyBeanSetter;
import com.zaxxer.hikari.util.UtilityElf;

public abstract class AbstractHikariConfig implements HikariConfigMXBean
{
   private static final Logger LOGGER = LoggerFactory.getLogger(HikariConfig.class);

   private static final long CONNECTION_TIMEOUT = TimeUnit.SECONDS.toMillis(30);
   private static final long VALIDATION_TIMEOUT = TimeUnit.SECONDS.toMillis(5);
   private static final long IDLE_TIMEOUT = TimeUnit.MINUTES.toMillis(10);
   private static final long MAX_LIFETIME = TimeUnit.MINUTES.toMillis(30);

   private static int poolNumber;
   private static boolean unitTest;

   // Properties changeable at runtime through the MBean
   //
   private volatile long connectionTimeout;
   private volatile long validationTimeout;
   private volatile long idleTimeout;
   private volatile long leakDetectionThreshold;
   private volatile long maxLifetime;
   private volatile int maxPoolSize;
   private volatile int minIdle;

   // Properties NOT changeable at runtime
   //
   private String catalog;
   private String connectionCustomizerClassName;
   private String connectionInitSql;
   private String connectionTestQuery;
   private String dataSourceClassName;
   private String dataSourceJndiName;
   private String driverClassName;
   private String jdbcUrl;
   private String password;
   private String poolName;
   private String transactionIsolationName;
   private String username;
   private boolean isAutoCommit;
   private boolean isReadOnly;
   private boolean isInitializationFailFast;
   private boolean isIsolateInternalQueries;
   private boolean isRegisterMbeans;
   private boolean isAllowPoolSuspension;
   private DataSource dataSource;
   private Properties dataSourceProperties;
   private IConnectionCustomizer customizer;
   private ThreadFactory threadFactory;
   private Object metricRegistry;
   private Object healthCheckRegistry;
   private Properties healthCheckProperties; 

   /**
    * Default constructor
    */
   public AbstractHikariConfig()
   {
      dataSourceProperties = new Properties();
      healthCheckProperties = new Properties();

      connectionTimeout = CONNECTION_TIMEOUT;
      validationTimeout = VALIDATION_TIMEOUT;
      idleTimeout = IDLE_TIMEOUT;
      isAutoCommit = true;
      isInitializationFailFast = true;
      minIdle = -1;
      maxPoolSize = 10;
      maxLifetime = MAX_LIFETIME;
      customizer = new IConnectionCustomizer() {
         @Override
         public void customize(Connection connection) throws SQLException
         {
         }
      };

      String systemProp = System.getProperty("hikaricp.configurationFile");
      if ( systemProp != null) {
         loadProperties(systemProp);
      }
   }

   /**
    * Construct a HikariConfig from the specified properties object.
    *
    * @param properties the name of the property file
    */
   public AbstractHikariConfig(Properties properties)
   {
      this();
      PropertyBeanSetter.setTargetFromProperties(this, properties);
   }

   /**
    * Construct a HikariConfig from the specified property file name.  propertyFileName
    * will first be treated as a path in the file-system, and if that fails the 
    * ClassLoader.getResourceAsStream(propertyFileName) will be tried.
    *
    * @param propertyFileName the name of the property file
    */
   public AbstractHikariConfig(String propertyFileName)
   {
      this();

      loadProperties(propertyFileName);
   }

   /**
    * Get the default catalog name to be set on connections.
    *
    * @return the default catalog name
    */
   public String getCatalog()
   {
      return catalog;
   }

   /**
    * Set the default catalog name to be set on connections.
    *
    * @param catalog the catalog name, or null
    */
   public void setCatalog(String catalog)
   {
      this.catalog = catalog;
   }

   /**
    * Get the name of the connection customizer class to instantiate and execute
    * on all new connections.
    *
    * @return the name of the customizer class, or null
    */
   @Deprecated
   public String getConnectionCustomizerClassName()
   {
      return connectionCustomizerClassName;
   }

   /**
    * Set the name of the connection customizer class to instantiate and execute
    * on all new connections.
    *
    * @param connectionCustomizerClassName the name of the customizer class
    */
   @Deprecated
   public void setConnectionCustomizerClassName(String connectionCustomizerClassName)
   {
      this.connectionCustomizerClassName = connectionCustomizerClassName;
      LOGGER.warn("The connectionCustomizerClassName property has been deprecated and may be removed in a future release");
   }

   /**
    * Get the customizer instance specified by the user.
    *
    * @return an instance of IConnectionCustomizer
    */
   @Deprecated
   public IConnectionCustomizer getConnectionCustomizer()
   {
      return customizer;
   }

   /**
    * Set the connection customizer to be used by the pool.
    *
    * @param customizer an instance of IConnectionCustomizer
    */
   @Deprecated
   public void setConnectionCustomizer(IConnectionCustomizer customizer)
   {
      this.customizer = customizer;
      LOGGER.warn("The connectionCustomizer property has been deprecated and may be removed in a future release");
   }

   /**
    * Get the SQL query to be executed to test the validity of connections.
    * 
    * @return the SQL query string, or null 
    */
   public String getConnectionTestQuery()
   {
      return connectionTestQuery;
   }

   /**
    * Set the SQL query to be executed to test the validity of connections. Using
    * the JDBC4 Connection.isValid() method to test connection validity can
    * be more efficient on some databases and is recommended.  See 
    * {@link HikariConfig#setJdbc4ConnectionTest(boolean)}.
    *
    * @param connectionTestQuery a SQL query string
    */
   public void setConnectionTestQuery(String connectionTestQuery)
   {
      this.connectionTestQuery = connectionTestQuery;
   }

   /**
    * Get the SQL string that will be executed on all new connections when they are
    * created, before they are added to the pool.
    *
    * @return the SQL to execute on new connections, or null
    */
   public String getConnectionInitSql()
   {
      return connectionInitSql;
   }

   /**
    * Set the SQL string that will be executed on all new connections when they are
    * created, before they are added to the pool.  If this query fails, it will be
    * treated as a failed connection attempt.
    *
    * @param connectionInitSql the SQL to execute on new connections
    */
   public void setConnectionInitSql(String connectionInitSql)
   {
      this.connectionInitSql = connectionInitSql;
   }

   /** {@inheritDoc} */
   @Override
   public long getConnectionTimeout()
   {
      return connectionTimeout;
   }

   /** {@inheritDoc} */
   @Override
   public void setConnectionTimeout(long connectionTimeoutMs)
   {
      if (connectionTimeoutMs == 0) {
         this.connectionTimeout = Integer.MAX_VALUE;
      }
      else if (connectionTimeoutMs < 1000) {
         throw new IllegalArgumentException("connectionTimeout cannot be less than 1000ms");
      }
      else {
         this.connectionTimeout = connectionTimeoutMs;
      }
   }

   /** {@inheritDoc} */
   @Override
   public long getValidationTimeout()
   {
      return validationTimeout;
   }

   /** {@inheritDoc} */
   @Override
   public void setValidationTimeout(long validationTimeoutMs)
   {
      if (validationTimeoutMs < 1000) {
         throw new IllegalArgumentException("validationTimeout cannot be less than 1000ms");
      }
      else {
         this.validationTimeout = validationTimeoutMs;
      }
   }

   /**
    * Get the {@link DataSource} that has been explicitly specified to be wrapped by the
    * pool.
    *
    * @return the {@link DataSource} instance, or null
    */
   public DataSource getDataSource()
   {
      return dataSource;
   }

   /**
    * Set a {@link DataSource} for the pool to explicitly wrap.  This setter is not
    * available through property file based initialization.
    *
    * @param dataSource a specific {@link DataSource} to be wrapped by the pool
    */
   public void setDataSource(DataSource dataSource)
   {
      this.dataSource = dataSource;
   }

   public String getDataSourceClassName()
   {
      return dataSourceClassName;
   }

   public void setDataSourceClassName(String className)
   {
      this.dataSourceClassName = className;
   }

   public void addDataSourceProperty(String propertyName, Object value)
   {
      dataSourceProperties.put(propertyName, value);
   }

   public String getDataSourceJNDI()
   {
      return this.dataSourceJndiName;
   }

   public void setDataSourceJNDI(String jndiDataSource)
   {
      this.dataSourceJndiName = jndiDataSource;
   }

   public Properties getDataSourceProperties()
   {
      return dataSourceProperties;
   }

   public void setDataSourceProperties(Properties dsProperties)
   {
      dataSourceProperties.putAll(dsProperties);
   }

   public String getDriverClassName()
   {
      return driverClassName;
   }

   public void setDriverClassName(String driverClassName)
   {
      try {
         Class driverClass = this.getClass().getClassLoader().loadClass(driverClassName);
         driverClass.newInstance();
         this.driverClassName = driverClassName;
      }
      catch (Exception e) {
         throw new RuntimeException("driverClassName specified class '" + driverClassName + "' could not be loaded", e);
      }
   }

   /** {@inheritDoc} */
   @Override
   public long getIdleTimeout()
   {
      return idleTimeout;
   }

   /** {@inheritDoc} */
   @Override
   public void setIdleTimeout(long idleTimeoutMs)
   {
      if (idleTimeoutMs < 0) {
         throw new IllegalArgumentException("idleTimeout cannot be negative");
      }
      this.idleTimeout = idleTimeoutMs;
   }

   public String getJdbcUrl()
   {
      return jdbcUrl;
   }

   public void setJdbcUrl(String jdbcUrl)
   {
      this.jdbcUrl = jdbcUrl;
   }

   /**
    * Get the default auto-commit behavior of connections in the pool.
    *
    * @return the default auto-commit behavior of connections
    */
   public boolean isAutoCommit()
   {
      return isAutoCommit;
   }

   /**
    * Set the default auto-commit behavior of connections in the pool.
    *
    * @param isAutoCommit the desired auto-commit default for connections
    */
   public void setAutoCommit(boolean isAutoCommit)
   {
      this.isAutoCommit = isAutoCommit;
   }

   /**
    * Get the pool suspension behavior (allowed or disallowed).
    *
    * @return the pool suspension behavior
    */
   public boolean isAllowPoolSuspension()
   {
      return isAllowPoolSuspension;
   }

   /**
    * Set whether or not pool suspension is allowed.  There is a performance
    * impact when pool suspension is enabled.  Unless you need it (for a
    * redundancy system for example) do not enable it.
    *
    * @param isAllowPoolSuspension the desired pool suspension allowance
    */
   public void setAllowPoolSuspension(boolean isAllowPoolSuspension)
   {
      this.isAllowPoolSuspension = isAllowPoolSuspension;
   }

   /**
    * Get whether or not the construction of the pool should throw an exception
    * if the minimum number of connections cannot be created.
    *
    * @return whether or not initialization should fail on error immediately
    */
   public boolean isInitializationFailFast()
   {
      return isInitializationFailFast;
   }

   /**
    * Set whether or not the construction of the pool should throw an exception
    * if the minimum number of connections cannot be created.
    *
    * @param failFast true if the pool should fail if the minimum connections cannot be created
    */
   public void setInitializationFailFast(boolean failFast)
   {
      isInitializationFailFast = failFast;
   }

   public boolean isIsolateInternalQueries()
   {
      return isIsolateInternalQueries;
   }

   public void setIsolateInternalQueries(boolean isolate)
   {
      this.isIsolateInternalQueries = isolate;
   }

   @Deprecated
   public boolean isJdbc4ConnectionTest()
   {
      return false;
   }

   @Deprecated
   public void setJdbc4ConnectionTest(boolean useIsValid)
   {
      // ignored deprecated property
      LOGGER.warn("The jdbcConnectionTest property is now deprecated, see the documentation for connectionTestQuery");
   }

   /**
    * Get the Codahale MetricRegistry, could be null.
    *
    * @return the codahale MetricRegistry instance
    */
   public Object getMetricRegistry()
   {
      return metricRegistry;
   }

   /**
    * Set a Codahale MetricRegistry to use for HikariCP.
    *
    * @param metricRegistry the Codahale MetricRegistry to set
    */
   public void setMetricRegistry(Object metricRegistry)
   {
      if (metricRegistry != null) {
         if (metricRegistry instanceof String) {
            try {
               InitialContext initCtx = new InitialContext();
               metricRegistry = (MetricRegistry) initCtx.lookup((String) metricRegistry);
            }
            catch (NamingException e) {
               throw new IllegalArgumentException(e);
            }
         }

         if (!(metricRegistry instanceof MetricRegistry)) {
            throw new IllegalArgumentException("Class must be an instance of com.codahale.metrics.MetricRegistry");            
         }
      }

      this.metricRegistry = metricRegistry;
   }

   /**
    * Get the Codahale HealthCheckRegistry, could be null.
    *
    * @return the Codahale HealthCheckRegistry instance
    */
   public Object getHealthCheckRegistry()
   {
      return healthCheckRegistry;
   }

   /**
    * Set a Codahale HealthCheckRegistry to use for HikariCP.
    *
    * @param healthCheckRegistry the Codahale HealthCheckRegistry to set
    */
   public void setHealthCheckRegistry(Object healthCheckRegistry)
   {
      if (healthCheckRegistry != null) {
         if (healthCheckRegistry instanceof String) {
            try {
               InitialContext initCtx = new InitialContext();
               healthCheckRegistry = (HealthCheckRegistry) initCtx.lookup((String) healthCheckRegistry);
            }
            catch (NamingException e) {
               throw new IllegalArgumentException(e);
            }
         }

         if (!(healthCheckRegistry instanceof HealthCheckRegistry)) {
            throw new IllegalArgumentException("Class must be an instance of com.codahale.metrics.health.HealthCheckRegistry");
         }
      }

      this.healthCheckRegistry = healthCheckRegistry;
   }

   public Properties getHealthCheckProperties()
   {
      return healthCheckProperties;
   }

   public void setHealthCheckProperties(Properties healthCheckProperties)
   {
      this.healthCheckProperties.putAll(healthCheckProperties);
   }

   public void addHealthCheckProperty(String key, String value)
   {
      healthCheckProperties.setProperty(key, value);
   }

   public boolean isReadOnly()
   {
      return isReadOnly;
   }

   public void setReadOnly(boolean readOnly)
   {
      this.isReadOnly = readOnly;
   }

   public boolean isRegisterMbeans()
   {
      return isRegisterMbeans;
   }

   public void setRegisterMbeans(boolean register)
   {
      this.isRegisterMbeans = register;
   }

   /** {@inheritDoc} */
   @Override
   public long getLeakDetectionThreshold()
   {
      return leakDetectionThreshold;
   }

   /** {@inheritDoc} */
   @Override
   public void setLeakDetectionThreshold(long leakDetectionThresholdMs)
   {
      this.leakDetectionThreshold = leakDetectionThresholdMs;
   }

   /** {@inheritDoc} */
   @Override
   public long getMaxLifetime()
   {
      return maxLifetime;
   }

   /** {@inheritDoc} */
   @Override
   public void setMaxLifetime(long maxLifetimeMs)
   {
      this.maxLifetime = maxLifetimeMs;
   }

   /** {@inheritDoc} */
   @Override
   public int getMaximumPoolSize()
   {
      return maxPoolSize;
   }

   /** {@inheritDoc} */
   @Override
   public void setMaximumPoolSize(int maxPoolSize)
   {
      if (maxPoolSize < 1) {
         throw new IllegalArgumentException("maxPoolSize cannot be less than 1");
      }
      this.maxPoolSize = maxPoolSize;
   }

   /** {@inheritDoc} */
   @Override
   public int getMinimumIdle()
   {
      return minIdle;
   }

   /** {@inheritDoc} */
   @Override
   public void setMinimumIdle(int minIdle)
   {
      if (minIdle < 0) {
         throw new IllegalArgumentException("minimumIdle cannot be negative");
      }
      this.minIdle = minIdle;
   }

   /**
    * Get the default password to use for DataSource.getConnection(username, password) calls.
    * @return the password
    */
   public String getPassword()
   {
      return password;
   }

   /**
    * Set the default password to use for DataSource.getConnection(username, password) calls.
    * @param password the password
    */
   public void setPassword(String password)
   {
      this.password = password;
   }

   /** {@inheritDoc} */
   @Override
   public String getPoolName()
   {
      return poolName;
   }

   /**
    * Set the name of the connection pool.  This is primarily used for the MBean
    * to uniquely identify the pool configuration.
    *
    * @param poolName the name of the connection pool to use
    */
   public void setPoolName(String poolName)
   {
      this.poolName = poolName;
   }

   public String getTransactionIsolation()
   {
      return transactionIsolationName;
   }

   /**
    * Set the default transaction isolation level.  The specified value is the
    * constant name from the Connection class, eg. 
    * TRANSACTION_REPEATABLE_READ.
    *
    * @param isolationLevel the name of the isolation level
    */
   public void setTransactionIsolation(String isolationLevel)
   {
      this.transactionIsolationName = isolationLevel;
   }

   /**
    * Get the default username used for DataSource.getConnection(username, password) calls.
    *
    * @return the username
    */
   public String getUsername()
   {
      return username;
   }

   /**
    * Set the default username used for DataSource.getConnection(username, password) calls.
    *
    * @param username the username
    */
   public void setUsername(String username)
   {
      this.username = username;
   }

   /**
    * Get the thread factory used to create threads.
    *
    * @return the thread factory (may be null, in which case the default thread factory is used)
    */
   public ThreadFactory getThreadFactory()
   {
      return threadFactory;
   }

   /**
    * Set the thread factory to be used to create threads.
    *
    * @param threadFactory the thread factory (setting to null causes the default thread factory to be used)
    */
   public void setThreadFactory(ThreadFactory threadFactory)
   {
      this.threadFactory = threadFactory;
   }

   public void validate()
   {
      Logger logger = LoggerFactory.getLogger(getClass());

      validateNumerics();

      if (connectionCustomizerClassName != null) {
         try {
            getClass().getClassLoader().loadClass(connectionCustomizerClassName);
         }
         catch (Exception e) {
            logger.warn("connectionCustomizationClass specified class '" + connectionCustomizerClassName + "' could not be loaded", e);
            connectionCustomizerClassName = null;
         }
      }

      if (driverClassName != null && jdbcUrl == null) {
         logger.error("when specifying driverClassName, jdbcUrl must also be specified");
         throw new IllegalStateException("when specifying driverClassName, jdbcUrl must also be specified");
      }
      else if (driverClassName != null && dataSourceClassName != null) {
         logger.error("both driverClassName and dataSourceClassName are specified, one or the other should be used");
         throw new IllegalStateException("both driverClassName and dataSourceClassName are specified, one or the other should be used");
      }
      else if (jdbcUrl != null) {
         // OK
      }
      else if (dataSource == null && dataSourceClassName == null) {
         logger.error("one of either dataSource, dataSourceClassName, or jdbcUrl and driverClassName must be specified");
         throw new IllegalArgumentException("one of either dataSource or dataSourceClassName must be specified");
      }
      else if (dataSource != null && dataSourceClassName != null) {
         logger.warn("both dataSource and dataSourceClassName are specified, ignoring dataSourceClassName");
      }

      if (transactionIsolationName != null) {
         UtilityElf.getTransactionIsolation(transactionIsolationName);
      }

      if (poolName == null) {
         poolName = "HikariPool-" + poolNumber++;
      }

      if (LOGGER.isDebugEnabled() || unitTest) {
         logConfiguration();
      }
   }

   private void validateNumerics()
   {
      Logger logger = LoggerFactory.getLogger(getClass());

      if (validationTimeout > connectionTimeout && connectionTimeout != 0) {
         logger.warn("validationTimeout is greater than connectionTimeout, setting validationTimeout to connectionTimeout.");
         validationTimeout = connectionTimeout;
      }

      if (minIdle < 0 || minIdle > maxPoolSize) {
         minIdle = maxPoolSize;
      }

      if (maxLifetime < 0) {
         logger.error("maxLifetime cannot be negative.");
         throw new IllegalArgumentException("maxLifetime cannot be negative.");
      }
      else if (maxLifetime > 0 && maxLifetime < TimeUnit.SECONDS.toMillis(30)) {
         logger.warn("maxLifetime is less than 30000ms, using default {}ms.", MAX_LIFETIME);
         maxLifetime = MAX_LIFETIME;
      }

      if (idleTimeout != 0 && idleTimeout < TimeUnit.SECONDS.toMillis(10)) {
         logger.warn("idleTimeout is less than 10000ms, using default {}ms.", IDLE_TIMEOUT);
         idleTimeout = IDLE_TIMEOUT;
      }
      else if (idleTimeout > maxLifetime && maxLifetime > 0) {
         logger.warn("idleTimeout is greater than maxLifetime, setting to maxLifetime.");
         idleTimeout = maxLifetime;
      }

      if (leakDetectionThreshold != 0 && leakDetectionThreshold < TimeUnit.SECONDS.toMillis(2) && !unitTest) {
         logger.warn("leakDetectionThreshold is less than 2000ms, setting to minimum 2000ms.");
         leakDetectionThreshold = 2000L;
      }
   }

   private void logConfiguration()
   {
      LOGGER.debug("HikariCP pool {} configuration:", poolName);
      final Set propertyNames = new TreeSet(PropertyBeanSetter.getPropertyNames(HikariConfig.class));
      for (String prop : propertyNames) {
         try {
            Object value = PropertyBeanSetter.getProperty(prop, this);
            if ("dataSourceProperties".equals(prop)) {
               Properties dsProps = PropertyBeanSetter.copyProperties(dataSourceProperties);
               dsProps.setProperty("password", "");
               value = dsProps;
            }
            value = (prop.contains("password") ? "" : value);
            LOGGER.debug((prop + "................................................").substring(0, 32) + (value != null ? value : ""));
         }
         catch (Exception e) {
            continue;
         }
      }
   }

   abstract protected void loadProperties(String propertyFileName);

   public void copyState(AbstractHikariConfig other)
   {
      for (Field field : AbstractHikariConfig.class.getDeclaredFields()) {
         if (!Modifier.isFinal(field.getModifiers())) {
            field.setAccessible(true);
            try {
               field.set(other, field.get(this));
            }
            catch (Exception e) {
               throw new RuntimeException("Exception copying HikariConfig state: " + e.getMessage(), e);
            }
         }
      }
   }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy