]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-17200 Move to HikariCP from Apache DBCP
authorJacek <jacek.poreda@sonarsource.com>
Thu, 10 Feb 2022 11:09:48 +0000 (12:09 +0100)
committersonartech <sonartech@sonarsource.com>
Mon, 29 Aug 2022 20:02:52 +0000 (20:02 +0000)
32 files changed:
build.gradle
server/sonar-ce/build.gradle
server/sonar-ce/src/main/java/org/sonar/ce/monitoring/CeDatabaseMBean.java
server/sonar-ce/src/main/java/org/sonar/ce/monitoring/CeDatabaseMBeanImpl.java
server/sonar-ce/src/test/java/org/sonar/ce/monitoring/CeDatabaseMBeanImplTest.java
server/sonar-db-core/build.gradle
server/sonar-db-core/src/main/java/org/sonar/db/DefaultDatabase.java
server/sonar-db-core/src/main/java/org/sonar/db/profiling/ConnectionInterceptor.java
server/sonar-db-core/src/main/java/org/sonar/db/profiling/NullConnectionInterceptor.java
server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfiledConnectionInterceptor.java
server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfiledDataSource.java
server/sonar-db-core/src/test/java/org/sonar/db/DefaultDatabaseTest.java
server/sonar-db-core/src/test/java/org/sonar/db/profiling/InvocationUtilsTest.java
server/sonar-db-core/src/test/java/org/sonar/db/profiling/ProfiledDataSourceTest.java
server/sonar-db-core/src/testFixtures/java/org/sonar/db/CoreH2Database.java
server/sonar-db-dao/src/main/java/org/sonar/db/DatabaseMBean.java [new file with mode: 0644]
server/sonar-db-dao/src/test/java/org/sonar/db/DatabaseMBeanTest.java [new file with mode: 0644]
server/sonar-db-dao/src/testFixtures/java/org/sonar/db/DbTester.java
server/sonar-docs/src/pages/instance-administration/monitoring.md
server/sonar-docs/src/pages/setup/upgrade-notes.md
server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java
server/sonar-process/src/main/java/org/sonar/process/systeminfo/BaseSectionMBean.java [new file with mode: 0644]
server/sonar-process/src/test/java/org/sonar/process/systeminfo/BaseSectionMBeanTest.java [new file with mode: 0644]
server/sonar-webserver-core/build.gradle
server/sonar-webserver-core/src/main/java/org/sonar/server/platform/monitoring/BaseSectionMBean.java [deleted file]
server/sonar-webserver-core/src/main/java/org/sonar/server/platform/monitoring/DbConnectionSection.java
server/sonar-webserver-core/src/main/java/org/sonar/server/platform/monitoring/DbConnectionSectionMBean.java
server/sonar-webserver-core/src/main/java/org/sonar/server/platform/monitoring/StandaloneSystemSection.java
server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/BaseSectionMBeanTest.java
server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/DbConnectionSectionTest.java
server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/FakeSection.java
sonar-application/src/main/assembly/conf/sonar.properties

