Plugin API: Add datasource interception extension point

This change extends the plugin API to support monitoring of Gerrit's
SQL activity by providing the extension point to create a DataSource
proxy to intercept execution of SQL statements.

To activate SQL monitoring the following must be done:

Add the interceptor class to gerrit.config:

  dataSourceInterceptorClass = javamelody.JavamelodyInterceptor

Implement the interface DataSourceInterceptor:

  public class JavamelodyInterceptor implements DataSourceInterceptor {
    @Override
    public DataSource intercept(String name, DataSource dataSource) {
      return JdbcWrapper.SINGLETON.createDataSourceProxy(name, dataSource);
    }
  }

Put the jar containing the interceptor into $gerrit_site/lib directory.

Working example of this concept can be found in javamelody-plugin.

Change-Id: I5f0b5ffe072394eae77e3b1735a76dceff4bdc42
This commit is contained in:
David Ostrovsky
2014-01-11 13:56:09 +01:00
committed by Shawn Pearce
parent 55bc67720e
commit 38a6f6cc2d
3 changed files with 50 additions and 2 deletions

View File

@@ -1221,6 +1221,13 @@ Default is `30 seconds`.
This setting only applies if
<<database.connectionPool,database.connectionPool>> is true.
[[database.dataSourceInterceptorClass]]database.dataSourceInterceptorClass::
Class that implements DataSourceInterceptor interface to monitor SQL activity.
This class must have default constructor and be available on Gerrit's bootstrap
classpath, e. g. in `$gerrit_site/lib` directory. Example implementation of
SQL monitoring can be found in javamelody-plugin.
[[download]]
=== Section download

View File

@@ -0,0 +1,21 @@
// 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.
// 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.google.gerrit.extensions.persistence;
import javax.sql.DataSource;
public interface DataSourceInterceptor {
DataSource intercept(String name, DataSource dataSource);
}

View File

@@ -19,6 +19,7 @@ import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.common.base.Strings;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.extensions.persistence.DataSourceInterceptor;
import com.google.gerrit.server.config.ConfigSection;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
@@ -32,6 +33,8 @@ import com.google.inject.Singleton;
import org.apache.commons.dbcp.BasicDataSource;
import org.eclipse.jgit.lib.Config;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.util.Properties;
@@ -102,6 +105,7 @@ public class DataSourceProvider implements Provider<DataSource>,
String username = dbs.optional("username");
String password = dbs.optional("password");
String interceptor = dbs.optional("dataSourceInterceptorClass");
boolean usePool;
if (context == Context.SINGLE_USER) {
@@ -126,7 +130,7 @@ public class DataSourceProvider implements Provider<DataSource>,
ds.setMaxWait(ConfigUtil.getTimeUnit(cfg, "database", null,
"poolmaxwait", MILLISECONDS.convert(30, SECONDS), MILLISECONDS));
ds.setInitialSize(ds.getMinIdle());
return ds;
return intercept(interceptor, ds);
} else {
// Don't use the connection pool.
@@ -141,10 +145,26 @@ public class DataSourceProvider implements Provider<DataSource>,
if (password != null) {
p.setProperty("password", password);
}
return new SimpleDataSource(p);
return intercept(interceptor, new SimpleDataSource(p));
} catch (SQLException se) {
throw new ProvisionException("Database unavailable", se);
}
}
}
private DataSource intercept(String interceptor, DataSource ds) {
if (interceptor == null) {
return ds;
}
try {
Constructor<?> c = Class.forName(interceptor).getConstructor();
DataSourceInterceptor datasourceInterceptor =
(DataSourceInterceptor) c.newInstance();
return datasourceInterceptor.intercept("reviewDb", ds);
} catch (ClassNotFoundException | SecurityException | NoSuchMethodException
| IllegalArgumentException | InstantiationException
| IllegalAccessException | InvocationTargetException e) {
throw new ProvisionException("Cannot intercept datasource", e);
}
}
}