]> source.dussan.org Git - sonarqube.git/commitdiff
Move org.sonar.server.db to org.sonar.server.platform.db
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Tue, 26 Jul 2016 12:25:50 +0000 (14:25 +0200)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Fri, 29 Jul 2016 08:31:31 +0000 (10:31 +0200)
42 files changed:
server/sonar-server/src/main/java/org/sonar/server/db/CheckDatabaseCharsetAtStartup.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/db/CheckDatabaseCollationDuringMigration.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/db/EmbeddedDatabase.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/db/EmbeddedDatabaseFactory.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigrator.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/db/migrations/PlatformDatabaseMigration.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/db/migrations/PlatformDatabaseMigrationExecutorService.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/db/migrations/PlatformDatabaseMigrationExecutorServiceImpl.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/db/migrations/package-info.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/db/package-info.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/platform/db/CheckDatabaseCharsetAtStartup.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/db/CheckDatabaseCollationDuringMigration.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/db/EmbeddedDatabase.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/db/EmbeddedDatabaseFactory.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/db/migrations/DatabaseMigrator.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/db/migrations/PlatformDatabaseMigration.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/db/migrations/PlatformDatabaseMigrationExecutorService.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/db/migrations/PlatformDatabaseMigrationExecutorServiceImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/db/migrations/package-info.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/db/package-info.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java
server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
server/sonar-server/src/test/java/org/sonar/server/db/CheckDatabaseCharsetAtStartupTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/db/CheckDatabaseCollationDuringMigrationTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/db/EmbeddedDatabaseFactoryTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/db/EmbeddedDatabaseTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/db/migrations/DatabaseMigratorTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/db/migrations/PlatformDatabaseMigrationAsynchronousTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/db/migrations/PlatformDatabaseMigrationConcurrentAccessTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/db/migrations/PlatformDatabaseMigrationExecutorServiceAdaptor.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/db/migrations/PlatformDatabaseMigrationTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/platform/db/CheckDatabaseCharsetAtStartupTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/platform/db/CheckDatabaseCollationDuringMigrationTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/platform/db/EmbeddedDatabaseFactoryTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/platform/db/EmbeddedDatabaseTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/platform/db/migrations/DatabaseMigratorTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/platform/db/migrations/PlatformDatabaseMigrationAsynchronousTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/platform/db/migrations/PlatformDatabaseMigrationConcurrentAccessTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/platform/db/migrations/PlatformDatabaseMigrationExecutorServiceAdaptor.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/platform/db/migrations/PlatformDatabaseMigrationTest.java [new file with mode: 0644]

diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/CheckDatabaseCharsetAtStartup.java b/server/sonar-server/src/main/java/org/sonar/server/db/CheckDatabaseCharsetAtStartup.java
deleted file mode 100644 (file)
index 91da7bd..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.server.db;
-
-import org.picocontainer.Startable;
-import org.sonar.api.platform.ServerUpgradeStatus;
-import org.sonar.db.charset.DatabaseCharsetChecker;
-
-import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.ENFORCE_UTF8;
-
-/**
- * Checks charset of all existing database columns at startup, before executing db migrations. This requires
- * to be defined in platform level 2 ({@link org.sonar.server.platform.platformlevel.PlatformLevel2}).
- */
-public class CheckDatabaseCharsetAtStartup implements Startable {
-
-  private final ServerUpgradeStatus upgradeStatus;
-  private final DatabaseCharsetChecker charsetChecker;
-
-  public CheckDatabaseCharsetAtStartup(ServerUpgradeStatus upgradeStatus, DatabaseCharsetChecker charsetChecker) {
-    this.upgradeStatus = upgradeStatus;
-    this.charsetChecker = charsetChecker;
-  }
-
-  @Override
-  public void start() {
-    check();
-  }
-
-  @Override
-  public void stop() {
-    // do nothing
-  }
-
-  protected final void check() {
-    if (upgradeStatus.isFreshInstall()) {
-      charsetChecker.check(ENFORCE_UTF8);
-    } else if (!upgradeStatus.isUpgraded()) {
-      charsetChecker.check();
-    }
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/CheckDatabaseCollationDuringMigration.java b/server/sonar-server/src/main/java/org/sonar/server/db/CheckDatabaseCollationDuringMigration.java
deleted file mode 100644 (file)
index ebb5a59..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.server.db;
-
-import org.picocontainer.Startable;
-import org.sonar.api.platform.ServerUpgradeStatus;
-import org.sonar.db.charset.DatabaseCharsetChecker;
-
-import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.AUTO_REPAIR_COLLATION;
-import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.ENFORCE_UTF8;
-
-/**
- * Checks charset of all database columns when at least one db migration has been executed. 
- */
-public class CheckDatabaseCollationDuringMigration implements Startable {
-
-  private final ServerUpgradeStatus upgradeStatus;
-  private final DatabaseCharsetChecker charsetChecker;
-
-  public CheckDatabaseCollationDuringMigration(ServerUpgradeStatus upgradeStatus, DatabaseCharsetChecker charsetChecker) {
-    this.upgradeStatus = upgradeStatus;
-    this.charsetChecker = charsetChecker;
-  }
-
-  @Override
-  public void start() {
-    if (upgradeStatus.isFreshInstall()) {
-      charsetChecker.check(ENFORCE_UTF8, AUTO_REPAIR_COLLATION);
-    } else if (upgradeStatus.isUpgraded()) {
-      charsetChecker.check(AUTO_REPAIR_COLLATION);
-    }
-  }
-
-  @Override
-  public void stop() {
-    // do nothing
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/EmbeddedDatabase.java b/server/sonar-server/src/main/java/org/sonar/server/db/EmbeddedDatabase.java
deleted file mode 100644 (file)
index 505d323..0000000
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.server.db;
-
-import java.io.File;
-import java.sql.DriverManager;
-import java.sql.SQLException;
-import org.apache.commons.lang.StringUtils;
-import org.h2.Driver;
-import org.h2.tools.Server;
-import org.picocontainer.Startable;
-import org.sonar.api.config.Settings;
-import org.sonar.api.utils.SonarException;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static java.lang.String.format;
-import static org.apache.commons.lang.StringUtils.isNotEmpty;
-import static org.sonar.api.database.DatabaseProperties.PROP_EMBEDDED_PORT;
-import static org.sonar.api.database.DatabaseProperties.PROP_PASSWORD;
-import static org.sonar.api.database.DatabaseProperties.PROP_PASSWORD_DEFAULT_VALUE;
-import static org.sonar.api.database.DatabaseProperties.PROP_URL;
-import static org.sonar.api.database.DatabaseProperties.PROP_USER;
-import static org.sonar.api.database.DatabaseProperties.PROP_USER_DEFAULT_VALUE;
-import static org.sonar.process.ProcessProperties.PATH_DATA;
-
-public class EmbeddedDatabase implements Startable {
-  private static final Logger LOG = Loggers.get(EmbeddedDatabase.class);
-  private final Settings settings;
-  private Server server;
-
-  public EmbeddedDatabase(Settings settings) {
-    this.settings = settings;
-  }
-
-  @Override
-  public void start() {
-    File dbHome = new File(getRequiredSetting(PATH_DATA));
-    if (!dbHome.exists()) {
-      dbHome.mkdirs();
-    }
-
-    startServer(dbHome);
-  }
-
-  private void startServer(File dbHome) {
-    String url = getRequiredSetting(PROP_URL);
-    String port = getRequiredSetting(PROP_EMBEDDED_PORT);
-    String user = getSetting(PROP_USER, PROP_USER_DEFAULT_VALUE);
-    String password = getSetting(PROP_PASSWORD, PROP_PASSWORD_DEFAULT_VALUE);
-    try {
-      if (url.contains("/mem:")) {
-        server = Server.createTcpServer("-tcpPort", port, "-tcpAllowOthers", "-baseDir", dbHome.getAbsolutePath());
-      } else {
-        createDatabase(dbHome, user, password);
-        server = Server.createTcpServer("-tcpPort", port, "-tcpAllowOthers", "-ifExists", "-baseDir", dbHome.getAbsolutePath());
-      }
-
-      LOG.info("Starting embedded database on port " + server.getPort() + " with url " + url);
-      server.start();
-
-      LOG.info("Embedded database started. Data stored in: " + dbHome.getAbsolutePath());
-    } catch (Exception e) {
-      throw new SonarException("Unable to start database", e);
-    }
-  }
-
-  @Override
-  public void stop() {
-    if (server != null) {
-      server.stop();
-      server = null;
-      LOG.info("Embedded database stopped");
-    }
-  }
-
-  private String getRequiredSetting(String property) {
-    String value = settings.getString(property);
-    checkArgument(isNotEmpty(value), "Missing property %s", property);
-    return value;
-  }
-
-  private String getSetting(String name, String defaultValue) {
-    return StringUtils.defaultIfBlank(settings.getString(name), defaultValue);
-  }
-
-  private static void createDatabase(File dbHome, String user, String password) throws SQLException {
-    String url = format("jdbc:h2:%s/sonar;USER=%s;PASSWORD=%s", dbHome.getAbsolutePath(), user, password);
-
-    DriverManager.registerDriver(new Driver());
-    DriverManager.getConnection(url).close();
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/EmbeddedDatabaseFactory.java b/server/sonar-server/src/main/java/org/sonar/server/db/EmbeddedDatabaseFactory.java
deleted file mode 100644 (file)
index 5a5f4e4..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.server.db;
-
-import com.google.common.annotations.VisibleForTesting;
-import org.picocontainer.Startable;
-import org.sonar.api.config.Settings;
-import org.sonar.api.database.DatabaseProperties;
-
-import static org.apache.commons.lang.StringUtils.startsWith;
-
-public class EmbeddedDatabaseFactory implements Startable {
-  private static final String URL_PREFIX = "jdbc:h2:tcp:";
-  private final Settings settings;
-  private EmbeddedDatabase embeddedDatabase;
-
-  public EmbeddedDatabaseFactory(Settings settings) {
-    this.settings = settings;
-  }
-
-  @Override
-  public void start() {
-    if (embeddedDatabase == null) {
-      String jdbcUrl = settings.getString(DatabaseProperties.PROP_URL);
-      if (startsWith(jdbcUrl, URL_PREFIX)) {
-        embeddedDatabase = createEmbeddedDatabase();
-        embeddedDatabase.start();
-      }
-    }
-  }
-
-  @Override
-  public void stop() {
-    if (embeddedDatabase != null) {
-      embeddedDatabase.stop();
-      embeddedDatabase = null;
-    }
-  }
-
-  @VisibleForTesting
-  EmbeddedDatabase createEmbeddedDatabase() {
-    return new EmbeddedDatabase(settings);
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigrator.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigrator.java
deleted file mode 100644 (file)
index cc09cfd..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.server.db.migrations;
-
-import com.google.common.annotations.VisibleForTesting;
-import java.sql.Connection;
-import org.apache.commons.dbutils.DbUtils;
-import org.apache.ibatis.session.SqlSession;
-import org.picocontainer.Startable;
-import org.sonar.api.platform.ServerUpgradeStatus;
-import org.sonar.api.server.ServerSide;
-import org.sonar.api.utils.log.Loggers;
-import org.sonar.db.DbClient;
-import org.sonar.db.DdlUtils;
-import org.sonar.db.version.MigrationStep;
-import org.sonar.server.plugins.ServerPluginRepository;
-
-/**
- * Restore schema by executing DDL scripts. Only H2 database is supported.
- * Other databases are created by Ruby on Rails migrations.
- *
- * @since 2.12
- */
-@ServerSide
-public class DatabaseMigrator implements Startable {
-
-  private final DbClient dbClient;
-  private final MigrationStep[] migrations;
-  private final ServerUpgradeStatus serverUpgradeStatus;
-
-  /**
-   * ServerPluginRepository is used to ensure H2 schema creation is done only after copy of bundle plugins have been done
-   */
-  public DatabaseMigrator(DbClient dbClient, MigrationStep[] migrations, ServerUpgradeStatus serverUpgradeStatus,
-    ServerPluginRepository unused) {
-    this.dbClient = dbClient;
-    this.migrations = migrations;
-    this.serverUpgradeStatus = serverUpgradeStatus;
-  }
-
-  @Override
-  public void start() {
-    createDatabase();
-  }
-
-  @Override
-  public void stop() {
-    // Nothing to do
-  }
-
-  /**
-   * @return true if the database has been created, false if this database is not supported or if database has already been created
-   */
-  @VisibleForTesting
-  boolean createDatabase() {
-    if (DdlUtils.supportsDialect(dbClient.getDatabase().getDialect().getId()) && serverUpgradeStatus.isFreshInstall()) {
-      Loggers.get(getClass()).info("Create database");
-      SqlSession session = dbClient.openSession(false);
-      Connection connection = null;
-      try {
-        connection = session.getConnection();
-        createSchema(connection, dbClient.getDatabase().getDialect().getId());
-        return true;
-      } finally {
-        session.close();
-
-        // The connection is probably already closed by session.close()
-        // but it's not documented in mybatis javadoc.
-        DbUtils.closeQuietly(connection);
-      }
-    }
-    return false;
-  }
-
-  public void executeMigration(String className) {
-    MigrationStep migration = getMigration(className);
-    try {
-      migration.execute();
-
-    } catch (Exception e) {
-      // duplication between log and exception because webapp does not correctly log initial stacktrace
-      String msg = "Fail to execute database migration: " + className;
-      Loggers.get(getClass()).error(msg, e);
-      throw new IllegalStateException(msg, e);
-    }
-  }
-
-  private MigrationStep getMigration(String className) {
-    for (MigrationStep migration : migrations) {
-      if (migration.getClass().getName().equals(className)) {
-        return migration;
-      }
-    }
-    throw new IllegalArgumentException("Database migration not found: " + className);
-  }
-
-  @VisibleForTesting
-  protected void createSchema(Connection connection, String dialectId) {
-    DdlUtils.createSchema(connection, dialectId);
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/PlatformDatabaseMigration.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/PlatformDatabaseMigration.java
deleted file mode 100644 (file)
index b6a89fe..0000000
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.server.db.migrations;
-
-import java.util.Date;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.locks.ReentrantLock;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
-import org.sonar.api.utils.log.Profiler;
-import org.sonar.db.version.DatabaseMigration;
-import org.sonar.server.platform.Platform;
-import org.sonar.server.ruby.RubyBridge;
-
-/**
- * Handles concurrency to make sure only one DB migration can run at a time.
- */
-public class PlatformDatabaseMigration implements DatabaseMigration {
-
-  private static final Logger LOGGER = Loggers.get(PlatformDatabaseMigration.class);
-
-  private final RubyBridge rubyBridge;
-  /**
-   * ExecutorService implements threads management.
-   */
-  private final PlatformDatabaseMigrationExecutorService executorService;
-  private final Platform platform;
-  /**
-   * This lock implements thread safety from concurrent calls of method {@link #startIt()}
-   */
-  private final ReentrantLock lock = new ReentrantLock();
-
-  /**
-   * This property acts as a semaphore to make sure at most one db migration task is created at a time.
-   * <p>
-   * It is set to {@code true} by the first thread to execute the {@link #startIt()} method and set to {@code false}
-   * by the thread executing the db migration.
-   * </p>
-   */
-  private final AtomicBoolean running = new AtomicBoolean(false);
-  private Status status = Status.NONE;
-  @Nullable
-  private Date startDate;
-  @Nullable
-  private Throwable failureError;
-
-  public PlatformDatabaseMigration(RubyBridge rubyBridge,
-    PlatformDatabaseMigrationExecutorService executorService, Platform platform) {
-    this.rubyBridge = rubyBridge;
-    this.executorService = executorService;
-    this.platform = platform;
-  }
-
-  @Override
-  public void startIt() {
-    if (lock.isLocked() || this.running.get()) {
-      LOGGER.trace("{}: lock is already taken or process is already running", Thread.currentThread().getName());
-      return;
-    }
-
-    if (lock.tryLock()) {
-      try {
-        startAsynchronousDBMigration();
-      } finally {
-        lock.unlock();
-      }
-    }
-  }
-
-  /**
-   * This method is not thread safe and must be externally protected from concurrent executions.
-   */
-  private void startAsynchronousDBMigration() {
-    if (this.running.get()) {
-      return;
-    }
-
-    running.set(true);
-    executorService.execute(this::doDatabaseMigration);
-  }
-
-  private void doDatabaseMigration() {
-    status = Status.RUNNING;
-    startDate = new Date();
-    failureError = null;
-    Profiler profiler = Profiler.create(LOGGER);
-    try {
-      profiler.startInfo("Starting DB Migration");
-      doUpgradeDb();
-      doRestartContainer();
-      doRecreateWebRoutes();
-      status = Status.SUCCEEDED;
-      profiler.stopInfo("DB Migration ended successfully");
-    } catch (Throwable t) {
-      profiler.stopInfo("DB migration failed");
-      LOGGER.error("DB Migration or container restart failed. Process ended with an exception", t);
-      status = Status.FAILED;
-      failureError = t;
-    } finally {
-      running.getAndSet(false);
-    }
-  }
-
-  private void doUpgradeDb() {
-    Profiler profiler = Profiler.createIfTrace(LOGGER);
-    profiler.startTrace("Starting DB Migration");
-    rubyBridge.databaseMigration().trigger();
-    profiler.stopTrace("DB Migration ended");
-  }
-
-  private void doRestartContainer() {
-    Profiler profiler = Profiler.createIfTrace(LOGGER);
-    profiler.startTrace("Restarting container");
-    platform.doStart();
-    profiler.stopTrace("Container restarted successfully");
-  }
-
-  private void doRecreateWebRoutes() {
-    Profiler profiler = Profiler.createIfTrace(LOGGER);
-    profiler.startTrace("Recreating web routes");
-    rubyBridge.railsRoutes().recreate();
-    profiler.startTrace("Routes recreated successfully");
-  }
-
-  @Override
-  @CheckForNull
-  public Date startedAt() {
-    return this.startDate;
-  }
-
-  @Override
-  public Status status() {
-    return this.status;
-  }
-
-  @Override
-  @CheckForNull
-  public Throwable failureError() {
-    return this.failureError;
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/PlatformDatabaseMigrationExecutorService.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/PlatformDatabaseMigrationExecutorService.java
deleted file mode 100644 (file)
index 5957c94..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.server.db.migrations;
-
-import java.util.concurrent.ExecutorService;
-
-/**
- * Flag interface for the ExecutorService to be used by the {@link PlatformDatabaseMigration}
- * component.
- */
-public interface PlatformDatabaseMigrationExecutorService extends ExecutorService {
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/PlatformDatabaseMigrationExecutorServiceImpl.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/PlatformDatabaseMigrationExecutorServiceImpl.java
deleted file mode 100644 (file)
index c1626d3..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.server.db.migrations;
-
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
-import org.sonar.server.util.AbstractStoppableExecutorService;
-
-import java.util.concurrent.Executors;
-
-/**
- * Since only one DB migration can run at a time, this implementation of PlatformDatabaseMigrationExecutorService
- * wraps a single thread executor from the JDK.
- */
-public class PlatformDatabaseMigrationExecutorServiceImpl
-  extends AbstractStoppableExecutorService
-  implements PlatformDatabaseMigrationExecutorService {
-
-  public PlatformDatabaseMigrationExecutorServiceImpl() {
-    super(
-      Executors.newSingleThreadExecutor(
-        new ThreadFactoryBuilder()
-          .setDaemon(false)
-          .setNameFormat("DB_migration-%d")
-          .build()
-        ));
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/package-info.java
deleted file mode 100644 (file)
index b366a32..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-@ParametersAreNonnullByDefault
-package org.sonar.server.db.migrations;
-
-import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/db/package-info.java
deleted file mode 100644 (file)
index 4809859..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.
- */
-@ParametersAreNonnullByDefault
-package org.sonar.server.db;
-
-import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/db/CheckDatabaseCharsetAtStartup.java b/server/sonar-server/src/main/java/org/sonar/server/platform/db/CheckDatabaseCharsetAtStartup.java
new file mode 100644 (file)
index 0000000..ee1d20c
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.platform.db;
+
+import org.picocontainer.Startable;
+import org.sonar.api.platform.ServerUpgradeStatus;
+import org.sonar.db.charset.DatabaseCharsetChecker;
+
+import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.ENFORCE_UTF8;
+
+/**
+ * Checks charset of all existing database columns at startup, before executing db migrations. This requires
+ * to be defined in platform level 2 ({@link org.sonar.server.platform.platformlevel.PlatformLevel2}).
+ */
+public class CheckDatabaseCharsetAtStartup implements Startable {
+
+  private final ServerUpgradeStatus upgradeStatus;
+  private final DatabaseCharsetChecker charsetChecker;
+
+  public CheckDatabaseCharsetAtStartup(ServerUpgradeStatus upgradeStatus, DatabaseCharsetChecker charsetChecker) {
+    this.upgradeStatus = upgradeStatus;
+    this.charsetChecker = charsetChecker;
+  }
+
+  @Override
+  public void start() {
+    check();
+  }
+
+  @Override
+  public void stop() {
+    // do nothing
+  }
+
+  protected final void check() {
+    if (upgradeStatus.isFreshInstall()) {
+      charsetChecker.check(ENFORCE_UTF8);
+    } else if (!upgradeStatus.isUpgraded()) {
+      charsetChecker.check();
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/db/CheckDatabaseCollationDuringMigration.java b/server/sonar-server/src/main/java/org/sonar/server/platform/db/CheckDatabaseCollationDuringMigration.java
new file mode 100644 (file)
index 0000000..f78b046
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.platform.db;
+
+import org.picocontainer.Startable;
+import org.sonar.api.platform.ServerUpgradeStatus;
+import org.sonar.db.charset.DatabaseCharsetChecker;
+
+import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.AUTO_REPAIR_COLLATION;
+import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.ENFORCE_UTF8;
+
+/**
+ * Checks charset of all database columns when at least one db migration has been executed. 
+ */
+public class CheckDatabaseCollationDuringMigration implements Startable {
+
+  private final ServerUpgradeStatus upgradeStatus;
+  private final DatabaseCharsetChecker charsetChecker;
+
+  public CheckDatabaseCollationDuringMigration(ServerUpgradeStatus upgradeStatus, DatabaseCharsetChecker charsetChecker) {
+    this.upgradeStatus = upgradeStatus;
+    this.charsetChecker = charsetChecker;
+  }
+
+  @Override
+  public void start() {
+    if (upgradeStatus.isFreshInstall()) {
+      charsetChecker.check(ENFORCE_UTF8, AUTO_REPAIR_COLLATION);
+    } else if (upgradeStatus.isUpgraded()) {
+      charsetChecker.check(AUTO_REPAIR_COLLATION);
+    }
+  }
+
+  @Override
+  public void stop() {
+    // do nothing
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/db/EmbeddedDatabase.java b/server/sonar-server/src/main/java/org/sonar/server/platform/db/EmbeddedDatabase.java
new file mode 100644 (file)
index 0000000..afacd97
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.platform.db;
+
+import java.io.File;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import org.apache.commons.lang.StringUtils;
+import org.h2.Driver;
+import org.h2.tools.Server;
+import org.picocontainer.Startable;
+import org.sonar.api.config.Settings;
+import org.sonar.api.utils.SonarException;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.lang.String.format;
+import static org.apache.commons.lang.StringUtils.isNotEmpty;
+import static org.sonar.api.database.DatabaseProperties.PROP_EMBEDDED_PORT;
+import static org.sonar.api.database.DatabaseProperties.PROP_PASSWORD;
+import static org.sonar.api.database.DatabaseProperties.PROP_PASSWORD_DEFAULT_VALUE;
+import static org.sonar.api.database.DatabaseProperties.PROP_URL;
+import static org.sonar.api.database.DatabaseProperties.PROP_USER;
+import static org.sonar.api.database.DatabaseProperties.PROP_USER_DEFAULT_VALUE;
+import static org.sonar.process.ProcessProperties.PATH_DATA;
+
+public class EmbeddedDatabase implements Startable {
+  private static final Logger LOG = Loggers.get(EmbeddedDatabase.class);
+  private final Settings settings;
+  private Server server;
+
+  public EmbeddedDatabase(Settings settings) {
+    this.settings = settings;
+  }
+
+  @Override
+  public void start() {
+    File dbHome = new File(getRequiredSetting(PATH_DATA));
+    if (!dbHome.exists()) {
+      dbHome.mkdirs();
+    }
+
+    startServer(dbHome);
+  }
+
+  private void startServer(File dbHome) {
+    String url = getRequiredSetting(PROP_URL);
+    String port = getRequiredSetting(PROP_EMBEDDED_PORT);
+    String user = getSetting(PROP_USER, PROP_USER_DEFAULT_VALUE);
+    String password = getSetting(PROP_PASSWORD, PROP_PASSWORD_DEFAULT_VALUE);
+    try {
+      if (url.contains("/mem:")) {
+        server = Server.createTcpServer("-tcpPort", port, "-tcpAllowOthers", "-baseDir", dbHome.getAbsolutePath());
+      } else {
+        createDatabase(dbHome, user, password);
+        server = Server.createTcpServer("-tcpPort", port, "-tcpAllowOthers", "-ifExists", "-baseDir", dbHome.getAbsolutePath());
+      }
+
+      LOG.info("Starting embedded database on port " + server.getPort() + " with url " + url);
+      server.start();
+
+      LOG.info("Embedded database started. Data stored in: " + dbHome.getAbsolutePath());
+    } catch (Exception e) {
+      throw new SonarException("Unable to start database", e);
+    }
+  }
+
+  @Override
+  public void stop() {
+    if (server != null) {
+      server.stop();
+      server = null;
+      LOG.info("Embedded database stopped");
+    }
+  }
+
+  private String getRequiredSetting(String property) {
+    String value = settings.getString(property);
+    checkArgument(isNotEmpty(value), "Missing property %s", property);
+    return value;
+  }
+
+  private String getSetting(String name, String defaultValue) {
+    return StringUtils.defaultIfBlank(settings.getString(name), defaultValue);
+  }
+
+  private static void createDatabase(File dbHome, String user, String password) throws SQLException {
+    String url = format("jdbc:h2:%s/sonar;USER=%s;PASSWORD=%s", dbHome.getAbsolutePath(), user, password);
+
+    DriverManager.registerDriver(new Driver());
+    DriverManager.getConnection(url).close();
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/db/EmbeddedDatabaseFactory.java b/server/sonar-server/src/main/java/org/sonar/server/platform/db/EmbeddedDatabaseFactory.java
new file mode 100644 (file)
index 0000000..a74b287
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.platform.db;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.picocontainer.Startable;
+import org.sonar.api.config.Settings;
+import org.sonar.api.database.DatabaseProperties;
+
+import static org.apache.commons.lang.StringUtils.startsWith;
+
+public class EmbeddedDatabaseFactory implements Startable {
+  private static final String URL_PREFIX = "jdbc:h2:tcp:";
+  private final Settings settings;
+  private EmbeddedDatabase embeddedDatabase;
+
+  public EmbeddedDatabaseFactory(Settings settings) {
+    this.settings = settings;
+  }
+
+  @Override
+  public void start() {
+    if (embeddedDatabase == null) {
+      String jdbcUrl = settings.getString(DatabaseProperties.PROP_URL);
+      if (startsWith(jdbcUrl, URL_PREFIX)) {
+        embeddedDatabase = createEmbeddedDatabase();
+        embeddedDatabase.start();
+      }
+    }
+  }
+
+  @Override
+  public void stop() {
+    if (embeddedDatabase != null) {
+      embeddedDatabase.stop();
+      embeddedDatabase = null;
+    }
+  }
+
+  @VisibleForTesting
+  EmbeddedDatabase createEmbeddedDatabase() {
+    return new EmbeddedDatabase(settings);
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/db/migrations/DatabaseMigrator.java b/server/sonar-server/src/main/java/org/sonar/server/platform/db/migrations/DatabaseMigrator.java
new file mode 100644 (file)
index 0000000..39a9bbc
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.platform.db.migrations;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.sql.Connection;
+import org.apache.commons.dbutils.DbUtils;
+import org.apache.ibatis.session.SqlSession;
+import org.picocontainer.Startable;
+import org.sonar.api.platform.ServerUpgradeStatus;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.db.DbClient;
+import org.sonar.db.DdlUtils;
+import org.sonar.db.version.MigrationStep;
+import org.sonar.server.plugins.ServerPluginRepository;
+
+/**
+ * Restore schema by executing DDL scripts. Only H2 database is supported.
+ * Other databases are created by Ruby on Rails migrations.
+ *
+ * @since 2.12
+ */
+@ServerSide
+public class DatabaseMigrator implements Startable {
+
+  private final DbClient dbClient;
+  private final MigrationStep[] migrations;
+  private final ServerUpgradeStatus serverUpgradeStatus;
+
+  /**
+   * ServerPluginRepository is used to ensure H2 schema creation is done only after copy of bundle plugins have been done
+   */
+  public DatabaseMigrator(DbClient dbClient, MigrationStep[] migrations, ServerUpgradeStatus serverUpgradeStatus,
+    ServerPluginRepository unused) {
+    this.dbClient = dbClient;
+    this.migrations = migrations;
+    this.serverUpgradeStatus = serverUpgradeStatus;
+  }
+
+  @Override
+  public void start() {
+    createDatabase();
+  }
+
+  @Override
+  public void stop() {
+    // Nothing to do
+  }
+
+  /**
+   * @return true if the database has been created, false if this database is not supported or if database has already been created
+   */
+  @VisibleForTesting
+  boolean createDatabase() {
+    if (DdlUtils.supportsDialect(dbClient.getDatabase().getDialect().getId()) && serverUpgradeStatus.isFreshInstall()) {
+      Loggers.get(getClass()).info("Create database");
+      SqlSession session = dbClient.openSession(false);
+      Connection connection = null;
+      try {
+        connection = session.getConnection();
+        createSchema(connection, dbClient.getDatabase().getDialect().getId());
+        return true;
+      } finally {
+        session.close();
+
+        // The connection is probably already closed by session.close()
+        // but it's not documented in mybatis javadoc.
+        DbUtils.closeQuietly(connection);
+      }
+    }
+    return false;
+  }
+
+  public void executeMigration(String className) {
+    MigrationStep migration = getMigration(className);
+    try {
+      migration.execute();
+
+    } catch (Exception e) {
+      // duplication between log and exception because webapp does not correctly log initial stacktrace
+      String msg = "Fail to execute database migration: " + className;
+      Loggers.get(getClass()).error(msg, e);
+      throw new IllegalStateException(msg, e);
+    }
+  }
+
+  private MigrationStep getMigration(String className) {
+    for (MigrationStep migration : migrations) {
+      if (migration.getClass().getName().equals(className)) {
+        return migration;
+      }
+    }
+    throw new IllegalArgumentException("Database migration not found: " + className);
+  }
+
+  @VisibleForTesting
+  protected void createSchema(Connection connection, String dialectId) {
+    DdlUtils.createSchema(connection, dialectId);
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/db/migrations/PlatformDatabaseMigration.java b/server/sonar-server/src/main/java/org/sonar/server/platform/db/migrations/PlatformDatabaseMigration.java
new file mode 100644 (file)
index 0000000..b034d41
--- /dev/null
@@ -0,0 +1,160 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.platform.db.migrations;
+
+import java.util.Date;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.ReentrantLock;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.api.utils.log.Profiler;
+import org.sonar.db.version.DatabaseMigration;
+import org.sonar.server.platform.Platform;
+import org.sonar.server.ruby.RubyBridge;
+
+/**
+ * Handles concurrency to make sure only one DB migration can run at a time.
+ */
+public class PlatformDatabaseMigration implements DatabaseMigration {
+
+  private static final Logger LOGGER = Loggers.get(PlatformDatabaseMigration.class);
+
+  private final RubyBridge rubyBridge;
+  /**
+   * ExecutorService implements threads management.
+   */
+  private final PlatformDatabaseMigrationExecutorService executorService;
+  private final Platform platform;
+  /**
+   * This lock implements thread safety from concurrent calls of method {@link #startIt()}
+   */
+  private final ReentrantLock lock = new ReentrantLock();
+
+  /**
+   * This property acts as a semaphore to make sure at most one db migration task is created at a time.
+   * <p>
+   * It is set to {@code true} by the first thread to execute the {@link #startIt()} method and set to {@code false}
+   * by the thread executing the db migration.
+   * </p>
+   */
+  private final AtomicBoolean running = new AtomicBoolean(false);
+  private Status status = Status.NONE;
+  @Nullable
+  private Date startDate;
+  @Nullable
+  private Throwable failureError;
+
+  public PlatformDatabaseMigration(RubyBridge rubyBridge,
+    PlatformDatabaseMigrationExecutorService executorService, Platform platform) {
+    this.rubyBridge = rubyBridge;
+    this.executorService = executorService;
+    this.platform = platform;
+  }
+
+  @Override
+  public void startIt() {
+    if (lock.isLocked() || this.running.get()) {
+      LOGGER.trace("{}: lock is already taken or process is already running", Thread.currentThread().getName());
+      return;
+    }
+
+    if (lock.tryLock()) {
+      try {
+        startAsynchronousDBMigration();
+      } finally {
+        lock.unlock();
+      }
+    }
+  }
+
+  /**
+   * This method is not thread safe and must be externally protected from concurrent executions.
+   */
+  private void startAsynchronousDBMigration() {
+    if (this.running.get()) {
+      return;
+    }
+
+    running.set(true);
+    executorService.execute(this::doDatabaseMigration);
+  }
+
+  private void doDatabaseMigration() {
+    status = Status.RUNNING;
+    startDate = new Date();
+    failureError = null;
+    Profiler profiler = Profiler.create(LOGGER);
+    try {
+      profiler.startInfo("Starting DB Migration");
+      doUpgradeDb();
+      doRestartContainer();
+      doRecreateWebRoutes();
+      status = Status.SUCCEEDED;
+      profiler.stopInfo("DB Migration ended successfully");
+    } catch (Throwable t) {
+      profiler.stopInfo("DB migration failed");
+      LOGGER.error("DB Migration or container restart failed. Process ended with an exception", t);
+      status = Status.FAILED;
+      failureError = t;
+    } finally {
+      running.getAndSet(false);
+    }
+  }
+
+  private void doUpgradeDb() {
+    Profiler profiler = Profiler.createIfTrace(LOGGER);
+    profiler.startTrace("Starting DB Migration");
+    rubyBridge.databaseMigration().trigger();
+    profiler.stopTrace("DB Migration ended");
+  }
+
+  private void doRestartContainer() {
+    Profiler profiler = Profiler.createIfTrace(LOGGER);
+    profiler.startTrace("Restarting container");
+    platform.doStart();
+    profiler.stopTrace("Container restarted successfully");
+  }
+
+  private void doRecreateWebRoutes() {
+    Profiler profiler = Profiler.createIfTrace(LOGGER);
+    profiler.startTrace("Recreating web routes");
+    rubyBridge.railsRoutes().recreate();
+    profiler.startTrace("Routes recreated successfully");
+  }
+
+  @Override
+  @CheckForNull
+  public Date startedAt() {
+    return this.startDate;
+  }
+
+  @Override
+  public Status status() {
+    return this.status;
+  }
+
+  @Override
+  @CheckForNull
+  public Throwable failureError() {
+    return this.failureError;
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/db/migrations/PlatformDatabaseMigrationExecutorService.java b/server/sonar-server/src/main/java/org/sonar/server/platform/db/migrations/PlatformDatabaseMigrationExecutorService.java
new file mode 100644 (file)
index 0000000..3efc61e
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.platform.db.migrations;
+
+import java.util.concurrent.ExecutorService;
+
+/**
+ * Flag interface for the ExecutorService to be used by the {@link PlatformDatabaseMigration}
+ * component.
+ */
+public interface PlatformDatabaseMigrationExecutorService extends ExecutorService {
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/db/migrations/PlatformDatabaseMigrationExecutorServiceImpl.java b/server/sonar-server/src/main/java/org/sonar/server/platform/db/migrations/PlatformDatabaseMigrationExecutorServiceImpl.java
new file mode 100644 (file)
index 0000000..39d438d
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.platform.db.migrations;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import org.sonar.server.util.AbstractStoppableExecutorService;
+
+import java.util.concurrent.Executors;
+
+/**
+ * Since only one DB migration can run at a time, this implementation of PlatformDatabaseMigrationExecutorService
+ * wraps a single thread executor from the JDK.
+ */
+public class PlatformDatabaseMigrationExecutorServiceImpl
+  extends AbstractStoppableExecutorService
+  implements PlatformDatabaseMigrationExecutorService {
+
+  public PlatformDatabaseMigrationExecutorServiceImpl() {
+    super(
+      Executors.newSingleThreadExecutor(
+        new ThreadFactoryBuilder()
+          .setDaemon(false)
+          .setNameFormat("DB_migration-%d")
+          .build()
+        ));
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/db/migrations/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/platform/db/migrations/package-info.java
new file mode 100644 (file)
index 0000000..e7c8a6c
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.platform.db.migrations;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/db/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/platform/db/package-info.java
new file mode 100644 (file)
index 0000000..81862b4
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.platform.db;
+
+import javax.annotation.ParametersAreNonnullByDefault;
index 54bcf9d83e920c739ea65faf7404cf541e75a2b9..321feee91b7482f9a6d6150a9420dc212ac61a83 100644 (file)
@@ -40,7 +40,7 @@ import org.sonar.db.semaphore.SemaphoresImpl;
 import org.sonar.db.version.DatabaseVersion;
 import org.sonar.server.app.ProcessCommandWrapperImpl;
 import org.sonar.server.app.RestartFlagHolderImpl;
-import org.sonar.server.db.EmbeddedDatabaseFactory;
+import org.sonar.server.platform.db.EmbeddedDatabaseFactory;
 import org.sonar.server.issue.index.IssueIndex;
 import org.sonar.server.platform.DatabaseServerCompatibility;
 import org.sonar.server.platform.ServerFileSystemImpl;
index 4c1455e2f07920a7a0846adb326bdf14961bc886..e37fe5f20ee411bbdbd5833e3fa0850ace8859bf 100644 (file)
@@ -26,10 +26,10 @@ import org.sonar.core.platform.PluginClassloaderFactory;
 import org.sonar.core.platform.PluginLoader;
 import org.sonar.db.charset.DatabaseCharsetChecker;
 import org.sonar.db.version.MigrationStepModule;
-import org.sonar.server.db.CheckDatabaseCharsetAtStartup;
-import org.sonar.server.db.migrations.DatabaseMigrator;
-import org.sonar.server.db.migrations.PlatformDatabaseMigration;
-import org.sonar.server.db.migrations.PlatformDatabaseMigrationExecutorServiceImpl;
+import org.sonar.server.platform.db.CheckDatabaseCharsetAtStartup;
+import org.sonar.server.platform.db.migrations.DatabaseMigrator;
+import org.sonar.server.platform.db.migrations.PlatformDatabaseMigration;
+import org.sonar.server.platform.db.migrations.PlatformDatabaseMigrationExecutorServiceImpl;
 import org.sonar.server.platform.DefaultServerUpgradeStatus;
 import org.sonar.server.platform.web.RailsAppsDeployer;
 import org.sonar.server.plugins.InstalledPluginReferentialFactory;
index 7ba5aec89e1851935e1995778af4c35cba2db3d3..c072bab99f2686318179a3a5e6e7e96043bea733 100644 (file)
@@ -20,7 +20,7 @@
 package org.sonar.server.platform.platformlevel;
 
 import org.sonar.server.app.ProcessCommandWrapper;
-import org.sonar.server.db.CheckDatabaseCollationDuringMigration;
+import org.sonar.server.platform.db.CheckDatabaseCollationDuringMigration;
 import org.sonar.server.es.IndexerStartupTask;
 import org.sonar.server.issue.filter.RegisterIssueFilters;
 import org.sonar.server.platform.ServerLifecycleNotifier;
index 8760f480ac98d3053169def181f787b5390fa3a6..9cb9deb58c64f31bd84e81bd29ed0e1a90c625ad 100644 (file)
@@ -53,7 +53,7 @@ import org.sonar.db.version.DatabaseVersion;
 import org.sonar.process.ProcessProperties;
 import org.sonar.server.authentication.IdentityProviderRepository;
 import org.sonar.server.component.ComponentCleanerService;
-import org.sonar.server.db.migrations.DatabaseMigrator;
+import org.sonar.server.platform.db.migrations.DatabaseMigrator;
 import org.sonar.server.measure.MeasureFilterEngine;
 import org.sonar.server.measure.MeasureFilterResult;
 import org.sonar.server.platform.Platform;
diff --git a/server/sonar-server/src/test/java/org/sonar/server/db/CheckDatabaseCharsetAtStartupTest.java b/server/sonar-server/src/test/java/org/sonar/server/db/CheckDatabaseCharsetAtStartupTest.java
deleted file mode 100644 (file)
index 16d7ec3..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.server.db;
-
-import org.junit.After;
-import org.junit.Test;
-import org.sonar.api.platform.ServerUpgradeStatus;
-import org.sonar.db.charset.DatabaseCharsetChecker;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.ENFORCE_UTF8;
-
-public class CheckDatabaseCharsetAtStartupTest {
-
-  ServerUpgradeStatus upgradeStatus = mock(ServerUpgradeStatus.class);
-  DatabaseCharsetChecker charsetChecker = mock(DatabaseCharsetChecker.class);
-  CheckDatabaseCharsetAtStartup underTest = new CheckDatabaseCharsetAtStartup(upgradeStatus, charsetChecker);
-
-  @After
-  public void tearDown() {
-    underTest.stop();
-  }
-
-  @Test
-  public void enforce_utf8_if_fresh_install() {
-    when(upgradeStatus.isFreshInstall()).thenReturn(true);
-
-    underTest.start();
-
-    verify(charsetChecker).check(ENFORCE_UTF8);
-  }
-
-  @Test
-  public void do_not_enforce_utf8_and_do_not_repair_at_startup_if_not_fresh_install() {
-    when(upgradeStatus.isFreshInstall()).thenReturn(false);
-
-    underTest.start();
-
-    verify(charsetChecker).check();
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/db/CheckDatabaseCollationDuringMigrationTest.java b/server/sonar-server/src/test/java/org/sonar/server/db/CheckDatabaseCollationDuringMigrationTest.java
deleted file mode 100644 (file)
index fdb4ef2..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.server.db;
-
-import org.junit.After;
-import org.junit.Test;
-import org.sonar.api.platform.ServerUpgradeStatus;
-import org.sonar.db.charset.DatabaseCharsetChecker;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.AUTO_REPAIR_COLLATION;
-import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.ENFORCE_UTF8;
-
-public class CheckDatabaseCollationDuringMigrationTest {
-  ServerUpgradeStatus upgradeStatus = mock(ServerUpgradeStatus.class);
-  DatabaseCharsetChecker charsetChecker = mock(DatabaseCharsetChecker.class);
-  CheckDatabaseCollationDuringMigration underTest = new CheckDatabaseCollationDuringMigration(upgradeStatus, charsetChecker);
-
-  @After
-  public void tearDown() {
-    underTest.stop();
-  }
-
-  @Test
-  public void enforce_utf8_and_optionally_repair_collation_if_fresh_install() {
-    when(upgradeStatus.isFreshInstall()).thenReturn(true);
-
-    underTest.start();
-
-    verify(charsetChecker).check(ENFORCE_UTF8, AUTO_REPAIR_COLLATION);
-  }
-
-  @Test
-  public void repair_collation_but_do_not_enforce_utf8_if_db_upgrade() {
-    when(upgradeStatus.isFreshInstall()).thenReturn(false);
-    when(upgradeStatus.isUpgraded()).thenReturn(true);
-
-    underTest.start();
-
-    verify(charsetChecker).check(AUTO_REPAIR_COLLATION);
-  }
-
-  @Test
-  public void do_nothing_if_no_db_changes() {
-    when(upgradeStatus.isFreshInstall()).thenReturn(false);
-    when(upgradeStatus.isUpgraded()).thenReturn(false);
-
-    underTest.start();
-
-    verifyZeroInteractions(charsetChecker);
-  }
-
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/db/EmbeddedDatabaseFactoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/db/EmbeddedDatabaseFactoryTest.java
deleted file mode 100644 (file)
index 2a780dc..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.server.db;
-
-import org.junit.Test;
-import org.sonar.api.config.Settings;
-import org.sonar.api.database.DatabaseProperties;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-public class EmbeddedDatabaseFactoryTest {
-
-  Settings settings = new Settings();
-
-  @Test
-  public void should_start_and_stop_tcp_h2_database() {
-    settings.setProperty(DatabaseProperties.PROP_URL, "jdbc:h2:tcp:localhost");
-
-    EmbeddedDatabase embeddedDatabase = mock(EmbeddedDatabase.class);
-
-    EmbeddedDatabaseFactory databaseFactory = new EmbeddedDatabaseFactory(settings) {
-      @Override
-      EmbeddedDatabase createEmbeddedDatabase() {
-        return embeddedDatabase;
-      }
-    };
-    databaseFactory.start();
-    databaseFactory.stop();
-
-    verify(embeddedDatabase).start();
-    verify(embeddedDatabase).stop();
-  }
-
-  @Test
-  public void should_not_start_mem_h2_database() {
-    settings.setProperty(DatabaseProperties.PROP_URL, "jdbc:h2:mem");
-
-    EmbeddedDatabase embeddedDatabase = mock(EmbeddedDatabase.class);
-
-    EmbeddedDatabaseFactory databaseFactory = new EmbeddedDatabaseFactory(settings) {
-      @Override
-      EmbeddedDatabase createEmbeddedDatabase() {
-        return embeddedDatabase;
-      }
-    };
-    databaseFactory.start();
-
-    verify(embeddedDatabase, never()).start();
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/db/EmbeddedDatabaseTest.java b/server/sonar-server/src/test/java/org/sonar/server/db/EmbeddedDatabaseTest.java
deleted file mode 100644 (file)
index e88c715..0000000
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.server.db;
-
-import java.io.IOException;
-import java.sql.DriverManager;
-import org.h2.Driver;
-import org.junit.After;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.TemporaryFolder;
-import org.junit.rules.Timeout;
-import org.sonar.api.config.Settings;
-import org.sonar.api.utils.log.LogTester;
-import org.sonar.process.NetworkUtils;
-
-import static junit.framework.Assert.fail;
-import static org.sonar.api.database.DatabaseProperties.PROP_EMBEDDED_PORT;
-import static org.sonar.api.database.DatabaseProperties.PROP_PASSWORD;
-import static org.sonar.api.database.DatabaseProperties.PROP_PASSWORD_DEFAULT_VALUE;
-import static org.sonar.api.database.DatabaseProperties.PROP_URL;
-import static org.sonar.api.database.DatabaseProperties.PROP_USER;
-import static org.sonar.api.database.DatabaseProperties.PROP_USER_DEFAULT_VALUE;
-import static org.sonar.process.ProcessProperties.PATH_DATA;
-
-public class EmbeddedDatabaseTest {
-
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-  @Rule
-  public LogTester logTester = new LogTester();
-  @Rule
-  public TemporaryFolder temporaryFolder = new TemporaryFolder();
-  @Rule
-  public Timeout timeout = Timeout.seconds(10);
-
-  private EmbeddedDatabase underTest;
-
-  @After
-  public void tearDown() throws Exception {
-    if (underTest != null) {
-      underTest.stop();
-    }
-  }
-
-  @Test
-  public void start_fails_with_IAE_if_property_Data_Path_is_not_set() {
-    EmbeddedDatabase underTest = new EmbeddedDatabase(new Settings());
-
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Missing property " + PATH_DATA);
-
-    underTest.start();
-  }
-
-  @Test
-  public void start_fails_with_IAE_if_property_Data_Path_is_empty() {
-    EmbeddedDatabase underTest = new EmbeddedDatabase(new Settings()
-      .setProperty(PATH_DATA, ""));
-
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Missing property " + PATH_DATA);
-
-    underTest.start();
-  }
-
-  @Test
-  public void start_fails_with_IAE_if_JDBC_URL_settings_is_not_set() throws IOException {
-    EmbeddedDatabase underTest = new EmbeddedDatabase(new Settings()
-      .setProperty(PATH_DATA, temporaryFolder.newFolder().getAbsolutePath()));
-
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Missing property " + PROP_URL);
-
-    underTest.start();
-  }
-
-  @Test
-  public void start_fails_with_IAE_if_embedded_port_settings_is_not_set() throws IOException {
-    EmbeddedDatabase underTest = new EmbeddedDatabase(new Settings()
-      .setProperty(PATH_DATA, temporaryFolder.newFolder().getAbsolutePath())
-      .setProperty(PROP_URL, "jdbc url"));
-
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Missing property " + PROP_EMBEDDED_PORT);
-
-    underTest.start();
-  }
-
-  @Test
-  public void start_ignores_URL_to_create_database_and_uses_default_username_and_password_when_then_are_not_set() throws IOException {
-    int port = NetworkUtils.freePort();
-    underTest = new EmbeddedDatabase(new Settings()
-      .setProperty(PATH_DATA, temporaryFolder.newFolder().getAbsolutePath())
-      .setProperty(PROP_URL, "jdbc url")
-      .setProperty(PROP_EMBEDDED_PORT, "" + port));
-
-    underTest.start();
-
-    checkDbIsUp(port, PROP_USER_DEFAULT_VALUE, PROP_PASSWORD_DEFAULT_VALUE);
-  }
-
-  @Test
-  public void start_creates_db_with_specified_user_and_password() throws IOException {
-    int port = NetworkUtils.freePort();
-    underTest = new EmbeddedDatabase(new Settings()
-      .setProperty(PATH_DATA, temporaryFolder.newFolder().getAbsolutePath())
-      .setProperty(PROP_URL, "jdbc url")
-      .setProperty(PROP_EMBEDDED_PORT, "" + port)
-      .setProperty(PROP_USER, "foo")
-      .setProperty(PROP_PASSWORD, "bar"));
-
-    underTest.start();
-
-    checkDbIsUp(port, "foo", "bar");
-  }
-
-  @Test
-  public void start_supports_in_memory_H2_JDBC_URL() throws IOException {
-    int port = NetworkUtils.freePort();
-    underTest = new EmbeddedDatabase(new Settings()
-      .setProperty(PATH_DATA, temporaryFolder.newFolder().getAbsolutePath())
-      .setProperty(PROP_URL, "jdbc:h2:mem:sonar")
-      .setProperty(PROP_EMBEDDED_PORT, "" + port)
-      .setProperty(PROP_USER, "foo")
-      .setProperty(PROP_PASSWORD, "bar"));
-
-    underTest.start();
-
-    checkDbIsUp(port, "foo", "bar");
-  }
-
-  private void checkDbIsUp(int port, String user, String password) {
-    try {
-      String driverUrl = String.format("jdbc:h2:tcp://localhost:%d/sonar;USER=%s;PASSWORD=%s", port, user, password);
-      DriverManager.registerDriver(new Driver());
-      DriverManager.getConnection(driverUrl).close();
-    } catch (Exception ex) {
-      fail("Unable to connect after start");
-    }
-  }
-
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/db/migrations/DatabaseMigratorTest.java b/server/sonar-server/src/test/java/org/sonar/server/db/migrations/DatabaseMigratorTest.java
deleted file mode 100644 (file)
index 3082a08..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.server.db.migrations;
-
-import java.sql.Connection;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.mockito.Mockito;
-import org.sonar.api.platform.ServerUpgradeStatus;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.dialect.Dialect;
-import org.sonar.db.dialect.H2;
-import org.sonar.db.dialect.MySql;
-import org.sonar.db.version.MigrationStep;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class DatabaseMigratorTest {
-
-  @Rule
-  public ExpectedException thrown = ExpectedException.none();
-
-  DbClient dbClient = mock(DbClient.class, Mockito.RETURNS_DEEP_STUBS);
-  MigrationStep[] migrations = new MigrationStep[] {new FakeMigrationStep()};
-  ServerUpgradeStatus serverUpgradeStatus = mock(ServerUpgradeStatus.class);
-  DatabaseMigrator migrator;
-
-  @Before
-  public void setUp() {
-    migrator = new DatabaseMigrator(dbClient, migrations, serverUpgradeStatus, null);
-  }
-
-  @Test
-  public void should_support_only_creation_of_h2_database() {
-    when(dbClient.getDatabase().getDialect()).thenReturn(new MySql());
-
-    assertThat(migrator.createDatabase()).isFalse();
-    verify(dbClient, never()).openSession(anyBoolean());
-  }
-
-  @Test
-  public void fail_if_execute_unknown_migration() {
-    thrown.expect(IllegalArgumentException.class);
-    thrown.expectMessage("Database migration not found: org.xxx.UnknownMigration");
-
-    migrator.executeMigration("org.xxx.UnknownMigration");
-  }
-
-  @Test
-  public void execute_migration() {
-    assertThat(FakeMigrationStep.executed).isFalse();
-    migrator.executeMigration(FakeMigrationStep.class.getName());
-    assertThat(FakeMigrationStep.executed).isTrue();
-  }
-
-  @Test
-  public void should_create_schema_on_h2() {
-    Dialect supportedDialect = new H2();
-    when(dbClient.getDatabase().getDialect()).thenReturn(supportedDialect);
-    Connection connection = mock(Connection.class);
-    DbSession session = mock(DbSession.class);
-    when(session.getConnection()).thenReturn(connection);
-    when(dbClient.openSession(false)).thenReturn(session);
-    when(serverUpgradeStatus.isFreshInstall()).thenReturn(true);
-
-    DatabaseMigrator databaseMigrator = new DatabaseMigrator(dbClient, migrations, serverUpgradeStatus, null) {
-      @Override
-      protected void createSchema(Connection connection, String dialectId) {
-      }
-    };
-
-    assertThat(databaseMigrator.createDatabase()).isTrue();
-  }
-
-  public static class FakeMigrationStep implements MigrationStep {
-    static boolean executed = false;
-
-    @Override
-    public void execute() {
-      executed = true;
-    }
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/db/migrations/PlatformDatabaseMigrationAsynchronousTest.java b/server/sonar-server/src/test/java/org/sonar/server/db/migrations/PlatformDatabaseMigrationAsynchronousTest.java
deleted file mode 100644 (file)
index 4c5e407..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.server.db.migrations;
-
-import org.junit.Test;
-import org.sonar.server.platform.Platform;
-import org.sonar.server.ruby.RubyBridge;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-
-public class PlatformDatabaseMigrationAsynchronousTest {
-
-  private boolean taskSuppliedForAsyncProcess = false;
-  /**
-   * Implementation of execute wraps specified Runnable to add a delay of 200 ms before passing it
-   * to a SingleThread executor to execute asynchronously.
-   */
-  PlatformDatabaseMigrationExecutorService executorService = new PlatformDatabaseMigrationExecutorServiceAdaptor() {
-    @Override
-    public void execute(final Runnable command) {
-      taskSuppliedForAsyncProcess = true;
-    }
-  };
-  RubyBridge rubyBridge = mock(RubyBridge.class);
-  Platform platform = mock(Platform.class);
-  PlatformDatabaseMigration underTest = new PlatformDatabaseMigration(rubyBridge, executorService, platform);
-
-  @Test
-  public void testName() throws Exception {
-    underTest.startIt();
-
-    assertThat(taskSuppliedForAsyncProcess).isTrue();
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/db/migrations/PlatformDatabaseMigrationConcurrentAccessTest.java b/server/sonar-server/src/test/java/org/sonar/server/db/migrations/PlatformDatabaseMigrationConcurrentAccessTest.java
deleted file mode 100644 (file)
index d11dfff..0000000
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.server.db.migrations;
-
-import com.google.common.base.Throwables;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import org.junit.After;
-import org.junit.Test;
-import org.sonar.server.platform.Platform;
-import org.sonar.server.ruby.RubyBridge;
-import org.sonar.server.ruby.RubyDatabaseMigration;
-import org.sonar.server.ruby.RubyRailsRoutes;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class PlatformDatabaseMigrationConcurrentAccessTest {
-
-  private ExecutorService pool = Executors.newFixedThreadPool(2);
-  /**
-   * Latch is used to make sure both testing threads try and call {@link PlatformDatabaseMigration#startIt()} at the
-   * same time
-   */
-  private CountDownLatch latch = new CountDownLatch(2);
-
-  /**
-   * Implementation of execute runs Runnable synchronously
-   */
-  private PlatformDatabaseMigrationExecutorService executorService = new PlatformDatabaseMigrationExecutorServiceAdaptor() {
-    @Override
-    public void execute(Runnable command) {
-      command.run();
-    }
-  };
-  /**
-   * thread-safe counter of calls to the trigger method of {@link #rubyDatabaseMigration}
-   */
-  private AtomicInteger triggerCount = new AtomicInteger();
-  /**
-   * Implementation of RubyDatabaseMigration which trigger method increments a thread-safe counter and add a delay of 200ms
-   */
-  RubyDatabaseMigration rubyDatabaseMigration = new RubyDatabaseMigration() {
-    @Override
-    public void trigger() {
-      triggerCount.incrementAndGet();
-      try {
-        Thread.currentThread().sleep(1000);
-      } catch (InterruptedException e) {
-        Throwables.propagate(e);
-      }
-    }
-  };
-  RubyBridge rubyBridge = mock(RubyBridge.class);
-  Platform platform = mock(Platform.class);
-  RubyRailsRoutes railsRoutes = mock(RubyRailsRoutes.class);
-  PlatformDatabaseMigration underTest = new PlatformDatabaseMigration(rubyBridge, executorService, platform);
-
-  @After
-  public void tearDown() {
-    pool.shutdownNow();
-  }
-
-  @Test
-  public void two_concurrent_calls_to_startit_call_trigger_only_once() throws Exception {
-    when(rubyBridge.databaseMigration()).thenReturn(rubyDatabaseMigration);
-    when(rubyBridge.railsRoutes()).thenReturn(railsRoutes);
-
-    pool.submit(new CallStartit());
-    pool.submit(new CallStartit());
-
-    pool.awaitTermination(2, TimeUnit.SECONDS);
-
-    assertThat(triggerCount.get()).isEqualTo(1);
-  }
-
-  private class CallStartit implements Runnable {
-    @Override
-    public void run() {
-      latch.countDown();
-      try {
-        latch.await();
-      } catch (InterruptedException e) {
-        // propagate interruption
-        Thread.currentThread().interrupt();
-      }
-      underTest.startIt();
-    }
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/db/migrations/PlatformDatabaseMigrationExecutorServiceAdaptor.java b/server/sonar-server/src/test/java/org/sonar/server/db/migrations/PlatformDatabaseMigrationExecutorServiceAdaptor.java
deleted file mode 100644 (file)
index df7f0e9..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.server.db.migrations;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Adaptor for the PlatformDatabaseMigrationExecutorService interface which implementation of methods all throw
- * UnsupportedOperationException.
- */
-class PlatformDatabaseMigrationExecutorServiceAdaptor implements PlatformDatabaseMigrationExecutorService {
-
-  @Override
-  public void execute(Runnable command) {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  public void shutdown() {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  public List<Runnable> shutdownNow() {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  public boolean isShutdown() {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  public boolean isTerminated() {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  public <T> Future<T> submit(Callable<T> task) {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  public <T> Future<T> submit(Runnable task, T result) {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  public Future<?> submit(Runnable task) {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
-    throw new UnsupportedOperationException();
-  }
-
-  @Override
-  public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
-    throw new UnsupportedOperationException();
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/db/migrations/PlatformDatabaseMigrationTest.java b/server/sonar-server/src/test/java/org/sonar/server/db/migrations/PlatformDatabaseMigrationTest.java
deleted file mode 100644 (file)
index c4251d4..0000000
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program 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.
- *
- * This program 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.server.db.migrations;
-
-import java.util.Date;
-import org.junit.Test;
-import org.mockito.InOrder;
-import org.sonar.db.version.DatabaseMigration;
-import org.sonar.server.platform.Platform;
-import org.sonar.server.ruby.RubyBridge;
-import org.sonar.server.ruby.RubyDatabaseMigration;
-import org.sonar.server.ruby.RubyRailsRoutes;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-/**
- * Unit test for PlatformDatabaseMigration which does not test any of its concurrency management and asynchronous execution code.
- */
-public class PlatformDatabaseMigrationTest {
-  private static final Throwable AN_ERROR = new RuntimeException("runtime exception created on purpose");
-
-  /**
-   * Implementation of execute runs Runnable synchronously.
-   */
-  PlatformDatabaseMigrationExecutorService executorService = new PlatformDatabaseMigrationExecutorServiceAdaptor() {
-    @Override
-    public void execute(Runnable command) {
-      command.run();
-    }
-  };
-  RubyBridge rubyBridge = mock(RubyBridge.class);
-  RubyDatabaseMigration rubyDatabaseMigration = mock(RubyDatabaseMigration.class);
-  RubyRailsRoutes rubyRailsRoutes = mock(RubyRailsRoutes.class);
-  Platform platform = mock(Platform.class);
-  InOrder inOrder = inOrder(rubyDatabaseMigration, rubyBridge, rubyRailsRoutes, platform);
-
-  PlatformDatabaseMigration underTest = new PlatformDatabaseMigration(rubyBridge, executorService, platform);
-
-  @Test
-  public void status_is_NONE_when_component_is_created() {
-    assertThat(underTest.status()).isEqualTo(DatabaseMigration.Status.NONE);
-  }
-
-  @Test
-  public void startedAt_is_null_when_component_is_created() {
-    assertThat(underTest.startedAt()).isNull();
-  }
-
-  @Test
-  public void failureError_is_null_when_component_is_created() {
-    assertThat(underTest.failureError()).isNull();
-  }
-
-  @Test
-  public void startit_calls_databasemigration_trigger_in_a_separate_thread() {
-    when(rubyBridge.databaseMigration()).thenReturn(rubyDatabaseMigration);
-    when(rubyBridge.railsRoutes()).thenReturn(rubyRailsRoutes);
-
-    underTest.startIt();
-
-    inOrder.verify(rubyBridge).databaseMigration();
-    inOrder.verify(rubyDatabaseMigration).trigger();
-    inOrder.verify(platform).doStart();
-    inOrder.verify(rubyBridge).railsRoutes();
-    inOrder.verify(rubyRailsRoutes).recreate();
-    inOrder.verifyNoMoreInteractions();
-  }
-
-  @Test
-  public void status_is_SUCCEEDED_and_failure_is_null_when_trigger_runs_without_an_exception() {
-    when(rubyBridge.databaseMigration()).thenReturn(rubyDatabaseMigration);
-    when(rubyBridge.railsRoutes()).thenReturn(rubyRailsRoutes);
-
-    underTest.startIt();
-
-    assertThat(underTest.status()).isEqualTo(DatabaseMigration.Status.SUCCEEDED);
-    assertThat(underTest.failureError()).isNull();
-    assertThat(underTest.startedAt()).isNotNull();
-  }
-
-  @Test
-  public void status_is_FAILED_and_failure_stores_the_exception_when_trigger_throws_an_exception() {
-    mockTriggerThrowsError();
-
-    underTest.startIt();
-
-    assertThat(underTest.status()).isEqualTo(DatabaseMigration.Status.FAILED);
-    assertThat(underTest.failureError()).isSameAs(AN_ERROR);
-    assertThat(underTest.startedAt()).isNotNull();
-  }
-
-  @Test
-  public void successive_calls_to_startIt_reset_status_startedAt_and_failureError() {
-    mockTriggerThrowsError();
-
-    underTest.startIt();
-
-    assertThat(underTest.status()).isEqualTo(DatabaseMigration.Status.FAILED);
-    assertThat(underTest.failureError()).isSameAs(AN_ERROR);
-    Date firstStartDate = underTest.startedAt();
-    assertThat(firstStartDate).isNotNull();
-
-    mockTriggerDoesNothing();
-
-    underTest.startIt();
-
-    assertThat(underTest.status()).isEqualTo(DatabaseMigration.Status.SUCCEEDED);
-    assertThat(underTest.failureError()).isNull();
-    assertThat(underTest.startedAt()).isNotSameAs(firstStartDate);
-  }
-
-  private void mockTriggerThrowsError() {
-    when(rubyBridge.databaseMigration()).thenReturn(rubyDatabaseMigration);
-    doThrow(AN_ERROR).when(rubyDatabaseMigration).trigger();
-    when(rubyBridge.railsRoutes()).thenReturn(rubyRailsRoutes);
-  }
-
-  private void mockTriggerDoesNothing() {
-    when(rubyBridge.databaseMigration()).thenReturn(rubyDatabaseMigration);
-    doNothing().when(rubyDatabaseMigration).trigger();
-    when(rubyBridge.railsRoutes()).thenReturn(rubyRailsRoutes);
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/db/CheckDatabaseCharsetAtStartupTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/db/CheckDatabaseCharsetAtStartupTest.java
new file mode 100644 (file)
index 0000000..e3711be
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.platform.db;
+
+import org.junit.After;
+import org.junit.Test;
+import org.sonar.api.platform.ServerUpgradeStatus;
+import org.sonar.db.charset.DatabaseCharsetChecker;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.ENFORCE_UTF8;
+
+public class CheckDatabaseCharsetAtStartupTest {
+
+  ServerUpgradeStatus upgradeStatus = mock(ServerUpgradeStatus.class);
+  DatabaseCharsetChecker charsetChecker = mock(DatabaseCharsetChecker.class);
+  CheckDatabaseCharsetAtStartup underTest = new CheckDatabaseCharsetAtStartup(upgradeStatus, charsetChecker);
+
+  @After
+  public void tearDown() {
+    underTest.stop();
+  }
+
+  @Test
+  public void enforce_utf8_if_fresh_install() {
+    when(upgradeStatus.isFreshInstall()).thenReturn(true);
+
+    underTest.start();
+
+    verify(charsetChecker).check(ENFORCE_UTF8);
+  }
+
+  @Test
+  public void do_not_enforce_utf8_and_do_not_repair_at_startup_if_not_fresh_install() {
+    when(upgradeStatus.isFreshInstall()).thenReturn(false);
+
+    underTest.start();
+
+    verify(charsetChecker).check();
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/db/CheckDatabaseCollationDuringMigrationTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/db/CheckDatabaseCollationDuringMigrationTest.java
new file mode 100644 (file)
index 0000000..0437504
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.platform.db;
+
+import org.junit.After;
+import org.junit.Test;
+import org.sonar.api.platform.ServerUpgradeStatus;
+import org.sonar.db.charset.DatabaseCharsetChecker;
+import org.sonar.server.platform.db.CheckDatabaseCollationDuringMigration;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.AUTO_REPAIR_COLLATION;
+import static org.sonar.db.charset.DatabaseCharsetChecker.Flag.ENFORCE_UTF8;
+
+public class CheckDatabaseCollationDuringMigrationTest {
+  ServerUpgradeStatus upgradeStatus = mock(ServerUpgradeStatus.class);
+  DatabaseCharsetChecker charsetChecker = mock(DatabaseCharsetChecker.class);
+  CheckDatabaseCollationDuringMigration underTest = new CheckDatabaseCollationDuringMigration(upgradeStatus, charsetChecker);
+
+  @After
+  public void tearDown() {
+    underTest.stop();
+  }
+
+  @Test
+  public void enforce_utf8_and_optionally_repair_collation_if_fresh_install() {
+    when(upgradeStatus.isFreshInstall()).thenReturn(true);
+
+    underTest.start();
+
+    verify(charsetChecker).check(ENFORCE_UTF8, AUTO_REPAIR_COLLATION);
+  }
+
+  @Test
+  public void repair_collation_but_do_not_enforce_utf8_if_db_upgrade() {
+    when(upgradeStatus.isFreshInstall()).thenReturn(false);
+    when(upgradeStatus.isUpgraded()).thenReturn(true);
+
+    underTest.start();
+
+    verify(charsetChecker).check(AUTO_REPAIR_COLLATION);
+  }
+
+  @Test
+  public void do_nothing_if_no_db_changes() {
+    when(upgradeStatus.isFreshInstall()).thenReturn(false);
+    when(upgradeStatus.isUpgraded()).thenReturn(false);
+
+    underTest.start();
+
+    verifyZeroInteractions(charsetChecker);
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/db/EmbeddedDatabaseFactoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/db/EmbeddedDatabaseFactoryTest.java
new file mode 100644 (file)
index 0000000..7c7ec08
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.platform.db;
+
+import org.junit.Test;
+import org.sonar.api.config.Settings;
+import org.sonar.api.database.DatabaseProperties;
+import org.sonar.server.platform.db.EmbeddedDatabase;
+import org.sonar.server.platform.db.EmbeddedDatabaseFactory;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+public class EmbeddedDatabaseFactoryTest {
+
+  Settings settings = new Settings();
+
+  @Test
+  public void should_start_and_stop_tcp_h2_database() {
+    settings.setProperty(DatabaseProperties.PROP_URL, "jdbc:h2:tcp:localhost");
+
+    EmbeddedDatabase embeddedDatabase = mock(EmbeddedDatabase.class);
+
+    EmbeddedDatabaseFactory databaseFactory = new EmbeddedDatabaseFactory(settings) {
+      @Override
+      EmbeddedDatabase createEmbeddedDatabase() {
+        return embeddedDatabase;
+      }
+    };
+    databaseFactory.start();
+    databaseFactory.stop();
+
+    verify(embeddedDatabase).start();
+    verify(embeddedDatabase).stop();
+  }
+
+  @Test
+  public void should_not_start_mem_h2_database() {
+    settings.setProperty(DatabaseProperties.PROP_URL, "jdbc:h2:mem");
+
+    EmbeddedDatabase embeddedDatabase = mock(EmbeddedDatabase.class);
+
+    EmbeddedDatabaseFactory databaseFactory = new EmbeddedDatabaseFactory(settings) {
+      @Override
+      EmbeddedDatabase createEmbeddedDatabase() {
+        return embeddedDatabase;
+      }
+    };
+    databaseFactory.start();
+
+    verify(embeddedDatabase, never()).start();
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/db/EmbeddedDatabaseTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/db/EmbeddedDatabaseTest.java
new file mode 100644 (file)
index 0000000..fb17b9e
--- /dev/null
@@ -0,0 +1,162 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.platform.db;
+
+import java.io.IOException;
+import java.sql.DriverManager;
+import org.h2.Driver;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.junit.rules.Timeout;
+import org.sonar.api.config.Settings;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.process.NetworkUtils;
+import org.sonar.server.platform.db.EmbeddedDatabase;
+
+import static junit.framework.Assert.fail;
+import static org.sonar.api.database.DatabaseProperties.PROP_EMBEDDED_PORT;
+import static org.sonar.api.database.DatabaseProperties.PROP_PASSWORD;
+import static org.sonar.api.database.DatabaseProperties.PROP_PASSWORD_DEFAULT_VALUE;
+import static org.sonar.api.database.DatabaseProperties.PROP_URL;
+import static org.sonar.api.database.DatabaseProperties.PROP_USER;
+import static org.sonar.api.database.DatabaseProperties.PROP_USER_DEFAULT_VALUE;
+import static org.sonar.process.ProcessProperties.PATH_DATA;
+
+public class EmbeddedDatabaseTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+  @Rule
+  public LogTester logTester = new LogTester();
+  @Rule
+  public TemporaryFolder temporaryFolder = new TemporaryFolder();
+  @Rule
+  public Timeout timeout = Timeout.seconds(10);
+
+  private EmbeddedDatabase underTest;
+
+  @After
+  public void tearDown() throws Exception {
+    if (underTest != null) {
+      underTest.stop();
+    }
+  }
+
+  @Test
+  public void start_fails_with_IAE_if_property_Data_Path_is_not_set() {
+    EmbeddedDatabase underTest = new EmbeddedDatabase(new Settings());
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Missing property " + PATH_DATA);
+
+    underTest.start();
+  }
+
+  @Test
+  public void start_fails_with_IAE_if_property_Data_Path_is_empty() {
+    EmbeddedDatabase underTest = new EmbeddedDatabase(new Settings()
+      .setProperty(PATH_DATA, ""));
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Missing property " + PATH_DATA);
+
+    underTest.start();
+  }
+
+  @Test
+  public void start_fails_with_IAE_if_JDBC_URL_settings_is_not_set() throws IOException {
+    EmbeddedDatabase underTest = new EmbeddedDatabase(new Settings()
+      .setProperty(PATH_DATA, temporaryFolder.newFolder().getAbsolutePath()));
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Missing property " + PROP_URL);
+
+    underTest.start();
+  }
+
+  @Test
+  public void start_fails_with_IAE_if_embedded_port_settings_is_not_set() throws IOException {
+    EmbeddedDatabase underTest = new EmbeddedDatabase(new Settings()
+      .setProperty(PATH_DATA, temporaryFolder.newFolder().getAbsolutePath())
+      .setProperty(PROP_URL, "jdbc url"));
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Missing property " + PROP_EMBEDDED_PORT);
+
+    underTest.start();
+  }
+
+  @Test
+  public void start_ignores_URL_to_create_database_and_uses_default_username_and_password_when_then_are_not_set() throws IOException {
+    int port = NetworkUtils.freePort();
+    underTest = new EmbeddedDatabase(new Settings()
+      .setProperty(PATH_DATA, temporaryFolder.newFolder().getAbsolutePath())
+      .setProperty(PROP_URL, "jdbc url")
+      .setProperty(PROP_EMBEDDED_PORT, "" + port));
+
+    underTest.start();
+
+    checkDbIsUp(port, PROP_USER_DEFAULT_VALUE, PROP_PASSWORD_DEFAULT_VALUE);
+  }
+
+  @Test
+  public void start_creates_db_with_specified_user_and_password() throws IOException {
+    int port = NetworkUtils.freePort();
+    underTest = new EmbeddedDatabase(new Settings()
+      .setProperty(PATH_DATA, temporaryFolder.newFolder().getAbsolutePath())
+      .setProperty(PROP_URL, "jdbc url")
+      .setProperty(PROP_EMBEDDED_PORT, "" + port)
+      .setProperty(PROP_USER, "foo")
+      .setProperty(PROP_PASSWORD, "bar"));
+
+    underTest.start();
+
+    checkDbIsUp(port, "foo", "bar");
+  }
+
+  @Test
+  public void start_supports_in_memory_H2_JDBC_URL() throws IOException {
+    int port = NetworkUtils.freePort();
+    underTest = new EmbeddedDatabase(new Settings()
+      .setProperty(PATH_DATA, temporaryFolder.newFolder().getAbsolutePath())
+      .setProperty(PROP_URL, "jdbc:h2:mem:sonar")
+      .setProperty(PROP_EMBEDDED_PORT, "" + port)
+      .setProperty(PROP_USER, "foo")
+      .setProperty(PROP_PASSWORD, "bar"));
+
+    underTest.start();
+
+    checkDbIsUp(port, "foo", "bar");
+  }
+
+  private void checkDbIsUp(int port, String user, String password) {
+    try {
+      String driverUrl = String.format("jdbc:h2:tcp://localhost:%d/sonar;USER=%s;PASSWORD=%s", port, user, password);
+      DriverManager.registerDriver(new Driver());
+      DriverManager.getConnection(driverUrl).close();
+    } catch (Exception ex) {
+      fail("Unable to connect after start");
+    }
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/db/migrations/DatabaseMigratorTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/db/migrations/DatabaseMigratorTest.java
new file mode 100644 (file)
index 0000000..6fca4c3
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.platform.db.migrations;
+
+import java.sql.Connection;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.Mockito;
+import org.sonar.api.platform.ServerUpgradeStatus;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.dialect.Dialect;
+import org.sonar.db.dialect.H2;
+import org.sonar.db.dialect.MySql;
+import org.sonar.db.version.MigrationStep;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class DatabaseMigratorTest {
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  DbClient dbClient = mock(DbClient.class, Mockito.RETURNS_DEEP_STUBS);
+  MigrationStep[] migrations = new MigrationStep[] {new FakeMigrationStep()};
+  ServerUpgradeStatus serverUpgradeStatus = mock(ServerUpgradeStatus.class);
+  DatabaseMigrator migrator;
+
+  @Before
+  public void setUp() {
+    migrator = new DatabaseMigrator(dbClient, migrations, serverUpgradeStatus, null);
+  }
+
+  @Test
+  public void should_support_only_creation_of_h2_database() {
+    when(dbClient.getDatabase().getDialect()).thenReturn(new MySql());
+
+    assertThat(migrator.createDatabase()).isFalse();
+    verify(dbClient, never()).openSession(anyBoolean());
+  }
+
+  @Test
+  public void fail_if_execute_unknown_migration() {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("Database migration not found: org.xxx.UnknownMigration");
+
+    migrator.executeMigration("org.xxx.UnknownMigration");
+  }
+
+  @Test
+  public void execute_migration() {
+    assertThat(FakeMigrationStep.executed).isFalse();
+    migrator.executeMigration(FakeMigrationStep.class.getName());
+    assertThat(FakeMigrationStep.executed).isTrue();
+  }
+
+  @Test
+  public void should_create_schema_on_h2() {
+    Dialect supportedDialect = new H2();
+    when(dbClient.getDatabase().getDialect()).thenReturn(supportedDialect);
+    Connection connection = mock(Connection.class);
+    DbSession session = mock(DbSession.class);
+    when(session.getConnection()).thenReturn(connection);
+    when(dbClient.openSession(false)).thenReturn(session);
+    when(serverUpgradeStatus.isFreshInstall()).thenReturn(true);
+
+    DatabaseMigrator databaseMigrator = new DatabaseMigrator(dbClient, migrations, serverUpgradeStatus, null) {
+      @Override
+      protected void createSchema(Connection connection, String dialectId) {
+      }
+    };
+
+    assertThat(databaseMigrator.createDatabase()).isTrue();
+  }
+
+  public static class FakeMigrationStep implements MigrationStep {
+    static boolean executed = false;
+
+    @Override
+    public void execute() {
+      executed = true;
+    }
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/db/migrations/PlatformDatabaseMigrationAsynchronousTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/db/migrations/PlatformDatabaseMigrationAsynchronousTest.java
new file mode 100644 (file)
index 0000000..aa612c1
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.platform.db.migrations;
+
+import org.junit.Test;
+import org.sonar.server.platform.Platform;
+import org.sonar.server.ruby.RubyBridge;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class PlatformDatabaseMigrationAsynchronousTest {
+
+  private boolean taskSuppliedForAsyncProcess = false;
+  /**
+   * Implementation of execute wraps specified Runnable to add a delay of 200 ms before passing it
+   * to a SingleThread executor to execute asynchronously.
+   */
+  PlatformDatabaseMigrationExecutorService executorService = new PlatformDatabaseMigrationExecutorServiceAdaptor() {
+    @Override
+    public void execute(final Runnable command) {
+      taskSuppliedForAsyncProcess = true;
+    }
+  };
+  RubyBridge rubyBridge = mock(RubyBridge.class);
+  Platform platform = mock(Platform.class);
+  PlatformDatabaseMigration underTest = new PlatformDatabaseMigration(rubyBridge, executorService, platform);
+
+  @Test
+  public void testName() throws Exception {
+    underTest.startIt();
+
+    assertThat(taskSuppliedForAsyncProcess).isTrue();
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/db/migrations/PlatformDatabaseMigrationConcurrentAccessTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/db/migrations/PlatformDatabaseMigrationConcurrentAccessTest.java
new file mode 100644 (file)
index 0000000..bd46874
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.platform.db.migrations;
+
+import com.google.common.base.Throwables;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.After;
+import org.junit.Test;
+import org.sonar.server.platform.Platform;
+import org.sonar.server.ruby.RubyBridge;
+import org.sonar.server.ruby.RubyDatabaseMigration;
+import org.sonar.server.ruby.RubyRailsRoutes;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class PlatformDatabaseMigrationConcurrentAccessTest {
+
+  private ExecutorService pool = Executors.newFixedThreadPool(2);
+  /**
+   * Latch is used to make sure both testing threads try and call {@link PlatformDatabaseMigration#startIt()} at the
+   * same time
+   */
+  private CountDownLatch latch = new CountDownLatch(2);
+
+  /**
+   * Implementation of execute runs Runnable synchronously
+   */
+  private PlatformDatabaseMigrationExecutorService executorService = new PlatformDatabaseMigrationExecutorServiceAdaptor() {
+    @Override
+    public void execute(Runnable command) {
+      command.run();
+    }
+  };
+  /**
+   * thread-safe counter of calls to the trigger method of {@link #rubyDatabaseMigration}
+   */
+  private AtomicInteger triggerCount = new AtomicInteger();
+  /**
+   * Implementation of RubyDatabaseMigration which trigger method increments a thread-safe counter and add a delay of 200ms
+   */
+  RubyDatabaseMigration rubyDatabaseMigration = new RubyDatabaseMigration() {
+    @Override
+    public void trigger() {
+      triggerCount.incrementAndGet();
+      try {
+        Thread.currentThread().sleep(1000);
+      } catch (InterruptedException e) {
+        Throwables.propagate(e);
+      }
+    }
+  };
+  RubyBridge rubyBridge = mock(RubyBridge.class);
+  Platform platform = mock(Platform.class);
+  RubyRailsRoutes railsRoutes = mock(RubyRailsRoutes.class);
+  PlatformDatabaseMigration underTest = new PlatformDatabaseMigration(rubyBridge, executorService, platform);
+
+  @After
+  public void tearDown() {
+    pool.shutdownNow();
+  }
+
+  @Test
+  public void two_concurrent_calls_to_startit_call_trigger_only_once() throws Exception {
+    when(rubyBridge.databaseMigration()).thenReturn(rubyDatabaseMigration);
+    when(rubyBridge.railsRoutes()).thenReturn(railsRoutes);
+
+    pool.submit(new CallStartit());
+    pool.submit(new CallStartit());
+
+    pool.awaitTermination(2, TimeUnit.SECONDS);
+
+    assertThat(triggerCount.get()).isEqualTo(1);
+  }
+
+  private class CallStartit implements Runnable {
+    @Override
+    public void run() {
+      latch.countDown();
+      try {
+        latch.await();
+      } catch (InterruptedException e) {
+        // propagate interruption
+        Thread.currentThread().interrupt();
+      }
+      underTest.startIt();
+    }
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/db/migrations/PlatformDatabaseMigrationExecutorServiceAdaptor.java b/server/sonar-server/src/test/java/org/sonar/server/platform/db/migrations/PlatformDatabaseMigrationExecutorServiceAdaptor.java
new file mode 100644 (file)
index 0000000..2e05ba0
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.platform.db.migrations;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Adaptor for the PlatformDatabaseMigrationExecutorService interface which implementation of methods all throw
+ * UnsupportedOperationException.
+ */
+class PlatformDatabaseMigrationExecutorServiceAdaptor implements PlatformDatabaseMigrationExecutorService {
+
+  @Override
+  public void execute(Runnable command) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public void shutdown() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public List<Runnable> shutdownNow() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean isShutdown() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean isTerminated() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public <T> Future<T> submit(Callable<T> task) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public <T> Future<T> submit(Runnable task, T result) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Future<?> submit(Runnable task) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
+    throw new UnsupportedOperationException();
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/db/migrations/PlatformDatabaseMigrationTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/db/migrations/PlatformDatabaseMigrationTest.java
new file mode 100644 (file)
index 0000000..21c6b91
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.server.platform.db.migrations;
+
+import java.util.Date;
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.sonar.db.version.DatabaseMigration;
+import org.sonar.server.platform.Platform;
+import org.sonar.server.ruby.RubyBridge;
+import org.sonar.server.ruby.RubyDatabaseMigration;
+import org.sonar.server.ruby.RubyRailsRoutes;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Unit test for PlatformDatabaseMigration which does not test any of its concurrency management and asynchronous execution code.
+ */
+public class PlatformDatabaseMigrationTest {
+  private static final Throwable AN_ERROR = new RuntimeException("runtime exception created on purpose");
+
+  /**
+   * Implementation of execute runs Runnable synchronously.
+   */
+  PlatformDatabaseMigrationExecutorService executorService = new PlatformDatabaseMigrationExecutorServiceAdaptor() {
+    @Override
+    public void execute(Runnable command) {
+      command.run();
+    }
+  };
+  RubyBridge rubyBridge = mock(RubyBridge.class);
+  RubyDatabaseMigration rubyDatabaseMigration = mock(RubyDatabaseMigration.class);
+  RubyRailsRoutes rubyRailsRoutes = mock(RubyRailsRoutes.class);
+  Platform platform = mock(Platform.class);
+  InOrder inOrder = inOrder(rubyDatabaseMigration, rubyBridge, rubyRailsRoutes, platform);
+
+  PlatformDatabaseMigration underTest = new PlatformDatabaseMigration(rubyBridge, executorService, platform);
+
+  @Test
+  public void status_is_NONE_when_component_is_created() {
+    assertThat(underTest.status()).isEqualTo(DatabaseMigration.Status.NONE);
+  }
+
+  @Test
+  public void startedAt_is_null_when_component_is_created() {
+    assertThat(underTest.startedAt()).isNull();
+  }
+
+  @Test
+  public void failureError_is_null_when_component_is_created() {
+    assertThat(underTest.failureError()).isNull();
+  }
+
+  @Test
+  public void startit_calls_databasemigration_trigger_in_a_separate_thread() {
+    when(rubyBridge.databaseMigration()).thenReturn(rubyDatabaseMigration);
+    when(rubyBridge.railsRoutes()).thenReturn(rubyRailsRoutes);
+
+    underTest.startIt();
+
+    inOrder.verify(rubyBridge).databaseMigration();
+    inOrder.verify(rubyDatabaseMigration).trigger();
+    inOrder.verify(platform).doStart();
+    inOrder.verify(rubyBridge).railsRoutes();
+    inOrder.verify(rubyRailsRoutes).recreate();
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void status_is_SUCCEEDED_and_failure_is_null_when_trigger_runs_without_an_exception() {
+    when(rubyBridge.databaseMigration()).thenReturn(rubyDatabaseMigration);
+    when(rubyBridge.railsRoutes()).thenReturn(rubyRailsRoutes);
+
+    underTest.startIt();
+
+    assertThat(underTest.status()).isEqualTo(DatabaseMigration.Status.SUCCEEDED);
+    assertThat(underTest.failureError()).isNull();
+    assertThat(underTest.startedAt()).isNotNull();
+  }
+
+  @Test
+  public void status_is_FAILED_and_failure_stores_the_exception_when_trigger_throws_an_exception() {
+    mockTriggerThrowsError();
+
+    underTest.startIt();
+
+    assertThat(underTest.status()).isEqualTo(DatabaseMigration.Status.FAILED);
+    assertThat(underTest.failureError()).isSameAs(AN_ERROR);
+    assertThat(underTest.startedAt()).isNotNull();
+  }
+
+  @Test
+  public void successive_calls_to_startIt_reset_status_startedAt_and_failureError() {
+    mockTriggerThrowsError();
+
+    underTest.startIt();
+
+    assertThat(underTest.status()).isEqualTo(DatabaseMigration.Status.FAILED);
+    assertThat(underTest.failureError()).isSameAs(AN_ERROR);
+    Date firstStartDate = underTest.startedAt();
+    assertThat(firstStartDate).isNotNull();
+
+    mockTriggerDoesNothing();
+
+    underTest.startIt();
+
+    assertThat(underTest.status()).isEqualTo(DatabaseMigration.Status.SUCCEEDED);
+    assertThat(underTest.failureError()).isNull();
+    assertThat(underTest.startedAt()).isNotSameAs(firstStartDate);
+  }
+
+  private void mockTriggerThrowsError() {
+    when(rubyBridge.databaseMigration()).thenReturn(rubyDatabaseMigration);
+    doThrow(AN_ERROR).when(rubyDatabaseMigration).trigger();
+    when(rubyBridge.railsRoutes()).thenReturn(rubyRailsRoutes);
+  }
+
+  private void mockTriggerDoesNothing() {
+    when(rubyBridge.databaseMigration()).thenReturn(rubyDatabaseMigration);
+    doNothing().when(rubyDatabaseMigration).trigger();
+    when(rubyBridge.railsRoutes()).thenReturn(rubyRailsRoutes);
+  }
+}