]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-4925 Proxify JDBC components to allow profiling of SQL requests
authorJean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com>
Wed, 5 Feb 2014 16:37:06 +0000 (17:37 +0100)
committerJean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com>
Wed, 5 Feb 2014 16:37:26 +0000 (17:37 +0100)
12 files changed:
sonar-core/src/main/java/org/sonar/core/persistence/DefaultDatabase.java
sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java
sonar-core/src/main/java/org/sonar/core/persistence/profiling/PersistenceProfiling.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingConnectionHandler.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingDataSource.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingPreparedStatementHandler.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingStatementHandler.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/persistence/profiling/SqlProfiling.java [new file with mode: 0644]
sonar-core/src/test/java/org/sonar/core/persistence/DefaultDatabaseTest.java
sonar-core/src/test/java/org/sonar/core/persistence/MyBatisTest.java
sonar-core/src/test/java/org/sonar/core/persistence/profiling/PersistenceProfilingTest.java [new file with mode: 0644]
sonar-server/src/main/resources/org/sonar/server/platform/logback.xml

index d90aa39929800758a08ad2f216e7e6c18cdeb8fa..bec9bf8aafa115ce32ddc87732de69bcf710f4cf 100644 (file)
@@ -32,8 +32,7 @@ import org.sonar.api.database.DatabaseProperties;
 import org.sonar.core.persistence.dialect.Dialect;
 import org.sonar.core.persistence.dialect.DialectUtils;
 import org.sonar.core.persistence.dialect.H2;
-import org.sonar.core.persistence.dialect.Oracle;
-import org.sonar.core.persistence.dialect.PostgreSql;
+import org.sonar.core.persistence.profiling.PersistenceProfiling;
 import org.sonar.jpa.session.CustomHibernateConnectionProvider;
 
 import javax.sql.DataSource;
