aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-core
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@gmail.com>2013-07-04 19:23:31 +0200
committerSimon Brandhof <simon.brandhof@gmail.com>2013-07-04 19:26:01 +0200
commitccb95087fd7aab08d2e8e057eecd21797ed7f9b6 (patch)
tree2bcb2aaccbe363ed93dea95e0d96e182692c264c /sonar-core
parenta60604f5d250121fd594bb3d331a0f85fe889797 (diff)
downloadsonarqube-ccb95087fd7aab08d2e8e057eecd21797ed7f9b6.tar.gz
sonarqube-ccb95087fd7aab08d2e8e057eecd21797ed7f9b6.zip
SONAR-4470 Performance issue when migrating DB from 3.5 to 3.6 (mainly with Oracle)
Diffstat (limited to 'sonar-core')
-rw-r--r--sonar-core/pom.xml4
-rw-r--r--sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterExecutor.java4
-rw-r--r--sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterSql.java6
-rw-r--r--sonar-core/src/main/java/org/sonar/core/persistence/DatabaseMigrator.java68
-rw-r--r--sonar-core/src/main/java/org/sonar/core/persistence/DatabaseUtils.java128
-rw-r--r--sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java60
-rw-r--r--sonar-core/src/main/java/org/sonar/core/persistence/DbTemplate.java25
-rw-r--r--sonar-core/src/main/java/org/sonar/core/persistence/DdlUtils.java2
-rw-r--r--sonar-core/src/main/java/org/sonar/core/persistence/DefaultDatabase.java3
-rw-r--r--sonar-core/src/test/java/org/sonar/core/persistence/AbstractDaoTestCase.java4
-rw-r--r--sonar-core/src/test/java/org/sonar/core/persistence/DatabaseCommands.java13
-rw-r--r--sonar-core/src/test/java/org/sonar/core/persistence/DatabaseUtilsTest.java146
-rw-r--r--sonar-core/src/test/java/org/sonar/core/persistence/H2Database.java25
-rw-r--r--sonar-core/src/test/java/org/sonar/core/persistence/H2DatabaseTest.java2
-rw-r--r--sonar-core/src/test/java/org/sonar/core/persistence/MyBatisTest.java2
-rw-r--r--sonar-core/src/test/java/org/sonar/core/persistence/TestDatabase.java293
-rw-r--r--sonar-core/src/test/java/org/sonar/jpa/test/AbstractDbUnitTestCase.java2
17 files changed, 414 insertions, 373 deletions
diff --git a/sonar-core/pom.xml b/sonar-core/pom.xml
index c7c65fa9e66..7047fc7d230 100644
--- a/sonar-core/pom.xml
+++ b/sonar-core/pom.xml
@@ -61,6 +61,10 @@
<artifactId>commons-dbcp</artifactId>
</dependency>
<dependency>
+ <groupId>commons-dbutils</groupId>
+ <artifactId>commons-dbutils</artifactId>
+ </dependency>
+ <dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-classworlds</artifactId>
</dependency>
diff --git a/sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterExecutor.java b/sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterExecutor.java
index 2e90c431243..81168944cf2 100644
--- a/sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterExecutor.java
+++ b/sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterExecutor.java
@@ -20,10 +20,10 @@
package org.sonar.core.measure;
import com.google.common.base.Strings;
+import org.apache.commons.dbutils.DbUtils;
import org.apache.ibatis.session.SqlSession;
import org.sonar.api.ServerComponent;
import org.sonar.core.persistence.Database;
-import org.sonar.core.persistence.DatabaseUtils;
import org.sonar.core.persistence.MyBatis;
import org.sonar.core.resource.ResourceDao;
@@ -67,7 +67,7 @@ public class MeasureFilterExecutor implements ServerComponent {
} finally {
MyBatis.closeQuietly(session);
// connection is supposed to be closed by the session
- DatabaseUtils.closeQuietly(connection);
+ DbUtils.closeQuietly(connection);
}
return rows;
diff --git a/sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterSql.java b/sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterSql.java
index 8545d98be8f..bed09124a09 100644
--- a/sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterSql.java
+++ b/sonar-core/src/main/java/org/sonar/core/measure/MeasureFilterSql.java
@@ -22,10 +22,10 @@ package org.sonar.core.measure;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
+import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.sonar.core.persistence.Database;
-import org.sonar.core.persistence.DatabaseUtils;
import org.sonar.core.resource.SnapshotDto;
import javax.annotation.Nullable;
@@ -67,8 +67,8 @@ class MeasureFilterSql {
return process(rs);
} finally {
- DatabaseUtils.closeQuietly(rs);
- DatabaseUtils.closeQuietly(statement);
+ DbUtils.closeQuietly(rs);
+ DbUtils.closeQuietly(statement);
}
}
diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseMigrator.java b/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseMigrator.java
deleted file mode 100644
index b15db5f3711..00000000000
--- a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseMigrator.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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;
-
-import org.apache.ibatis.session.SqlSession;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.ServerComponent;
-
-import java.sql.Connection;
-
-/**
- * Restore schema by executing DDL scripts. Only H2 database is supported.
- * Other databases are created by Ruby on Rails migrations.
- *
- * @since 2.12
- */
-public class DatabaseMigrator implements ServerComponent {
-
- private MyBatis myBatis;
- private Database database;
-
- public DatabaseMigrator(MyBatis myBatis, Database database) {
- this.myBatis = myBatis;
- this.database = database;
- }
-
- /**
- * @return true if the database has been created, false if this database is not supported
- */
- public boolean createDatabase() {
- if (!DdlUtils.supportsDialect(database.getDialect().getId())) {
- return false;
- }
-
- LoggerFactory.getLogger(getClass()).info("Create database");
- SqlSession session = null;
- Connection connection = null;
- try {
- session = myBatis.openSession();
- connection = session.getConnection();
- DdlUtils.createSchema(connection, database.getDialect().getId());
- } finally {
- MyBatis.closeQuietly(session);
-
- // The connection is probably already closed by session.close()
- // but it's not documented in mybatis javadoc.
- DatabaseUtils.closeQuietly(connection);
- }
- return true;
- }
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseUtils.java b/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseUtils.java
deleted file mode 100644
index aba584bcaa3..00000000000
--- a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseUtils.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * 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;
-
-import org.slf4j.LoggerFactory;
-
-import javax.annotation.Nullable;
-
-import java.sql.Connection;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-
-/**
- * @since 2.13
- */
-public final class DatabaseUtils {
- private DatabaseUtils() {
- }
-
- /**
- * List of all the tables.
- * This list is hardcoded because we didn't succeed in using java.sql.DatabaseMetaData#getTables() in the same way
- * for all the supported databases, particularly due to Oracle results.
- */
- static final String[] TABLE_NAMES = {
- "action_plans",
- "active_dashboards",
- "active_rules",
- "active_rule_changes",
- "active_rule_parameters",
- "active_rule_param_changes",
- "alerts",
- "authors",
- "characteristics",
- "characteristic_edges",
- "characteristic_properties",
- "dashboards",
- "dependencies",
- "duplications_index",
- "events",
- "graphs",
- "groups",
- "groups_users",
- "group_roles",
- "issues",
- "issue_changes",
- "issue_filters",
- "issue_filter_favourites",
- "loaded_templates",
- "manual_measures",
- "measure_data",
- "measure_filters",
- "measure_filter_favourites",
- "metrics",
- "notifications",
- "permission_templates",
- "perm_templates_users",
- "perm_templates_groups",
- "projects",
- "project_links",
- "project_measures",
- "properties",
- "quality_models",
- "resource_index",
- "rules",
- "rules_parameters",
- "rules_profiles",
- "semaphores",
- "schema_migrations",
- "snapshots",
- "snapshot_sources",
- "snapshot_data",
- "users",
- "user_roles",
- "widgets",
- "widget_properties"};
-
- public static void closeQuietly(@Nullable Connection connection) {
- if (connection != null) {
- try {
- connection.close();
- } catch (SQLException e) {
- LoggerFactory.getLogger(DatabaseUtils.class).warn("Fail to close connection", e);
- // ignore
- }
- }
- }
-
- public static void closeQuietly(@Nullable Statement stmt) {
- if (stmt != null) {
- try {
- stmt.close();
- } catch (SQLException e) {
- LoggerFactory.getLogger(DatabaseUtils.class).warn("Fail to close statement", e);
- // ignore
- }
- }
- }
-
- public static void closeQuietly(@Nullable ResultSet rs) {
- if (rs != null) {
- try {
- rs.close();
- } catch (SQLException e) {
- LoggerFactory.getLogger(DatabaseUtils.class).warn("Fail to close result set", e);
- // ignore
- }
- }
- }
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java b/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java
index 16bde3cd397..e77f001bd6b 100644
--- a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java
+++ b/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java
@@ -20,6 +20,7 @@
package org.sonar.core.persistence;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
import org.apache.ibatis.session.SqlSession;
import org.sonar.api.BatchComponent;
import org.sonar.api.ServerComponent;
@@ -38,6 +39,65 @@ public class DatabaseVersion implements BatchComponent, ServerComponent {
UP_TO_DATE, REQUIRES_UPGRADE, REQUIRES_DOWNGRADE, FRESH_INSTALL
}
+ /**
+ * List of all the tables.
+ * This list is hardcoded because we didn't succeed in using java.sql.DatabaseMetaData#getTables() in the same way
+ * for all the supported databases, particularly due to Oracle results.
+ */
+ public static final List<String> TABLES = ImmutableList.of(
+ "action_plans",
+ "active_dashboards",
+ "active_rules",
+ "active_rule_changes",
+ "active_rule_parameters",
+ "active_rule_param_changes",
+ "alerts",
+ "authors",
+ "characteristics",
+ "characteristic_edges",
+ "characteristic_properties",
+ "dashboards",
+ "dependencies",
+ "duplications_index",
+ "events",
+ "graphs",
+ "groups",
+ "groups_users",
+ "group_roles",
+ "issues",
+ "issue_changes",
+ "issue_filters",
+ "issue_filter_favourites",
+ "loaded_templates",
+ "manual_measures",
+ "measure_data",
+ "measure_filters",
+ "measure_filter_favourites",
+ "metrics",
+ "notifications",
+ "permission_templates",
+ "perm_templates_users",
+ "perm_templates_groups",
+ "projects",
+ "project_links",
+ "project_measures",
+ "properties",
+ "quality_models",
+ "resource_index",
+ "rules",
+ "rules_parameters",
+ "rules_profiles",
+ "semaphores",
+ "schema_migrations",
+ "snapshots",
+ "snapshot_sources",
+ "snapshot_data",
+ "users",
+ "user_roles",
+ "widgets",
+ "widget_properties"
+ );
+
private MyBatis mybatis;
public DatabaseVersion(MyBatis mybatis) {
diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/DbTemplate.java b/sonar-core/src/main/java/org/sonar/core/persistence/DbTemplate.java
index 52e2dbd1c41..2a1fb7d9d1b 100644
--- a/sonar-core/src/main/java/org/sonar/core/persistence/DbTemplate.java
+++ b/sonar-core/src/main/java/org/sonar/core/persistence/DbTemplate.java
@@ -23,6 +23,7 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import org.apache.commons.dbcp.BasicDataSource;
+import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -89,12 +90,12 @@ public class DbTemplate implements ServerComponent {
LOG.error("Fail to copy table " + table, e);
throw new IllegalStateException("Fail to copy table " + table, e);
} finally {
- DatabaseUtils.closeQuietly(destStatement);
- DatabaseUtils.closeQuietly(destResultSet);
- DatabaseUtils.closeQuietly(destConnection);
- DatabaseUtils.closeQuietly(sourceResultSet);
- DatabaseUtils.closeQuietly(sourceStatement);
- DatabaseUtils.closeQuietly(sourceConnection);
+ DbUtils.closeQuietly(destStatement);
+ DbUtils.closeQuietly(destResultSet);
+ DbUtils.closeQuietly(destConnection);
+ DbUtils.closeQuietly(sourceResultSet);
+ DbUtils.closeQuietly(sourceStatement);
+ DbUtils.closeQuietly(sourceConnection);
}
return this;
@@ -158,9 +159,9 @@ public class DbTemplate implements ServerComponent {
LOG.error("Fail to get row count for table " + table, e);
throw new SonarException("Fail to get row count for table " + table, e);
} finally {
- DatabaseUtils.closeQuietly(resultSet);
- DatabaseUtils.closeQuietly(statement);
- DatabaseUtils.closeQuietly(connection);
+ DbUtils.closeQuietly(resultSet);
+ DbUtils.closeQuietly(statement);
+ DbUtils.closeQuietly(connection);
}
}
@@ -175,8 +176,8 @@ public class DbTemplate implements ServerComponent {
LOG.error("Fail to truncate table " + table, e);
throw new SonarException("Fail to truncate table " + table, e);
} finally {
- DatabaseUtils.closeQuietly(statement);
- DatabaseUtils.closeQuietly(connection);
+ DbUtils.closeQuietly(statement);
+ DbUtils.closeQuietly(connection);
}
return this;
@@ -200,7 +201,7 @@ public class DbTemplate implements ServerComponent {
LOG.error("Fail to createSchema local database schema", e);
throw new SonarException("Fail to createSchema local database schema", e);
} finally {
- DatabaseUtils.closeQuietly(connection);
+ DbUtils.closeQuietly(connection);
}
return this;
diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/DdlUtils.java b/sonar-core/src/main/java/org/sonar/core/persistence/DdlUtils.java
index 94ac7774d43..0ea501c2083 100644
--- a/sonar-core/src/main/java/org/sonar/core/persistence/DdlUtils.java
+++ b/sonar-core/src/main/java/org/sonar/core/persistence/DdlUtils.java
@@ -48,7 +48,7 @@ public final class DdlUtils {
executeScript(connection, "org/sonar/core/persistence/rows-" + dialect + ".sql");
}
- private static void executeScript(Connection connection, String path) {
+ public static void executeScript(Connection connection, String path) {
ScriptRunner scriptRunner = newScriptRunner(connection);
try {
scriptRunner.runScript(Resources.getResourceAsReader(path));
diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/DefaultDatabase.java b/sonar-core/src/main/java/org/sonar/core/persistence/DefaultDatabase.java
index 3b8d5c5c2d6..5d0752f0d4d 100644
--- a/sonar-core/src/main/java/org/sonar/core/persistence/DefaultDatabase.java
+++ b/sonar-core/src/main/java/org/sonar/core/persistence/DefaultDatabase.java
@@ -22,6 +22,7 @@ package org.sonar.core.persistence;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
+import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.lang.StringUtils;
import org.hibernate.cfg.Environment;
import org.slf4j.Logger;
@@ -141,7 +142,7 @@ public class DefaultDatabase implements Database {
} catch (Exception e) {
LOG.error("Can not connect to database. Please check connectivity and settings (see the properties prefixed by 'sonar.jdbc.').", e);
} finally {
- DatabaseUtils.closeQuietly(connection);
+ DbUtils.closeQuietly(connection);
}
}
diff --git a/sonar-core/src/test/java/org/sonar/core/persistence/AbstractDaoTestCase.java b/sonar-core/src/test/java/org/sonar/core/persistence/AbstractDaoTestCase.java
index a6e1e1b5d05..4d208829db1 100644
--- a/sonar-core/src/test/java/org/sonar/core/persistence/AbstractDaoTestCase.java
+++ b/sonar-core/src/test/java/org/sonar/core/persistence/AbstractDaoTestCase.java
@@ -60,7 +60,7 @@ import java.util.Properties;
import static org.junit.Assert.fail;
public abstract class AbstractDaoTestCase {
- private static Logger LOG = LoggerFactory.getLogger(AbstractDaoTestCase.class);
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractDaoTestCase.class);
private static Database database;
private static DatabaseCommands databaseCommands;
private static IDatabaseTester databaseTester;
@@ -80,7 +80,7 @@ public abstract class AbstractDaoTestCase {
if (hasDialect) {
database = new DefaultDatabase(settings);
} else {
- database = new H2Database("h2Tests");
+ database = new H2Database("h2Tests", true);
}
database.start();
LOG.info("Test Database: " + database);
diff --git a/sonar-core/src/test/java/org/sonar/core/persistence/DatabaseCommands.java b/sonar-core/src/test/java/org/sonar/core/persistence/DatabaseCommands.java
index 2205ac573f5..21fb4c923c1 100644
--- a/sonar-core/src/test/java/org/sonar/core/persistence/DatabaseCommands.java
+++ b/sonar-core/src/test/java/org/sonar/core/persistence/DatabaseCommands.java
@@ -119,9 +119,14 @@ public abstract class DatabaseCommands {
connection.setAutoCommit(false);
Statement statement = connection.createStatement();
- for (String table : DatabaseUtils.TABLE_NAMES) {
- statement.executeUpdate("TRUNCATE TABLE " + table);
- connection.commit();
+ for (String table : DatabaseVersion.TABLES) {
+ try {
+ statement.executeUpdate("TRUNCATE TABLE " + table);
+ connection.commit();
+ } catch (Exception e) {
+ // ignore
+ connection.rollback();
+ }
}
statement.close();
@@ -133,7 +138,7 @@ public abstract class DatabaseCommands {
connection.setAutoCommit(false);
Statement statement = connection.createStatement();
- for (String table : DatabaseUtils.TABLE_NAMES) {
+ for (String table : DatabaseVersion.TABLES) {
try {
ResultSet result = statement.executeQuery("SELECT CASE WHEN MAX(ID) IS NULL THEN 1 ELSE MAX(ID)+1 END FROM " + table);
result.next();
diff --git a/sonar-core/src/test/java/org/sonar/core/persistence/DatabaseUtilsTest.java b/sonar-core/src/test/java/org/sonar/core/persistence/DatabaseUtilsTest.java
deleted file mode 100644
index 28e1ef948f7..00000000000
--- a/sonar-core/src/test/java/org/sonar/core/persistence/DatabaseUtilsTest.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * 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;
-
-import org.junit.Test;
-import org.sonar.core.persistence.dialect.Oracle;
-
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-public class DatabaseUtilsTest extends AbstractDaoTestCase {
-
- @Test
- public void should_close_connection() throws SQLException {
- Connection connection = getConnection();
- assertThat(isClosed(connection)).isFalse();
-
- DatabaseUtils.closeQuietly(connection);
- assertThat(isClosed(connection)).isTrue();
- }
-
- @Test
- public void should_support_null_connection() {
- DatabaseUtils.closeQuietly((Connection) null);
- // no failure
- }
-
- @Test
- public void should_close_statement_and_resultset() throws SQLException {
- Connection connection = getConnection();
- try {
- PreparedStatement statement = connection.prepareStatement(selectDual());
- ResultSet rs = statement.executeQuery();
-
- DatabaseUtils.closeQuietly(rs);
- DatabaseUtils.closeQuietly(statement);
-
- assertThat(isClosed(statement)).isTrue();
- assertThat(isClosed(rs)).isTrue();
- } finally {
- DatabaseUtils.closeQuietly(connection);
- }
- }
-
- @Test
- public void should_not_fail_on_connection_errors() throws SQLException {
- Connection connection = mock(Connection.class);
- doThrow(new SQLException()).when(connection).close();
-
- DatabaseUtils.closeQuietly(connection);
-
- // no failure
- verify(connection).close(); // just to be sure
- }
-
- @Test
- public void should_not_fail_on_statement_errors() throws SQLException {
- Statement statement = mock(Statement.class);
- doThrow(new SQLException()).when(statement).close();
-
- DatabaseUtils.closeQuietly(statement);
-
- // no failure
- verify(statement).close(); // just to be sure
- }
-
- @Test
- public void should_not_fail_on_resulset_errors() throws SQLException {
- ResultSet rs = mock(ResultSet.class);
- doThrow(new SQLException()).when(rs).close();
-
- DatabaseUtils.closeQuietly(rs);
-
- // no failure
- verify(rs).close(); // just to be sure
- }
-
- /**
- * Connection.isClosed() has been introduced in java 1.6
- */
- private boolean isClosed(Connection c) {
- try {
- c.createStatement().execute(selectDual());
- return false;
- } catch (Exception e) {
- return true;
- }
- }
-
- /**
- * Statement.isClosed() has been introduced in java 1.6
- */
- private boolean isClosed(Statement s) {
- try {
- s.execute("SELECT 1");
- return false;
- } catch (Exception e) {
- return true;
- }
- }
-
- /**
- * ResultSet.isClosed() has been introduced in java 1.6
- */
- private boolean isClosed(ResultSet rs) {
- try {
- rs.next();
- return false;
- } catch (Exception e) {
- return true;
- }
- }
-
- private String selectDual() {
- String sql = "SELECT 1";
- if (Oracle.ID.equals(getDatabase().getDialect().getId())) {
- sql = "SELECT 1 FROM DUAL";
- }
- return sql;
- }
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/persistence/H2Database.java b/sonar-core/src/test/java/org/sonar/core/persistence/H2Database.java
index 4536993eb1d..31c6619aea7 100644
--- a/sonar-core/src/test/java/org/sonar/core/persistence/H2Database.java
+++ b/sonar-core/src/test/java/org/sonar/core/persistence/H2Database.java
@@ -20,6 +20,7 @@
package org.sonar.core.persistence;
import org.apache.commons.dbcp.BasicDataSource;
+import org.apache.commons.dbutils.DbUtils;
import org.hibernate.cfg.Environment;
import org.sonar.core.persistence.dialect.Dialect;
import org.sonar.core.persistence.dialect.H2;
@@ -38,18 +39,22 @@ import java.util.Properties;
*/
public class H2Database implements Database {
private final String name;
+ private final boolean createSchema;
private BasicDataSource datasource;
/**
* IMPORTANT: change DB name in order to not conflict with {@link DefaultDatabaseTest}
*/
- public H2Database(String name) {
+ public H2Database(String name, boolean createSchema) {
this.name = name;
+ this.createSchema = createSchema;
}
public H2Database start() {
startDatabase();
- createSchema();
+ if (createSchema) {
+ createSchema();
+ }
return this;
}
@@ -70,10 +75,24 @@ public class H2Database implements Database {
try {
connection = datasource.getConnection();
DdlUtils.createSchema(connection, "h2");
+
} catch (SQLException e) {
throw new IllegalStateException("Fail to create schema", e);
} finally {
- DatabaseUtils.closeQuietly(connection);
+ DbUtils.closeQuietly(connection);
+ }
+ }
+
+ public void executeScript(String classloaderPath) {
+ Connection connection = null;
+ try {
+ connection = datasource.getConnection();
+ DdlUtils.executeScript(connection, classloaderPath);
+
+ } catch (SQLException e) {
+ throw new IllegalStateException("Fail to execute script: " + classloaderPath, e);
+ } finally {
+ DbUtils.closeQuietly(connection);
}
}
diff --git a/sonar-core/src/test/java/org/sonar/core/persistence/H2DatabaseTest.java b/sonar-core/src/test/java/org/sonar/core/persistence/H2DatabaseTest.java
index 67703eda4f5..19ab908c98a 100644
--- a/sonar-core/src/test/java/org/sonar/core/persistence/H2DatabaseTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/persistence/H2DatabaseTest.java
@@ -30,7 +30,7 @@ import java.sql.SQLException;
import static org.fest.assertions.Assertions.assertThat;
public class H2DatabaseTest {
- H2Database db = new H2Database("sonar2");
+ H2Database db = new H2Database("sonar2", true);
@Before
public void startDb() {
diff --git a/sonar-core/src/test/java/org/sonar/core/persistence/MyBatisTest.java b/sonar-core/src/test/java/org/sonar/core/persistence/MyBatisTest.java
index d0455791383..3a3bfcad71a 100644
--- a/sonar-core/src/test/java/org/sonar/core/persistence/MyBatisTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/persistence/MyBatisTest.java
@@ -41,7 +41,7 @@ public class MyBatisTest {
@BeforeClass
public static void start() {
- database = new H2Database("sonar2");
+ database = new H2Database("sonar2", true);
database.start();
}
diff --git a/sonar-core/src/test/java/org/sonar/core/persistence/TestDatabase.java b/sonar-core/src/test/java/org/sonar/core/persistence/TestDatabase.java
new file mode 100644
index 00000000000..39d0bf63762
--- /dev/null
+++ b/sonar-core/src/test/java/org/sonar/core/persistence/TestDatabase.java
@@ -0,0 +1,293 @@
+/*
+ * 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;
+
+import com.google.common.collect.Maps;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.dbutils.DbUtils;
+import org.apache.commons.dbutils.QueryRunner;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.text.StrSubstitutor;
+import org.dbunit.Assertion;
+import org.dbunit.DataSourceDatabaseTester;
+import org.dbunit.DatabaseUnitException;
+import org.dbunit.IDatabaseTester;
+import org.dbunit.assertion.DiffCollectingFailureHandler;
+import org.dbunit.assertion.Difference;
+import org.dbunit.database.DatabaseConfig;
+import org.dbunit.database.IDatabaseConnection;
+import org.dbunit.dataset.CompositeDataSet;
+import org.dbunit.dataset.IDataSet;
+import org.dbunit.dataset.ReplacementDataSet;
+import org.dbunit.dataset.xml.FlatXmlDataSet;
+import org.dbunit.ext.mssql.InsertIdentityOperation;
+import org.dbunit.operation.DatabaseOperation;
+import org.junit.rules.ExternalResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.config.Settings;
+import org.sonar.core.config.Logback;
+import org.sonar.core.persistence.dialect.Dialect;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import static org.junit.Assert.fail;
+
+public class TestDatabase extends ExternalResource {
+
+ private static final Logger LOG = LoggerFactory.getLogger(TestDatabase.class);
+
+ private Database db;
+ private DatabaseCommands commands;
+ private IDatabaseTester tester;
+ private MyBatis myBatis;
+ private String schemaPath = null;
+
+
+ public TestDatabase schema(Class baseClass, String filename) {
+ String path = StringUtils.replaceChars(baseClass.getCanonicalName(), '.', '/');
+ schemaPath = path + "/" + filename;
+ return this;
+ }
+
+ @Override
+ protected void before() throws Throwable {
+ if (db == null) {
+ Settings settings = new Settings().setProperties(Maps.fromProperties(System.getProperties()));
+ if (settings.hasKey("orchestrator.configUrl")) {
+ loadOrchestratorSettings(settings);
+ }
+ for (String key : settings.getKeysStartingWith("sonar.jdbc")) {
+ LOG.info(key + ": " + settings.getString(key));
+ }
+ boolean hasDialect = settings.hasKey("sonar.jdbc.dialect");
+ if (hasDialect) {
+ db = new DefaultDatabase(settings);
+ } else {
+ db = new H2Database("h2Tests" + DigestUtils.md5Hex(StringUtils.defaultString(schemaPath)), schemaPath == null);
+ }
+ db.start();
+ if (schemaPath != null) {
+ // will fail if not H2
+ ((H2Database) db).executeScript(schemaPath);
+ }
+ LOG.info("Test Database: " + db);
+
+ commands = DatabaseCommands.forDialect(db.getDialect());
+ tester = new DataSourceDatabaseTester(db.getDataSource());
+
+ myBatis = new MyBatis(db, settings, new Logback());
+ myBatis.start();
+ }
+ commands.truncateDatabase(db.getDataSource());
+ }
+
+
+ @Override
+ protected void after() {
+ if (db != null) {
+ db.stop();
+ db = null;
+ commands = null;
+ myBatis = null;
+ }
+ }
+
+ public Database database() {
+ return db;
+ }
+
+ public Dialect dialect() {
+ return db.getDialect();
+ }
+
+ public DatabaseCommands commands() {
+ return commands;
+ }
+
+ public MyBatis myBatis() {
+ return myBatis;
+ }
+
+ public Connection openConnection() throws SQLException {
+ return db.getDataSource().getConnection();
+ }
+
+ public void executeUpdateSql(String sql) {
+ Connection connection = null;
+ try {
+ connection = openConnection();
+ new QueryRunner().update(connection, sql);
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to execute sql: " + sql);
+ } finally {
+ DbUtils.commitAndCloseQuietly(connection);
+ }
+ }
+
+ public void prepareDbUnit(Class testClass, String... testNames) {
+ InputStream[] streams = new InputStream[testNames.length];
+ try {
+ for (int i = 0; i < testNames.length; i++) {
+ String path = "/" + testClass.getName().replace('.', '/') + "/" + testNames[i];
+ streams[i] = testClass.getResourceAsStream(path);
+ if (streams[i] == null) {
+ throw new IllegalStateException("DbUnit file not found: " + path);
+ }
+ }
+
+ prepareDbUnit(streams);
+ commands.resetPrimaryKeys(db.getDataSource());
+ } catch (SQLException e) {
+ throw translateException("Could not setup DBUnit data", e);
+ } finally {
+ for (InputStream stream : streams) {
+ IOUtils.closeQuietly(stream);
+ }
+ }
+ }
+
+ private void prepareDbUnit(InputStream... dataSetStream) {
+ IDatabaseConnection connection = null;
+ try {
+ IDataSet[] dataSets = new IDataSet[dataSetStream.length];
+ for (int i = 0; i < dataSetStream.length; i++) {
+ dataSets[i] = dbUnitDataSet(dataSetStream[i]);
+ }
+ tester.setDataSet(new CompositeDataSet(dataSets));
+ connection = dbUnitConnection();
+ new InsertIdentityOperation(DatabaseOperation.INSERT).execute(connection, tester.getDataSet());
+ } catch (Exception e) {
+ throw translateException("Could not setup DBUnit data", e);
+ } finally {
+ closeQuietly(connection);
+ }
+ }
+
+ public void assertDbUnit(Class testClass, String filename, String... tables) {
+ IDatabaseConnection connection = null;
+ try {
+ connection = dbUnitConnection();
+ IDataSet dataSet = connection.createDataSet();
+ String path = "/" + testClass.getName().replace('.', '/') + "/" + filename;
+ IDataSet expectedDataSet = dbUnitDataSet(testClass.getResourceAsStream(path));
+ for (String table : tables) {
+ DiffCollectingFailureHandler diffHandler = new DiffCollectingFailureHandler();
+
+ Assertion.assertEquals(expectedDataSet.getTable(table), dataSet.getTable(table), diffHandler);
+ // Evaluate the differences and ignore some column values
+ List diffList = diffHandler.getDiffList();
+ for (Object o : diffList) {
+ Difference diff = (Difference) o;
+ if (!"[ignore]".equals(diff.getExpectedValue())) {
+ throw new DatabaseUnitException(diff.toString());
+ }
+ }
+ }
+ } catch (DatabaseUnitException e) {
+ fail(e.getMessage());
+ } catch (Exception e) {
+ throw translateException("Error while checking results", e);
+ } finally {
+ closeQuietly(connection);
+ }
+ }
+
+ private IDataSet dbUnitDataSet(InputStream stream) {
+ try {
+ ReplacementDataSet dataSet = new ReplacementDataSet(new FlatXmlDataSet(stream));
+ dataSet.addReplacementObject("[null]", null);
+ dataSet.addReplacementObject("[false]", Boolean.FALSE);
+ dataSet.addReplacementObject("[true]", Boolean.TRUE);
+ return dataSet;
+ } catch (Exception e) {
+ throw translateException("Could not read the dataset stream", e);
+ }
+ }
+
+ private IDatabaseConnection dbUnitConnection() {
+ try {
+ IDatabaseConnection connection = tester.getConnection();
+ connection.getConfig().setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, commands.getDbUnitFactory());
+ return connection;
+ } catch (Exception e) {
+ throw translateException("Error while getting connection", e);
+ }
+ }
+
+ private void closeQuietly(IDatabaseConnection connection) {
+ try {
+ if (connection != null) {
+ connection.close();
+ }
+ } catch (SQLException e) {
+ // ignore
+ }
+ }
+
+ private static RuntimeException translateException(String msg, Exception cause) {
+ RuntimeException runtimeException = new RuntimeException(String.format("%s: [%s] %s", msg, cause.getClass().getName(), cause.getMessage()));
+ runtimeException.setStackTrace(cause.getStackTrace());
+ return runtimeException;
+ }
+
+ private void loadOrchestratorSettings(Settings settings) throws URISyntaxException, IOException {
+ String url = settings.getString("orchestrator.configUrl");
+ URI uri = new URI(url);
+ InputStream input = null;
+ try {
+ if (url.startsWith("file:")) {
+ File file = new File(uri);
+ input = FileUtils.openInputStream(file);
+ } else {
+ HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection();
+ int responseCode = connection.getResponseCode();
+ if (responseCode >= 400) {
+ throw new IllegalStateException("Fail to request: " + uri + ". Status code=" + responseCode);
+ }
+
+ input = connection.getInputStream();
+
+ }
+ Properties props = new Properties();
+ props.load(input);
+ settings.addProperties(props);
+ for (Map.Entry<String, String> entry : settings.getProperties().entrySet()) {
+ String interpolatedValue = StrSubstitutor.replace(entry.getValue(), System.getenv(), "${", "}");
+ settings.setProperty(entry.getKey(), interpolatedValue);
+ }
+ } finally {
+ IOUtils.closeQuietly(input);
+ }
+ }
+
+}
diff --git a/sonar-core/src/test/java/org/sonar/jpa/test/AbstractDbUnitTestCase.java b/sonar-core/src/test/java/org/sonar/jpa/test/AbstractDbUnitTestCase.java
index 097ff170350..7998f074bd7 100644
--- a/sonar-core/src/test/java/org/sonar/jpa/test/AbstractDbUnitTestCase.java
+++ b/sonar-core/src/test/java/org/sonar/jpa/test/AbstractDbUnitTestCase.java
@@ -63,7 +63,7 @@ public abstract class AbstractDbUnitTestCase {
@Before
public void startDatabase() throws SQLException {
if (database == null) {
- database = new H2Database("sonarHibernate");
+ database = new H2Database("sonarHibernate", true);
database.start();
databaseCommands = DatabaseCommands.forDialect(database.getDialect());