Merge branch 'stable-2.14' into stable-2.15
* stable-2.14:
Upgrade bazlets to latest stable-2.14 to build with 2.14.21 API
Change-Id: I150bec0ec981662673102f74aebd7adc5da9db8d
diff --git a/WORKSPACE b/WORKSPACE
index 5204a9a..2223b91 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -3,7 +3,7 @@
load("//:bazlets.bzl", "load_bazlets")
load_bazlets(
- commit = "78c35a7eb33ee5ea0980923e246c7dba37347193",
+ commit = "f53f51fb660552d0581aa0ba52c3836ed63d56a3",
#local_path = "/home/<user>/projects/bazlets",
)
diff --git a/external_plugin_deps.bzl b/external_plugin_deps.bzl
index 5e88fc1..b3f9594 100644
--- a/external_plugin_deps.bzl
+++ b/external_plugin_deps.bzl
@@ -3,8 +3,8 @@
def external_plugin_deps():
maven_jar(
name = "mockito",
- artifact = "org.mockito:mockito-core:2.27.0",
- sha1 = "835fc3283b481f4758b8ef464cd560c649c08b00",
+ artifact = "org.mockito:mockito-core:2.28.2",
+ sha1 = "91110215a8cb9b77a46e045ee758f77d79167cc0",
deps = [
"@byte-buddy//jar",
"@byte-buddy-agent//jar",
diff --git a/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventCleanerQueue.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventCleanerQueue.java
index d8e2aff..66fcc81 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventCleanerQueue.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventCleanerQueue.java
@@ -14,36 +14,38 @@
package com.ericsson.gerrit.plugins.eventslog;
+import com.google.gerrit.extensions.annotations.PluginName;
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.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ScheduledExecutorService;
@Singleton
public class EventCleanerQueue implements LifecycleListener {
private final WorkQueue workQueue;
- private WorkQueue.Executor pool;
+ private final String pluginName;
+ private ScheduledExecutorService pool;
@Inject
- public EventCleanerQueue(WorkQueue workQueue) {
+ public EventCleanerQueue(WorkQueue workQueue, @PluginName String pluginName) {
this.workQueue = workQueue;
+ this.pluginName = pluginName;
}
@Override
public void start() {
- pool = workQueue.createQueue(1, "[events-log] Remove events");
+ pool = workQueue.createQueue(1, String.format("[%s] Remove events", pluginName));
}
@Override
public void stop() {
if (pool != null) {
- pool.unregisterWorkQueue();
pool = null;
}
}
- public ScheduledThreadPoolExecutor getPool() {
- return pool;
+ 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 262316c..15f830a 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventHandler.java
@@ -18,15 +18,15 @@
import com.google.gerrit.server.events.Event;
import com.google.gerrit.server.events.ProjectEvent;
import com.google.inject.Inject;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ScheduledExecutorService;
/** Listen to Events and store them into the EventStore */
class EventHandler implements EventListener {
private final EventStore store;
- private final ScheduledThreadPoolExecutor pool;
+ private final ScheduledExecutorService pool;
@Inject
- EventHandler(EventStore store, @EventPool ScheduledThreadPoolExecutor pool) {
+ EventHandler(EventStore store, @EventPool ScheduledExecutorService pool) {
this.store = store;
this.pool = 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 56f8802..b888425 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventModule.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventModule.java
@@ -23,7 +23,7 @@
import com.google.inject.Provides;
import com.google.inject.Scopes;
import com.google.inject.internal.UniqueAnnotations;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ScheduledExecutorService;
/** Configures handling for an event queue while providing its pool. */
public class EventModule extends AbstractModule {
@@ -42,13 +42,13 @@
@Provides
@EventPool
- ScheduledThreadPoolExecutor provideEventPool(EventQueue queue) {
+ ScheduledExecutorService provideEventPool(EventQueue queue) {
return queue.getPool();
}
@Provides
@EventCleanerPool
- ScheduledThreadPoolExecutor provideEventCleanerPool(EventCleanerQueue queue) {
+ 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 32e9465..e4646b0 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventPool.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventPool.java
@@ -19,7 +19,7 @@
import com.google.inject.BindingAnnotation;
import java.lang.annotation.Retention;
-/** Annotation applied to a ScheduledThreadPoolExecutor. */
+/** Annotation applied to a ScheduledExecutorService. */
@Retention(RUNTIME)
@BindingAnnotation
public @interface EventPool {}
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 eca051b..4d2ba5d 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventQueue.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/EventQueue.java
@@ -14,36 +14,38 @@
package com.ericsson.gerrit.plugins.eventslog;
+import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.server.git.WorkQueue;
import com.google.inject.Inject;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ScheduledExecutorService;
/** A queue for events to store. */
public class EventQueue implements LifecycleListener {
private final WorkQueue workQueue;
- private WorkQueue.Executor pool;
+ private final String pluginName;
+ private ScheduledExecutorService pool;
@Inject
- EventQueue(WorkQueue workQueue) {
+ EventQueue(WorkQueue workQueue, @PluginName String pluginName) {
this.workQueue = workQueue;
+ this.pluginName = pluginName;
}
/** {@inheritDoc} Create a new executor queue in WorkQueue for storing events. */
@Override
public void start() {
- pool = workQueue.createQueue(1, "Store events");
+ pool = workQueue.createQueue(1, String.format("[%s] Store events", pluginName));
}
@Override
public void stop() {
if (pool != null) {
- pool.unregisterWorkQueue();
pool = null;
}
}
- ScheduledThreadPoolExecutor getPool() {
+ ScheduledExecutorService getPool() {
return this.pool;
}
}
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
index 3c152e8..efff521 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/EventsLogCleaner.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/EventsLogCleaner.java
@@ -15,6 +15,7 @@
package com.ericsson.gerrit.plugins.eventslog.sql;
import com.ericsson.gerrit.plugins.eventslog.EventCleanerPool;
+import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.events.ProjectDeletedListener;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -22,7 +23,7 @@
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Singleton
@@ -31,13 +32,17 @@
private static final long INTERVAL = TimeUnit.DAYS.toSeconds(1);
private final SQLClient eventsDb;
+ private final String pluginName;
- private ScheduledThreadPoolExecutor pool;
+ private ScheduledExecutorService pool;
@Inject
EventsLogCleaner(
- @EventsDb SQLClient eventsDb, @EventCleanerPool ScheduledThreadPoolExecutor pool) {
+ @EventsDb SQLClient eventsDb,
+ @EventCleanerPool ScheduledExecutorService pool,
+ @PluginName String pluginName) {
this.eventsDb = eventsDb;
+ this.pluginName = pluginName;
this.pool = pool;
}
@@ -47,12 +52,12 @@
}
public void removeProjectEventsAsync(String projectName) {
- pool.submit(() -> eventsDb.removeProjectEvents(projectName));
+ pool.submit(new RemoveProjectEventsTask(pluginName, projectName));
}
public void scheduleCleaningWith(int maxAge) {
pool.scheduleAtFixedRate(
- () -> eventsDb.removeOldEvents(maxAge), getInitialDelay(), INTERVAL, TimeUnit.SECONDS);
+ new RemoveOldEventsTask(pluginName, maxAge), getInitialDelay(), INTERVAL, TimeUnit.SECONDS);
}
private long getInitialDelay() {
@@ -63,4 +68,44 @@
}
return Duration.between(now, next).getSeconds();
}
+
+ private class RemoveProjectEventsTask implements Runnable {
+ private final String projectName;
+ private final String taskName;
+
+ RemoveProjectEventsTask(String prefix, String projectName) {
+ this.projectName = projectName;
+ this.taskName = String.format("[%s] Remove events for project %s", prefix, projectName);
+ }
+
+ @Override
+ public void run() {
+ eventsDb.removeProjectEvents(projectName);
+ }
+
+ @Override
+ public String toString() {
+ return taskName;
+ }
+ }
+
+ private class RemoveOldEventsTask implements Runnable {
+ private final int maxAge;
+ private final String taskName;
+
+ RemoveOldEventsTask(String prefix, int maxAge) {
+ this.maxAge = maxAge;
+ this.taskName = String.format("[%s] Remove old events", prefix);
+ }
+
+ @Override
+ public void run() {
+ eventsDb.removeOldEvents(maxAge);
+ }
+
+ @Override
+ public String toString() {
+ return taskName;
+ }
+ }
}
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 05f663f..9e538e6 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
@@ -48,7 +48,7 @@
class SQLClient {
private static final Logger log = LoggerFactory.getLogger(SQLClient.class);
private final Gson gson;
- private final boolean isPostgresql;
+ private final SQLDialect databaseDialect;
private HikariDataSource ds;
@@ -56,7 +56,8 @@
ds = new HikariDataSource(config);
gson = new GsonBuilder().registerTypeAdapter(Supplier.class, new SupplierSerializer()).create();
- isPostgresql = config.getJdbcUrl().contains("postgresql");
+
+ databaseDialect = SQLDialect.fromJdbcUrl(config.getJdbcUrl());
}
/**
@@ -65,8 +66,8 @@
* @throws SQLException If there was a problem with the database
*/
void createDBIfNotCreated() throws SQLException {
- execute(SQLTable.createTableQuery(isPostgresql));
- execute(SQLTable.createIndexes(isPostgresql));
+ execute(SQLTable.createTableQuery(databaseDialect));
+ execute(SQLTable.createIndexes(databaseDialect));
}
/**
diff --git a/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLDialect.java b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLDialect.java
new file mode 100644
index 0000000..10df147
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLDialect.java
@@ -0,0 +1,38 @@
+// Copyright (C) 2019 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;
+
+/** SQLDialect provides enumerations for the various supported dialects of SQL. */
+public enum SQLDialect {
+ H2,
+ MYSQL,
+ POSTGRESQL;
+
+ /**
+ * This attempts to determine the SQL dialect from the JDBC URL. If the URL does not match one of
+ * the supported dialects, then H2 will be returned by default.
+ *
+ * @param jdbcUrl The JDBC URL.
+ * @return The dialect for the JDBC URL.
+ */
+ public static SQLDialect fromJdbcUrl(String jdbcUrl) {
+ if (jdbcUrl.contains("postgresql")) {
+ return POSTGRESQL;
+ } else if (jdbcUrl.contains("mysql")) {
+ return MYSQL;
+ }
+ return H2;
+ }
+}
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 8e2ab52..ba38c4e 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
@@ -23,12 +23,15 @@
import com.ericsson.gerrit.plugins.eventslog.EventsLogException;
import com.ericsson.gerrit.plugins.eventslog.ServiceUnavailableException;
import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.events.ProjectEvent;
-import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -41,8 +44,8 @@
import java.util.Collection;
import java.util.List;
import java.util.Map.Entry;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -52,7 +55,6 @@
private static final Logger log = LoggerFactory.getLogger(SQLStore.class);
private static final String H2_DB_SUFFIX = ".h2.db";
- private final ProjectControl.GenericFactory projectControlFactory;
private final Provider<CurrentUser> userProvider;
private final EventsLogCleaner eventsLogCleaner;
private SQLClient eventsDb;
@@ -63,31 +65,35 @@
private final int connectTime;
private boolean online = true;
private boolean copyLocal;
- private final ScheduledThreadPoolExecutor pool;
+ private final ScheduledExecutorService pool;
+ private final PermissionBackend permissionBackend;
+ private final String pluginName;
private ScheduledFuture<?> checkConnTask;
private Path localPath;
@Inject
SQLStore(
- ProjectControl.GenericFactory projectControlFactory,
Provider<CurrentUser> userProvider,
EventsLogConfig cfg,
@EventsDb SQLClient eventsDb,
@LocalEventsDb SQLClient localEventsDb,
- @EventPool ScheduledThreadPoolExecutor pool,
- EventsLogCleaner eventsLogCleaner) {
+ @EventPool ScheduledExecutorService pool,
+ PermissionBackend permissionBackend,
+ EventsLogCleaner eventsLogCleaner,
+ @PluginName String pluginName) {
this.maxAge = cfg.getMaxAge();
this.maxTries = cfg.getMaxTries();
this.waitTime = cfg.getWaitTime();
this.connectTime = cfg.getConnectTime();
this.copyLocal = cfg.getCopyLocal();
- this.projectControlFactory = projectControlFactory;
this.userProvider = userProvider;
this.eventsDb = eventsDb;
this.localEventsDb = localEventsDb;
this.eventsLogCleaner = eventsLogCleaner;
this.pool = pool;
+ this.permissionBackend = permissionBackend;
this.localPath = cfg.getLocalStorePath();
+ this.pluginName = pluginName;
}
@Override
@@ -118,18 +124,15 @@
for (Entry<String, Collection<SQLEntry>> entry : eventsDb.getEvents(query).asMap().entrySet()) {
String projectName = entry.getKey();
try {
- if (projectControlFactory
- .controlFor(new Project.NameKey(projectName), userProvider.get())
- .isVisible()) {
- entries.addAll(entry.getValue());
- }
- } catch (NoSuchProjectException e) {
- log.warn(
- "Database contains a non-existing project, {}; removing project from database",
- projectName);
- eventsLogCleaner.removeProjectEventsAsync(projectName);
- } catch (IOException e) {
- log.warn("Cannot get project visibility info for {} from cache", projectName, e);
+ permissionBackend
+ .user(userProvider)
+ .project(new Project.NameKey(projectName))
+ .check(ProjectPermission.ACCESS);
+ entries.addAll(entry.getValue());
+ } catch (AuthException e) {
+ // Ignore
+ } catch (PermissionBackendException e) {
+ log.warn("Cannot check project access permission", e);
}
}
return entries.stream().sorted().map(SQLEntry::getEvent).collect(toList());
@@ -205,7 +208,7 @@
if (!online) {
checkConnTask =
pool.scheduleWithFixedDelay(
- new CheckConnectionTask(), 0, connectTime, TimeUnit.MILLISECONDS);
+ new CheckConnectionTask(pluginName), 0, connectTime, TimeUnit.MILLISECONDS);
} else {
cancelCheckConnectionTaskIfScheduled(false);
}
@@ -246,7 +249,11 @@
}
class CheckConnectionTask implements Runnable {
- CheckConnectionTask() {}
+ private final String taskName;
+
+ CheckConnectionTask(String prefix) {
+ this.taskName = String.format("[%s] Connect to database", prefix);
+ }
@Override
public void run() {
@@ -258,7 +265,7 @@
@Override
public String toString() {
- return "(Events-log) Connect to database";
+ return taskName;
}
private boolean checkConnection() {
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 0db07a2..6093a38 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,9 +23,28 @@
static final String DATE_ENTRY = "date_created";
static final String EVENT_ENTRY = "event_info";
+ /** This is the name of the index that tracks the created date. */
private static final String CREATED_INDEX = "created_idx";
+ /** This is the name of the index that tracks the project. */
private static final String PROJECT_INDEX = "project_idx";
+ /**
+ * This is the H2 idempotent index-creation query format. Inputs, in order: index-name,
+ * table-name, index-column
+ */
private static final String H2_INDEX_CREATION_FORMAT = "CREATE INDEX IF NOT EXISTS %s ON %s (%s)";
+ /**
+ * This is the MySQL idempotent index-creation query format. Inputs, in order: table-name,
+ * index-name, table-name, index-name, index-column
+ */
+ private static final String MYSQL_INDEX_CREATION_FORMAT =
+ "SET @x := (SELECT COUNT(*) FROM information_schema.statistics WHERE table_name = '%s' AND index_name = '%s' AND table_schema = DATABASE());\n"
+ + "SET @sql := IF( @x > 0, 'SELECT ''Index exists.''', 'ALTER TABLE %s ADD INDEX %s (%s);');\n"
+ + "PREPARE stmt FROM @sql;\n"
+ + "EXECUTE stmt";
+ /**
+ * This is the Postgres idempotent index-creation query format. Inputs, in order: index-name,
+ * index-name, table-name, index-column
+ */
private static final String POSTGRESQL_INDEX_CREATION_FORMAT =
"DO $$\n"
+ "BEGIN\n"
@@ -42,13 +61,17 @@
private SQLTable() {}
- static String createTableQuery(boolean postgresql) {
+ static String createTableQuery(SQLDialect databaseDialect) {
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));
- } else {
- query.append(format("%s INT AUTO_INCREMENT PRIMARY KEY,", PRIMARY_ENTRY));
+ switch (databaseDialect) {
+ case POSTGRESQL:
+ query.append(format("%s SERIAL PRIMARY KEY,", PRIMARY_ENTRY));
+ break;
+ case MYSQL:
+ case H2:
+ default:
+ query.append(format("%s INT AUTO_INCREMENT PRIMARY KEY,", PRIMARY_ENTRY));
}
query.append(format("%s VARCHAR(255),", PROJECT_ENTRY));
query.append(format("%s TIMESTAMP DEFAULT NOW(),", DATE_ENTRY));
@@ -56,11 +79,19 @@
return query.toString();
}
- static String createIndexes(boolean postgresql) {
- return postgresql ? getPostgresqlQuery() : getH2Query();
+ static String createIndexes(SQLDialect databaseDialect) {
+ switch (databaseDialect) {
+ case POSTGRESQL:
+ return getPostgresqlIndexQuery();
+ case MYSQL:
+ return getMysqlIndexQuery();
+ case H2:
+ default:
+ return getH2IndexQuery();
+ }
}
- private static String getPostgresqlQuery() {
+ private static String getPostgresqlIndexQuery() {
StringBuilder query = new StringBuilder(540);
query.append(
format(
@@ -80,7 +111,29 @@
return query.toString();
}
- private static String getH2Query() {
+ private static String getMysqlIndexQuery() {
+ StringBuilder query = new StringBuilder();
+ query.append(
+ format(
+ MYSQL_INDEX_CREATION_FORMAT,
+ TABLE_NAME,
+ CREATED_INDEX,
+ TABLE_NAME,
+ CREATED_INDEX,
+ DATE_ENTRY));
+ query.append(";");
+ query.append(
+ format(
+ MYSQL_INDEX_CREATION_FORMAT,
+ TABLE_NAME,
+ PROJECT_INDEX,
+ TABLE_NAME,
+ PROJECT_INDEX,
+ PROJECT_ENTRY));
+ return query.toString();
+ }
+
+ private static String getH2IndexQuery() {
StringBuilder query = new StringBuilder();
query.append(format(H2_INDEX_CREATION_FORMAT, CREATED_INDEX, TABLE_NAME, DATE_ENTRY));
query.append(";");
diff --git a/src/main/resources/Documentation/build.md b/src/main/resources/Documentation/build.md
index 48ee3c6..130cc2a 100644
--- a/src/main/resources/Documentation/build.md
+++ b/src/main/resources/Documentation/build.md
@@ -78,7 +78,7 @@
To execute the tests run:
```
- bazel test plugins/@PLUGIN@:events_log_tests
+ bazel test plugins/@PLUGIN@:events-log_tests
```
More information about Buck can be found in the [Gerrit
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 4421212..1c74a20 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -26,6 +26,10 @@
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.
+ Supported database engines:
+* h2 (default)
+* postgresql
+* mysql
plugin.@PLUGIN@.localStorePath
: Specify the path to the directory in which to keep the back up database.
@@ -42,8 +46,12 @@
plugin.@PLUGIN@.urlOptions
: Options to append to the database url. Each option should be specified in a
separate line using the option=value format. For example:
- urlOptions = loglevel=INFO
- urlOptions = logUnclosedConnections=true
+* `urlOptions = loglevel=INFO`
+* `urlOptions = logUnclosedConnections=true`
+
+When using `mysql`, this option must be specified:
+
+* `urlOptions = allowMultiQueries=true`
plugin.@PLUGIN@.maxTries
: Maximum number of times the plugin should attempt to store the event if a
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 f3eb207..0007ab8 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/eventslog/EventHandlerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/eventslog/EventHandlerTest.java
@@ -21,6 +21,7 @@
import com.google.gerrit.server.events.ChangeEvent;
import com.google.gerrit.server.events.Event;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import org.junit.Before;
import org.junit.Test;
@@ -35,7 +36,7 @@
@Before
public void setUp() {
- ScheduledThreadPoolExecutor poolMock = new PoolMock();
+ ScheduledExecutorService poolMock = new PoolMock();
eventHandler = new EventHandler(storeMock, poolMock);
}
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
index 09c21dc..11fa891 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/eventslog/sql/EventsLogCleanerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/eventslog/sql/EventsLogCleanerTest.java
@@ -32,6 +32,7 @@
@RunWith(MockitoJUnitRunner.class)
public class EventsLogCleanerTest {
private static final String PROJECT = "testProject";
+ private static final String PLUGIN_NAME = "events-log";
@Mock private EventsLogConfig cfgMock;
@Mock private EventsLogCleaner logCleanerMock;
@@ -44,7 +45,7 @@
@Before
public void setUp() throws Exception {
when(event.getProjectName()).thenReturn(PROJECT);
- eventsLogCleaner = new EventsLogCleaner(eventsDb, executor);
+ eventsLogCleaner = new EventsLogCleaner(eventsDb, executor, PLUGIN_NAME);
}
@Test
diff --git a/src/test/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLDialectTest.java b/src/test/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLDialectTest.java
new file mode 100644
index 0000000..60b7cb1
--- /dev/null
+++ b/src/test/java/com/ericsson/gerrit/plugins/eventslog/sql/SQLDialectTest.java
@@ -0,0 +1,38 @@
+// 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 com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+public class SQLDialectTest {
+ @Test
+ public void defaultIsH2() throws Exception {
+ assertThat(SQLDialect.fromJdbcUrl("")).isEqualTo(SQLDialect.H2);
+ assertThat(SQLDialect.fromJdbcUrl("jdbc:")).isEqualTo(SQLDialect.H2);
+ assertThat(SQLDialect.fromJdbcUrl("jdbc:whatever://")).isEqualTo(SQLDialect.H2);
+ }
+
+ @Test
+ public void mysqlIsParsed() throws Exception {
+ assertThat(SQLDialect.fromJdbcUrl("jdbc:mysql://")).isEqualTo(SQLDialect.MYSQL);
+ }
+
+ @Test
+ public void postgresqlIsParsed() throws Exception {
+ assertThat(SQLDialect.fromJdbcUrl("jdbc:postgresql://")).isEqualTo(SQLDialect.POSTGRESQL);
+ }
+}
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 bbc4d05..e759637 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
@@ -17,6 +17,7 @@
import static com.ericsson.gerrit.plugins.eventslog.sql.SQLTable.TABLE_NAME;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -28,15 +29,14 @@
import com.ericsson.gerrit.plugins.eventslog.ServiceUnavailableException;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.Project.NameKey;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.events.ProjectEvent;
-import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.gson.Gson;
import com.google.inject.Provider;
import com.zaxxer.hikari.HikariConfig;
-import java.io.IOException;
import java.net.ConnectException;
import java.sql.Connection;
import java.sql.DriverManager;
@@ -45,9 +45,11 @@
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -67,92 +69,69 @@
private static final String TERM_CONN_MSG = "terminating connection";
private static final String MSG = "message";
private static final String GENERIC_QUERY = "SELECT * FROM " + TABLE_NAME;
- private static final boolean PROJECT_VISIBLE_TO_USER = true;
- private static final boolean PROJECT_NOT_VISIBLE_TO_USER = false;
+ private static final String PLUGIN_NAME = "events-log";
- @Mock private ProjectControl.GenericFactory pcFactoryMock;
@Mock private Provider<CurrentUser> userProviderMock;
@Mock private EventsLogConfig cfgMock;
+ @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 ScheduledThreadPoolExecutor poolMock;
private Statement stat;
+ private MockEvent mockEvent;
@Rule public TemporaryFolder testFolder = new TemporaryFolder();
@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();
poolMock = new PoolMock();
when(cfgMock.getMaxAge()).thenReturn(5);
when(cfgMock.getLocalStorePath()).thenReturn(testFolder.getRoot().toPath());
}
- private void tearDown() throws Exception {
- stat.execute("DROP TABLE " + TABLE_NAME);
+ @After
+ public void tearDown() throws Exception {
+ stat.execute("DROP TABLE IF EXISTS " + TABLE_NAME);
store.stop();
}
- private void setUpClient() {
- config.setJdbcUrl(TEST_URL);
- eventsDb = new SQLClient(config);
- config.setJdbcUrl(TEST_LOCAL_URL);
- localEventsDb = new SQLClient(config);
- store =
- new SQLStore(
- pcFactoryMock,
- userProviderMock,
- cfgMock,
- eventsDb,
- localEventsDb,
- poolMock,
- logCleanerMock);
- store.start();
- }
-
- private void setUpClientMock() throws SQLException {
- eventsDb = mock(SQLClient.class);
- localEventsDb = mock(SQLClient.class);
- when(localEventsDb.dbExists()).thenReturn(true);
- }
-
@Test
public void storeThenQueryVisible() throws Exception {
- MockEvent mockEvent = setUpMocks(PROJECT_VISIBLE_TO_USER);
+ when(permissionBackendMock.user(userProviderMock)).thenReturn(withUserMock);
+ when(withUserMock.project(any(Project.NameKey.class))).thenReturn(forProjectMock);
+ doNothing().when(forProjectMock).check(ProjectPermission.ACCESS);
+ setUpClient();
store.storeEvent(mockEvent);
List<String> events = store.queryChangeEvents(GENERIC_QUERY);
String json = new Gson().toJson(mockEvent);
assertThat(events).containsExactly(json).inOrder();
- tearDown();
}
@Test
public void storeThenQueryNotVisible() throws Exception {
- MockEvent mockEvent = setUpMocks(PROJECT_NOT_VISIBLE_TO_USER);
+ when(permissionBackendMock.user(userProviderMock)).thenReturn(withUserMock);
+ when(withUserMock.project(any(Project.NameKey.class))).thenReturn(forProjectMock);
+ doThrow(new PermissionBackendException(""))
+ .when(forProjectMock)
+ .check(ProjectPermission.ACCESS);
+ setUpClient();
store.storeEvent(mockEvent);
List<String> events = store.queryChangeEvents(GENERIC_QUERY);
assertThat(events).isEmpty();
- tearDown();
- }
-
- private MockEvent setUpMocks(boolean isVisible) throws NoSuchProjectException, IOException {
- MockEvent mockEvent = new MockEvent();
- ProjectControl pcMock = mock(ProjectControl.class);
- CurrentUser userMock = mock(CurrentUser.class);
- when(userProviderMock.get()).thenReturn(userMock);
- when(pcFactoryMock.controlFor(mockEvent.getProjectNameKey(), userMock)).thenReturn(pcMock);
- when(pcMock.isVisible()).thenReturn(isVisible);
- setUpClient();
- return mockEvent;
}
@Test(expected = MalformedQueryException.class)
@@ -163,40 +142,20 @@
}
@Test
- public void notReturnEventOfNonExistingProject() throws Exception {
- MockEvent mockEvent = new MockEvent();
- Project.NameKey projectMock = mock(Project.NameKey.class);
- CurrentUser userMock = mock(CurrentUser.class);
- when(userProviderMock.get()).thenReturn(userMock);
- NameKey projectNameKey = mockEvent.getProjectNameKey();
- doThrow(new NoSuchProjectException(projectMock))
- .when(pcFactoryMock)
- .controlFor(projectNameKey, userMock);
- setUpClient();
- store.storeEvent(mockEvent);
- List<String> events = store.queryChangeEvents(GENERIC_QUERY);
- assertThat(events).isEmpty();
- verify(logCleanerMock).removeProjectEventsAsync(mockEvent.project);
- tearDown();
- }
-
- @Test
public void notReturnEventWithNoVisibilityInfo() throws Exception {
- MockEvent mockEvent = new MockEvent();
- CurrentUser userMock = mock(CurrentUser.class);
- when(userProviderMock.get()).thenReturn(userMock);
- NameKey projectNameKey = mockEvent.getProjectNameKey();
- doThrow(new IOException()).when(pcFactoryMock).controlFor(projectNameKey, userMock);
+ when(permissionBackendMock.user(userProviderMock)).thenReturn(withUserMock);
+ when(withUserMock.project(any(Project.NameKey.class))).thenReturn(forProjectMock);
+ doThrow(new PermissionBackendException(""))
+ .when(forProjectMock)
+ .check(ProjectPermission.ACCESS);
setUpClient();
store.storeEvent(mockEvent);
List<String> events = store.queryChangeEvents(GENERIC_QUERY);
assertThat(events).isEmpty();
- tearDown();
}
@Test
public void retryOnConnectException() throws Exception {
- MockEvent mockEvent = new MockEvent();
when(cfgMock.getMaxTries()).thenReturn(3);
Throwable[] exceptions = new Throwable[3];
Arrays.fill(exceptions, new SQLException(new ConnectException()));
@@ -205,13 +164,15 @@
doThrow(exceptions).doNothing().when(eventsDb).queryOne();
store =
new SQLStore(
- pcFactoryMock,
userProviderMock,
cfgMock,
eventsDb,
localEventsDb,
poolMock,
- logCleanerMock);
+ permissionBackendMock,
+ logCleanerMock,
+ PLUGIN_NAME);
+
store.start();
store.storeEvent(mockEvent);
verify(eventsDb, times(3)).storeEvent(mockEvent);
@@ -220,7 +181,6 @@
@Test
public void retryOnMessage() throws Exception {
- MockEvent mockEvent = new MockEvent();
when(cfgMock.getMaxTries()).thenReturn(3);
Throwable[] exceptions = new Throwable[3];
Arrays.fill(exceptions, new SQLException(TERM_CONN_MSG));
@@ -229,13 +189,15 @@
doThrow(exceptions).doNothing().when(eventsDb).queryOne();
store =
new SQLStore(
- pcFactoryMock,
userProviderMock,
cfgMock,
eventsDb,
localEventsDb,
poolMock,
- logCleanerMock);
+ permissionBackendMock,
+ logCleanerMock,
+ PLUGIN_NAME);
+
store.start();
store.storeEvent(mockEvent);
verify(eventsDb, times(3)).storeEvent(mockEvent);
@@ -244,19 +206,20 @@
@Test
public void noRetryOnMessage() throws Exception {
- MockEvent mockEvent = new MockEvent();
when(cfgMock.getMaxTries()).thenReturn(3);
setUpClientMock();
doThrow(new SQLException(MSG)).when(eventsDb).storeEvent(mockEvent);
store =
new SQLStore(
- pcFactoryMock,
userProviderMock,
cfgMock,
eventsDb,
localEventsDb,
poolMock,
- logCleanerMock);
+ permissionBackendMock,
+ logCleanerMock,
+ PLUGIN_NAME);
+
store.start();
store.storeEvent(mockEvent);
verify(eventsDb, times(1)).storeEvent(mockEvent);
@@ -264,7 +227,6 @@
@Test
public void noRetryOnZeroMaxTries() throws Exception {
- MockEvent mockEvent = new MockEvent();
when(cfgMock.getMaxTries()).thenReturn(0);
Throwable[] exceptions = new Throwable[3];
Arrays.fill(exceptions, new SQLException(new ConnectException()));
@@ -273,13 +235,15 @@
doThrow(exceptions).doNothing().when(eventsDb).queryOne();
store =
new SQLStore(
- pcFactoryMock,
userProviderMock,
cfgMock,
eventsDb,
localEventsDb,
poolMock,
- logCleanerMock);
+ permissionBackendMock,
+ logCleanerMock,
+ PLUGIN_NAME);
+
store.start();
store.storeEvent(mockEvent);
verify(eventsDb, times(1)).storeEvent(mockEvent);
@@ -287,38 +251,32 @@
@Test(expected = ServiceUnavailableException.class)
public void throwSQLExceptionIfNotOnline() throws Exception {
- MockEvent mockEvent = new MockEvent();
setUpClientMock();
doThrow(new SQLException(new ConnectException())).when(eventsDb).createDBIfNotCreated();
doThrow(new SQLException()).when(eventsDb).queryOne();
store =
new SQLStore(
- pcFactoryMock,
userProviderMock,
cfgMock,
eventsDb,
localEventsDb,
poolMock,
- logCleanerMock);
+ permissionBackendMock,
+ logCleanerMock,
+ PLUGIN_NAME);
+
store.start();
store.storeEvent(mockEvent);
store.queryChangeEvents(GENERIC_QUERY);
}
@Test
- public void restoreFromLocalAndRemoveUnfoundProjectEvents() throws Exception {
+ public void restoreEventsFromLocalDb() throws Exception {
MockEvent mockEvent = new MockEvent();
MockEvent mockEvent2 = new MockEvent("proj");
- MockEvent mockEvent3 = new MockEvent("unfound");
-
- ProjectControl pc = mock(ProjectControl.class);
- NoSuchProjectException e = mock(NoSuchProjectException.class);
- CurrentUser userMock = mock(CurrentUser.class);
- when(userProviderMock.get()).thenReturn(userMock);
- when(pcFactoryMock.controlFor((mockEvent.getProjectNameKey()), userMock)).thenReturn(pc);
- when(pcFactoryMock.controlFor((mockEvent2.getProjectNameKey()), userMock)).thenReturn(pc);
- when(pc.isVisible()).thenReturn(true);
- doThrow(e).when(pcFactoryMock).controlFor((mockEvent3.getProjectNameKey()), userMock);
+ when(permissionBackendMock.user(userProviderMock)).thenReturn(withUserMock);
+ when(withUserMock.project(any(Project.NameKey.class))).thenReturn(forProjectMock);
+ doNothing().when(forProjectMock).check(ProjectPermission.ACCESS);
config.setJdbcUrl(TEST_URL);
eventsDb = new SQLClient(config);
@@ -326,25 +284,25 @@
localEventsDb = new SQLClient(config);
store =
new SQLStore(
- pcFactoryMock,
userProviderMock,
cfgMock,
eventsDb,
localEventsDb,
poolMock,
- logCleanerMock);
+ permissionBackendMock,
+ logCleanerMock,
+ PLUGIN_NAME);
localEventsDb.createDBIfNotCreated();
localEventsDb.storeEvent(mockEvent);
localEventsDb.storeEvent(mockEvent2);
- localEventsDb.storeEvent(mockEvent3);
store.start();
+
List<String> events = store.queryChangeEvents(GENERIC_QUERY);
Gson gson = new Gson();
String json = gson.toJson(mockEvent);
String json2 = gson.toJson(mockEvent2);
assertThat(events).containsExactly(json, json2).inOrder();
- tearDown();
}
@Test
@@ -354,32 +312,35 @@
doThrow(new SQLException()).when(eventsDb).queryOne();
store =
new SQLStore(
- pcFactoryMock,
userProviderMock,
cfgMock,
eventsDb,
localEventsDb,
poolMock,
- logCleanerMock);
+ permissionBackendMock,
+ logCleanerMock,
+ PLUGIN_NAME);
+
store.start();
verify(localEventsDb).createDBIfNotCreated();
}
@Test
public void storeLocalOffline() throws Exception {
- MockEvent mockEvent = new MockEvent();
setUpClientMock();
doThrow(new SQLException(new ConnectException())).when(eventsDb).createDBIfNotCreated();
doThrow(new SQLException()).when(eventsDb).queryOne();
store =
new SQLStore(
- pcFactoryMock,
userProviderMock,
cfgMock,
eventsDb,
localEventsDb,
poolMock,
- logCleanerMock);
+ permissionBackendMock,
+ logCleanerMock,
+ PLUGIN_NAME);
+
store.start();
store.storeEvent(mockEvent);
verify(localEventsDb).storeEvent(mockEvent);
@@ -387,25 +348,48 @@
@Test
public void storeLocalOfflineAfterNoRetry() throws Exception {
- MockEvent mockEvent = new MockEvent();
setUpClientMock();
when(cfgMock.getMaxTries()).thenReturn(0);
doThrow(new SQLException(new ConnectException())).when(eventsDb).createDBIfNotCreated();
doThrow(new SQLException()).when(eventsDb).queryOne();
store =
new SQLStore(
- pcFactoryMock,
userProviderMock,
cfgMock,
eventsDb,
localEventsDb,
poolMock,
- logCleanerMock);
+ permissionBackendMock,
+ logCleanerMock,
+ PLUGIN_NAME);
+
store.start();
store.storeEvent(mockEvent);
verify(localEventsDb).storeEvent(mockEvent);
}
+ private void setUpClient() {
+ eventsDb = new SQLClient(config);
+ localEventsDb = new SQLClient(config);
+ store =
+ new SQLStore(
+ userProviderMock,
+ cfgMock,
+ eventsDb,
+ localEventsDb,
+ poolMock,
+ permissionBackendMock,
+ logCleanerMock,
+ PLUGIN_NAME);
+ store.start();
+ }
+
+ private void setUpClientMock() throws SQLException {
+ eventsDb = mock(SQLClient.class);
+ localEventsDb = mock(SQLClient.class);
+ when(localEventsDb.dbExists()).thenReturn(true);
+ }
+
/**
* For this test we expect that if we can connect to main database, then we should come back
* online and try setting up again. We just want to make sure that restoreEventsFromLocal gets
@@ -420,15 +404,18 @@
when(localEventsDb.getAll()).thenReturn(ImmutableList.of(mock(SQLEntry.class)));
store =
new SQLStore(
- pcFactoryMock,
userProviderMock,
cfgMock,
eventsDb,
localEventsDb,
poolMock,
- logCleanerMock);
+ permissionBackendMock,
+ logCleanerMock,
+ PLUGIN_NAME);
+
store.start();
- poolMock.scheduleWithFixedDelay(store.new CheckConnectionTask(), 0, 0, TimeUnit.MILLISECONDS);
+ poolMock.scheduleWithFixedDelay(
+ store.new CheckConnectionTask(PLUGIN_NAME), 0, 0, TimeUnit.MILLISECONDS);
verify(localEventsDb, times(2)).removeOldEvents(0);
}
@@ -443,7 +430,6 @@
}
private void checkConnectionAndRestore(boolean copy) throws Exception {
- MockEvent mockEvent = new MockEvent();
eventsDb = mock(SQLClient.class);
config.setJdbcUrl(TEST_LOCAL_URL);
localEventsDb = new SQLClient(config);
@@ -460,13 +446,15 @@
store =
new SQLStore(
- pcFactoryMock,
userProviderMock,
cfgMock,
eventsDb,
localEventsDb,
poolMock,
- logCleanerMock);
+ permissionBackendMock,
+ logCleanerMock,
+ PLUGIN_NAME);
+
store.start();
verify(eventsDb).queryOne();
verify(eventsDb).storeEvent(any(String.class), any(Timestamp.class), any(String.class));