@@ -125,6 +124,7 @@ public class DefaultDatabase implements Database {
     datasource = (BasicDataSource) BasicDataSourceFactory.createDataSource(extractCommonsDbcpProperties(properties));
     datasource.setConnectionInitSqls(dialect.getConnectionInitStatements());
     datasource.setValidationQuery(dialect.getValidationQuery());
+    datasource = PersistenceProfiling.addProfilingIfNeeded(datasource, settings);
   }
 
   private void checkConnection() {
index ab2b3472488883d33fea89e4709d7040606ffdd9..e98bf8ff668be6eef0e2267dd8f128f97e7115e6 100644 (file)
@@ -52,7 +52,6 @@ import org.sonar.core.measure.db.MeasureFilterMapper;
 import org.sonar.core.notification.db.NotificationQueueDto;
 import org.sonar.core.notification.db.NotificationQueueMapper;
 import org.sonar.core.permission.*;
-import org.sonar.core.profiling.Profiling;
 import org.sonar.core.properties.PropertiesMapper;
 import org.sonar.core.properties.PropertyDto;
 import org.sonar.core.purge.PurgeMapper;
@@ -202,13 +201,8 @@ public class MyBatis implements BatchComponent, ServerComponent {
    * See http://www.mybatis.org/core/logging.html :
    */
   private void configureLogback(Class<?>... mapperClasses) {
-    Level level = Level.INFO;
-    Profiling.Level profilingLevel = Profiling.Level.fromConfigString(settings.getString(Profiling.CONFIG_PROFILING_LEVEL));
-    if (profilingLevel == Profiling.Level.FULL) {
-      level = Level.TRACE;
-    }
     for (Class mapperClass : mapperClasses) {
-      logback.setLoggerLevel(mapperClass.getName(), level);
+      logback.setLoggerLevel(mapperClass.getName(), Level.INFO);
     }
   }
 
diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/profiling/PersistenceProfiling.java b/sonar-core/src/main/java/org/sonar/core/persistence/profiling/PersistenceProfiling.java
new file mode 100644 (file)
index 0000000..335e702
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.core.persistence.profiling;
+
+import org.apache.commons.dbcp.BasicDataSource;
+import org.sonar.api.config.Settings;
+import org.sonar.core.profiling.Profiling;
+
+/**
+ * @since 4.2
+ */
+public final class PersistenceProfiling {
+
+  private PersistenceProfiling() {
+    // Static stuff only
+  }
+
+  public static BasicDataSource addProfilingIfNeeded(BasicDataSource datasource, Settings settings) {
+    Profiling.Level profilingLevel = Profiling.Level.fromConfigString(settings.getString(Profiling.CONFIG_PROFILING_LEVEL));
+    if (profilingLevel == Profiling.Level.FULL) {
+      return new ProfilingDataSource(datasource);
+    } else {
+      return datasource;
+    }
+  }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingConnectionHandler.java b/sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingConnectionHandler.java
new file mode 100644 (file)
index 0000000..1a8c86e
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.core.persistence.profiling;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.Statement;
+
+class ProfilingConnectionHandler implements InvocationHandler {
+
+  private final Connection connection;
+
+  ProfilingConnectionHandler(Connection connection) {
+    this.connection = connection;
+  }
+
+  @Override
+  public Object invoke(Object target, Method method, Object[] args) throws Throwable {
+    Object result = method.invoke(connection, args);
+    if ("prepareStatement".equals(method.getName())) {
+      PreparedStatement statement = (PreparedStatement) result;
+      String sql = (String) args[0];
+      return Proxy.newProxyInstance(ProfilingConnectionHandler.class.getClassLoader(), new Class[] { PreparedStatement.class }, new ProfilingPreparedStatementHandler(statement, sql));
+
+    } else if ("createStatement".equals(method.getName())) {
+      Statement statement = (Statement) result;
+      return Proxy.newProxyInstance(ProfilingConnectionHandler.class.getClassLoader(), new Class[] { Statement.class }, new ProfilingStatementHandler(statement));
+
+    } else {
+      return result;
+    }
+  }
+
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingDataSource.java b/sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingDataSource.java
new file mode 100644 (file)
index 0000000..90f0dd2
--- /dev/null
@@ -0,0 +1,402 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.core.persistence.profiling;
+
+import org.apache.commons.dbcp.BasicDataSource;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Proxy;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Collection;
+
+class ProfilingDataSource extends BasicDataSource {
+
+  private final BasicDataSource delegate;
+
+  public ProfilingDataSource(BasicDataSource delegate) {
+    this.delegate = delegate;
+  }
+
+  @Override
+  public boolean getDefaultAutoCommit() {
+    return delegate.getDefaultAutoCommit();
+  }
+
+  @Override
+  public void setDefaultAutoCommit(boolean defaultAutoCommit) {
+    delegate.setDefaultAutoCommit(defaultAutoCommit);
+  }
+
+  @Override
+  public boolean getDefaultReadOnly() {
+    return delegate.getDefaultReadOnly();
+  }
+
+  @Override
+  public void setDefaultReadOnly(boolean defaultReadOnly) {
+    delegate.setDefaultReadOnly(defaultReadOnly);
+  }
+
+  @Override
+  public int getDefaultTransactionIsolation() {
+    return delegate.getDefaultTransactionIsolation();
+  }
+
+  @Override
+  public void setDefaultTransactionIsolation(int defaultTransactionIsolation) {
+    delegate.setDefaultTransactionIsolation(defaultTransactionIsolation);
+  }
+
+  @Override
+  public String getDefaultCatalog() {
+    return delegate.getDefaultCatalog();
+  }
+
+  @Override
+  public void setDefaultCatalog(String defaultCatalog) {
+    delegate.setDefaultCatalog(defaultCatalog);
+  }
+
+  @Override
+  public synchronized String getDriverClassName() {
+    return delegate.getDriverClassName();
+  }
+
+  @Override
+  public synchronized void setDriverClassName(String driverClassName) {
+    delegate.setDriverClassName(driverClassName);
+  }
+
+  @Override
+  public synchronized ClassLoader getDriverClassLoader() {
+    return delegate.getDriverClassLoader();
+  }
+
+  @Override
+  public synchronized void setDriverClassLoader(ClassLoader driverClassLoader) {
+    delegate.setDriverClassLoader(driverClassLoader);
+  }
+
+  @Override
+  public synchronized int getMaxActive() {
+    return delegate.getMaxActive();
+  }
+
+  @Override
+  public synchronized void setMaxActive(int maxActive) {
+    delegate.setMaxActive(maxActive);
+  }
+
+  @Override
+  public synchronized int getMaxIdle() {
+    return delegate.getMaxIdle();
+  }
+
+  @Override
+  public synchronized void setMaxIdle(int maxIdle) {
+    delegate.setMaxIdle(maxIdle);
+  }
+
+  @Override
+  public synchronized int getMinIdle() {
+    return delegate.getMinIdle();
+  }
+
+  @Override
+  public synchronized void setMinIdle(int minIdle) {
+    delegate.setMinIdle(minIdle);
+  }
+
+  @Override
+  public synchronized int getInitialSize() {
+    return delegate.getInitialSize();
+  }
+
+  @Override
+  public synchronized void setInitialSize(int initialSize) {
+    delegate.setInitialSize(initialSize);
+  }
+
+  @Override
+  public synchronized long getMaxWait() {
+    return delegate.getMaxWait();
+  }
+
+  @Override
+  public synchronized void setMaxWait(long maxWait) {
+    delegate.setMaxWait(maxWait);
+  }
+
+  @Override
+  public synchronized boolean isPoolPreparedStatements() {
+    return delegate.isPoolPreparedStatements();
+  }
+
+  @Override
+  public synchronized void setPoolPreparedStatements(boolean poolingStatements) {
+    delegate.setPoolPreparedStatements(poolingStatements);
+  }
+
+  @Override
+  public synchronized int getMaxOpenPreparedStatements() {
+    return delegate.getMaxOpenPreparedStatements();
+  }
+
+  @Override
+  public synchronized void setMaxOpenPreparedStatements(int maxOpenStatements) {
+    delegate.setMaxOpenPreparedStatements(maxOpenStatements);
+  }
+
+  @Override
+  public synchronized boolean getTestOnBorrow() {
+    return delegate.getTestOnBorrow();
+  }
+
+  @Override
+  public synchronized void setTestOnBorrow(boolean testOnBorrow) {
+    delegate.setTestOnBorrow(testOnBorrow);
+  }
+
+  @Override
+  public synchronized boolean getTestOnReturn() {
+    return delegate.getTestOnReturn();
+  }
+
+  @Override
+  public synchronized void setTestOnReturn(boolean testOnReturn) {
+    delegate.setTestOnReturn(testOnReturn);
+  }
+
+  @Override
+  public synchronized long getTimeBetweenEvictionRunsMillis() {
+    return delegate.getTimeBetweenEvictionRunsMillis();
+  }
+
+  @Override
+  public synchronized void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) {
+    delegate.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
+  }
+
+  @Override
+  public synchronized int getNumTestsPerEvictionRun() {
+    return delegate.getNumTestsPerEvictionRun();
+  }
+
+  @Override
+  public synchronized void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) {
+    delegate.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
+  }
+
+  @Override
+  public synchronized long getMinEvictableIdleTimeMillis() {
+    return delegate.getMinEvictableIdleTimeMillis();
+  }
+
+  @Override
+  public synchronized void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) {
+    delegate.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
+  }
+
+  @Override
+  public synchronized boolean getTestWhileIdle() {
+    return delegate.getTestWhileIdle();
+  }
+
+  @Override
+  public synchronized void setTestWhileIdle(boolean testWhileIdle) {
+    delegate.setTestWhileIdle(testWhileIdle);
+  }
+
+  @Override
+  public synchronized int getNumActive() {
+    return delegate.getNumActive();
+  }
+
+  @Override
+  public synchronized int getNumIdle() {
+    return delegate.getNumIdle();
+  }
+
+  @Override
+  public String getPassword() {
+    return delegate.getPassword();
+  }
+
+  @Override
+  public void setPassword(String password) {
+    delegate.setPassword(password);
+  }
+
+  @Override
+  public synchronized String getUrl() {
+    return delegate.getUrl();
+  }
+
+  @Override
+  public synchronized void setUrl(String url) {
+    delegate.setUrl(url);
+  }
+
+  @Override
+  public String getUsername() {
+    return delegate.getUsername();
+  }
+
+  @Override
+  public void setUsername(String username) {
+    delegate.setUsername(username);
+  }
+
+  @Override
+  public String getValidationQuery() {
+    return delegate.getValidationQuery();
+  }
+
+  @Override
+  public void setValidationQuery(String validationQuery) {
+    delegate.setValidationQuery(validationQuery);
+  }
+
+  @Override
+  public int getValidationQueryTimeout() {
+    return delegate.getValidationQueryTimeout();
+  }
+
+  @Override
+  public void setValidationQueryTimeout(int timeout) {
+    delegate.setValidationQueryTimeout(timeout);
+  }
+
+  @Override
+  public Collection getConnectionInitSqls() {
+    return delegate.getConnectionInitSqls();
+  }
+
+  @Override
+  public void setConnectionInitSqls(Collection connectionInitSqls) {
+    delegate.setConnectionInitSqls(connectionInitSqls);
+  }
+
+  @Override
+  public synchronized boolean isAccessToUnderlyingConnectionAllowed() {
+    return delegate.isAccessToUnderlyingConnectionAllowed();
+  }
+
+  @Override
+  public synchronized void setAccessToUnderlyingConnectionAllowed(boolean allow) {
+    delegate.setAccessToUnderlyingConnectionAllowed(allow);
+  }
+
+  @Override
+  public Connection getConnection() throws SQLException {
+    return (Connection) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] { Connection.class }, new ProfilingConnectionHandler(delegate.getConnection()));
+  }
+
+  @Override
+  public Connection getConnection(String user, String pass) throws SQLException {
+    return (Connection) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] { Connection.class }, new ProfilingConnectionHandler(delegate.getConnection(user, pass)));
+  }
+
+  @Override
+  public int getLoginTimeout() throws SQLException {
+    return delegate.getLoginTimeout();
+  }
+
+  @Override
+  public PrintWriter getLogWriter() throws SQLException {
+    return delegate.getLogWriter();
+  }
+
+  @Override
+  public void setLoginTimeout(int loginTimeout) throws SQLException {
+    delegate.setLoginTimeout(loginTimeout);
+  }
+
+  @Override
+  public void setLogWriter(PrintWriter logWriter) throws SQLException {
+    delegate.setLogWriter(logWriter);
+  }
+
+  @Override
+  public boolean getRemoveAbandoned() {
+    return delegate.getRemoveAbandoned();
+  }
+
+  @Override
+  public void setRemoveAbandoned(boolean removeAbandoned) {
+    delegate.setRemoveAbandoned(removeAbandoned);
+  }
+
+  @Override
+  public int getRemoveAbandonedTimeout() {
+    return delegate.getRemoveAbandonedTimeout();
+  }
+
+  @Override
+  public void setRemoveAbandonedTimeout(int removeAbandonedTimeout) {
+    delegate.setRemoveAbandonedTimeout(removeAbandonedTimeout);
+  }
+
+  @Override
+  public boolean getLogAbandoned() {
+    return delegate.getLogAbandoned();
+  }
+
+  @Override
+  public void setLogAbandoned(boolean logAbandoned) {
+    delegate.setLogAbandoned(logAbandoned);
+  }
+
+  @Override
+  public void addConnectionProperty(String name, String value) {
+    delegate.addConnectionProperty(name, value);
+  }
+
+  @Override
+  public void removeConnectionProperty(String name) {
+    delegate.removeConnectionProperty(name);
+  }
+
+  @Override
+  public void setConnectionProperties(String connectionProperties) {
+    delegate.setConnectionProperties(connectionProperties);
+  }
+
+  @Override
+  public synchronized void close() throws SQLException {
+    delegate.close();
+  }
+
+  @Override
+  public synchronized boolean isClosed() {
+    return delegate.isClosed();
+  }
+
+  @Override
+  public boolean isWrapperFor(Class<?> iface) throws SQLException {
+    return delegate.isWrapperFor(iface);
+  }
+
+  @Override
+  public <T> T unwrap(Class<T> iface) throws SQLException {
+    return delegate.unwrap(iface);
+  }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingPreparedStatementHandler.java b/sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingPreparedStatementHandler.java
new file mode 100644 (file)
index 0000000..f773d8f
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.core.persistence.profiling;
+
+import org.sonar.core.profiling.StopWatch;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.sql.PreparedStatement;
+
+class ProfilingPreparedStatementHandler implements InvocationHandler {
+
+  private static final SqlProfiling profiling = new SqlProfiling();
+  private final PreparedStatement statement;
+  private final String sql;
+
+  ProfilingPreparedStatementHandler(PreparedStatement statement, String sql) {
+    this.statement = statement;
+    this.sql = sql;
+  }
+
+  @Override
+  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+    if (method.getName().startsWith("execute")) {
+      StopWatch watch = profiling.start();
+      Object result = method.invoke(statement, args);
+      profiling.stop(watch, sql);
+      return result;
+    } else {
+      return method.invoke(statement, args);
+    }
+  }
+
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingStatementHandler.java b/sonar-core/src/main/java/org/sonar/core/persistence/profiling/ProfilingStatementHandler.java
new file mode 100644 (file)
index 0000000..3180bf0
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.core.persistence.profiling;
+
+import org.sonar.core.profiling.StopWatch;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.sql.Statement;
+
+class ProfilingStatementHandler implements InvocationHandler {
+
+  private static final SqlProfiling profiling = new SqlProfiling();
+  private final Statement statement;
+
+  ProfilingStatementHandler(Statement statement) {
+    this.statement = statement;
+  }
+
+  @Override
+  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+    if (method.getName().startsWith("execute")) {
+      StopWatch watch = profiling.start();
+      Object result = method.invoke(statement, args);
+      profiling.stop(watch, (String) args[0]);
+      return result;
+    } else {
+      return method.invoke(statement, args);
+    }
+  }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/profiling/SqlProfiling.java b/sonar-core/src/main/java/org/sonar/core/persistence/profiling/SqlProfiling.java
new file mode 100644 (file)
index 0000000..05bff18
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.core.persistence.profiling;
+
+import org.sonar.api.config.Settings;
+import org.sonar.core.profiling.Profiling;
+import org.sonar.core.profiling.Profiling.Level;
+import org.sonar.core.profiling.StopWatch;
+
+class SqlProfiling {
+
+  private final Profiling profiling;
+
+  SqlProfiling() {
+    Settings settings = new Settings();
+    settings.setProperty(Profiling.CONFIG_PROFILING_LEVEL, Profiling.Level.FULL.toString());
+    profiling = new Profiling(settings);
+  }
+
+  StopWatch start() {
+    return profiling.start("sql", Level.FULL);
+  }
+
+  void stop(StopWatch watch, String sql) {
+    watch.stop(String.format("Executed SQL: %s", sql.replaceAll("\\s+", " ")));
+  }
+}
index 4a3866264f52cde93a4aa2565a1d85dd4c8cf03c..827c4dc0b06ce820ec2d1e4beb6728c556cc7a32 100644 (file)
@@ -22,7 +22,6 @@ package org.sonar.core.persistence;
 import org.apache.commons.dbcp.BasicDataSource;
 import org.junit.Test;
 import org.sonar.api.config.Settings;
