Merge branch 'stable-2.15'

* stable-2.15:
  Upgrade bazlets to the latest stable-2.15 revision
  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 copyright to AOSP
  Inline private method
  Upgrade bazlets to latest stable-2.14 revision
  Automate the SonarQube analysis with bazel
  Format all files with google-java-format
  Compile against Gerrit 2.15
  Fix errorprone issue about tearDown method
  Update bazlets to latest stable-2.14 to use 2.14.7 release API
  Upgrade Mockito to 2.15.0
  Format all Java files with google-java-format
  SQLStoreTest: Fix Spotbugs warning about return value ignored
  SQLEntryTest: Suppress 'unlikely argument type' warning
  SQLStoreTest: Reduce visibility of tearDown method
  Update bazlets to latest revision on stable-2.14
  Build with API version 2.11.7

Change-Id: Iabfa051b2257f5665cfc9f24e53eff3ff677c2b2
diff --git a/BUILD b/BUILD
index cd34b32..04f201a 100644
--- a/BUILD
+++ b/BUILD
@@ -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/external_plugin_deps.bzl b/external_plugin_deps.bzl
index 53fbc09..4ca722d 100644
--- a/external_plugin_deps.bzl
+++ b/external_plugin_deps.bzl
@@ -3,22 +3,37 @@
 def external_plugin_deps():
     maven_jar(
         name = "mockito",
-        artifact = "org.mockito:mockito-core:2.5.0",
-        sha1 = "be28d46a52c7f2563580adeca350145e9ce916f8",
+        artifact = "org.mockito:mockito-core:2.21.0",
+        sha1 = "cdd1d0d5b2edbd2a7040735ccf88318c031f458b",
         deps = [
             "@byte_buddy//jar",
+            "@byte_buddy_agent//jar",
             "@objenesis//jar",
         ],
     )
 
+    BYTE_BUDDY_VER = "1.8.15"
+
     maven_jar(
         name = "byte_buddy",
-        artifact = "net.bytebuddy:byte-buddy:1.5.12",
-        sha1 = "b1ba1d15f102b36ed43b826488114678d6d413da",
+        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 = "a2dbe3457401f65ad4022617fbb3fc0e5f427c7d",
     )
 
     maven_jar(
         name = "objenesis",
-        artifact = "org.objenesis:objenesis:2.4",
-        sha1 = "2916b6c96b50c5b3ec4452ed99401db745aabb27",
+        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/EventHandler.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventHandler.java
index 6364e97..6a020c5 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventHandler.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Ericsson
+// Copyright (C) 2014 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.
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 2851c03..3f905c7 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventModule.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventModule.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Ericsson
+// Copyright (C) 2014 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.
@@ -14,7 +14,9 @@
 
 package com.ericsson.gerrit.plugins.eventslog;
 
+import com.ericsson.gerrit.plugins.eventslog.sql.EventsLogCleaner;
 import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.extensions.events.ProjectDeletedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.server.events.EventListener;
 import com.google.inject.AbstractModule;
@@ -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/EventPool.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventPool.java
index 2c7e962..e4646b0 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventPool.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventPool.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Ericsson
+// Copyright (C) 2015 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.
diff --git a/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventQueue.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventQueue.java
index 334976f..72c3c5a 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventQueue.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventQueue.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Ericsson
+// Copyright (C) 2015 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.
diff --git a/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventStore.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventStore.java
index 0b782f1..468404e 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventStore.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventStore.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Ericsson
+// Copyright (C) 2014 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.
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 24dffe7..c1a9b4a 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventsLogConfig.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventsLogConfig.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Ericsson
+// Copyright (C) 2015 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.
@@ -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,18 +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 = concatenate(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);
-  }
-
-  private String concatenate(String[] stringList) {
-    return Joiner.on(";").join(stringList);
+    maxConnections = Math.max(cfg.getInt(CONFIG_MAX_CONNECTIONS, DEFAULT_MAX_CONNECTIONS), 1);
   }
 
   public int getMaxAge() {
@@ -105,15 +98,11 @@
     return connectTime;
   }
 
-  public String getStoreDriver() {
-    return storeDriver;
-  }
-
   public String getStoreUrl() {
     return storeUrl;
   }
 
