Merge branch 'stable-2.14' into stable-2.15

* stable-2.14:
  Replace Apache commons-dbcp with HikariCP
  Periodically remove older database entries
  Do not remove entries from local DB
  Remove entries from deleted projects asynchronously
  Upgrade mockito to latest version
  Convert local variable to field
  Remove unneeded JDBC driver class name
  Remove unneeded annotation
  Use already defined constant
  Inline one-time use variables
  Update bazlets to latest stable-2.14 to use 2.14.11 API
  Fix index creation
  Update bazlets to latest stable-2.14 to use 2.14.10 API
  Add indexes to speed up queries
  Configure DB connection pool
  Simplify sorting of query events result
  Avoid using non portable separator character
  Use Java Files instead of Guava one
  Simplify configuration
  Make default query more readable
  Format build files with buildifier 0.12.0
  Use built-in log formatter
  SQLQueryMaker: add order-by clause to the query
  EventsLogIT: Format with google-java-format 1.6

Change-Id: Ie204612e9e4f37215b51066e91adf6f2ca5f0843
diff --git a/BUILD b/BUILD
index da14708..04f201a 100644
--- a/BUILD
+++ b/BUILD
@@ -1,9 +1,9 @@
 load("//tools/bzl:junit.bzl", "junit_tests")
 load(
     "//tools/bzl:plugin.bzl",
-    "gerrit_plugin",
     "PLUGIN_DEPS",
     "PLUGIN_TEST_DEPS",
+    "gerrit_plugin",
 )
 
 gerrit_plugin(
@@ -17,6 +17,7 @@
         "Gerrit-HttpModule: com.ericsson.gerrit.plugins.eventslog.HttpModule",
     ],
     resources = glob(["src/main/resources/**/*"]),
+    deps = ["@hikaricp//jar"],
 )
 
 junit_tests(
@@ -36,5 +37,6 @@
     exports = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [
         ":events-log__plugin",
         "@mockito//jar",
+        "@hikaricp//jar",
     ],
 )
diff --git a/bazlets.bzl b/bazlets.bzl
index e14e488..f97b72c 100644
--- a/bazlets.bzl
+++ b/bazlets.bzl
@@ -1,17 +1,16 @@
 NAME = "com_googlesource_gerrit_bazlets"
 
 def load_bazlets(
-    commit,
-    local_path = None
-  ):
-  if not local_path:
-      native.git_repository(
-          name = NAME,
-          remote = "https://gerrit.googlesource.com/bazlets",
-          commit = commit,
-      )
-  else:
-      native.local_repository(
-          name = NAME,
-          path = local_path,
-      )
+        commit,
+        local_path = None):
+    if not local_path:
+        native.git_repository(
+            name = NAME,
+            remote = "https://gerrit.googlesource.com/bazlets",
+            commit = commit,
+        )
+    else:
+        native.local_repository(
+            name = NAME,
+            path = local_path,
+        )
diff --git a/external_plugin_deps.bzl b/external_plugin_deps.bzl
index e26b362..4ca722d 100644
--- a/external_plugin_deps.bzl
+++ b/external_plugin_deps.bzl
@@ -1,33 +1,39 @@
 load("//tools/bzl:maven_jar.bzl", "maven_jar")
 
 def external_plugin_deps():
-  maven_jar(
-    name = "mockito",
-    artifact = "org.mockito:mockito-core:2.15.0",
-    sha1 = "b84bfbbc29cd22c9529409627af6ea2897f4fa85",
-    deps = [
-      "@byte_buddy//jar",
-      "@byte_buddy_agent//jar",
-      "@objenesis//jar",
-    ],
-  )
+    maven_jar(
+        name = "mockito",
+        artifact = "org.mockito:mockito-core:2.21.0",
+        sha1 = "cdd1d0d5b2edbd2a7040735ccf88318c031f458b",
+        deps = [
+            "@byte_buddy//jar",
+            "@byte_buddy_agent//jar",
+            "@objenesis//jar",
+        ],
+    )
 
-  BYTE_BUDDY_VER = "1.7.9"
+    BYTE_BUDDY_VER = "1.8.15"
 
-  maven_jar(
-    name = "byte_buddy",
-    artifact = "net.bytebuddy:byte-buddy:" + BYTE_BUDDY_VER,
-    sha1 = "51218a01a882c04d0aba8c028179cce488bbcb58",
-  )
+    maven_jar(
+        name = "byte_buddy",
+        artifact = "net.bytebuddy:byte-buddy:" + BYTE_BUDDY_VER,
+        sha1 = "cb36fe3c70ead5fcd016856a7efff908402d86b8",
+    )
 
-  maven_jar(
-    name = "byte_buddy_agent",
-    artifact = "net.bytebuddy:byte-buddy-agent:" + BYTE_BUDDY_VER,
-    sha1 = "a6c65f9da7f467ee1f02ff2841ffd3155aee2fc9",
-  )
+    maven_jar(
+        name = "byte_buddy_agent",
+        artifact = "net.bytebuddy:byte-buddy-agent:" + BYTE_BUDDY_VER,
+        sha1 = "a2dbe3457401f65ad4022617fbb3fc0e5f427c7d",
+    )
 