-import org.sonar.core.persistence.dialect.Oracle;
 import org.sonar.core.persistence.dialect.PostgreSql;
 
 import java.util.Properties;
index 5b02464e35c5812a2a2e995c7fd6cc6a3a3fa604..44be4f26eb7b27bf13d42bd5ecd795d0e97260c4 100644 (file)
@@ -19,7 +19,6 @@
  */
 package org.sonar.core.persistence;
 
-import ch.qos.logback.classic.Level;
 import org.apache.ibatis.session.Configuration;
 import org.apache.ibatis.session.SqlSession;
 import org.hamcrest.core.Is;
@@ -33,7 +32,6 @@ import org.sonar.core.rule.RuleMapper;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.junit.Assert.assertThat;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
 
 public class MyBatisTest {
   private static H2Database database;
@@ -74,15 +72,4 @@ public class MyBatisTest {
       session.close();
     }
   }
-
-  @Test
-  public void log_sql_requests_and_responses() {
-    Settings settings = new Settings()
-        .setProperty("sonar.log.profilingLevel", "FULL");
-
-    MyBatis myBatis = new MyBatis(database, settings, logback);
-    myBatis.start();
-
-    verify(logback).setLoggerLevel("org.sonar.core.resource.ResourceIndexerMapper", Level.TRACE);
-  }
 }