-  public String getUrlOptions() {
+  public String[] getUrlOptions() {
     return urlOptions;
   }
 
@@ -129,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;
   }
@@ -142,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/EventsLogException.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventsLogException.java
index ad110ff..70a8b74 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventsLogException.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventsLogException.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Ericsson
+// Copyright (C) 2015 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.
diff --git a/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventsRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventsRestApiServlet.java
index 1258771..f05e33d 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventsRestApiServlet.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventsRestApiServlet.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Ericsson
+// Copyright (C) 2014 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.
diff --git a/src/main/java/com/ericsson/gerrit/plugins/eventslog/HttpModule.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/HttpModule.java
index 9bec33f..c1db514 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/HttpModule.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/HttpModule.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Ericsson
+// Copyright (C) 2014 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.
diff --git a/src/main/java/com/ericsson/gerrit/plugins/eventslog/MalformedQueryException.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/MalformedQueryException.java
index dc98cc3..35f829d 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/MalformedQueryException.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/MalformedQueryException.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Ericsson
+// Copyright (C) 2014 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.
diff --git a/src/main/java/com/ericsson/gerrit/plugins/eventslog/QueryMaker.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/QueryMaker.java
index ec04d34..1969755 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/QueryMaker.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/QueryMaker.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Ericsson
+// Copyright (C) 2014 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.
diff --git a/src/main/java/com/ericsson/gerrit/plugins/eventslog/ServiceUnavailableException.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/ServiceUnavailableException.java
index 3684432..8689eb8 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/ServiceUnavailableException.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/ServiceUnavailableException.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Ericsson
+// Copyright (C) 2015 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.
diff --git a/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/EventsDb.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/EventsDb.java
index eb589f7..0016fde 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/EventsDb.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/EventsDb.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Ericsson
+// Copyright (C) 2015 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.
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/LocalEventsDb.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/LocalEventsDb.java
index bd93ec3..654d9e8 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/LocalEventsDb.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/LocalEventsDb.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Ericsson
+// Copyright (C) 2015 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.
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 68ef630..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
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Ericsson
+// Copyright (C) 2015 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.
@@ -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/SQLEntry.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLEntry.java
index 75542d2..1ae9228 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLEntry.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLEntry.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Ericsson
+// Copyright (C) 2015 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.
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 6b9639c..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
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Ericsson
+// Copyright (C) 2015 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.
@@ -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 b79cc09..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
@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Ericsson
+// Copyright (C) 2014 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.
@@ -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 72ed8fb..cc8b3d8 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
@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Ericsson
+// Copyright (C) 2014 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.
@@ -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;
@@ -32,14 +32,13 @@
 import com.google.gerrit.server.permissions.ProjectPermission;
 import com.google.inject.Inject;
 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;
@@ -53,6 +52,7 @@
   private static final Logger log = LoggerFactory.getLogger(SQLStore.class);
   private static final String H2_DB_SUFFIX = ".h2.db";
 
+  private final EventsLogCleaner eventsLogCleaner;
   private SQLClient eventsDb;
   private SQLClient localEventsDb;
   private final int maxAge;
@@ -72,7 +72,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();
@@ -80,6 +81,7 @@
     this.copyLocal = cfg.getCopyLocal();
     this.eventsDb = eventsDb;
     this.localEventsDb = localEventsDb;
+    this.eventsLogCleaner = eventsLogCleaner;
     this.pool = pool;
     this.permissionBackend = permissionBackend;
     this.localPath = cfg.getLocalStorePath();
@@ -88,6 +90,7 @@
   @Override
   public void start() {
     setUp();
+    eventsLogCleaner.scheduleCleaningWith(maxAge);
   }
 
   @Override