index 5d6e47a450262fb5b707f8cd599f4ec72a5bd68a..2359031daddf8baa520a84182b86320b2fd1c8d1 100644 (file)
@@ -261,7 +261,7 @@ subprojects {
       dependency 'org.awaitility:awaitility:4.2.0'
       dependency 'org.apache.commons:commons-csv:1.9.0'
       dependency 'org.apache.commons:commons-email:1.5'
-      dependency 'org.apache.commons:commons-dbcp2:2.9.0'
+      dependency 'com.zaxxer:HikariCP:5.0.1'
       dependency('org.apache.httpcomponents:httpclient:4.5.13'){
         exclude 'commons-logging:commons-logging'
       }
@@ -304,6 +304,7 @@ subprojects {
       dependency('org.mockito:mockito-core:4.7.0') {
         exclude 'org.hamcrest:hamcrest-core'
       }
+      dependency('org.mockito:mockito-inline:4.7.0')
       dependency 'org.mybatis:mybatis:3.5.10'
       dependency 'org.nanohttpd:nanohttpd:2.3.1'
       dependencySet(group: 'org.slf4j', version: '1.7.30') {
index 054ac8d7c5141e814d21e8a15f82612a0d47526c..f66a080cfa782d8a7c52abc7a7470e2c1ab27c72 100644 (file)
@@ -13,7 +13,7 @@ dependencies {
   compile 'com.hazelcast:hazelcast'
   compile 'com.hazelcast:hazelcast-kubernetes'
   compile 'commons-io:commons-io'
-  compile 'org.apache.commons:commons-dbcp2'
+  compile 'com.zaxxer:HikariCP'
   compile 'org.sonarsource.api.plugin:sonar-plugin-api'
   compile project(':server:sonar-ce-common')
   compile project(':server:sonar-ce-task')
index fa8d6f7ad3edae80bbbd733dffb5915f60d6396d..a2e69a63094e52cc5ce222bc6665afc7a234c8c9 100644 (file)
@@ -21,23 +21,18 @@ package org.sonar.ce.monitoring;
 
 public interface CeDatabaseMBean {
 
-  String OBJECT_NAME = "SonarQube:name=ComputeEngineDatabaseConnection";
-
   int getPoolActiveConnections();
 
-  int getPoolMaxActiveConnections();
+  int getPoolMaxConnections();
 
-  int getPoolIdleConnections();
+  int getPoolTotalConnections();
 
-  int getPoolMaxIdleConnections();
+  int getPoolIdleConnections();
 
   int getPoolMinIdleConnections();
 
-  int getPoolInitialSize();
+  long getPoolMaxLifeTimeMillis();
 
   long getPoolMaxWaitMillis();
 
-  boolean getPoolRemoveAbandoned();
-
-  int getPoolRemoveAbandonedTimeoutSeconds();
 }
index c392abd1e050403889a567ca7ea841d609a7058f..2adb3b36c1139f20df1cd76b3944528f765f1777 100644 (file)
  */
 package org.sonar.ce.monitoring;
 
-import org.apache.commons.dbcp2.BasicDataSource;
-import org.sonar.api.Startable;
+import org.sonar.db.DatabaseMBean;
 import org.sonar.db.DbClient;
-import org.sonar.process.Jmx;
-import org.sonar.process.systeminfo.SystemInfoSection;
 import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
 
-public class CeDatabaseMBeanImpl implements CeDatabaseMBean, Startable, SystemInfoSection {
-  private final DbClient dbClient;
+public class CeDatabaseMBeanImpl extends DatabaseMBean implements CeDatabaseMBean {
 
-  public CeDatabaseMBeanImpl(DbClient dbClient) {
-    this.dbClient = dbClient;
-  }
-
-  @Override
-  public void start() {
-    Jmx.register(OBJECT_NAME, this);
-  }
-
-  /**
-   * Unregister, if needed
-   */
-  @Override
-  public void stop() {
-    Jmx.unregister(OBJECT_NAME);
-  }
-
-  @Override
-  public int getPoolActiveConnections() {
-    return commonsDbcp().getNumActive();
-  }
-
-  @Override
-  public int getPoolMaxActiveConnections() {
-    return commonsDbcp().getMaxTotal();
-  }
-
-  @Override
-  public int getPoolIdleConnections() {
-    return commonsDbcp().getNumIdle();
-  }
+  private static final String OBJECT_NAME = "ComputeEngineDatabaseConnection";
 
-  @Override
-  public int getPoolMaxIdleConnections() {
-    return commonsDbcp().getMaxIdle();
-  }
-
-  @Override
-  public int getPoolMinIdleConnections() {
-    return commonsDbcp().getMinIdle();
-  }
-
-  @Override
-  public int getPoolInitialSize() {
-    return commonsDbcp().getInitialSize();
-  }
-
-  @Override
-  public long getPoolMaxWaitMillis() {
-    return commonsDbcp().getMaxWaitMillis();
-  }
-
-  @Override
-  public boolean getPoolRemoveAbandoned() {
-    return commonsDbcp().getRemoveAbandonedOnBorrow();
+  public CeDatabaseMBeanImpl(DbClient dbClient) {
+    super(dbClient);
   }
 
   @Override
-  public int getPoolRemoveAbandonedTimeoutSeconds() {
-    return commonsDbcp().getRemoveAbandonedTimeout();
-  }
-
-  private BasicDataSource commonsDbcp() {
-    return (BasicDataSource) dbClient.getDatabase().getDataSource();
+  protected String name() {
+    return OBJECT_NAME;
   }
 
   @Override
   public ProtobufSystemInfo.Section toProtobuf() {
     ProtobufSystemInfo.Section.Builder builder = ProtobufSystemInfo.Section.newBuilder();
     builder.setName("Compute Engine Database Connection");
-    builder.addAttributesBuilder().setKey("Pool Initial Size").setLongValue(getPoolInitialSize()).build();
+    builder.addAttributesBuilder().setKey("Pool Total Connections").setLongValue(getPoolTotalConnections()).build();
     builder.addAttributesBuilder().setKey("Pool Active Connections").setLongValue(getPoolActiveConnections()).build();
     builder.addAttributesBuilder().setKey("Pool Idle Connections").setLongValue(getPoolIdleConnections()).build();
-    builder.addAttributesBuilder().setKey("Pool Max Active Connections").setLongValue(getPoolMaxActiveConnections()).build();
-    builder.addAttributesBuilder().setKey("Pool Max Idle Connections").setLongValue(getPoolMaxIdleConnections()).build();
+    builder.addAttributesBuilder().setKey("Pool Max Connections").setLongValue(getPoolMaxConnections()).build();
     builder.addAttributesBuilder().setKey("Pool Min Idle Connections").setLongValue(getPoolMinIdleConnections()).build();
     builder.addAttributesBuilder().setKey("Pool Max Wait (ms)").setLongValue(getPoolMaxWaitMillis()).build();
-    builder.addAttributesBuilder().setKey("Pool Remove Abandoned").setBooleanValue(getPoolRemoveAbandoned()).build();
-    builder.addAttributesBuilder().setKey("Pool Remove Abandoned Timeout (sec)").setLongValue(getPoolRemoveAbandonedTimeoutSeconds()).build();
+    builder.addAttributesBuilder().setKey("Pool Max Lifetime (ms)").setLongValue(getPoolMaxLifeTimeMillis()).build();
     return builder.build();
   }
 }
index 35894dd9fdb11c23ebbbf06366cd763522d1b387..cd8e0de1eaa333f11cf17a4954de3494d5af587c 100644 (file)
@@ -33,7 +33,6 @@ import org.sonar.process.Jmx;
 import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.ce.monitoring.CeDatabaseMBean.OBJECT_NAME;
 
 public class CeDatabaseMBeanImplTest {
 
@@ -45,7 +44,7 @@ public class CeDatabaseMBeanImplTest {
   @BeforeClass
   public static void beforeClass() {
     // if any other class starts a container where CeDatabaseMBeanImpl is added, it will have been registered
-    Jmx.unregister(OBJECT_NAME);
+    Jmx.unregister("SonarQube:name=ComputeEngineDatabaseConnection");
   }
 
   @Test
@@ -63,15 +62,15 @@ public class CeDatabaseMBeanImplTest {
   public void export_system_info() {
     ProtobufSystemInfo.Section section = underTest.toProtobuf();
     assertThat(section.getName()).isEqualTo("Compute Engine Database Connection");
-    assertThat(section.getAttributesCount()).isEqualTo(9);
-    assertThat(section.getAttributes(0).getKey()).isEqualTo("Pool Initial Size");
-    assertThat(section.getAttributes(0).getLongValue()).isGreaterThanOrEqualTo(0);
+    assertThat(section.getAttributesCount()).isEqualTo(7);
+    assertThat(section.getAttributes(0).getKey()).isEqualTo("Pool Total Connections");
+    assertThat(section.getAttributes(0).getLongValue()).isPositive();
   }
 
   @CheckForNull
   private ObjectInstance getMBean() throws Exception {
     try {
-      return ManagementFactory.getPlatformMBeanServer().getObjectInstance(new ObjectName(OBJECT_NAME));
+      return ManagementFactory.getPlatformMBeanServer().getObjectInstance(new ObjectName("SonarQube:name=ComputeEngineDatabaseConnection"));
     } catch (InstanceNotFoundException e) {
       return null;
     }
index 83bd1e9c8c20a5a79133d8bd463223889939d8a0..c37d4764667d43e30450da3faeeccb430fb7c8dc 100644 (file)
@@ -12,7 +12,7 @@ dependencies {
   compile 'com.google.guava:guava'
   compile 'commons-io:commons-io'
   compile 'commons-lang:commons-lang'
-  compile 'org.apache.commons:commons-dbcp2'
+  compile 'com.zaxxer:HikariCP'
   compile 'org.mybatis:mybatis'
   compile 'org.slf4j:slf4j-api'
   compile 'org.sonarsource.api.plugin:sonar-plugin-api'
@@ -27,7 +27,9 @@ dependencies {
   testCompile 'com.oracle.database.jdbc:ojdbc8'
   testCompile 'com.tngtech.java:junit-dataprovider'
   testCompile 'org.mockito:mockito-core'
+  testCompile 'org.mockito:mockito-inline'
   testCompile 'org.postgresql:postgresql'
+
   testCompile project(':sonar-testing-harness')
 
   testRuntime 'com.h2database:h2'
index be9cd2b872cfd3cd4b85493b97fed0e2a5a864c8..837b941f445a4f074953a67a0558a26af41ba344 100644 (file)
@@ -21,15 +21,15 @@ package org.sonar.db;
 
 import ch.qos.logback.classic.Level;
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableMap;
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
 import java.sql.Connection;
 import java.sql.SQLException;
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
+import java.util.Set;
 import javax.sql.DataSource;
-import org.apache.commons.dbcp2.BasicDataSource;
-import org.apache.commons.dbcp2.BasicDataSourceFactory;
 import org.apache.commons.lang.StringUtils;
 import org.sonar.api.config.internal.Settings;
 import org.sonar.api.utils.log.Logger;
@@ -43,7 +43,12 @@ import org.sonar.process.logging.LogbackHelper;
 
 import static com.google.common.base.Preconditions.checkState;
 import static java.lang.String.format;
+import static org.sonar.process.ProcessProperties.Property.JDBC_DRIVER_PATH;
+import static org.sonar.process.ProcessProperties.Property.JDBC_EMBEDDED_PORT;
+import static org.sonar.process.ProcessProperties.Property.JDBC_MIN_IDLE;
+import static org.sonar.process.ProcessProperties.Property.JDBC_PASSWORD;
 import static org.sonar.process.ProcessProperties.Property.JDBC_URL;
+import static org.sonar.process.ProcessProperties.Property.JDBC_USERNAME;
 
 /**
  * @since 2.12
@@ -57,12 +62,25 @@ public class DefaultDatabase implements Database {
   private static final String SONAR_JDBC_DIALECT = "sonar.jdbc.dialect";
   private static final String SONAR_JDBC_DRIVER = "sonar.jdbc.driverClassName";
   private static final String SONAR_JDBC_MAX_ACTIVE = "sonar.jdbc.maxActive";
-  private static final String DBCP_JDBC_MAX_ACTIVE = "maxTotal";
   private static final String SONAR_JDBC_MAX_WAIT = "sonar.jdbc.maxWait";
-  private static final String DBCP_JDBC_MAX_WAIT = "maxWaitMillis";
-  private static final Map<String, String> SONAR_JDBC_TO_DBCP_PROPERTY_MAPPINGS = ImmutableMap.of(
-    SONAR_JDBC_MAX_ACTIVE, DBCP_JDBC_MAX_ACTIVE,
-    SONAR_JDBC_MAX_WAIT, DBCP_JDBC_MAX_WAIT);
+  private static final Set<String> DEPRECATED_SONAR_PROPERTIES = Set.of(
+    "sonar.jdbc.maxIdle",
+    "sonar.jdbc.minEvictableIdleTimeMillis",
+    "sonar.jdbc.timeBetweenEvictionRunsMillis");
+
+  private static final Set<String> IGNORED_SONAR_PROPERTIES = Set.of(SONAR_JDBC_DIALECT, JDBC_DRIVER_PATH.getKey(),
+    "sonar.jdbc.maxIdle",
+    "sonar.jdbc.minEvictableIdleTimeMillis",
+    "sonar.jdbc.timeBetweenEvictionRunsMillis");
+
+  private static final Map<String, String> SONAR_JDBC_TO_HIKARI_PROPERTY_MAPPINGS = Map.of(
+    JDBC_USERNAME.getKey(), "dataSource.user",
+    JDBC_PASSWORD.getKey(), "dataSource.password",
+    JDBC_EMBEDDED_PORT.getKey(), "dataSource.portNumber",
+    JDBC_URL.getKey(), "jdbcUrl",
+    SONAR_JDBC_MAX_WAIT, "connectionTimeout",
+    SONAR_JDBC_MAX_ACTIVE, "maximumPoolSize",
+    JDBC_MIN_IDLE.getKey(), "minimumIdle");
 
   private final LogbackHelper logbackHelper;
   private final Settings settings;
@@ -99,16 +117,22 @@ public class DefaultDatabase implements Database {
     properties.setProperty(SONAR_JDBC_DRIVER, dialect.getDefaultDriverClassName());
   }
 
-  private void initDataSource() throws Exception {
-    // but it's correctly caught by start()
-    LOG.info("Create JDBC data source for {}", properties.getProperty(JDBC_URL.getKey()), DEFAULT_URL);
-    BasicDataSource basicDataSource = BasicDataSourceFactory.createDataSource(extractCommonsDbcpProperties(properties));
-    datasource = new ProfiledDataSource(basicDataSource, NullConnectionInterceptor.INSTANCE);
-    datasource.setConnectionInitSqls(dialect.getConnectionInitStatements());
-    datasource.setValidationQuery(dialect.getValidationQuery());
+  private void initDataSource() {
+    LOG.info("Create JDBC data source for {}", properties.getProperty(JDBC_URL.getKey(), DEFAULT_URL));
+    HikariDataSource ds = createHikariDataSource();
+    datasource = new ProfiledDataSource(ds, NullConnectionInterceptor.INSTANCE);
     enableSqlLogging(datasource, logbackHelper.getLoggerLevel("sql") == Level.TRACE);
   }
 
+  private HikariDataSource createHikariDataSource() {
+    HikariConfig config = new HikariConfig(extractCommonsHikariProperties(properties));
+    if (!dialect.getConnectionInitStatements().isEmpty()) {
+      config.setConnectionInitSql(dialect.getConnectionInitStatements().get(0));
+    }
+    config.setConnectionTestQuery(dialect.getValidationQuery());
+    return new HikariDataSource(config);
+  }
+
   private void checkConnection() {
     Connection connection = null;
     try {
@@ -124,11 +148,7 @@ public class DefaultDatabase implements Database {
   @Override
   public void stop() {
     if (datasource != null) {
-      try {
-        datasource.close();
-      } catch (SQLException e) {
-        throw new IllegalStateException("Fail to stop JDBC connection pool", e);
-      }
+      datasource.close();
     }
   }
 
@@ -171,17 +191,22 @@ public class DefaultDatabase implements Database {
   }
 
   @VisibleForTesting
-  static Properties extractCommonsDbcpProperties(Properties properties) {
+  static Properties extractCommonsHikariProperties(Properties properties) {
     Properties result = new Properties();
-    result.setProperty("accessToUnderlyingConnectionAllowed", "true");
     for (Map.Entry<Object, Object> entry : properties.entrySet()) {
       String key = (String) entry.getKey();
+      if (IGNORED_SONAR_PROPERTIES.contains(key)) {
+        if (DEPRECATED_SONAR_PROPERTIES.contains(key)) {
+          LOG.warn("Property [{}] has no effect as pool connection implementation changed, check 9.7 upgrade notes.", key);
+        }
+        continue;
+      }
       if (StringUtils.startsWith(key, SONAR_JDBC)) {
-        String resolvedKey = toDbcpPropertyKey(key);
+        String resolvedKey = toHikariPropertyKey(key);
         String existingValue = (String) result.setProperty(resolvedKey, (String) entry.getValue());
         checkState(existingValue == null || existingValue.equals(entry.getValue()),
           "Duplicate property declaration for resolved jdbc key '%s': conflicting values are '%s' and '%s'", resolvedKey, existingValue, entry.getValue());
-        result.setProperty(toDbcpPropertyKey(key), (String) entry.getValue());
+        result.setProperty(resolvedKey, (String) entry.getValue());
       }
     }
     return result;
@@ -193,9 +218,9 @@ public class DefaultDatabase implements Database {
     }
   }
 
-  private static String toDbcpPropertyKey(String key) {
-    if (SONAR_JDBC_TO_DBCP_PROPERTY_MAPPINGS.containsKey(key)) {
-      return SONAR_JDBC_TO_DBCP_PROPERTY_MAPPINGS.get(key);
+  private static String toHikariPropertyKey(String key) {
+    if (SONAR_JDBC_TO_HIKARI_PROPERTY_MAPPINGS.containsKey(key)) {
+      return SONAR_JDBC_TO_HIKARI_PROPERTY_MAPPINGS.get(key);
     }
 
     return StringUtils.removeStart(key, SONAR_JDBC);
index 1a856ca23ce5d68fe36f129d0eddd83f8c28f682..650405c19c56c365363988ec07988ae0542ae948 100644 (file)
@@ -21,12 +21,12 @@ package org.sonar.db.profiling;
 
 import java.sql.Connection;
 import java.sql.SQLException;
-import org.apache.commons.dbcp2.BasicDataSource;
+import javax.sql.DataSource;
 
 public interface ConnectionInterceptor {
 
-  Connection getConnection(BasicDataSource dataSource) throws SQLException;
+  Connection getConnection(DataSource dataSource) throws SQLException;
 
-  Connection getConnection(BasicDataSource dataSource, String login, String password) throws SQLException;
+  Connection getConnection(DataSource dataSource, String login, String password) throws SQLException;
 
 }
index 9e069ccb40875b2ec0a1684283662d3af8d95901..c8d4d3b2915313ac82c0300b099002fd25eea30a 100644 (file)
@@ -21,18 +21,18 @@ package org.sonar.db.profiling;
 
 import java.sql.Connection;
 import java.sql.SQLException;
-import org.apache.commons.dbcp2.BasicDataSource;
+import javax.sql.DataSource;
 
 public enum NullConnectionInterceptor implements ConnectionInterceptor {
   INSTANCE;
 
   @Override
-  public Connection getConnection(BasicDataSource dataSource) throws SQLException {
+  public Connection getConnection(DataSource dataSource) throws SQLException {
     return dataSource.getConnection();
   }
 
   @Override
-  public Connection getConnection(BasicDataSource dataSource, String user, String password) throws SQLException {
+  public Connection getConnection(DataSource dataSource, String user, String password) throws SQLException {
     return dataSource.getConnection(user, password);
   }
 }
index 43f79c54c6833be849bb62b65832ca3b00512983..b2c384f3572522995c01e3c6d5c13e67d12b8c6b 100644 (file)
@@ -22,18 +22,18 @@ package org.sonar.db.profiling;
 import java.lang.reflect.Proxy;
 import java.sql.Connection;
 import java.sql.SQLException;
-import org.apache.commons.dbcp2.BasicDataSource;
+import javax.sql.DataSource;
 
 public enum ProfiledConnectionInterceptor implements ConnectionInterceptor {
   INSTANCE;
 
   @Override
-  public Connection getConnection(BasicDataSource dataSource) throws SQLException {
+  public Connection getConnection(DataSource dataSource) throws SQLException {
     return buildConnectionProxy(new ProfilingConnectionHandler(dataSource.getConnection()));
   }
 
   @Override
-  public Connection getConnection(BasicDataSource dataSource, String login, String password) throws SQLException {
+  public Connection getConnection(DataSource dataSource, String login, String password) throws SQLException {
     return buildConnectionProxy(new ProfilingConnectionHandler(dataSource.getConnection(login, password)));
   }
 
index 579a4dc2bea26980a1947610373bd15bd2c9c265..410aecb0e7a565e4ccb5a2cdb54bc3c9a2354e88 100644 (file)
  */
 package org.sonar.db.profiling;
 
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariConfigMXBean;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.HikariPoolMXBean;
+import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
 import java.io.PrintWriter;
 import java.sql.Connection;
-import java.sql.Driver;
+import java.sql.ConnectionBuilder;
 import java.sql.SQLException;
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-import javax.management.MBeanServer;
-import javax.management.ObjectName;
-import org.apache.commons.dbcp2.BasicDataSource;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.ShardingKeyBuilder;
+import java.util.Properties;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import javax.sql.DataSource;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
 
-public class ProfiledDataSource extends BasicDataSource {
+public class ProfiledDataSource extends HikariDataSource {
 
   static final Logger SQL_LOGGER = Loggers.get("sql");
 
-  private final BasicDataSource delegate;
+  private final HikariDataSource delegate;
   private ConnectionInterceptor connectionInterceptor;
 
-  public ProfiledDataSource(BasicDataSource delegate, ConnectionInterceptor connectionInterceptor) {
+  public ProfiledDataSource(HikariDataSource delegate, ConnectionInterceptor connectionInterceptor) {
     this.delegate = delegate;
     this.connectionInterceptor = connectionInterceptor;
   }
 
-  public BasicDataSource getDelegate() {
+  public HikariDataSource getDelegate() {
     return delegate;
   }
 
@@ -53,33 +58,33 @@ public class ProfiledDataSource extends BasicDataSource {
   }
 
   @Override
-  public Boolean getDefaultAutoCommit() {
-    return delegate.getDefaultAutoCommit();
+  public boolean isAutoCommit() {
+    return delegate.isAutoCommit();
   }
 
   @Override
-  public Boolean getDefaultReadOnly() {
-    return delegate.getDefaultReadOnly();
+  public boolean isReadOnly() {
+    return delegate.isReadOnly();
   }
 
   @Override
-  public int getDefaultTransactionIsolation() {
-    return delegate.getDefaultTransactionIsolation();
+  public String getTransactionIsolation() {
+    return delegate.getTransactionIsolation();
   }
 
   @Override
-  public void setDefaultTransactionIsolation(int defaultTransactionIsolation) {
-    delegate.setDefaultTransactionIsolation(defaultTransactionIsolation);
+  public void setTransactionIsolation(String defaultTransactionIsolation) {
+    delegate.setTransactionIsolation(defaultTransactionIsolation);
   }
 
   @Override
-  public String getDefaultCatalog() {
-    return delegate.getDefaultCatalog();
+  public String getCatalog() {
+    return delegate.getCatalog();
   }
 
   @Override
-  public void setDefaultCatalog(String defaultCatalog) {
-    delegate.setDefaultCatalog(defaultCatalog);
+  public void setCatalog(String defaultCatalog) {
+    delegate.setCatalog(defaultCatalog);
   }
 
   @Override
@@ -93,153 +98,158 @@ public class ProfiledDataSource extends BasicDataSource {
   }
 
   @Override
-  public synchronized ClassLoader getDriverClassLoader() {
-    return delegate.getDriverClassLoader();
+  public int getMaximumPoolSize() {
+    return delegate.getMaximumPoolSize();
   }
 
   @Override
-  public synchronized void setDriverClassLoader(ClassLoader driverClassLoader) {
-    delegate.setDriverClassLoader(driverClassLoader);
+  public void setMaximumPoolSize(int maxActive) {
+    delegate.setMaximumPoolSize(maxActive);
   }
 
   @Override
-  public synchronized int getMaxTotal() {
-    return delegate.getMaxTotal();
+  public Connection getConnection() throws SQLException {
+    return connectionInterceptor.getConnection(delegate);
   }
 
   @Override
-  public synchronized void setMaxTotal(int maxActive) {
-    delegate.setMaxTotal(maxActive);
+  public Connection getConnection(String login, String password) throws SQLException {
+    return connectionInterceptor.getConnection(this, login, password);
   }
 
   @Override
-  public synchronized int getMaxIdle() {
-    return delegate.getMaxIdle();
+  public PrintWriter getLogWriter() throws SQLException {
+    return delegate.getLogWriter();
   }
 
   @Override
-  public synchronized void setMaxIdle(int maxIdle) {
-    delegate.setMaxIdle(maxIdle);
+  public void setLogWriter(PrintWriter out) throws SQLException {
+    delegate.setLogWriter(out);
   }
 
   @Override
-  public synchronized int getMinIdle() {
-    return delegate.getMinIdle();
+  public void setLoginTimeout(int seconds) throws SQLException {
+    delegate.setLoginTimeout(seconds);
   }
 
   @Override
-  public synchronized void setMinIdle(int minIdle) {
-    delegate.setMinIdle(minIdle);
+  public int getLoginTimeout() throws SQLException {
+    return delegate.getLoginTimeout();
   }
 
   @Override
-  public synchronized int getInitialSize() {
-    return delegate.getInitialSize();
+  public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException {
+    return delegate.getParentLogger();
   }
 
   @Override
-  public synchronized void setInitialSize(int initialSize) {
-    delegate.setInitialSize(initialSize);
+  public <T> T unwrap(Class<T> iface) throws SQLException {
+    return delegate.unwrap(iface);
   }
 
   @Override
-  public synchronized long getMaxWaitMillis() {
-    return delegate.getMaxWaitMillis();
+  public boolean isWrapperFor(Class<?> iface) throws SQLException {
+    return delegate.isWrapperFor(iface);
   }
 
   @Override
-  public synchronized void setMaxWaitMillis(long maxWait) {
-    delegate.setMaxWaitMillis(maxWait);
+  public void setMetricRegistry(Object metricRegistry) {
+    delegate.setMetricRegistry(metricRegistry);
   }
 
   @Override
-  public synchronized boolean isPoolPreparedStatements() {
-    return delegate.isPoolPreparedStatements();
+  public void setMetricsTrackerFactory(MetricsTrackerFactory metricsTrackerFactory) {
+    delegate.setMetricsTrackerFactory(metricsTrackerFactory);
   }
 
   @Override
-  public synchronized void setPoolPreparedStatements(boolean poolingStatements) {
-    delegate.setPoolPreparedStatements(poolingStatements);
+  public void setHealthCheckRegistry(Object healthCheckRegistry) {
+    delegate.setHealthCheckRegistry(healthCheckRegistry);
   }
 
   @Override
-  public synchronized int getMaxOpenPreparedStatements() {
-    return delegate.getMaxOpenPreparedStatements();
+  public boolean isRunning() {
+    return delegate.isRunning();
   }
 
   @Override
-  public synchronized void setMaxOpenPreparedStatements(int maxOpenStatements) {
-    delegate.setMaxOpenPreparedStatements(maxOpenStatements);
+  public HikariPoolMXBean getHikariPoolMXBean() {
+    return delegate.getHikariPoolMXBean();
   }
 
   @Override
-  public synchronized boolean getTestOnBorrow() {
-    return delegate.getTestOnBorrow();
+  public HikariConfigMXBean getHikariConfigMXBean() {
+    return delegate.getHikariConfigMXBean();
   }
 
   @Override
-  public synchronized void setTestOnBorrow(boolean testOnBorrow) {
-    delegate.setTestOnBorrow(testOnBorrow);
+  public void evictConnection(Connection connection) {
+    delegate.evictConnection(connection);
   }
 
   @Override
-  public synchronized boolean getTestOnReturn() {
-    return delegate.getTestOnReturn();
+  public void close() {
+    delegate.close();
   }
 
   @Override
-  public synchronized void setTestOnReturn(boolean testOnReturn) {
-    delegate.setTestOnReturn(testOnReturn);
+  public boolean isClosed() {
+    return delegate.isClosed();
   }
 
   @Override
-  public synchronized long getTimeBetweenEvictionRunsMillis() {
-    return delegate.getTimeBetweenEvictionRunsMillis();
+  public String toString() {
+    return delegate.toString();
   }
 
   @Override
-  public synchronized void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) {
-    delegate.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
+  public long getConnectionTimeout() {
+    return delegate.getConnectionTimeout();
   }
 
   @Override
-  public synchronized int getNumTestsPerEvictionRun() {
-    return delegate.getNumTestsPerEvictionRun();
+  public void setConnectionTimeout(long connectionTimeoutMs) {
+    delegate.setConnectionTimeout(connectionTimeoutMs);
   }
 
   @Override
-  public synchronized void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) {
-    delegate.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
+  public long getIdleTimeout() {
+    return delegate.getIdleTimeout();
   }
 
   @Override
-  public synchronized long getMinEvictableIdleTimeMillis() {
-    return delegate.getMinEvictableIdleTimeMillis();
+  public void setIdleTimeout(long idleTimeoutMs) {
+    delegate.setIdleTimeout(idleTimeoutMs);
   }
 
   @Override
-  public synchronized void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) {
-    delegate.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
+  public long getLeakDetectionThreshold() {
+    return delegate.getLeakDetectionThreshold();
   }
 
   @Override
-  public synchronized boolean getTestWhileIdle() {
-    return delegate.getTestWhileIdle();
+  public void setLeakDetectionThreshold(long leakDetectionThresholdMs) {
+    delegate.setLeakDetectionThreshold(leakDetectionThresholdMs);
   }
 
   @Override
-  public synchronized void setTestWhileIdle(boolean testWhileIdle) {
-    delegate.setTestWhileIdle(testWhileIdle);
+  public long getMaxLifetime() {
+    return delegate.getMaxLifetime();
   }
 
   @Override
-  public synchronized int getNumActive() {
-    return delegate.getNumActive();
+  public void setMaxLifetime(long maxLifetimeMs) {
+    delegate.setMaxLifetime(maxLifetimeMs);
   }
 
   @Override
-  public synchronized int getNumIdle() {
-    return delegate.getNumIdle();
+  public int getMinimumIdle() {
+    return delegate.getMinimumIdle();
+  }
+
+  @Override
+  public void setMinimumIdle(int minIdle) {
+    delegate.setMinimumIdle(minIdle);
   }
 
   @Override
@@ -252,16 +262,6 @@ public class ProfiledDataSource extends BasicDataSource {
     delegate.setPassword(password);
   }
 
-  @Override
-  public synchronized String getUrl() {
-    return delegate.getUrl();
-  }
-
-  @Override
-  public synchronized void setUrl(String url) {
-    delegate.setUrl(url);
-  }
-
   @Override
   public String getUsername() {
     return delegate.getUsername();
@@ -273,368 +273,249 @@ public class ProfiledDataSource extends BasicDataSource {
   }
 
   @Override
-  public String getValidationQuery() {
-    return delegate.getValidationQuery();
-  }
-
-  @Override
-  public void setValidationQuery(String validationQuery) {
-    delegate.setValidationQuery(validationQuery);
-  }
-
-  @Override
-  public int getValidationQueryTimeout() {
-    return delegate.getValidationQueryTimeout();
-  }
-
-  @Override
-  public void setValidationQueryTimeout(int timeout) {
-    delegate.setValidationQueryTimeout(timeout);
-  }
-
-  @Override
-  public List<String> getConnectionInitSqls() {
-    return delegate.getConnectionInitSqls();
-  }
-
-  @Override
-  public void setConnectionInitSqls(Collection<String> connectionInitSqls) {
-    delegate.setConnectionInitSqls(connectionInitSqls);
-  }
-
-  @Override
-  public synchronized boolean isAccessToUnderlyingConnectionAllowed() {
-    return delegate.isAccessToUnderlyingConnectionAllowed();
-  }
-
-  @Override
-  public synchronized void setAccessToUnderlyingConnectionAllowed(boolean allow) {
-    delegate.setAccessToUnderlyingConnectionAllowed(allow);
-  }
-
-  @Override
-  public Connection getConnection() throws SQLException {
-    return connectionInterceptor.getConnection(delegate);
-  }
-
-  @Override
-  public Connection getConnection(String login, String password) throws SQLException {
-    return connectionInterceptor.getConnection(this, login, password);
-  }
-
-  @Override
-  public int getLoginTimeout() throws SQLException {
-    return delegate.getLoginTimeout();
-  }
-
-  @Override
-  public java.util.logging.Logger getParentLogger() {
-    return java.util.logging.Logger.getLogger(getClass().getName());
-  }
-
-  @Override
-  public PrintWriter getLogWriter() throws SQLException {
-    return delegate.getLogWriter();
-  }
-
-  @Override
-  public void setLoginTimeout(int loginTimeout) throws SQLException {
-    delegate.setLoginTimeout(loginTimeout);
-  }
-
-  @Override
-  public void setLogWriter(PrintWriter logWriter) throws SQLException {
-    delegate.setLogWriter(logWriter);
-  }
-
-  @Override
-  public boolean getRemoveAbandonedOnBorrow() {
-    return delegate.getRemoveAbandonedOnBorrow();
-  }
-
-  @Override
-  public void setRemoveAbandonedOnBorrow(boolean removeAbandoned) {
-    delegate.setRemoveAbandonedOnBorrow(removeAbandoned);
-  }
-
-
-  @Override
-  public boolean getRemoveAbandonedOnMaintenance() {
-    return delegate.getRemoveAbandonedOnMaintenance();
+  public long getValidationTimeout() {
+    return delegate.getValidationTimeout();
   }
 
   @Override
-  public void setRemoveAbandonedOnMaintenance(boolean removeAbandoned) {
-    delegate.setRemoveAbandonedOnMaintenance(removeAbandoned);
+  public void setValidationTimeout(long validationTimeoutMs) {
+    delegate.setValidationTimeout(validationTimeoutMs);
   }
 
   @Override
-  public int getRemoveAbandonedTimeout() {
-    return delegate.getRemoveAbandonedTimeout();
+  public String getConnectionTestQuery() {
+    return delegate.getConnectionTestQuery();
   }
 
   @Override
-  public void setRemoveAbandonedTimeout(int removeAbandonedTimeout) {
-    delegate.setRemoveAbandonedTimeout(removeAbandonedTimeout);
+  public void setConnectionTestQuery(String connectionTestQuery) {
+    delegate.setConnectionTestQuery(connectionTestQuery);
   }
 
   @Override
-  public boolean getLogAbandoned() {
-    return delegate.getLogAbandoned();
+  public String getConnectionInitSql() {
+    return delegate.getConnectionInitSql();
   }
 
   @Override
-  public void setLogAbandoned(boolean logAbandoned) {
-    delegate.setLogAbandoned(logAbandoned);
+  public void setConnectionInitSql(String connectionInitSql) {
+    delegate.setConnectionInitSql(connectionInitSql);
   }
 
   @Override
-  public void addConnectionProperty(String name, String value) {
-    delegate.addConnectionProperty(name, value);
+  public DataSource getDataSource() {
+    return delegate.getDataSource();
   }
 
   @Override
-  public void removeConnectionProperty(String name) {
-    delegate.removeConnectionProperty(name);
+  public void setDataSource(DataSource dataSource) {
+    delegate.setDataSource(dataSource);
   }
 
   @Override
-  public void setConnectionProperties(String connectionProperties) {
-    delegate.setConnectionProperties(connectionProperties);
+  public String getDataSourceClassName() {
+    return delegate.getDataSourceClassName();
   }
 
   @Override
-  public synchronized void close() throws SQLException {
-    delegate.close();
-  }
-
-  @Override
-  public synchronized boolean isClosed() {
-    return delegate.isClosed();
+  public void setDataSourceClassName(String className) {
+    delegate.setDataSourceClassName(className);
   }
 
   @Override
-  public boolean isWrapperFor(Class<?> iface) throws SQLException {
-    return delegate.isWrapperFor(iface);
+  public void addDataSourceProperty(String propertyName, Object value) {
+    delegate.addDataSourceProperty(propertyName, value);
   }
 
   @Override
-  public <T> T unwrap(Class<T> iface) throws SQLException {
-    return delegate.unwrap(iface);
+  public String getDataSourceJNDI() {
+    return delegate.getDataSourceJNDI();
   }
 
   @Override
-  public void setDefaultAutoCommit(Boolean defaultAutoCommit) {
-    delegate.setDefaultAutoCommit(defaultAutoCommit);
+  public void setDataSourceJNDI(String jndiDataSource) {
+    delegate.setDataSourceJNDI(jndiDataSource);
   }
 
   @Override
-  public void setDefaultReadOnly(Boolean defaultReadOnly) {
-    delegate.setDefaultReadOnly(defaultReadOnly);
+  public Properties getDataSourceProperties() {
+    return delegate.getDataSourceProperties();
   }
 
   @Override
-  public Integer getDefaultQueryTimeout() {
-    return delegate.getDefaultQueryTimeout();
+  public void setDataSourceProperties(Properties dsProperties) {
+    delegate.setDataSourceProperties(dsProperties);
   }
 
   @Override
-  public void setDefaultQueryTimeout(Integer defaultQueryTimeoutSeconds) {
-    delegate.setDefaultQueryTimeout(defaultQueryTimeoutSeconds);
+  public String getJdbcUrl() {
+    return delegate.getJdbcUrl();
   }
 
   @Override
-  public String getDefaultSchema() {
-    return delegate.getDefaultSchema();
+  public void setJdbcUrl(String jdbcUrl) {
+    delegate.setJdbcUrl(jdbcUrl);
   }
 
   @Override
-  public void setDefaultSchema(String defaultSchema) {
-    delegate.setDefaultSchema(defaultSchema);
+  public void setAutoCommit(boolean isAutoCommit) {
+    delegate.setAutoCommit(isAutoCommit);
   }
 
   @Override
-  public boolean getCacheState() {
-    return delegate.getCacheState();
+  public boolean isAllowPoolSuspension() {
+    return delegate.isAllowPoolSuspension();
   }
 
   @Override
-  public void setCacheState(boolean cacheState) {
-    delegate.setCacheState(cacheState);
+  public void setAllowPoolSuspension(boolean isAllowPoolSuspension) {
+    delegate.setAllowPoolSuspension(isAllowPoolSuspension);
   }
 
   @Override
-  public synchronized Driver getDriver() {
-    return delegate.getDriver();
+  public long getInitializationFailTimeout() {
+    return delegate.getInitializationFailTimeout();
   }
 
   @Override
-  public synchronized void setDriver(Driver driver) {
-    delegate.setDriver(driver);
+  public void setInitializationFailTimeout(long initializationFailTimeout) {
+    delegate.setInitializationFailTimeout(initializationFailTimeout);
   }
 
   @Override
-  public synchronized boolean getLifo() {
-    return delegate.getLifo();
+  public boolean isIsolateInternalQueries() {
+    return delegate.isIsolateInternalQueries();
   }
 
   @Override
-  public synchronized void setLifo(boolean lifo) {
-    delegate.setLifo(lifo);
+  public void setIsolateInternalQueries(boolean isolate) {
+    delegate.setIsolateInternalQueries(isolate);
   }
 
   @Override
-  public synchronized boolean getTestOnCreate() {
-    return delegate.getTestOnCreate();
+  public MetricsTrackerFactory getMetricsTrackerFactory() {
+    return delegate.getMetricsTrackerFactory();
   }
 
   @Override
-  public synchronized void setTestOnCreate(boolean testOnCreate) {
-    delegate.setTestOnCreate(testOnCreate);
+  public Object getMetricRegistry() {
+    return delegate.getMetricRegistry();
   }
 
   @Override
-  public synchronized void setSoftMinEvictableIdleTimeMillis(long softMinEvictableIdleTimeMillis) {
-    delegate.setSoftMinEvictableIdleTimeMillis(softMinEvictableIdleTimeMillis);
+  public Object getHealthCheckRegistry() {
+    return delegate.getHealthCheckRegistry();
   }
 
   @Override
-  public synchronized long getSoftMinEvictableIdleTimeMillis() {
-    return delegate.getSoftMinEvictableIdleTimeMillis();
+  public Properties getHealthCheckProperties() {
+    return delegate.getHealthCheckProperties();
   }
 
   @Override
-  public synchronized String getEvictionPolicyClassName() {
-    return delegate.getEvictionPolicyClassName();
+  public void setHealthCheckProperties(Properties healthCheckProperties) {
+    delegate.setHealthCheckProperties(healthCheckProperties);
   }
 
   @Override
-  public synchronized void setEvictionPolicyClassName(String evictionPolicyClassName) {
-    delegate.setEvictionPolicyClassName(evictionPolicyClassName);
+  public void addHealthCheckProperty(String key, String value) {
+    delegate.addHealthCheckProperty(key, value);
   }
 
   @Override
-  public String[] getConnectionInitSqlsAsArray() {
-    return delegate.getConnectionInitSqlsAsArray();
+  public long getKeepaliveTime() {
+    return delegate.getKeepaliveTime();
   }
 
   @Override
-  public long getMaxConnLifetimeMillis() {
-    return delegate.getMaxConnLifetimeMillis();
+  public void setKeepaliveTime(long keepaliveTimeMs) {
+    delegate.setKeepaliveTime(keepaliveTimeMs);
   }
 
   @Override
-  public boolean getLogExpiredConnections() {
-    return delegate.getLogExpiredConnections();
+  public void setReadOnly(boolean readOnly) {
+    delegate.setReadOnly(readOnly);
   }
 
   @Override
-  public void setMaxConnLifetimeMillis(long maxConnLifetimeMillis) {
-    delegate.setMaxConnLifetimeMillis(maxConnLifetimeMillis);
+  public boolean isRegisterMbeans() {
+    return delegate.isRegisterMbeans();
   }
 
   @Override
-  public void setLogExpiredConnections(boolean logExpiredConnections) {
-    delegate.setLogExpiredConnections(logExpiredConnections);
+  public void setRegisterMbeans(boolean register) {
+    delegate.setRegisterMbeans(register);
   }
 
   @Override
-  public String getJmxName() {
-    return delegate.getJmxName();
+  public String getPoolName() {
+    return delegate.getPoolName();
   }
 
   @Override
-  public void setJmxName(String jmxName) {
-    delegate.setJmxName(jmxName);
+  public void setPoolName(String poolName) {
+    delegate.setPoolName(poolName);
   }
 
   @Override
-  public boolean getEnableAutoCommitOnReturn() {
-    return delegate.getEnableAutoCommitOnReturn();
+  public ScheduledExecutorService getScheduledExecutor() {
+    return delegate.getScheduledExecutor();
   }
 
   @Override
-  public void setEnableAutoCommitOnReturn(boolean enableAutoCommitOnReturn) {
-    delegate.setEnableAutoCommitOnReturn(enableAutoCommitOnReturn);
+  public void setScheduledExecutor(ScheduledExecutorService executor) {
+    delegate.setScheduledExecutor(executor);
   }
 
   @Override
-  public boolean getRollbackOnReturn() {
-    return delegate.getRollbackOnReturn();
+  public String getSchema() {
+    return delegate.getSchema();
   }
 
   @Override
-  public void setRollbackOnReturn(boolean rollbackOnReturn) {
-    delegate.setRollbackOnReturn(rollbackOnReturn);
+  public void setSchema(String schema) {
+    delegate.setSchema(schema);
   }
 
   @Override
-  public Set<String> getDisconnectionSqlCodes() {
-    return delegate.getDisconnectionSqlCodes();
+  public String getExceptionOverrideClassName() {
+    return delegate.getExceptionOverrideClassName();
   }
 
   @Override
-  public String[] getDisconnectionSqlCodesAsArray() {
-    return delegate.getDisconnectionSqlCodesAsArray();
+  public void setExceptionOverrideClassName(String exceptionOverrideClassName) {
+    delegate.setExceptionOverrideClassName(exceptionOverrideClassName);
   }
 
   @Override
-  public void setDisconnectionSqlCodes(Collection<String> disconnectionSqlCodes) {
-    delegate.setDisconnectionSqlCodes(disconnectionSqlCodes);
+  public ThreadFactory getThreadFactory() {
+    return delegate.getThreadFactory();
   }
 
   @Override
-  public boolean getFastFailValidation() {
-    return delegate.getFastFailValidation();
+  public void setThreadFactory(ThreadFactory threadFactory) {
+    delegate.setThreadFactory(threadFactory);
   }
 
   @Override
-  public void setFastFailValidation(boolean fastFailValidation) {
-    delegate.setFastFailValidation(fastFailValidation);
+  public void copyStateTo(HikariConfig other) {
+    delegate.copyStateTo(other);
   }
 
   @Override
-  public PrintWriter getAbandonedLogWriter() {
-    return delegate.getAbandonedLogWriter();
+  public void validate() {
+    delegate.validate();
   }
 
   @Override
-  public void setAbandonedLogWriter(PrintWriter logWriter) {
-    delegate.setAbandonedLogWriter(logWriter);
+  public ConnectionBuilder createConnectionBuilder() throws SQLException {
+    return delegate.createConnectionBuilder();
   }
 
   @Override
-  public boolean getAbandonedUsageTracking() {
-    return delegate.getAbandonedUsageTracking();
+  public ShardingKeyBuilder createShardingKeyBuilder() throws SQLException {
+    return delegate.createShardingKeyBuilder();
   }
 
-  @Override
-  public void setAbandonedUsageTracking(boolean usageTracking) {
-    delegate.setAbandonedUsageTracking(usageTracking);
-  }
 
-  @Override
-  public void invalidateConnection(Connection connection) {
-    delegate.invalidateConnection(connection);
-  }
-
-  @Override
-  public ObjectName preRegister(MBeanServer server, ObjectName objectName) {
-    return delegate.preRegister(server, objectName);
-  }
-
-  @Override
-  public void postRegister(Boolean registrationDone) {
-    delegate.postRegister(registrationDone);
-  }
-
-  @Override
-  public void preDeregister() throws Exception {
-    delegate.preDeregister();
-  }
-
-  @Override
-  public void postDeregister() {
-    delegate.postDeregister();
-  }
 }
index 67d05f4e1e887e7fec739513f7f103e559e114c3..07488e08e9062bd5ef0229f9693b9f5814731199 100644 (file)
@@ -22,11 +22,13 @@ package org.sonar.db;
 import com.tngtech.java.junit.dataprovider.DataProvider;
 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
 import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import com.zaxxer.hikari.HikariDataSource;
 import java.util.Properties;
-import org.apache.commons.dbcp2.BasicDataSource;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.utils.log.LogTester;
 import org.sonar.db.dialect.PostgreSql;
 import org.sonar.process.logging.LogbackHelper;
 
@@ -38,9 +40,11 @@ import static org.mockito.Mockito.mock;
 @RunWith(DataProviderRunner.class)
 public class DefaultDatabaseTest {
 
-  private LogbackHelper logbackHelper = mock(LogbackHelper.class);
+  private final LogbackHelper logbackHelper = mock(LogbackHelper.class);
   private static final String SONAR_JDBC = "sonar.jdbc.";
 
+  @Rule
+  public final LogTester logTester = new LogTester();
 
   @Test
   public void shouldLoadDefaultValues() {
@@ -54,19 +58,36 @@ public class DefaultDatabaseTest {
   }
 
   @Test
-  public void shouldExtractCommonsDbcpProperties() {
+  public void shouldExtractHikariProperties() {
     Properties props = new Properties();
     props.setProperty("sonar.jdbc.driverClassName", "my.Driver");
     props.setProperty("sonar.jdbc.username", "me");
-    props.setProperty("sonar.jdbc.maxActive", "5");
-    props.setProperty("sonar.jdbc.maxWait", "5000");
+    props.setProperty("sonar.jdbc.maximumPoolSize", "5");
+    props.setProperty("sonar.jdbc.connectionTimeout", "5000");
 
-    Properties commonsDbcpProps = DefaultDatabase.extractCommonsDbcpProperties(props);
+    Properties hikariProps = DefaultDatabase.extractCommonsHikariProperties(props);
 
-    assertThat(commonsDbcpProps.getProperty("username")).isEqualTo("me");
-    assertThat(commonsDbcpProps.getProperty("driverClassName")).isEqualTo("my.Driver");
-    assertThat(commonsDbcpProps.getProperty("maxTotal")).isEqualTo("5");
-    assertThat(commonsDbcpProps.getProperty("maxWaitMillis")).isEqualTo("5000");
+    assertThat(hikariProps.getProperty("dataSource.user")).isEqualTo("me");
+    assertThat(hikariProps.getProperty("driverClassName")).isEqualTo("my.Driver");
+    assertThat(hikariProps.getProperty("maximumPoolSize")).isEqualTo("5");
+    assertThat(hikariProps.getProperty("connectionTimeout")).isEqualTo("5000");
+  }
+
+  @Test
+  public void logWarningIfDeprecatedPropertyUsed() {
+    Properties props = new Properties();
+
+    props.setProperty("sonar.jdbc.maxIdle", "5");
+    props.setProperty("sonar.jdbc.minEvictableIdleTimeMillis", "300000");
+    props.setProperty("sonar.jdbc.timeBetweenEvictionRunsMillis", "1000");
+    props.setProperty("sonar.jdbc.connectionTimeout", "8000");
+
+    DefaultDatabase.extractCommonsHikariProperties(props);
+
+    assertThat(logTester.logs())
+      .contains("Property [sonar.jdbc.maxIdle] has no effect as pool connection implementation changed, check 9.7 upgrade notes.")
+      .contains("Property [sonar.jdbc.minEvictableIdleTimeMillis] has no effect as pool connection implementation changed, check 9.7 upgrade notes.")
+      .contains("Property [sonar.jdbc.timeBetweenEvictionRunsMillis] has no effect as pool connection implementation changed, check 9.7 upgrade notes.");
   }
 
   @Test
@@ -76,21 +97,21 @@ public class DefaultDatabaseTest {
     props.setProperty(jdbcProperty, "100");
     props.setProperty(dbcpProperty, "100");
 
-    Properties commonsDbcpProps = DefaultDatabase.extractCommonsDbcpProperties(props);
+    Properties commonsDbcpProps = DefaultDatabase.extractCommonsHikariProperties(props);
 
     assertThat(commonsDbcpProps.getProperty(removeStart(dbcpProperty, SONAR_JDBC))).isEqualTo("100");
   }
 
   @Test
   @UseDataProvider("sonarJdbcAndDbcpProperties")
-  public void shouldThrowISEIfDuplicatedResolvedPropertiesWithDifferentValue(String jdbcProperty, String dbcpProperty) {
+  public void shouldThrowISEIfDuplicatedResolvedPropertiesWithDifferentValue(String jdbcProperty, String hikariProperty) {
     Properties props = new Properties();
     props.setProperty(jdbcProperty, "100");
-    props.setProperty(dbcpProperty, "200");
+    props.setProperty(hikariProperty, "200");
 
-    assertThatThrownBy(() -> DefaultDatabase.extractCommonsDbcpProperties(props))
+    assertThatThrownBy(() -> DefaultDatabase.extractCommonsHikariProperties(props))
       .isInstanceOf(IllegalStateException.class)
-      .hasMessageContaining(String.format("Duplicate property declaration for resolved jdbc key '%s': conflicting values are", removeStart(dbcpProperty, SONAR_JDBC)));
+      .hasMessageContaining(String.format("Duplicate property declaration for resolved jdbc key '%s': conflicting values are", removeStart(hikariProperty, SONAR_JDBC)));
   }
 
   @Test
@@ -117,14 +138,14 @@ public class DefaultDatabaseTest {
     settings.setProperty("sonar.jdbc.driverClassName", "org.h2.Driver");
     settings.setProperty("sonar.jdbc.username", "sonar");
     settings.setProperty("sonar.jdbc.password", "sonar");
-    settings.setProperty("sonar.jdbc.maxActive", "1");
+    settings.setProperty("sonar.jdbc.maximumPoolSize", "1");
 
     DefaultDatabase db = new DefaultDatabase(logbackHelper, settings);
     db.start();
     db.stop();
 
     assertThat(db.getDialect().getId()).isEqualTo("h2");
-    assertThat(((BasicDataSource) db.getDataSource()).getMaxTotal()).isOne();
+    assertThat(((HikariDataSource) db.getDataSource()).getMaximumPoolSize()).isOne();
   }
 
   @Test
@@ -152,8 +173,8 @@ public class DefaultDatabaseTest {
   @DataProvider
   public static Object[][] sonarJdbcAndDbcpProperties() {
     return new Object[][] {
-      {"sonar.jdbc.maxActive", "sonar.jdbc.maxTotal"},
-      {"sonar.jdbc.maxWait", "sonar.jdbc.maxWaitMillis"}
+      {"sonar.jdbc.maxWait", "sonar.jdbc.connectionTimeout"},
+      {"sonar.jdbc.maxActive", "sonar.jdbc.maximumPoolSize"}
     };
   }
 }
index 80f897169a1059043d11048636ad0789472c7d90..4d657ace44c1629c84bd60c97f4f2965ba3f2111 100644 (file)
@@ -23,6 +23,7 @@ import java.lang.reflect.Method;
 import java.sql.Connection;
 import java.sql.SQLException;
 import org.junit.Assert;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.sonar.test.TestUtils;
 
@@ -51,16 +52,6 @@ public class InvocationUtilsTest {
     Assert.assertThrows(SQLException.class, () -> InvocationUtils.invokeQuietly(target, prepareStatement, new Object[] {failSql}));
   }
 
-  @Test
-  public void should_wrap_undeclared_exception() throws Throwable {
-    Connection target = mock(Connection.class);
-    String failSql = "any sql";
-    when(target.prepareStatement(failSql)).thenThrow(new SQLException("Expected"));
-    Method wait = Object.class.getMethod("wait");
-
-    Assert.assertThrows(IllegalStateException.class, () -> InvocationUtils.invokeQuietly(target, wait, new Object[0]));
-  }
-
   @Test
   public void only_static_methods() {
     assertThat(TestUtils.hasOnlyPrivateConstructors(InvocationUtils.class)).isTrue();
index cd1db27b42ce3bbf776e9a5235f30a453fa4dc90..5a6a09a5d27831c507e34e5e27936347e1b0036d 100644 (file)
@@ -19,6 +19,8 @@
  */
 package org.sonar.db.profiling;
 
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
 import java.io.ByteArrayInputStream;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
@@ -27,7 +29,9 @@ import java.sql.Date;
 import java.sql.PreparedStatement;
 import java.sql.Statement;
 import java.sql.Timestamp;
-import org.apache.commons.dbcp2.BasicDataSource;
+import java.util.Properties;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
 import org.junit.Rule;
 import org.junit.Test;
 import org.sonar.api.utils.log.LogTester;
@@ -36,6 +40,7 @@ import org.sonar.api.utils.log.LoggerLevel;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 public class ProfiledDataSourceTest {
@@ -43,7 +48,7 @@ public class ProfiledDataSourceTest {
   @Rule
   public LogTester logTester = new LogTester();
 
-  BasicDataSource originDataSource = mock(BasicDataSource.class);
+  HikariDataSource originDataSource = mock(HikariDataSource.class);
 
   @Test
   public void execute_and_log_statement() throws Exception {
@@ -59,7 +64,7 @@ public class ProfiledDataSourceTest {
 
     ProfiledDataSource underTest = new ProfiledDataSource(originDataSource, ProfiledConnectionInterceptor.INSTANCE);
 
-    assertThat(underTest.getUrl()).isNull();
+    assertThat(underTest.getJdbcUrl()).isNull();
     assertThat(underTest.getConnection().getClientInfo()).isNull();
     final Statement statementProxy = underTest.getConnection().createStatement();
     assertThat(statementProxy.getConnection()).isNull();
@@ -90,7 +95,7 @@ public class ProfiledDataSourceTest {
 
     ProfiledDataSource ds = new ProfiledDataSource(originDataSource, ProfiledConnectionInterceptor.INSTANCE);
 
-    assertThat(ds.getUrl()).isNull();
+    assertThat(ds.getJdbcUrl()).isNull();
     assertThat(ds.getConnection().getClientInfo()).isNull();
     PreparedStatement preparedStatementProxy = ds.getConnection().prepareStatement(sqlWithParams);
     preparedStatementProxy.setInt(1, param1);
@@ -121,7 +126,7 @@ public class ProfiledDataSourceTest {
 
     ProfiledDataSource ds = new ProfiledDataSource(originDataSource, ProfiledConnectionInterceptor.INSTANCE);
 
-    assertThat(ds.getUrl()).isNull();
+    assertThat(ds.getJdbcUrl()).isNull();
     assertThat(ds.getConnection().getClientInfo()).isNull();
     PreparedStatement preparedStatementProxy = ds.getConnection().prepareStatement(sqlWithParams);
     assertThat(preparedStatementProxy.getConnection()).isNull();
@@ -144,7 +149,38 @@ public class ProfiledDataSourceTest {
     for (Method method : ProfiledDataSource.class.getDeclaredMethods()) {
       if (method.getParameterTypes().length == 0 && Modifier.isPublic(method.getModifiers())) {
         method.invoke(proxy);
+      } else if (method.getParameterTypes().length == 1 && method.getParameterTypes()[0].equals(String.class) && Modifier.isPublic(method.getModifiers())) {
+        method.invoke(proxy, "test");
+      } else if (method.getParameterTypes().length == 1 && method.getParameterTypes()[0].equals(Boolean.TYPE) && Modifier.isPublic(method.getModifiers())) {
+        method.invoke(proxy, true);
+      } else if (method.getParameterTypes().length == 1 && method.getParameterTypes()[0].equals(Long.TYPE) && Modifier.isPublic(method.getModifiers())) {
+        method.invoke(proxy, 1L);
+      } else if (method.getParameterTypes().length == 1 && method.getParameterTypes()[0].equals(Integer.TYPE) && Modifier.isPublic(method.getModifiers())) {
+        method.invoke(proxy, 1);
       }
     }
+
+    proxy.addHealthCheckProperty("test", "test");
+    verify(originDataSource).addHealthCheckProperty("test", "test");
+
+    var schedulerMock = mock(ScheduledExecutorService.class);
+    proxy.setScheduledExecutor(schedulerMock);
+    verify(originDataSource).setScheduledExecutor(schedulerMock);
+
+    var hikariConfigMock = mock(HikariConfig.class);
+    proxy.copyStateTo(hikariConfigMock);
+    verify(originDataSource).copyStateTo(hikariConfigMock);
+
+    var threadFactoryMock = mock(ThreadFactory.class);
+    proxy.setThreadFactory(threadFactoryMock);
+    verify(originDataSource).setThreadFactory(threadFactoryMock);
+
+    var properties = new Properties();
+    proxy.setHealthCheckProperties(properties);
+    verify(originDataSource).setHealthCheckProperties(properties);
+
+    proxy.setDataSourceProperties(properties);
+    verify(originDataSource).setDataSourceProperties(properties);
+
   }
 }
index 3a46a6b56e28b694b3a128c662de323b20a3711d..926b4e3458310d6e102f230daa8de2adf17f2b1d 100644 (file)
  */
 package org.sonar.db;
 
+import com.zaxxer.hikari.HikariDataSource;
 import java.io.PrintWriter;
 import java.sql.Connection;
 import java.sql.SQLException;
 import javax.sql.DataSource;
-import org.apache.commons.dbcp2.BasicDataSource;
 import org.apache.commons.io.output.NullWriter;
 import org.apache.ibatis.io.Resources;
 import org.apache.ibatis.jdbc.ScriptRunner;
@@ -38,7 +38,7 @@ import static java.lang.String.format;
 public class CoreH2Database implements Database {
   private static final String IGNORED_KEYWORDS_OPTION = ";NON_KEYWORDS=VALUE";
   private final String name;
-  private BasicDataSource datasource;
+  private HikariDataSource datasource;
 
   /**
    * IMPORTANT: change DB name in order to not conflict with {@link DefaultDatabaseTest}
@@ -54,11 +54,11 @@ public class CoreH2Database implements Database {
 
   private void startDatabase() {
     try {
-      datasource = new BasicDataSource();
+      datasource = new HikariDataSource();
       datasource.setDriverClassName("org.h2.Driver");
       datasource.setUsername("sonar");
       datasource.setPassword("sonar");
-      datasource.setUrl("jdbc:h2:mem:" + name);
+      datasource.setJdbcUrl("jdbc:h2:mem:" + name);
     } catch (Exception e) {
       throw new IllegalStateException("Fail to start H2", e);
     }
@@ -93,11 +93,7 @@ public class CoreH2Database implements Database {
 
   @Override
   public void stop() {
-    try {
-      datasource.close();
-    } catch (SQLException e) {
-      // Ignore error
-    }
+    datasource.close();
   }
 
   public DataSource getDataSource() {
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DatabaseMBean.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DatabaseMBean.java
new file mode 100644 (file)
index 0000000..77012a0
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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 3 of the License, or (at your option) any later version.
+ *
+ * This program 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.db;
+
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.HikariPoolMXBean;
+import java.util.function.Function;
+import org.sonar.process.systeminfo.BaseSectionMBean;
+
+import static java.util.Optional.ofNullable;
+
+public abstract class DatabaseMBean extends BaseSectionMBean {
+
+  private final DbClient dbClient;
+
+  protected DatabaseMBean(DbClient dbClient) {
+    this.dbClient = dbClient;
+  }
+
+  public int getPoolActiveConnections() {
+    return getIntPropertyOrZero(HikariPoolMXBean::getActiveConnections);
+  }
+
+  public int getPoolTotalConnections() {
+    return getIntPropertyOrZero(HikariPoolMXBean::getTotalConnections);
+  }
+
+  public int getPoolIdleConnections() {
+    return getIntPropertyOrZero(HikariPoolMXBean::getIdleConnections);
+  }
+
+  public int getPoolMaxConnections() {
+    return hikariDatasource().getHikariConfigMXBean().getMaximumPoolSize();
+  }
+
+  public int getPoolMinIdleConnections() {
+    return hikariDatasource().getHikariConfigMXBean().getMinimumIdle();
+  }
+
+  public long getPoolMaxLifeTimeMillis() {
+    return hikariDatasource().getHikariConfigMXBean().getMaxLifetime();
+  }
+
+  public long getPoolMaxWaitMillis() {
+    return hikariDatasource().getHikariConfigMXBean().getConnectionTimeout();
+  }
+
+  private HikariDataSource hikariDatasource() {
+    return (HikariDataSource) dbClient.getDatabase().getDataSource();
+  }
+
+  private int getIntPropertyOrZero(Function<HikariPoolMXBean, Integer> function) {
+    return ofNullable(hikariDatasource().getHikariPoolMXBean())
+        .map(function)
+        .orElse(0);
+  }
+
+}
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/DatabaseMBeanTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/DatabaseMBeanTest.java
new file mode 100644 (file)
index 0000000..65cb41e
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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 3 of the License, or (at your option) any later version.
+ *
+ * This program 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.db;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo.Section;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DatabaseMBeanTest {
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private final FakeDatabaseMBean underTest = new FakeDatabaseMBean(dbTester.getDbClient());
+
+  @Test
+  public void verify_mBean() {
+
+    assertThat(underTest.getPoolActiveConnections()).isNotNegative();
+    assertThat(underTest.getPoolIdleConnections()).isNotNegative();
+    assertThat(underTest.getPoolMaxConnections()).isNotNegative();
+    assertThat(underTest.getPoolMaxLifeTimeMillis()).isNotNegative();
+    assertThat(underTest.getPoolMinIdleConnections()).isNotNegative();
+    assertThat(underTest.getPoolTotalConnections()).isNotNegative();
+    assertThat(underTest.getPoolMaxWaitMillis()).isNotNegative();
+    assertThat(underTest.name()).isEqualTo("FakeMBean");
+  }
+
+  private static class FakeDatabaseMBean extends DatabaseMBean {
+
+    protected FakeDatabaseMBean(DbClient dbClient) {
+      super(dbClient);
+    }
+
+    @Override
+    protected String name() {
+      return "FakeMBean";
+    }
+
+    @Override
+    public Section toProtobuf() {
+      return Section.newBuilder().build();
+    }
+  }
+}
index 1e2f4154dd80991b406c533ae40ee2710d2aba5a..8eb0c9f87b1be4d26a22bfbd16b393c09b0c6b88 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.db;
 
+import com.zaxxer.hikari.HikariDataSource;
 import java.sql.Connection;
 import java.sql.SQLException;
 import java.util.Arrays;
@@ -27,7 +28,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.stream.Stream;
 import javax.annotation.Nullable;
-import org.apache.commons.dbcp2.BasicDataSource;
 import org.sonar.api.utils.System2;
 import org.sonar.core.util.SequenceUuidFactory;
 import org.sonar.core.util.UuidFactory;
@@ -308,7 +308,7 @@ public class DbTester extends AbstractDbTester<TestDbImpl> {
   }
 
   public String getUrl() {
-    return ((BasicDataSource) db.getDatabase().getDataSource()).getUrl();
+    return ((HikariDataSource) db.getDatabase().getDataSource()).getJdbcUrl();
   }
 
   private static class DbSessionConnectionSupplier implements ConnectionSupplier {
index dc3793942dd27afeead9060dfe1de80ce0817f64..86454696cb78c0065240a39f1917e0badfd1585e 100644 (file)
@@ -72,14 +72,11 @@ All these MBeans are read-only. It's not possible to modify or reset their value
 | Attribute Name | Description
 | ---|---
 | MigrationStatus | Possible values are: UP_TO_DATE, REQUIRES_UPGRADE, REQUIRES_DOWNGRADE, FRESH_INSTALL (only available for WebServer).
-| PoolActiveConnections        | Number of active database connections
+| PoolActiveConnections | Number of active database connections
 | PoolIdleConnections | Number of database connections waiting to be used
-| PoolInitialSize | Initial size of the database connections pool.
-| PoolMaxActiveConnections | Maximum number of active database connections
-| PoolMaxIdleConnections | Maximum number of database connections waiting to be used
-| PoolMaxWaitMillis | In milliseconds
-| PoolRemoveAbandoned | Possible values : true, false
-| PoolRemoveAbandonedTimeoutSeconds | In Seconds
+| PoolMaxConnections | Maximum number of active database connections
+| PoolTotalConnections | Total number of connections currently in the pool
+| PoolMaxWaitMillis | Maximum number of milliseconds that a client will wait for a connection from the pool
 
 [[collapse]]
 | ## SonarQube MBean
index 3499865db4dfeef52ea87de8a67ae375c9febba3..5761e3f41f9d1168caa9413cf19e306327146a51 100644 (file)
@@ -3,6 +3,11 @@ title: Release Upgrade Notes
 url: /setup/upgrade-notes/
 ---
 
+## Release 9.7 Upgrade notes
+
+**Database Connection Pool Changed**  
+Database connection pool has been changed, following properties will no longer have effect: sonar.jdbc.maxIdle, sonar.jdbc.minEvictableIdleTimeMillis, sonar.jdbc.timeBetweenEvictionRunsMillis. See documentation for changes in [monitoring database connection pool](/instance-administration/monitoring/). 
+
 ## Release 9.6 Upgrade notes
 **Microsoft SQL Server changes in configuration and Integrated Authentication**  
 * If your Microsoft SQL Server doesn't support encryption, you will need to add `encrypt=false` to the JDBC URL connection string. ([SONAR-16249](https://jira.sonarsource.com/browse/SONAR-16249)).
index a23af65a16305365787c8fe80c44c68ce47ba135..82a0f51c2355097b9915afd16f0606c0bc032103 100644 (file)
@@ -55,11 +55,8 @@ public class ProcessProperties {
     JDBC_PASSWORD("sonar.jdbc.password", ""),
     JDBC_DRIVER_PATH("sonar.jdbc.driverPath"),
     JDBC_MAX_ACTIVE("sonar.jdbc.maxActive", "60"),
-    JDBC_MAX_IDLE("sonar.jdbc.maxIdle", "5"),
-    JDBC_MIN_IDLE("sonar.jdbc.minIdle", "2"),
-    JDBC_MAX_WAIT("sonar.jdbc.maxWait", "5000"),
-    JDBC_MIN_EVICTABLE_IDLE_TIME_MILLIS("sonar.jdbc.minEvictableIdleTimeMillis", "600000"),
-    JDBC_TIME_BETWEEN_EVICTION_RUNS_MILLIS("sonar.jdbc.timeBetweenEvictionRunsMillis", "30000"),
+    JDBC_MIN_IDLE("sonar.jdbc.minIdle", "10"),
+    JDBC_MAX_WAIT("sonar.jdbc.maxWait", "8000"),
     JDBC_EMBEDDED_PORT("sonar.embeddedDatabase.port"),
 
     PATH_DATA("sonar.path.data", "data"),
diff --git a/server/sonar-process/src/main/java/org/sonar/process/systeminfo/BaseSectionMBean.java b/server/sonar-process/src/main/java/org/sonar/process/systeminfo/BaseSectionMBean.java
new file mode 100644 (file)
index 0000000..b88e650
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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 3 of the License, or (at your option) any later version.
+ *
+ * This program 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.process.systeminfo;
+
+import org.sonar.api.Startable;
+import org.sonar.process.Jmx;
+
+/**
+ * Base implementation of a {@link SystemInfoSection}
+ * that is exported as a JMX bean
+ */
+public abstract class BaseSectionMBean implements SystemInfoSection, Startable {
+
+  /**
+   * Auto-registers to MBean server
+   */
+  @Override
+  public void start() {
+    Jmx.register(objectName(), this);
+  }
+
+  /**
+   * Unregister, if needed
+   */
+  @Override
+  public void stop() {
+    Jmx.unregister(objectName());
+  }
+
+  public String objectName() {
+    return "SonarQube:name=" + name();
+  }
+
+  /**
+   * Name of section in System Info page
+   */
+  protected abstract String name();
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/systeminfo/BaseSectionMBeanTest.java b/server/sonar-process/src/test/java/org/sonar/process/systeminfo/BaseSectionMBeanTest.java
new file mode 100644 (file)
index 0000000..f77f6ba
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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 3 of the License, or (at your option) any later version.
+ *
+ * This program 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.process.systeminfo;
+
+import java.lang.management.ManagementFactory;
+import javax.annotation.CheckForNull;
+import javax.management.InstanceNotFoundException;
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
+import org.junit.Test;
+import org.sonar.process.jmx.FakeMBean;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo.Section;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class BaseSectionMBeanTest {
+
+  private static final String EXPECTED_NAME = "SonarQube:name=FakeName";
+
+  private final FakeBaseSectionMBean underTest = new FakeBaseSectionMBean();
+
+  @Test
+  public void verify_mbean() throws Exception {
+    assertThat(lookupMBean()).isNull();
+
+    underTest.start();
+    assertThat(lookupMBean()).isNotNull();
+
+    underTest.stop();
+    assertThat(lookupMBean()).isNull();
+
+    assertThat(underTest.name()).isEqualTo("FakeName");
+    assertThat(underTest.toProtobuf()).isNotNull();
+  }
+
+  @CheckForNull
+  private ObjectInstance lookupMBean() throws Exception {
+    try {
+      return ManagementFactory.getPlatformMBeanServer().getObjectInstance(new ObjectName(EXPECTED_NAME));
+    } catch (InstanceNotFoundException e) {
+      return null;
+    }
+  }
+
+  private static class FakeBaseSectionMBean extends BaseSectionMBean implements FakeMBean {
+
+    @Override
+    protected String name() {
+      return "FakeName";
+    }
+
+    @Override
+    public Section toProtobuf() {
+      return Section.newBuilder().build();
+    }
+
+    @Override
+    public void foo() {
+      // nothing to do
+    }
+  }
+}
index 622bbfb5a959717578d8fca04581a40b2b3025cf..6443e5c4524438e18d9aab9c6bf92beb656b575c 100644 (file)
@@ -28,7 +28,7 @@ dependencies {
   compile 'org.apache.httpcomponents:httpclient'
   compile 'org.apache.logging.log4j:log4j-api'
   compile 'org.apache.tomcat.embed:tomcat-embed-core'
-  compile 'org.apache.commons:commons-dbcp2'
+  compile 'com.zaxxer:HikariCP'
   compile 'org.slf4j:jul-to-slf4j'
   compile 'org.slf4j:slf4j-api'
   compile 'org.sonarsource.api.plugin:sonar-plugin-api'
diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/monitoring/BaseSectionMBean.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/platform/monitoring/BaseSectionMBean.java
deleted file mode 100644 (file)
index b5afeaf..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2022 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program 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 3 of the License, or (at your option) any later version.
- *
- * This program 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 program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-import org.sonar.api.Startable;
-import org.sonar.process.Jmx;
-import org.sonar.process.systeminfo.SystemInfoSection;
-
-/**
- * Base implementation of a {@link SystemInfoSection}
- * that is exported as a JMX bean
- */
-public abstract class BaseSectionMBean implements SystemInfoSection, Startable {
-
-  /**
-   * Auto-registers to MBean server
-   */
-  @Override
-  public void start() {
-    Jmx.register(objectName(), this);
-  }
-
-  /**
-   * Unregister, if needed
-   */
-  @Override
-  public void stop() {
-    Jmx.unregister(objectName());
-  }
-
-  String objectName() {
-    return "SonarQube:name=" + name();
-  }
-
-  /**
-   * Name of section in System Info page
-   */
-  abstract String name();
-}
index 8c59cbc012893e44b9aee788a6408ac2082db827..4d55ae4669399b197c4b370ebbc7bf9197a35760 100644 (file)
@@ -19,9 +19,9 @@
  */
 package org.sonar.server.platform.monitoring;
 
-import org.apache.commons.dbcp2.BasicDataSource;
 import org.sonar.api.SonarQubeSide;
 import org.sonar.api.SonarRuntime;
+import org.sonar.db.DatabaseMBean;
 import org.sonar.db.DbClient;
 import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo.Section;
 import org.sonar.server.platform.db.migration.version.DatabaseVersion;
@@ -31,15 +31,14 @@ import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;
 /**
  * Information about database connection pool
  */
-public class DbConnectionSection extends BaseSectionMBean implements DbConnectionSectionMBean {
+public class DbConnectionSection extends DatabaseMBean implements DbConnectionSectionMBean {
 
   private final DatabaseVersion dbVersion;
-  private final DbClient dbClient;
   private final SonarRuntime runtime;
 
   public DbConnectionSection(DatabaseVersion dbVersion, DbClient dbClient, SonarRuntime runtime) {
+    super(dbClient);
     this.dbVersion = dbVersion;
-    this.dbClient = dbClient;
     this.runtime = runtime;
   }
 
@@ -53,51 +52,6 @@ public class DbConnectionSection extends BaseSectionMBean implements DbConnectio
     return dbVersion.getStatus().name();
   }
 
-  @Override
-  public int getPoolActiveConnections() {
-    return commonsDbcp().getNumActive();
-  }
-
-  @Override
-  public int getPoolMaxActiveConnections() {
-    return commonsDbcp().getMaxTotal();
-  }
-
-  @Override
-  public int getPoolIdleConnections() {
-    return commonsDbcp().getNumIdle();
-  }
-
-  @Override
-  public int getPoolMaxIdleConnections() {
-    return commonsDbcp().getMaxIdle();
-  }
-
-  @Override
-  public int getPoolMinIdleConnections() {
-    return commonsDbcp().getMinIdle();
-  }
-
-  @Override
-  public int getPoolInitialSize() {
-    return commonsDbcp().getInitialSize();
-  }
-
-  @Override
-  public long getPoolMaxWaitMillis() {
-    return commonsDbcp().getMaxWaitMillis();
-  }
-
-  @Override
-  public boolean getPoolRemoveAbandoned() {
-    return commonsDbcp().getRemoveAbandonedOnBorrow();
-  }
-
-  @Override
-  public int getPoolRemoveAbandonedTimeoutSeconds() {
-    return commonsDbcp().getRemoveAbandonedTimeout();
-  }
-
   @Override
   public Section toProtobuf() {
     Section.Builder protobuf = Section.newBuilder();
@@ -108,18 +62,13 @@ public class DbConnectionSection extends BaseSectionMBean implements DbConnectio
   }
 
   private void completePoolAttributes(Section.Builder protobuf) {
+    setAttribute(protobuf, "Pool Total Connections", getPoolTotalConnections());
     setAttribute(protobuf, "Pool Active Connections", getPoolActiveConnections());
-    setAttribute(protobuf, "Pool Max Connections", getPoolMaxActiveConnections());
-    setAttribute(protobuf, "Pool Initial Size", getPoolInitialSize());
     setAttribute(protobuf, "Pool Idle Connections", getPoolIdleConnections());
+    setAttribute(protobuf, "Pool Max Connections", getPoolMaxConnections());
     setAttribute(protobuf, "Pool Min Idle Connections", getPoolMinIdleConnections());
-    setAttribute(protobuf, "Pool Max Idle Connections", getPoolMaxIdleConnections());
     setAttribute(protobuf, "Pool Max Wait (ms)", getPoolMaxWaitMillis());
-    setAttribute(protobuf, "Pool Remove Abandoned", getPoolRemoveAbandoned());
-    setAttribute(protobuf, "Pool Remove Abandoned Timeout (seconds)", getPoolRemoveAbandonedTimeoutSeconds());
+    setAttribute(protobuf, "Pool Max Lifetime (ms)", getPoolMaxLifeTimeMillis());
   }
 
-  private BasicDataSource commonsDbcp() {
-    return (BasicDataSource) dbClient.getDatabase().getDataSource();
-  }
 }
index 59fccd64b463da1a6904a99f103d7e7454e40fac..44144825efcd41a3261ef5fb08cf6ebdd146d4e5 100644 (file)
@@ -27,21 +27,24 @@ public interface DbConnectionSectionMBean {
   String getMigrationStatus();
 
   /**
-   *
+   * Get the number of currently active connections in the pool.
    */
   int getPoolActiveConnections();
 
   /**
-   * The maximum number of active connections that can be allocated from this pool at the same time, or negative for no limit.
+   * The maximum number of connections that can be allocated from this pool at the same time, or negative for no limit.
    */
-  int getPoolMaxActiveConnections();
+  int getPoolMaxConnections();
 
-  int getPoolIdleConnections();
+  /**
+   * Total number of connections currently in the pool 
+   */
+  int getPoolTotalConnections();
 
   /**
-   * The maximum number of connections that can remain idle in the pool, without extra ones being released, or negative for no limit.
+   * Get the number of currently idle connections in the pool.
    */
-  int getPoolMaxIdleConnections();
+  int getPoolIdleConnections();
 
   /**
    * The minimum number of connections that can remain idle in the pool, without extra ones being created, or zero to create none.
@@ -49,9 +52,9 @@ public interface DbConnectionSectionMBean {
   int getPoolMinIdleConnections();
 
   /**
-   * The initial number of connections that are created when the pool is started.
+   * Maximum lifetime of a connection in the pool.
    */
-  int getPoolInitialSize();
+  long getPoolMaxLifeTimeMillis();
 
   /**
    * The maximum number of milliseconds that the pool will wait
@@ -59,13 +62,4 @@ public interface DbConnectionSectionMBean {
    */
   long getPoolMaxWaitMillis();
 
-  /**
-   * Flag to remove abandoned connections if they exceed the {@link #getPoolRemoveAbandonedTimeoutSeconds()}.
-   */
-  boolean getPoolRemoveAbandoned();
-
-  /**
-   * Timeout in seconds before an abandoned connection can be removed.
-   */
-  int getPoolRemoveAbandonedTimeoutSeconds();
 }
index 0c728efc507ce040f2c09978136e7d0390b08d60..457b63779517b402b033bd75ff3479e4902d5f51 100644 (file)
@@ -29,6 +29,7 @@ import org.sonar.api.platform.Server;
 import org.sonar.api.security.SecurityRealm;
 import org.sonar.api.server.authentication.IdentityProvider;
 import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.process.systeminfo.BaseSectionMBean;
 import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
 import org.sonar.server.authentication.IdentityProviderRepository;
 import org.sonar.server.log.ServerLogging;
index 556c5a7544f3d9371f3177e9532f2f4e7b63b47d..cb2ed96362672f9121578d29eae5748425b8ea5b 100644 (file)
@@ -30,7 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 
 public class BaseSectionMBeanTest {
 
-  private FakeSection underTest = new FakeSection();
+  private final FakeSection underTest = new FakeSection();
 
   @Test
   public void test_registration() throws Exception {
index 1363e1b15e2a66efe4e6f2310841cafd42b6ab82..1086c1c1a8edb7507d599bfc27ff8578734f387b 100644 (file)
@@ -38,9 +38,9 @@ public class DbConnectionSectionTest {
   @Rule
   public DbTester dbTester = DbTester.create(System2.INSTANCE);
 
-  private DatabaseVersion databaseVersion = mock(DatabaseVersion.class);
-  private SonarRuntime runtime = mock(SonarRuntime.class);
-  private DbConnectionSection underTest = new DbConnectionSection(databaseVersion, dbTester.getDbClient(), runtime);
+  private final DatabaseVersion databaseVersion = mock(DatabaseVersion.class);
+  private final SonarRuntime runtime = mock(SonarRuntime.class);
+  private final DbConnectionSection underTest = new DbConnectionSection(databaseVersion, dbTester.getDbClient(), runtime);
 
   @Test
   public void jmx_name_is_not_empty() {
@@ -50,13 +50,12 @@ public class DbConnectionSectionTest {
   @Test
   public void pool_info() {
     ProtobufSystemInfo.Section section = underTest.toProtobuf();
-    assertThat(attribute(section, "Pool Max Connections").getLongValue()).isGreaterThan(0L);
-    assertThat(attribute(section, "Pool Idle Connections").getLongValue()).isGreaterThanOrEqualTo(0L);
-    assertThat(attribute(section, "Pool Min Idle Connections").getLongValue()).isGreaterThanOrEqualTo(0L);
-    assertThat(attribute(section, "Pool Max Idle Connections").getLongValue()).isGreaterThanOrEqualTo(0L);
-    assertThat(attribute(section, "Pool Max Wait (ms)")).isNotNull();
-    assertThat(attribute(section, "Pool Remove Abandoned")).isNotNull();
-    assertThat(attribute(section, "Pool Remove Abandoned Timeout (seconds)").getLongValue()).isGreaterThanOrEqualTo(0L);
+    assertThat(attribute(section, "Pool Total Connections").getLongValue()).isNotNegative();
+    assertThat(attribute(section, "Pool Active Connections").getLongValue()).isNotNegative();
+    assertThat(attribute(section, "Pool Idle Connections").getLongValue()).isNotNegative();
+    assertThat(attribute(section, "Pool Max Connections").getLongValue()).isNotNegative();
+    assertThat(attribute(section, "Pool Min Idle Connections")).isNotNull();
+    assertThat(attribute(section, "Pool Max Lifetime (ms)")).isNotNull();
   }
 
   @Test
@@ -64,7 +63,7 @@ public class DbConnectionSectionTest {
     when(runtime.getSonarQubeSide()).thenReturn(SonarQubeSide.COMPUTE_ENGINE);
     assertThat(underTest.toProtobuf().getName()).isEqualTo("Compute Engine Database Connection");
 
-    when(runtime.getSonarQubeSide()).thenReturn(SonarQubeSide.SERVER );
+    when(runtime.getSonarQubeSide()).thenReturn(SonarQubeSide.SERVER);
     assertThat(underTest.toProtobuf().getName()).isEqualTo("Web Database Connection");
   }
 }
index efc1955fd271569576ce415d864596b4d768d595..6374fee3d5a49c67416d589ef7ab4f3b5fe913eb 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.platform.monitoring;
 
+import org.sonar.process.systeminfo.BaseSectionMBean;
 import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
 
 public class FakeSection extends BaseSectionMBean implements FakeSectionMBean {
index e862e0632b79b3a2710414f362f8dcf1e16c94d0..d24fd2dc23ac4a838ffc93d983a65baff16af8e5 100644 (file)
 
 # The minimum number of connections that can remain idle in the pool,
 # without extra ones being created, or zero to create none.
-#sonar.jdbc.minIdle=2
+#sonar.jdbc.minIdle=10
 
 # The maximum number of milliseconds that the pool will wait (when there
 # are no available connections) for a connection to be returned before
 # throwing an exception, or <= 0 to wait indefinitely.
-#sonar.jdbc.maxWait=5000
-
-#sonar.jdbc.minEvictableIdleTimeMillis=600000
-#sonar.jdbc.timeBetweenEvictionRunsMillis=30000
+#sonar.jdbc.maxWait=8000
 
 
 
 # The default value is 25.
 #sonar.web.http.acceptCount=25
 
-# The number of milliseconds this Connector will wait for another HTTP request before closing the 
+# The number of milliseconds this Connector will wait for another HTTP request before closing the
 # connection. The default value is to use the value that has been set for the connectionTimeout 
 # attribute. Use a value of -1 to indicate no (i.e. infinite) timeout.
 # The default value is 60000 (ms).