diff --git a/sonar-core/src/test/java/org/sonar/core/persistence/profiling/PersistenceProfilingTest.java b/sonar-core/src/test/java/org/sonar/core/persistence/profiling/PersistenceProfilingTest.java
new file mode 100644 (file)
index 0000000..b21a3a2
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.core.persistence.profiling;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.ContextBase;
+import ch.qos.logback.core.read.ListAppender;
+import org.apache.commons.dbcp.BasicDataSource;
+import org.junit.Test;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.config.Settings;
+import org.sonar.core.profiling.Profiling;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.Statement;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class PersistenceProfilingTest {
+
+  @Test
+  public void should_be_transparent_when_profiling_less_than_full() {
+    BasicDataSource datasource = mock(BasicDataSource.class);
+    assertThat(PersistenceProfiling.addProfilingIfNeeded(datasource , new Settings())).isEqualTo(datasource);
+  }
+
+  @Test
+  public void should_enable_profiling_when_profiling_is_full() throws Exception {
+    final Logger sqlLogger = (Logger) LoggerFactory.getLogger("sql");
+    ListAppender<ILoggingEvent> appender = new ListAppender<ILoggingEvent>();
+    appender.setContext(new ContextBase());
+    appender.start();
+    sqlLogger.addAppender(appender);
+
+    BasicDataSource originDataSource = mock(BasicDataSource.class);
+
+    Connection connection = mock(Connection.class);
+    when(originDataSource.getConnection()).thenReturn(connection);
+
+    String sql = "select 'polop' from dual;";
+
+    PreparedStatement preparedStatement = mock(PreparedStatement.class);
+    when(connection.prepareStatement(sql)).thenReturn(preparedStatement);
+    when(preparedStatement.execute()).thenReturn(true);
+
+    Statement statement = mock(Statement.class);
+    when(connection.createStatement()).thenReturn(statement);
+    when(statement.execute(sql)).thenReturn(true);
+
+    Settings settings = new Settings();
+    settings.setProperty(Profiling.CONFIG_PROFILING_LEVEL, Profiling.Level.FULL.toString());
+
+    BasicDataSource resultDataSource = PersistenceProfiling.addProfilingIfNeeded(originDataSource , settings);
+
+    assertThat(resultDataSource).isInstanceOf(ProfilingDataSource.class);
+    assertThat(resultDataSource.getUrl()).isNull();
+    assertThat(resultDataSource.getConnection().getClientInfo()).isNull();
+    PreparedStatement preparedStatementProxy = resultDataSource.getConnection().prepareStatement(sql);
+    assertThat(preparedStatementProxy.getConnection()).isNull();
+    assertThat(preparedStatementProxy.execute()).isTrue();
+    final Statement statementProxy = resultDataSource.getConnection().createStatement();
+    assertThat(statementProxy.getConnection()).isNull();
+    assertThat(statementProxy.execute(sql)).isTrue();
+
+    assertThat(appender.list).hasSize(2);
+    for (ILoggingEvent event: appender.list) {
+      assertThat(event.getLevel()).isEqualTo(Level.INFO);
+      assertThat(event.getFormattedMessage()).contains(sql);
+    }
+  }
+}
index 0fef27fea70635642f7bc630316ff78055598d20..a4a640e59b134d062d92c43c9cba334b461506bb 100644 (file)
@@ -42,7 +42,7 @@
 
   <!-- Display Rails warnings and errors  -->
   <logger name="rails">
-    <level value="${RAILS_LOGGER_LEVEL}"/>
+    <level value="WARN"/>
   </logger>
 
   <logger name="org.hibernate.cache.ReadWriteCache">