-  maven_jar(
-    name = "objenesis",
-    artifact = "org.objenesis:objenesis:2.6",
-    sha1 = "639033469776fd37c08358c6b92a4761feb2af4b",
-  )
+    maven_jar(
+        name = "objenesis",
+        artifact = "org.objenesis:objenesis:2.6",
+        sha1 = "639033469776fd37c08358c6b92a4761feb2af4b",
+    )
+
+    maven_jar(
+        name = "hikaricp",
+        artifact = "com.zaxxer:HikariCP:3.2.0",
+        sha1 = "6c66db1c636ee90beb4c65fe34abd8ba9396bca6",
+    )
diff --git a/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventCleanerPool.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventCleanerPool.java
new file mode 100644
index 0000000..9efdeae
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventCleanerPool.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// 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.ericsson.gerrit.plugins.eventslog;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.BindingAnnotation;
+import java.lang.annotation.Retention;
+
+/** Annotation applied to a ScheduledThreadPoolExecutor. */
+@Retention(RUNTIME)
+@BindingAnnotation
+public @interface EventCleanerPool {}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventCleanerQueue.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventCleanerQueue.java
new file mode 100644
index 0000000..b457018
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventCleanerQueue.java
@@ -0,0 +1,48 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// 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.ericsson.gerrit.plugins.eventslog;
+
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.server.git.WorkQueue;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.util.concurrent.ScheduledExecutorService;
+
+@Singleton
+public class EventCleanerQueue implements LifecycleListener {
+  private final WorkQueue workQueue;
+  private ScheduledExecutorService pool;
+
+  @Inject
+  public EventCleanerQueue(WorkQueue workQueue) {
+    this.workQueue = workQueue;
+  }
+
+  @Override
+  public void start() {
+    pool = workQueue.createQueue(1, "[events-log] Remove events");
+  }
+
+  @Override
+  public void stop() {
+    if (pool != null) {
+      pool = null;
+    }
+  }
+
+  ScheduledExecutorService getPool() {
+    return this.pool;
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventModule.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventModule.java
index b87afcc..b888425 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventModule.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventModule.java
@@ -14,8 +14,10 @@
 
 package com.ericsson.gerrit.plugins.eventslog;
 
+import com.ericsson.gerrit.plugins.eventslog.sql.EventsLogCleaner;
 import com.google.gerrit.common.EventListener;
 import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.extensions.events.ProjectDeletedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.inject.AbstractModule;
 import com.google.inject.Provides;
@@ -31,7 +33,11 @@
     bind(EventQueue.class).in(Scopes.SINGLETON);
     bind(EventHandler.class).in(Scopes.SINGLETON);
     bind(LifecycleListener.class).annotatedWith(UniqueAnnotations.create()).to(EventQueue.class);
+    bind(LifecycleListener.class)
+        .annotatedWith(UniqueAnnotations.create())
+        .to(EventCleanerQueue.class);
     DynamicSet.bind(binder(), EventListener.class).to(EventHandler.class);
+    DynamicSet.bind(binder(), ProjectDeletedListener.class).to(EventsLogCleaner.class);
   }
 
   @Provides
@@ -39,4 +45,10 @@
   ScheduledExecutorService provideEventPool(EventQueue queue) {
     return queue.getPool();
   }
+
+  @Provides
+  @EventCleanerPool
+  ScheduledExecutorService provideEventCleanerPool(EventCleanerQueue queue) {
+    return queue.getPool();
+  }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventsLogConfig.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventsLogConfig.java
index e912faf..c1a9b4a 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventsLogConfig.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventsLogConfig.java
@@ -14,7 +14,6 @@
 
 package com.ericsson.gerrit.plugins.eventslog;
 
-import com.google.common.base.Joiner;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.server.config.PluginConfig;
 import com.google.gerrit.server.config.PluginConfigFactory;
@@ -27,11 +26,12 @@
 /** Holder of all things related to events-log plugin configuration. */
 @Singleton
 public class EventsLogConfig {
+  public static final String H2_DB_PREFIX = "jdbc:h2:";
+
   static final String CONFIG_COPY_LOCAL = "copyLocal";
   static final String CONFIG_MAX_AGE = "maxAge";
   static final String CONFIG_MAX_TRIES = "maxTries";
   static final String CONFIG_RETURN_LIMIT = "returnLimit";
-  static final String CONFIG_DRIVER = "storeDriver";
   static final String CONFIG_URL = "storeUrl";
   static final String CONFIG_LOCAL_PATH = "localStorePath";
   static final String CONFIG_URL_OPTIONS = "urlOptions";
@@ -39,7 +39,7 @@
   static final String CONFIG_PASSWORD = "storePassword";
   static final String CONFIG_WAIT_TIME = "retryTimeout";
   static final String CONFIG_CONN_TIME = "connectTimeout";
-  static final String CONFIG_EVICT_IDLE_TIME = "evictIdleTime";
+  static final String CONFIG_MAX_CONNECTIONS = "maxConnections";
 
   static final boolean DEFAULT_COPY_LOCAL = false;
   static final int DEFAULT_MAX_AGE = 30;
@@ -47,8 +47,7 @@
   static final int DEFAULT_RETURN_LIMIT = 5000;
   static final int DEFAULT_WAIT_TIME = 1000;
   static final int DEFAULT_CONN_TIME = 1000;
-  static final String DEFAULT_DRIVER = "org.h2.Driver";
-  static final int DEFAULT_EVICT_IDLE_TIME = 1000 * 60;
+  static final int DEFAULT_MAX_CONNECTIONS = 8;
 
   private boolean copyLocal;
   private int maxAge;
@@ -56,18 +55,15 @@
   private int returnLimit;
   private int waitTime;
   private int connectTime;
-  private String storeDriver;
   private String storeUrl;
   private Path localStorePath;
-  private String urlOptions;
+  private String[] urlOptions;
   private String storeUsername;
   private String storePassword;
-  private int evictIdleTime;
-  private String defaultUrl;
+  private int maxConnections;
 
   @Inject
   EventsLogConfig(PluginConfigFactory cfgFactory, SitePaths site, @PluginName String pluginName) {
-    String defaultLocalPath = site.site_path.toString() + "/events-db/";
     PluginConfig cfg = cfgFactory.getFromGerritConfig(pluginName, true);
     copyLocal = cfg.getBoolean(CONFIG_COPY_LOCAL, DEFAULT_COPY_LOCAL);
     maxAge = cfg.getInt(CONFIG_MAX_AGE, DEFAULT_MAX_AGE);
@@ -75,14 +71,15 @@
     returnLimit = cfg.getInt(CONFIG_RETURN_LIMIT, DEFAULT_RETURN_LIMIT);
     waitTime = cfg.getInt(CONFIG_WAIT_TIME, DEFAULT_WAIT_TIME);
     connectTime = cfg.getInt(CONFIG_CONN_TIME, DEFAULT_CONN_TIME);
-    storeDriver = cfg.getString(CONFIG_DRIVER, DEFAULT_DRIVER);
-    defaultUrl = "jdbc:h2:" + site.data_dir.toString() + "/db";
-    storeUrl = cfg.getString(CONFIG_URL, defaultUrl);
-    localStorePath = Paths.get(cfg.getString(CONFIG_LOCAL_PATH, defaultLocalPath));
-    urlOptions = Joiner.on(";").join(cfg.getStringList(CONFIG_URL_OPTIONS));
+    storeUrl = cfg.getString(CONFIG_URL, H2_DB_PREFIX + site.data_dir.resolve("db").normalize());
+    localStorePath =
+        Paths.get(
+            cfg.getString(
+                CONFIG_LOCAL_PATH, site.site_path.resolve("events-db").normalize().toString()));
+    urlOptions = cfg.getStringList(CONFIG_URL_OPTIONS);
     storeUsername = cfg.getString(CONFIG_USERNAME);
     storePassword = cfg.getString(CONFIG_PASSWORD);
-    evictIdleTime = cfg.getInt(CONFIG_EVICT_IDLE_TIME, DEFAULT_EVICT_IDLE_TIME);
+    maxConnections = Math.max(cfg.getInt(CONFIG_MAX_CONNECTIONS, DEFAULT_MAX_CONNECTIONS), 1);
   }
 
   public int getMaxAge() {
@@ -101,15 +98,11 @@
     return connectTime;
   }
 
-  public String getStoreDriver() {
-    return storeDriver;
-  }
-
   public String getStoreUrl() {
     return storeUrl;
   }
 
-  public String getUrlOptions() {
+  public String[] getUrlOptions() {
     return urlOptions;
   }
 
@@ -125,11 +118,6 @@
     return maxTries;
   }
 
-  /** @return the local-store (database) driver which happens to be h2 */
-  public String getLocalStoreDriver() {
-    return DEFAULT_DRIVER;
-  }
-
   public Path getLocalStorePath() {
     return localStorePath;
   }
@@ -138,7 +126,7 @@
     return copyLocal;
   }
 
-  public int getEvictIdleTime() {
-    return evictIdleTime;
+  public int getMaxConnections() {
+    return maxConnections;
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/EventsLogCleaner.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/EventsLogCleaner.java
new file mode 100644
index 0000000..b092829
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/EventsLogCleaner.java
@@ -0,0 +1,65 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// 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.ericsson.gerrit.plugins.eventslog.sql;
+
+import com.ericsson.gerrit.plugins.eventslog.EventCleanerPool;
+import com.google.gerrit.extensions.events.ProjectDeletedListener;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.time.Duration;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+@Singleton
+public class EventsLogCleaner implements ProjectDeletedListener {
+  private static final int HOUR = 23;
+  private static final long INTERVAL = TimeUnit.DAYS.toSeconds(1);
+
+  private final SQLClient eventsDb;
+
+  private ScheduledExecutorService pool;
+
+  @Inject
+  EventsLogCleaner(@EventsDb SQLClient eventsDb, @EventCleanerPool ScheduledExecutorService pool) {
+    this.eventsDb = eventsDb;
+    this.pool = pool;
+  }
+
+  @Override
+  public void onProjectDeleted(Event event) {
+    removeProjectEventsAsync(event.getProjectName());
+  }
+
+  public void removeProjectEventsAsync(String projectName) {
+    pool.submit(() -> eventsDb.removeProjectEvents(projectName));
+  }
+
+  public void scheduleCleaningWith(int maxAge) {
+    pool.scheduleAtFixedRate(
+        () -> eventsDb.removeOldEvents(maxAge), getInitialDelay(), INTERVAL, TimeUnit.SECONDS);
+  }
+
+  private long getInitialDelay() {
+    ZonedDateTime now = ZonedDateTime.now(ZoneId.systemDefault());
+    ZonedDateTime next = now.withHour(HOUR).truncatedTo(ChronoUnit.HOURS);
+    if (now.isAfter(next)) {
+      next = next.plusDays(1);
+    }
+    return Duration.between(now, next).getSeconds();
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLClient.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLClient.java
index 97f9dad..05f663f 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLClient.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLClient.java
@@ -33,7 +33,8 @@
 import com.google.gerrit.server.events.SupplierSerializer;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
-import com.google.inject.Inject;
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
 import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.SQLException;
@@ -41,50 +42,21 @@
 import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.List;
-import org.apache.commons.dbcp.BasicDataSource;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 class SQLClient {
   private static final Logger log = LoggerFactory.getLogger(SQLClient.class);
   private final Gson gson;
-  private BasicDataSource ds;
+  private final boolean isPostgresql;
 
-  @Inject
-  SQLClient(String storeDriver, String storeUrl, String urlOptions) {
-    ds = new BasicDataSource();
-    ds.setDriverClassName(storeDriver);
-    ds.setUrl(storeUrl);
-    ds.setConnectionProperties(urlOptions);
+  private HikariDataSource ds;
+
+  public SQLClient(HikariConfig config) {
+    ds = new HikariDataSource(config);
+
     gson = new GsonBuilder().registerTypeAdapter(Supplier.class, new SupplierSerializer()).create();
-  }
-
-  /**
-   * Set the username to connect to the database.
-   *
-   * @param username the username as a string
-   */
-  void setUsername(String username) {
-    ds.setUsername(username);
-  }
-
-  /**
-   * Set the password to connect to the database.
-   *
-   * @param password the password as a string
-   */
-  void setPassword(String password) {
-    ds.setPassword(password);
-  }
-
-  /**
-   * Set the time before an idle connection is evicted as well as the time between eviction runs.
-   *
-   * @param evictIdleTime the time in milliseconds before eviction
-   */
-  void setEvictIdleTime(int evictIdleTime) {
-    ds.setMinEvictableIdleTimeMillis(evictIdleTime);
-    ds.setTimeBetweenEvictionRunsMillis(evictIdleTime / 2);
+    isPostgresql = config.getJdbcUrl().contains("postgresql");
   }
 
   /**
@@ -93,8 +65,8 @@
    * @throws SQLException If there was a problem with the database
    */
   void createDBIfNotCreated() throws SQLException {
-    boolean postgresql = ds.getDriverClassName().contains("postgresql");
-    execute(SQLTable.createTableQuery(postgresql));
+    execute(SQLTable.createTableQuery(isPostgresql));
+    execute(SQLTable.createIndexes(isPostgresql));
   }
 
   /**
@@ -111,11 +83,7 @@
   }
 
   void close() {
-    try {
-      ds.close();
-    } catch (SQLException e) {
-      log.warn("Cannot close datasource", e);
-    }
+    ds.close();
   }
 
   /**
@@ -175,8 +143,9 @@
               TABLE_NAME,
               DATE_ENTRY,
               new Timestamp(System.currentTimeMillis() - MILLISECONDS.convert(maxAge, DAYS))));
+      log.info("Events older than {} days were removed from database {}", maxAge, ds.getPoolName());
     } catch (SQLException e) {
-      log.warn("Cannot remove old event entries from database", e);
+      log.warn("Cannot remove old event entries from database {}", ds.getPoolName(), e);
     }
   }
 
@@ -189,7 +158,7 @@
     try {
       execute(format("DELETE FROM %s WHERE project = '%s'", TABLE_NAME, project));
     } catch (SQLException e) {
-      log.warn("Cannot remove project " + project + " events from database", e);
+      log.warn("Cannot remove project {} events from database", project, e);
     }
   }
 
diff --git a/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLModule.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLModule.java
index a75335b..df7f73b 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLModule.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLModule.java
@@ -14,20 +14,22 @@
 
 package com.ericsson.gerrit.plugins.eventslog.sql;
 
+import static com.ericsson.gerrit.plugins.eventslog.EventsLogConfig.H2_DB_PREFIX;
+
 import com.ericsson.gerrit.plugins.eventslog.EventModule;
 import com.ericsson.gerrit.plugins.eventslog.EventStore;
 import com.ericsson.gerrit.plugins.eventslog.EventsLogConfig;
 import com.ericsson.gerrit.plugins.eventslog.QueryMaker;
+import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.inject.AbstractModule;
 import com.google.inject.Provides;
 import com.google.inject.Singleton;
 import com.google.inject.internal.UniqueAnnotations;
+import com.zaxxer.hikari.HikariConfig;
 
 class SQLModule extends AbstractModule {
 
-  private static final String H2_DB_PREFIX = "jdbc:h2:";
-
   @Override
   protected void configure() {
     install(new EventModule());
@@ -39,27 +41,34 @@
   @Provides
   @Singleton
   @EventsDb
-  SQLClient provideSqlClient(EventsLogConfig cfg) {
-    SQLClient sqlClient =
-        new SQLClient(cfg.getStoreDriver(), cfg.getStoreUrl(), cfg.getUrlOptions());
-    sqlClient.setUsername(cfg.getStoreUsername());
-    sqlClient.setPassword(cfg.getStorePassword());
-    sqlClient.setEvictIdleTime(cfg.getEvictIdleTime());
-    return sqlClient;
+  SQLClient provideSqlClient(EventsLogConfig cfg, @PluginName String pluginName) {
+    HikariConfig dsConfig = new HikariConfig();
+    dsConfig.setJdbcUrl(cfg.getStoreUrl());
+    dsConfig.setUsername(cfg.getStoreUsername());
+    dsConfig.setPassword(cfg.getStorePassword());
+    dsConfig.setPoolName("[" + pluginName + "] EventsDb");
+    dsConfig.setMaximumPoolSize(cfg.getMaxConnections());
+    setDataSourceOptions(cfg, dsConfig);
+    return new SQLClient(dsConfig);
   }
 
   @Provides
   @Singleton
   @LocalEventsDb
-  SQLClient provideLocalSqlClient(EventsLogConfig cfg) {
-    String path = cfg.getLocalStorePath().toString();
-    path = path.endsWith("/") ? path : path + "/";
-    SQLClient sqlClient =
-        new SQLClient(
-            cfg.getLocalStoreDriver(),
-            H2_DB_PREFIX + path + SQLTable.TABLE_NAME,
-            cfg.getUrlOptions());
-    sqlClient.setEvictIdleTime(cfg.getEvictIdleTime());
-    return sqlClient;
+  SQLClient provideLocalSqlClient(EventsLogConfig cfg, @PluginName String pluginName) {
+    HikariConfig dsConfig = new HikariConfig();
+    dsConfig.setJdbcUrl(H2_DB_PREFIX + cfg.getLocalStorePath().resolve(SQLTable.TABLE_NAME));
+    dsConfig.setPoolName("[" + pluginName + "] LocalEventsDb");
+    setDataSourceOptions(cfg, dsConfig);
+    return new SQLClient(dsConfig);
+  }
+
+  private void setDataSourceOptions(EventsLogConfig cfg, HikariConfig dsConfig) {
+    for (String option : cfg.getUrlOptions()) {
+      int equalsPos = option.indexOf('=');
+      String key = option.substring(0, equalsPos);
+      String value = option.substring(equalsPos + 1);
+      dsConfig.addDataSourceProperty(key, value);
+    }
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLQueryMaker.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLQueryMaker.java
index 53e406c..09bba6d 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLQueryMaker.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLQueryMaker.java
@@ -59,22 +59,15 @@
       throw new MalformedQueryException(e);
     }
     return String.format(
-        "SELECT * FROM %s WHERE %s BETWEEN '%s' and '%s' LIMIT %d",
+        "SELECT * FROM %s WHERE %s BETWEEN '%s' and '%s' ORDER BY date_created LIMIT %d",
         TABLE_NAME, DATE_ENTRY, dates[0], dates[1], returnLimit);
   }
 
   @Override
   public String getDefaultQuery() {
-    return "SELECT * FROM(SELECT * FROM "
-        + TABLE_NAME
-        + " ORDER BY "
-        + PRIMARY_ENTRY
-        + " DESC LIMIT "
-        + returnLimit
-        + ")"
-        + " a ORDER BY "
-        + PRIMARY_ENTRY
-        + " ASC";
+    return String.format(
+        "SELECT * FROM (SELECT * FROM %s ORDER BY %s DESC LIMIT %s) a ORDER BY %s ASC",
+        TABLE_NAME, PRIMARY_ENTRY, returnLimit, PRIMARY_ENTRY);
   }
 
   private String[] parseDates(String dateOne, String dateTwo)
diff --git a/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLStore.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLStore.java
index 9df84d3..8a06904 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLStore.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLStore.java
@@ -15,13 +15,13 @@
 package com.ericsson.gerrit.plugins.eventslog.sql;
 
 import static com.ericsson.gerrit.plugins.eventslog.sql.SQLTable.TABLE_NAME;
+import static java.util.stream.Collectors.toList;
 
 import com.ericsson.gerrit.plugins.eventslog.EventPool;
 import com.ericsson.gerrit.plugins.eventslog.EventStore;
 import com.ericsson.gerrit.plugins.eventslog.EventsLogConfig;
 import com.ericsson.gerrit.plugins.eventslog.EventsLogException;
 import com.ericsson.gerrit.plugins.eventslog.ServiceUnavailableException;
-import com.google.common.io.Files;
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.extensions.restapi.AuthException;
@@ -34,14 +34,13 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
-import java.io.File;
 import java.io.IOException;
 import java.net.ConnectException;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map.Entry;
 import java.util.concurrent.ScheduledExecutorService;
@@ -56,6 +55,7 @@
   private static final String H2_DB_SUFFIX = ".h2.db";
 
   private final Provider<CurrentUser> userProvider;
+  private final EventsLogCleaner eventsLogCleaner;
   private SQLClient eventsDb;
   private SQLClient localEventsDb;
   private final int maxAge;
@@ -76,7 +76,8 @@
       @EventsDb SQLClient eventsDb,
       @LocalEventsDb SQLClient localEventsDb,
       @EventPool ScheduledExecutorService pool,
-      PermissionBackend permissionBackend) {
+      PermissionBackend permissionBackend,
+      EventsLogCleaner eventsLogCleaner) {
     this.maxAge = cfg.getMaxAge();
     this.maxTries = cfg.getMaxTries();
     this.waitTime = cfg.getWaitTime();
@@ -85,6 +86,7 @@
     this.userProvider = userProvider;
     this.eventsDb = eventsDb;
     this.localEventsDb = localEventsDb;
+    this.eventsLogCleaner = eventsLogCleaner;
     this.pool = pool;
     this.permissionBackend = permissionBackend;
     this.localPath = cfg.getLocalStorePath();
@@ -93,6 +95,7 @@
   @Override
   public void start() {
     setUp();
+    eventsLogCleaner.scheduleCleaningWith(maxAge);
   }
 
   @Override
@@ -128,16 +131,7 @@
         log.warn("Cannot check project access permission", e);
       }
     }
-    return sortedEventsFromEntries(entries);
-  }
-
-  private List<String> sortedEventsFromEntries(List<SQLEntry> entries) {
-    Collections.sort(entries);
-    List<String> events = new ArrayList<>();
-    for (SQLEntry entry : entries) {
-      events.add(entry.getEvent());
-    }
-    return events;
+    return entries.stream().sorted().map(SQLEntry::getEvent).collect(toList());
   }
 
   /**
@@ -158,14 +152,14 @@
       try {
         getEventsDb().storeEvent(event);
       } catch (SQLException e) {
-        log.warn("Cannot store ChangeEvent for: " + projectName.get(), e);
+        log.warn("Cannot store ChangeEvent for: {}", projectName.get(), e);
         if (e.getCause() instanceof ConnectException
             || e.getMessage().contains("terminating connection")) {
           done = false;
           try {
             retryIfAllowed(failedConnections);
           } catch (InterruptedException e1) {
-            log.warn("Cannot store ChangeEvent for: " + projectName.get() + ": Interrupted");
+            log.warn("Cannot store ChangeEvent for {}: Interrupted", projectName.get());
             Thread.currentThread().interrupt();
             return;
           }
@@ -180,7 +174,7 @@
       log.info("Retrying store event");
       Thread.sleep(waitTime);
     } else {
-      log.error("Failed to store event " + maxTries + " times");
+      log.error("Failed to store event {} times", maxTries);
       setOnline(false);
     }
   }
@@ -198,7 +192,6 @@
     if (online) {
       restoreEventsFromLocal();
     }
-    getEventsDb().removeOldEvents(maxAge);
   }
 
   private SQLClient getEventsDb() {
@@ -293,12 +286,10 @@
     if (!copyLocal) {
       return;
     }
-    File file = localPath.resolve(TABLE_NAME + H2_DB_SUFFIX).toFile();
-    File copyFile =
-        localPath
-            .resolve(
-                TABLE_NAME + (TimeUnit.MILLISECONDS.toSeconds(TimeUtil.nowMs())) + H2_DB_SUFFIX)
-            .toFile();
+    Path file = localPath.resolve(TABLE_NAME + H2_DB_SUFFIX);
+    Path copyFile =
+        localPath.resolve(
+            TABLE_NAME + (TimeUnit.MILLISECONDS.toSeconds(TimeUtil.nowMs())) + H2_DB_SUFFIX);
     try {
       Files.copy(file, copyFile);
     } catch (IOException e) {
diff --git a/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLTable.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLTable.java
index f25b618..0db07a2 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLTable.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLTable.java
@@ -23,10 +23,27 @@
   static final String DATE_ENTRY = "date_created";
   static final String EVENT_ENTRY = "event_info";
 
+  private static final String CREATED_INDEX = "created_idx";
+  private static final String PROJECT_INDEX = "project_idx";
+  private static final String H2_INDEX_CREATION_FORMAT = "CREATE INDEX IF NOT EXISTS %s ON %s (%s)";
+  private static final String POSTGRESQL_INDEX_CREATION_FORMAT =
+      "DO $$\n"
+          + "BEGIN\n"
+          + "IF NOT EXISTS (\n"
+          + "    SELECT 1\n"
+          + "    FROM   pg_class c\n"
+          + "    JOIN   pg_namespace n ON n.oid = c.relnamespace\n"
+          + "    WHERE  c.relname = '%s'\n"
+          + "    AND    n.nspname = 'public'\n"
+          + "    ) THEN\n"
+          + "    CREATE INDEX %s ON %s (%s);\n"
+          + "END IF;\n"
+          + "END$$;";
+
   private SQLTable() {}
 
   static String createTableQuery(boolean postgresql) {
-    StringBuilder query = new StringBuilder();
+    StringBuilder query = new StringBuilder(140);
     query.append(format("CREATE TABLE IF NOT EXISTS %s(", TABLE_NAME));
     if (postgresql) {
       query.append(format("%s SERIAL PRIMARY KEY,", PRIMARY_ENTRY));
@@ -38,4 +55,36 @@
     query.append(format("%s TEXT)", EVENT_ENTRY));
     return query.toString();
   }
+
+  static String createIndexes(boolean postgresql) {
+    return postgresql ? getPostgresqlQuery() : getH2Query();
+  }
+
+  private static String getPostgresqlQuery() {
+    StringBuilder query = new StringBuilder(540);
+    query.append(
+        format(
+            POSTGRESQL_INDEX_CREATION_FORMAT,
+            CREATED_INDEX,
+            CREATED_INDEX,
+            TABLE_NAME,
+            DATE_ENTRY));
+    query.append("\n;\n");
+    query.append(
+        format(
+            POSTGRESQL_INDEX_CREATION_FORMAT,
+            PROJECT_INDEX,
+            PROJECT_INDEX,
+            TABLE_NAME,
+            PROJECT_ENTRY));
+    return query.toString();
+  }
+
+  private static String getH2Query() {
+    StringBuilder query = new StringBuilder();
+    query.append(format(H2_INDEX_CREATION_FORMAT, CREATED_INDEX, TABLE_NAME, DATE_ENTRY));
+    query.append(";");
+    query.append(format(H2_INDEX_CREATION_FORMAT, PROJECT_INDEX, TABLE_NAME, PROJECT_ENTRY));
+    return query.toString();
+  }
 }
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 4e29811..4421212 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -16,17 +16,13 @@
 
 plugin.@PLUGIN@.maxAge
 :    Specify the maximum allowed age in days of the entries in the database.
-     Any entries that are older than this value will be removed on server startup.
-     When not specified, the default value is 30 days.
+     Any entries that are older than this value will be removed every day at
+     23:00 hours. When not specified, the default value is 30 days.
 
 plugin.@PLUGIN@.returnLimit
 :    Specify the max amount of events that will be returned for each query.
      When not specified, the default value is 5000.
 
-plugin.@PLUGIN@.storeDriver
-:    Specify the driver of the database. When not specified, the default driver is
-     org.h2.Driver.
-
 plugin.@PLUGIN@.storeUrl
 :    Specify the path to the directory in which to keep the database. When not
      specified, the default path is jdbc:h2:\<gerrit_site>/data/db.
@@ -71,7 +67,6 @@
      not be deleted and must be removed manually. When not specified, the default
      value is set to false.
 
-plugin.@PLUGIN@.evictIdleTime
-:    Interval of time in milliseconds after which an idle database connection is
-     evicted from the connection pool. When not specified, the default value is
-     set to 60000ms.
+plugin.@PLUGIN@.maxConnections
+:    Maximum number of instances in the connection pool to the database. Includes
+     active and idle connections. By default 8.
diff --git a/src/test/java/com/ericsson/gerrit/plugins/eventslog/EventsLogConfigTest.java b/src/test/java/com/ericsson/gerrit/plugins/eventslog/EventsLogConfigTest.java
index 0d19579..702328b 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/eventslog/EventsLogConfigTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/eventslog/EventsLogConfigTest.java
@@ -16,10 +16,9 @@
 
 import static com.ericsson.gerrit.plugins.eventslog.EventsLogConfig.CONFIG_CONN_TIME;
 import static com.ericsson.gerrit.plugins.eventslog.EventsLogConfig.CONFIG_COPY_LOCAL;
-import static com.ericsson.gerrit.plugins.eventslog.EventsLogConfig.CONFIG_DRIVER;
-import static com.ericsson.gerrit.plugins.eventslog.EventsLogConfig.CONFIG_EVICT_IDLE_TIME;
 import static com.ericsson.gerrit.plugins.eventslog.EventsLogConfig.CONFIG_LOCAL_PATH;
 import static com.ericsson.gerrit.plugins.eventslog.EventsLogConfig.CONFIG_MAX_AGE;
+import static com.ericsson.gerrit.plugins.eventslog.EventsLogConfig.CONFIG_MAX_CONNECTIONS;
 import static com.ericsson.gerrit.plugins.eventslog.EventsLogConfig.CONFIG_MAX_TRIES;
 import static com.ericsson.gerrit.plugins.eventslog.EventsLogConfig.CONFIG_PASSWORD;
 import static com.ericsson.gerrit.plugins.eventslog.EventsLogConfig.CONFIG_RETURN_LIMIT;
@@ -28,22 +27,22 @@
 import static com.ericsson.gerrit.plugins.eventslog.EventsLogConfig.CONFIG_USERNAME;
 import static com.ericsson.gerrit.plugins.eventslog.EventsLogConfig.CONFIG_WAIT_TIME;
 import static com.ericsson.gerrit.plugins.eventslog.EventsLogConfig.DEFAULT_CONN_TIME;
-import static com.ericsson.gerrit.plugins.eventslog.EventsLogConfig.DEFAULT_COPY_LOCAL;
-import static com.ericsson.gerrit.plugins.eventslog.EventsLogConfig.DEFAULT_DRIVER;
-import static com.ericsson.gerrit.plugins.eventslog.EventsLogConfig.DEFAULT_EVICT_IDLE_TIME;
 import static com.ericsson.gerrit.plugins.eventslog.EventsLogConfig.DEFAULT_MAX_AGE;
+import static com.ericsson.gerrit.plugins.eventslog.EventsLogConfig.DEFAULT_MAX_CONNECTIONS;
 import static com.ericsson.gerrit.plugins.eventslog.EventsLogConfig.DEFAULT_MAX_TRIES;
 import static com.ericsson.gerrit.plugins.eventslog.EventsLogConfig.DEFAULT_RETURN_LIMIT;
 import static com.ericsson.gerrit.plugins.eventslog.EventsLogConfig.DEFAULT_WAIT_TIME;
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Mockito.when;
 
-import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
 import com.google.gerrit.server.config.PluginConfig;
 import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.gerrit.server.config.SitePaths;
 import java.io.IOException;
 import java.nio.file.Files;
+import java.util.List;
+import org.eclipse.jgit.lib.Config;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -54,18 +53,15 @@
 
 @RunWith(MockitoJUnitRunner.class)
 public class EventsLogConfigTest {
+  private static final String LOCAL_STORE_PATH = "~/gerrit/events-db/";
+  private static final String PLUGIN = "plugin";
   private static final String PLUGIN_NAME = "eventsLog";
-  private static final int CUSTOM_EVICT_IDLE_TIME = 10000;
+  private static final int CUSTOM_MAX_CONNECTIONS = 32;
+  private static final List<String> urlOptions = ImmutableList.of("DB_CLOSE_DELAY=10");
 
   private SitePaths site;
-  private EventsLogConfig config;
-  private String defaultLocalStorePath;
-  private String defaultUrl;
-  private String localStorePath;
-  private String[] urlOptions = new String[] {"DB_CLOSE_DELAY=10"};
 
   @Mock private PluginConfigFactory cfgFactoryMock;
-  @Mock private PluginConfig configMock;
 
   @Rule public TemporaryFolder gerrit_site = new TemporaryFolder();
 
@@ -73,86 +69,62 @@
   public void setUp() throws IOException {
     site = new SitePaths(gerrit_site.getRoot().toPath());
     Files.createDirectories(site.etc_dir);
-    defaultLocalStorePath = site.site_path.toString() + "/events-db/";
-    defaultUrl = "jdbc:h2:" + site.data_dir.toString() + "/db";
-    when(cfgFactoryMock.getFromGerritConfig(PLUGIN_NAME, true)).thenReturn(configMock);
-  }
-
-  private void setUpDefaults() {
-    when(configMock.getBoolean(CONFIG_COPY_LOCAL, DEFAULT_COPY_LOCAL))
-        .thenReturn(DEFAULT_COPY_LOCAL);
-    when(configMock.getInt(CONFIG_MAX_AGE, DEFAULT_MAX_AGE)).thenReturn(DEFAULT_MAX_AGE);
-    when(configMock.getInt(CONFIG_MAX_TRIES, DEFAULT_MAX_TRIES)).thenReturn(DEFAULT_MAX_TRIES);
-    when(configMock.getInt(CONFIG_RETURN_LIMIT, DEFAULT_RETURN_LIMIT))
-        .thenReturn(DEFAULT_RETURN_LIMIT);
-    when(configMock.getInt(CONFIG_CONN_TIME, DEFAULT_CONN_TIME)).thenReturn(DEFAULT_CONN_TIME);
-    when(configMock.getInt(CONFIG_WAIT_TIME, DEFAULT_WAIT_TIME)).thenReturn(DEFAULT_WAIT_TIME);
-    when(configMock.getString(CONFIG_DRIVER, DEFAULT_DRIVER)).thenReturn(DEFAULT_DRIVER);
-    when(configMock.getString(CONFIG_URL, defaultUrl)).thenReturn(defaultUrl);
-    when(configMock.getString(CONFIG_LOCAL_PATH, defaultLocalStorePath))
-        .thenReturn(defaultLocalStorePath);
-    when(configMock.getStringList(CONFIG_URL_OPTIONS)).thenReturn(new String[] {});
-    when(configMock.getString(CONFIG_USERNAME)).thenReturn(null);
-    when(configMock.getString(CONFIG_PASSWORD)).thenReturn(null);
-    when(configMock.getInt(CONFIG_EVICT_IDLE_TIME, DEFAULT_EVICT_IDLE_TIME))
-        .thenReturn(DEFAULT_EVICT_IDLE_TIME);
-  }
-
-  private void setUpCustom() {
-    localStorePath = "~/gerrit/events-db/";
-    when(configMock.getBoolean(CONFIG_COPY_LOCAL, DEFAULT_COPY_LOCAL)).thenReturn(true);
-    when(configMock.getInt(CONFIG_MAX_AGE, DEFAULT_MAX_AGE)).thenReturn(20);
-    when(configMock.getInt(CONFIG_MAX_TRIES, DEFAULT_MAX_TRIES)).thenReturn(5);
-    when(configMock.getInt(CONFIG_RETURN_LIMIT, DEFAULT_RETURN_LIMIT)).thenReturn(10000);
-    when(configMock.getInt(CONFIG_CONN_TIME, DEFAULT_CONN_TIME)).thenReturn(5000);
-    when(configMock.getInt(CONFIG_WAIT_TIME, DEFAULT_WAIT_TIME)).thenReturn(5000);
-    when(configMock.getString(CONFIG_DRIVER, DEFAULT_DRIVER)).thenReturn("org.h2.Driver2");
-    when(configMock.getString(CONFIG_URL, defaultUrl)).thenReturn("jdbc:h2:~/gerrit/db");
-    when(configMock.getString(CONFIG_LOCAL_PATH, defaultLocalStorePath)).thenReturn(localStorePath);
-    when(configMock.getStringList(CONFIG_URL_OPTIONS)).thenReturn(urlOptions);
-    when(configMock.getString(CONFIG_USERNAME)).thenReturn("testUsername");
-    when(configMock.getString(CONFIG_PASSWORD)).thenReturn("testPassword");
-    when(configMock.getInt(CONFIG_EVICT_IDLE_TIME, DEFAULT_EVICT_IDLE_TIME))
-        .thenReturn(CUSTOM_EVICT_IDLE_TIME);
   }
 
   @Test
   public void shouldReturnDefaultsWhenMissingConfig() {
-    setUpDefaults();
-    config = new EventsLogConfig(cfgFactoryMock, site, PLUGIN_NAME);
-    assertThat(config.getCopyLocal()).isFalse();
-    assertThat(config.getMaxAge()).isEqualTo(30);
-    assertThat(config.getMaxTries()).isEqualTo(3);
-    assertThat(config.getReturnLimit()).isEqualTo(5000);
-    assertThat(config.getConnectTime()).isEqualTo(1000);
-    assertThat(config.getWaitTime()).isEqualTo(1000);
-    assertThat(config.getLocalStoreDriver()).isEqualTo(DEFAULT_DRIVER);
-    assertThat(config.getLocalStorePath().toString() + "/").isEqualTo(defaultLocalStorePath);
-    assertThat(config.getStoreDriver()).isEqualTo(DEFAULT_DRIVER);
-    assertThat(config.getStoreUrl()).isEqualTo(defaultUrl);
-    assertThat(config.getUrlOptions()).isEmpty();
-    assertThat(config.getStoreUsername()).isNull();
-    assertThat(config.getStorePassword()).isNull();
-    assertThat(config.getEvictIdleTime()).isEqualTo(DEFAULT_EVICT_IDLE_TIME);
+    PluginConfig pluginConfig = new PluginConfig(PLUGIN_NAME, new Config());
+    when(cfgFactoryMock.getFromGerritConfig(PLUGIN_NAME, true)).thenReturn(pluginConfig);
+    EventsLogConfig eventsLogConfig = new EventsLogConfig(cfgFactoryMock, site, PLUGIN_NAME);
+    assertThat(eventsLogConfig.getCopyLocal()).isFalse();
+    assertThat(eventsLogConfig.getMaxAge()).isEqualTo(DEFAULT_MAX_AGE);
+    assertThat(eventsLogConfig.getMaxTries()).isEqualTo(DEFAULT_MAX_TRIES);
+    assertThat(eventsLogConfig.getReturnLimit()).isEqualTo(DEFAULT_RETURN_LIMIT);
+    assertThat(eventsLogConfig.getConnectTime()).isEqualTo(DEFAULT_CONN_TIME);
+    assertThat(eventsLogConfig.getWaitTime()).isEqualTo(DEFAULT_WAIT_TIME);
+    assertThat(eventsLogConfig.getLocalStorePath().toString() + "/")
+        .isEqualTo(site.site_path.toString() + "/events-db/");
+    assertThat(eventsLogConfig.getStoreUrl())
+        .isEqualTo("jdbc:h2:" + site.data_dir.toString() + "/db");
+    assertThat(eventsLogConfig.getUrlOptions()).isEmpty();
+    assertThat(eventsLogConfig.getStoreUsername()).isNull();
+    assertThat(eventsLogConfig.getStorePassword()).isNull();
+    assertThat(eventsLogConfig.getMaxConnections()).isEqualTo(DEFAULT_MAX_CONNECTIONS);
   }
 
   @Test
   public void shouldReturnConfigValues() {
-    setUpCustom();
-    config = new EventsLogConfig(cfgFactoryMock, site, PLUGIN_NAME);
-    assertThat(config.getCopyLocal()).isTrue();
-    assertThat(config.getMaxAge()).isEqualTo(20);
-    assertThat(config.getMaxTries()).isEqualTo(5);
-    assertThat(config.getReturnLimit()).isEqualTo(10000);
-    assertThat(config.getConnectTime()).isEqualTo(5000);
-    assertThat(config.getWaitTime()).isEqualTo(5000);
-    assertThat(config.getLocalStoreDriver()).isEqualTo(DEFAULT_DRIVER);
-    assertThat(config.getLocalStorePath().toString() + "/").isEqualTo(localStorePath);
-    assertThat(config.getStoreDriver()).isEqualTo("org.h2.Driver2");
-    assertThat(config.getStoreUrl()).isEqualTo("jdbc:h2:~/gerrit/db");
-    assertThat(config.getUrlOptions()).isEqualTo(Joiner.on(";").join(urlOptions));
-    assertThat(config.getStoreUsername()).isEqualTo("testUsername");
-    assertThat(config.getStorePassword()).isEqualTo("testPassword");
-    assertThat(config.getEvictIdleTime()).isEqualTo(CUSTOM_EVICT_IDLE_TIME);
+    PluginConfig pluginConfig = new PluginConfig(PLUGIN_NAME, customConfig());
+    when(cfgFactoryMock.getFromGerritConfig(PLUGIN_NAME, true)).thenReturn(pluginConfig);
+    EventsLogConfig eventsLogConfig = new EventsLogConfig(cfgFactoryMock, site, PLUGIN_NAME);
+    assertThat(eventsLogConfig.getCopyLocal()).isTrue();
+    assertThat(eventsLogConfig.getMaxAge()).isEqualTo(20);
+    assertThat(eventsLogConfig.getMaxTries()).isEqualTo(5);
+    assertThat(eventsLogConfig.getReturnLimit()).isEqualTo(10000);
+    assertThat(eventsLogConfig.getConnectTime()).isEqualTo(5000);
+    assertThat(eventsLogConfig.getWaitTime()).isEqualTo(5000);
+    assertThat(eventsLogConfig.getLocalStorePath().toString() + "/").isEqualTo(LOCAL_STORE_PATH);
+    assertThat(eventsLogConfig.getStoreUrl()).isEqualTo("jdbc:h2:~/gerrit/db");
+    assertThat(eventsLogConfig.getUrlOptions()).asList().isEqualTo(urlOptions);
+    assertThat(eventsLogConfig.getStoreUsername()).isEqualTo("testUsername");
+    assertThat(eventsLogConfig.getStorePassword()).isEqualTo("testPassword");
+    assertThat(eventsLogConfig.getMaxConnections()).isEqualTo(CUSTOM_MAX_CONNECTIONS);
+  }
+
+  private Config customConfig() {
+    Config config = new Config();
+    config.setBoolean(PLUGIN, PLUGIN_NAME, CONFIG_COPY_LOCAL, true);
+    config.setInt(PLUGIN, PLUGIN_NAME, CONFIG_MAX_AGE, 20);
+    config.setInt(PLUGIN, PLUGIN_NAME, CONFIG_MAX_TRIES, 5);
+    config.setInt(PLUGIN, PLUGIN_NAME, CONFIG_RETURN_LIMIT, 10000);
+    config.setInt(PLUGIN, PLUGIN_NAME, CONFIG_CONN_TIME, 5000);
+    config.setInt(PLUGIN, PLUGIN_NAME, CONFIG_WAIT_TIME, 5000);
+    config.setString(PLUGIN, PLUGIN_NAME, CONFIG_URL, "jdbc:h2:~/gerrit/db");
+    config.setString(PLUGIN, PLUGIN_NAME, CONFIG_LOCAL_PATH, LOCAL_STORE_PATH);
+    config.setStringList(PLUGIN, PLUGIN_NAME, CONFIG_URL_OPTIONS, urlOptions);
+    config.setString(PLUGIN, PLUGIN_NAME, CONFIG_USERNAME, "testUsername");
+    config.setString(PLUGIN, PLUGIN_NAME, CONFIG_PASSWORD, "testPassword");
+    config.setInt(PLUGIN, PLUGIN_NAME, CONFIG_MAX_CONNECTIONS, CUSTOM_MAX_CONNECTIONS);
+    return config;
   }
 }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/eventslog/EventsLogIT.java b/src/test/java/com/ericsson/gerrit/plugins/eventslog/EventsLogIT.java
index d87832a..a6e9ac1 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/eventslog/EventsLogIT.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/eventslog/EventsLogIT.java
@@ -22,10 +22,9 @@
 import org.junit.Test;
 
 @TestPlugin(
-  name = "events-log",
-  sysModule = "com.ericsson.gerrit.plugins.eventslog.sql.SQLModule",
-  httpModule = "com.ericsson.gerrit.plugins.eventslog.HttpModule"
-)
+    name = "events-log",
+    sysModule = "com.ericsson.gerrit.plugins.eventslog.sql.SQLModule",
+    httpModule = "com.ericsson.gerrit.plugins.eventslog.HttpModule")
 public class EventsLogIT extends LightweightPluginDaemonTest {
 
   @Test
diff --git a/src/test/java/com/ericsson/gerrit/plugins/eventslog/sql/EventsLogCleanerTest.java b/src/test/java/com/ericsson/gerrit/plugins/eventslog/sql/EventsLogCleanerTest.java
new file mode 100644
index 0000000..09c21dc
--- /dev/null
+++ b/src/test/java/com/ericsson/gerrit/plugins/eventslog/sql/EventsLogCleanerTest.java
@@ -0,0 +1,61 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// 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.ericsson.gerrit.plugins.eventslog.sql;
+
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.ericsson.gerrit.plugins.eventslog.EventsLogConfig;
+import com.google.gerrit.extensions.events.ProjectDeletedListener;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class EventsLogCleanerTest {
+  private static final String PROJECT = "testProject";
+
+  @Mock private EventsLogConfig cfgMock;
+  @Mock private EventsLogCleaner logCleanerMock;
+  @Mock private SQLClient eventsDb;
+  @Mock private ProjectDeletedListener.Event event;
+
+  private ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
+  private EventsLogCleaner eventsLogCleaner;
+
+  @Before
+  public void setUp() throws Exception {
+    when(event.getProjectName()).thenReturn(PROJECT);
+    eventsLogCleaner = new EventsLogCleaner(eventsDb, executor);
+  }
+
+  @Test
+  public void testOnProjectDeleted() throws InterruptedException {
+    eventsLogCleaner.onProjectDeleted(event);
+    executor.awaitTermination(1, TimeUnit.SECONDS);
+    verify(eventsDb, times(1)).removeProjectEvents(PROJECT);
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    executor.shutdownNow();
+  }
+}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLStoreTest.java b/src/test/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLStoreTest.java
index 0babc3a..96ce35d 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLStoreTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLStoreTest.java
@@ -36,6 +36,7 @@
 import com.google.gerrit.server.permissions.ProjectPermission;
 import com.google.gson.Gson;
 import com.google.inject.Provider;
+import com.zaxxer.hikari.HikariConfig;
 import java.net.ConnectException;
 import java.sql.Connection;
 import java.sql.DriverManager;
@@ -64,7 +65,6 @@
   private static final Logger log = LoggerFactory.getLogger(SQLStoreTest.class);
   private static final String TEST_URL = "jdbc:h2:mem:" + TABLE_NAME;
   private static final String TEST_LOCAL_URL = "jdbc:h2:mem:test";
-  private static final String TEST_DRIVER = "org.h2.Driver";
   private static final String TEST_OPTIONS = "DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false";
   private static final String TERM_CONN_MSG = "terminating connection";
   private static final String MSG = "message";
@@ -75,11 +75,14 @@
   @Mock private PermissionBackend permissionBackendMock;
   @Mock private PermissionBackend.ForProject forProjectMock;
   @Mock private PermissionBackend.WithUser withUserMock;
+  @Mock private EventsLogCleaner logCleanerMock;
 
   private SQLClient eventsDb;
   private SQLClient localEventsDb;
   private SQLStore store;
   private ScheduledExecutorService poolMock;
+  private HikariConfig config;
+
   private Statement stat;
   private MockEvent mockEvent;
 
@@ -87,6 +90,10 @@
 
   @Before
   public void setUp() throws SQLException {
+    config = new HikariConfig();
+    config.setJdbcUrl(TEST_URL);
+    config.addDataSourceProperty("DB_CLOSE_DELAY", "-1");
+    config.addDataSourceProperty("DATABASE_TO_UPPER", "false");
     Connection conn = DriverManager.getConnection(TEST_URL + ";" + TEST_OPTIONS);
     mockEvent = new MockEvent();
     stat = conn.createStatement();
@@ -156,7 +163,14 @@
     doThrow(exceptions).doNothing().when(eventsDb).queryOne();
     store =
         new SQLStore(
-            userProviderMock, cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock);
+            userProviderMock,
+            cfgMock,
+            eventsDb,
+            localEventsDb,
+            poolMock,
+            permissionBackendMock,
+            logCleanerMock);
+
     store.start();
     store.storeEvent(mockEvent);
     verify(eventsDb, times(3)).storeEvent(mockEvent);
@@ -173,7 +187,14 @@
     doThrow(exceptions).doNothing().when(eventsDb).queryOne();
     store =
         new SQLStore(
-            userProviderMock, cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock);
+            userProviderMock,
+            cfgMock,
+            eventsDb,
+            localEventsDb,
+            poolMock,
+            permissionBackendMock,
+            logCleanerMock);
+
     store.start();
     store.storeEvent(mockEvent);
     verify(eventsDb, times(3)).storeEvent(mockEvent);
@@ -187,7 +208,14 @@
     doThrow(new SQLException(MSG)).when(eventsDb).storeEvent(mockEvent);
     store =
         new SQLStore(
-            userProviderMock, cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock);
+            userProviderMock,
+            cfgMock,
+            eventsDb,
+            localEventsDb,
+            poolMock,
+            permissionBackendMock,
+            logCleanerMock);
+
     store.start();
     store.storeEvent(mockEvent);
     verify(eventsDb, times(1)).storeEvent(mockEvent);
@@ -203,7 +231,14 @@
     doThrow(exceptions).doNothing().when(eventsDb).queryOne();
     store =
         new SQLStore(
-            userProviderMock, cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock);
+            userProviderMock,
+            cfgMock,
+            eventsDb,
+            localEventsDb,
+            poolMock,
+            permissionBackendMock,
+            logCleanerMock);
+
     store.start();
     store.storeEvent(mockEvent);
     verify(eventsDb, times(1)).storeEvent(mockEvent);
@@ -216,7 +251,14 @@
     doThrow(new SQLException()).when(eventsDb).queryOne();
     store =
         new SQLStore(
-            userProviderMock, cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock);
+            userProviderMock,
+            cfgMock,
+            eventsDb,
+            localEventsDb,
+            poolMock,
+            permissionBackendMock,
+            logCleanerMock);
+
     store.start();
     store.storeEvent(mockEvent);
     store.queryChangeEvents(GENERIC_QUERY);
@@ -230,11 +272,19 @@
     when(withUserMock.project(any(Project.NameKey.class))).thenReturn(forProjectMock);
     doNothing().when(forProjectMock).check(ProjectPermission.ACCESS);
 
-    eventsDb = new SQLClient(TEST_DRIVER, TEST_URL, TEST_OPTIONS);
-    localEventsDb = new SQLClient(TEST_DRIVER, TEST_LOCAL_URL, TEST_OPTIONS);
+    config.setJdbcUrl(TEST_URL);
+    eventsDb = new SQLClient(config);
+    config.setJdbcUrl(TEST_LOCAL_URL);
+    localEventsDb = new SQLClient(config);
     store =
         new SQLStore(
-            userProviderMock, cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock);
+            userProviderMock,
+            cfgMock,
+            eventsDb,
+            localEventsDb,
+            poolMock,
+            permissionBackendMock,
+            logCleanerMock);
 
     localEventsDb.createDBIfNotCreated();
     localEventsDb.storeEvent(mockEvent);
@@ -255,7 +305,14 @@
     doThrow(new SQLException()).when(eventsDb).queryOne();
     store =
         new SQLStore(
-            userProviderMock, cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock);
+            userProviderMock,
+            cfgMock,
+            eventsDb,
+            localEventsDb,
+            poolMock,
+            permissionBackendMock,
+            logCleanerMock);
+
     store.start();
     verify(localEventsDb).createDBIfNotCreated();
   }
@@ -267,7 +324,14 @@
     doThrow(new SQLException()).when(eventsDb).queryOne();
     store =
         new SQLStore(
-            userProviderMock, cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock);
+            userProviderMock,
+            cfgMock,
+            eventsDb,
+            localEventsDb,
+            poolMock,
+            permissionBackendMock,
+            logCleanerMock);
+
     store.start();
     store.storeEvent(mockEvent);
     verify(localEventsDb).storeEvent(mockEvent);
@@ -281,18 +345,31 @@
     doThrow(new SQLException()).when(eventsDb).queryOne();
     store =
         new SQLStore(
-            userProviderMock, cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock);
+            userProviderMock,
+            cfgMock,
+            eventsDb,
+            localEventsDb,
+            poolMock,
+            permissionBackendMock,
+            logCleanerMock);
+
     store.start();
     store.storeEvent(mockEvent);
     verify(localEventsDb).storeEvent(mockEvent);
   }
 
   private void setUpClient() {
-    eventsDb = new SQLClient(TEST_DRIVER, TEST_URL, TEST_OPTIONS);
-    localEventsDb = new SQLClient(TEST_DRIVER, TEST_LOCAL_URL, TEST_OPTIONS);
+    eventsDb = new SQLClient(config);
+    localEventsDb = new SQLClient(config);
     store =
         new SQLStore(
-            userProviderMock, cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock);
+            userProviderMock,
+            cfgMock,
+            eventsDb,
+            localEventsDb,
+            poolMock,
+            permissionBackendMock,
+            logCleanerMock);
     store.start();
   }
 
@@ -309,13 +386,21 @@
    */
   @Test
   public void testConnectionTask() throws Exception {
-    eventsDb = new SQLClient(TEST_DRIVER, TEST_URL, TEST_OPTIONS);
+    config.setJdbcUrl(TEST_URL);
+    eventsDb = new SQLClient(config);
     localEventsDb = mock(SQLClient.class);
     when(localEventsDb.dbExists()).thenReturn(true);
     when(localEventsDb.getAll()).thenReturn(ImmutableList.of(mock(SQLEntry.class)));
     store =
         new SQLStore(
-            userProviderMock, cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock);
+            userProviderMock,
+            cfgMock,
+            eventsDb,
+            localEventsDb,
+            poolMock,
+            permissionBackendMock,
+            logCleanerMock);
+
     store.start();
     poolMock.scheduleWithFixedDelay(store.new CheckConnectionTask(), 0, 0, TimeUnit.MILLISECONDS);
     verify(localEventsDb, times(2)).removeOldEvents(0);
@@ -333,7 +418,8 @@
 
   private void checkConnectionAndRestore(boolean copy) throws Exception {
     eventsDb = mock(SQLClient.class);
-    localEventsDb = new SQLClient(TEST_DRIVER, TEST_LOCAL_URL, TEST_OPTIONS);
+    config.setJdbcUrl(TEST_LOCAL_URL);
+    localEventsDb = new SQLClient(config);
     localEventsDb.createDBIfNotCreated();
     localEventsDb.storeEvent(mockEvent);
     doThrow(new SQLException(new ConnectException()))
@@ -347,7 +433,14 @@
 
     store =
         new SQLStore(
-            userProviderMock, cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock);
+            userProviderMock,
+            cfgMock,
+            eventsDb,
+            localEventsDb,
+            poolMock,
+            permissionBackendMock,
+            logCleanerMock);
+
     store.start();
     verify(eventsDb).queryOne();
     verify(eventsDb).storeEvent(any(String.class), any(Timestamp.class), any(String.class));
diff --git a/tools/bzl/plugin.bzl b/tools/bzl/plugin.bzl
index a2e438f..0b25d23 100644
--- a/tools/bzl/plugin.bzl
+++ b/tools/bzl/plugin.bzl
@@ -1,6 +1,6 @@
 load(
     "@com_googlesource_gerrit_bazlets//:gerrit_plugin.bzl",
-    "gerrit_plugin",
     "PLUGIN_DEPS",
     "PLUGIN_TEST_DEPS",
+    "gerrit_plugin",
 )