+++ /dev/null
-/*
- * 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();
- }
- }
-}
+++ /dev/null
-/*
- * 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
- }
-}
+++ /dev/null
-/*
- * 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();
- }
-}
+++ /dev/null
-/*
- * 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);
- }
-}
+++ /dev/null
-/*
- * 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);
- }
-}
+++ /dev/null
-/*
- * 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;
- }
-}
+++ /dev/null
-/*
- * 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 {
-}
+++ /dev/null
-/*
- * 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()
- ));
- }
-}
+++ /dev/null
-/*
- * 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;
+++ /dev/null
-/*
- * 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;
--- /dev/null
+/*
+ * 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();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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 {
+}
--- /dev/null
+/*
+ * 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()
+ ));
+ }
+}
--- /dev/null
+/*
+ * 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;
--- /dev/null
+/*
+ * 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;
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;
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;
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;
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;
+++ /dev/null
-/*
- * 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();
- }
-}
+++ /dev/null
-/*
- * 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);
- }
-
-}
+++ /dev/null
-/*
- * 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();
- }
-}
+++ /dev/null
-/*
- * 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");
- }
- }
-
-}
+++ /dev/null
-/*
- * 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;
- }
- }
-}
+++ /dev/null
-/*
- * 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();
- }
-}
+++ /dev/null
-/*
- * 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();
- }
- }
-}
+++ /dev/null
-/*
- * 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();
- }
-}
+++ /dev/null
-/*
- * 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);
- }
-}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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");
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}