@@ -123,16 +126,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());
   }
 
   /**
@@ -153,14 +147,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;
           }
@@ -175,7 +169,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);
     }
   }
@@ -193,7 +187,6 @@
     if (online) {
       restoreEventsFromLocal();
     }
-    getEventsDb().removeOldEvents(maxAge);
   }
 
   private SQLClient getEventsDb() {
@@ -288,12 +281,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 3de5229..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
@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Ericsson
+// Copyright (C) 2014 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.
@@ -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/EventHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/eventslog/EventHandlerTest.java
index bdebed8..0007ab8 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/eventslog/EventHandlerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/eventslog/EventHandlerTest.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Ericsson
+// Copyright (C) 2014 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.
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 f406f8f..702328b 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/eventslog/EventsLogConfigTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/eventslog/EventsLogConfigTest.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Ericsson
+// Copyright (C) 2015 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.
@@ -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 1d2ca32..a6e9ac1 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/eventslog/EventsLogIT.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/eventslog/EventsLogIT.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Ericsson
+// Copyright (C) 2015 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.
diff --git a/src/test/java/com/ericsson/gerrit/plugins/eventslog/EventsRestApiServletTest.java b/src/test/java/com/ericsson/gerrit/plugins/eventslog/EventsRestApiServletTest.java
index ea026d5..917d9d8 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/eventslog/EventsRestApiServletTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/eventslog/EventsRestApiServletTest.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Ericsson
+// Copyright (C) 2014 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.
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/QueryMakerTest.java b/src/test/java/com/ericsson/gerrit/plugins/eventslog/sql/QueryMakerTest.java
index 45a7426..cd2a61d 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/eventslog/sql/QueryMakerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/eventslog/sql/QueryMakerTest.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Ericsson
+// Copyright (C) 2014 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.
diff --git a/src/test/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLEntryTest.java b/src/test/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLEntryTest.java
index 4d0fdc4..3fd3217 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLEntryTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLEntryTest.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Ericsson
+// Copyright (C) 2015 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.
@@ -66,6 +66,7 @@
     assertThat(entry2.compareTo(entry1)).isEqualTo(-1);
   }
 
+  @SuppressWarnings("unlikely-arg-type")
   @Test
   public void testEquals() throws Exception {
     assertThat(entry1.equals(null)).isFalse();
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 cd89fcd..b473ad9 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
@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Ericsson
+// Copyright (C) 2014 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.
@@ -34,6 +34,7 @@
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.permissions.ProjectPermission;
 import com.google.gson.Gson;
+import com.zaxxer.hikari.HikariConfig;
 import java.net.ConnectException;
 import java.sql.Connection;
 import java.sql.DriverManager;
@@ -62,7 +63,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";
@@ -72,11 +72,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;
 
@@ -84,6 +87,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();
@@ -107,7 +114,7 @@
     store.storeEvent(mockEvent);
     List<String> events = store.queryChangeEvents(GENERIC_QUERY);
     String json = new Gson().toJson(mockEvent);
-    assertThat(events).containsExactly(json);
+    assertThat(events).containsExactly(json).inOrder();
   }
 
   @Test
@@ -151,7 +158,10 @@
     setUpClientMock();
     doThrow(exceptions).doNothing().when(eventsDb).storeEvent(mockEvent);
     doThrow(exceptions).doNothing().when(eventsDb).queryOne();
-    store = new SQLStore(cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock);
+    store =
+        new SQLStore(
+            cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock, logCleanerMock);
+
     store.start();
     store.storeEvent(mockEvent);
     verify(eventsDb, times(3)).storeEvent(mockEvent);
@@ -166,7 +176,11 @@
     setUpClientMock();
     doThrow(exceptions).doNothing().when(eventsDb).storeEvent(mockEvent);
     doThrow(exceptions).doNothing().when(eventsDb).queryOne();
-    store = new SQLStore(cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock);
+
+    store =
+        new SQLStore(
+            cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock, logCleanerMock);
+
     store.start();
     store.storeEvent(mockEvent);
     verify(eventsDb, times(3)).storeEvent(mockEvent);
@@ -178,7 +192,11 @@
     when(cfgMock.getMaxTries()).thenReturn(3);
     setUpClientMock();
     doThrow(new SQLException(MSG)).when(eventsDb).storeEvent(mockEvent);
-    store = new SQLStore(cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock);
+
+    store =
+        new SQLStore(
+            cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock, logCleanerMock);
+
     store.start();
     store.storeEvent(mockEvent);
     verify(eventsDb, times(1)).storeEvent(mockEvent);
@@ -192,7 +210,11 @@
     setUpClientMock();
     doThrow(exceptions).doNothing().when(eventsDb).storeEvent(mockEvent);
     doThrow(exceptions).doNothing().when(eventsDb).queryOne();
-    store = new SQLStore(cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock);
+
+    store =
+        new SQLStore(
+            cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock, logCleanerMock);
+
     store.start();
     store.storeEvent(mockEvent);
     verify(eventsDb, times(1)).storeEvent(mockEvent);
@@ -203,7 +225,11 @@
     setUpClientMock();
     doThrow(new SQLException(new ConnectException())).when(eventsDb).createDBIfNotCreated();
     doThrow(new SQLException()).when(eventsDb).queryOne();
-    store = new SQLStore(cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock);
+
+    store =
+        new SQLStore(
+            cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock, logCleanerMock);
+
     store.start();
     store.storeEvent(mockEvent);
     store.queryChangeEvents(GENERIC_QUERY);
@@ -213,13 +239,18 @@
   public void restoreEventsFromLocalDb() throws Exception {
     MockEvent mockEvent = new MockEvent();
     MockEvent mockEvent2 = new MockEvent("proj");
+
     when(permissionBackendMock.currentUser()).thenReturn(withUserMock);
     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);
-    store = new SQLStore(cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock);
+    config.setJdbcUrl(TEST_URL);
+    eventsDb = new SQLClient(config);
+    config.setJdbcUrl(TEST_LOCAL_URL);
+    localEventsDb = new SQLClient(config);
+    store =
+        new SQLStore(
+            cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock, logCleanerMock);
 
     localEventsDb.createDBIfNotCreated();
     localEventsDb.storeEvent(mockEvent);
@@ -230,7 +261,10 @@
     Gson gson = new Gson();
     String json = gson.toJson(mockEvent);
     String json2 = gson.toJson(mockEvent2);
+
     assertThat(events).containsExactly(json, json2);
+
+    assertThat(events).containsExactly(json, json2).inOrder();
   }
 
   @Test
@@ -238,7 +272,11 @@
     setUpClientMock();
     doThrow(new SQLException(new ConnectException())).when(eventsDb).createDBIfNotCreated();
     doThrow(new SQLException()).when(eventsDb).queryOne();
-    store = new SQLStore(cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock);
+
+    store =
+        new SQLStore(
+            cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock, logCleanerMock);
+
     store.start();
     verify(localEventsDb).createDBIfNotCreated();
   }
@@ -248,7 +286,11 @@
     setUpClientMock();
     doThrow(new SQLException(new ConnectException())).when(eventsDb).createDBIfNotCreated();
     doThrow(new SQLException()).when(eventsDb).queryOne();
-    store = new SQLStore(cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock);
+
+    store =
+        new SQLStore(
+            cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock, logCleanerMock);
+
     store.start();
     store.storeEvent(mockEvent);
     verify(localEventsDb).storeEvent(mockEvent);
@@ -260,16 +302,24 @@
     when(cfgMock.getMaxTries()).thenReturn(0);
     doThrow(new SQLException(new ConnectException())).when(eventsDb).createDBIfNotCreated();
     doThrow(new SQLException()).when(eventsDb).queryOne();
-    store = new SQLStore(cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock);
+
+    store =
+        new SQLStore(
+            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);
-    store = new SQLStore(cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock);
+
+    eventsDb = new SQLClient(config);
+    localEventsDb = new SQLClient(config);
+    store =
+        new SQLStore(
+            cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock, logCleanerMock);
+
     store.start();
   }
 
@@ -286,11 +336,16 @@
    */
   @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(cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock);
+
+    store =
+        new SQLStore(
+            cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock, logCleanerMock);
+
     store.start();
     poolMock.scheduleWithFixedDelay(store.new CheckConnectionTask(), 0, 0, TimeUnit.MILLISECONDS);
     verify(localEventsDb, times(2)).removeOldEvents(0);
@@ -308,7 +363,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()))
@@ -320,7 +376,10 @@
       when(cfgMock.getCopyLocal()).thenReturn(true);
     }
 
-    store = new SQLStore(cfgMock, eventsDb, localEventsDb, poolMock, permissionBackendMock);
+    store =
+        new SQLStore(
+            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/sonar/sonar.sh b/tools/sonar/sonar.sh
new file mode 100755
index 0000000..39df185
--- /dev/null
+++ b/tools/sonar/sonar.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+# 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.
+`bazel query @com_googlesource_gerrit_bazlets//tools/sonar:sonar --output location | sed s/BUILD:.*//`sonar.py