aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-db-migration
diff options
context:
space:
mode:
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>2016-12-08 18:25:20 +0100
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>2016-12-14 12:11:52 +0100
commitff766539d309bbaaa416b2ce05d8d478086b6176 (patch)
treeefdabc29e2931b71429f114090b2e098a0183829 /server/sonar-db-migration
parentea8ca620bf1792afe96bdcf2b24b943fa812a415 (diff)
downloadsonarqube-ff766539d309bbaaa416b2ce05d8d478086b6176.tar.gz
sonarqube-ff766539d309bbaaa416b2ce05d8d478086b6176.zip
SONAR-8445 run DB migration of SQ 6.3 with Java
add Java toolkit to run DB migrations port last 3 DB migrations (ie. from current SQ 6.3) to this Java toolkit and make it run after Ruby migration
Diffstat (limited to 'server/sonar-db-migration')
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationEngineModule.java49
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainer.java42
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainerImpl.java70
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainerPopulator.java25
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainerPopulatorImpl.java45
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationEngine.java33
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationEngineImpl.java57
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/package-info.java24
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/history/MigrationHistory.java56
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/history/MigrationHistoryImpl.java84
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/InternalMigrationStepRegistry.java27
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationNumber.java33
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStep.java27
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepExecutionException.java44
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepRegistry.java29
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepRegistryImpl.java60
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationSteps.java39
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepsExecutor.java38
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepsExecutorImpl.java88
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepsImpl.java69
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepsProvider.java44
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/RegisteredMigrationStep.java51
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/package-info.java24
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/DbVersion.java26
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/DbVersionModule.java31
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/package-info.java24
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v63/AddUuidToEvents.java46
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v63/DbVersion63.java33
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v63/MakeUuidNotNullOnEvents.java54
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v63/PopulateUuidColumnOfEvents.java56
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v63/package-info.java24
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/MigrationEngineModuleTest.java41
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/MigrationContainerImplTest.java96
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/MigrationContainerPopulatorImplTest.java92
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/MigrationEngineImplTest.java74
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/SimpleMigrationContainer.java30
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/history/MigrationHistoryImplTest.java81
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/history/NoTableMigrationHistoryImplTest.java46
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationNumberTest.java54
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepExecutionExceptionTest.java66
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepRegistryImplTest.java133
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepsExecutorImplTest.java213
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepsImplTest.java116
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepsProviderTest.java99
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/RegisteredMigrationStepTest.java59
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/DbVersionModuleTest.java42
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/DbVersionTestUtils.java67
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v63/AddUuidToEventsTest.java58
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v63/DbVersion63Test.java41
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v63/MakeUuidNotNullOnEventsTest.java87
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v63/PopulateUuidColumnOfEventsTest.java95
-rw-r--r--server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/history/NoTableMigrationHistoryImplTest/empty.sql0
-rw-r--r--server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v63/AddUuidToEventsTest/previous-events.sql11
-rw-r--r--server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v63/MakeUuidNotNullOnEventsTest/in_progress_events.sql12
-rw-r--r--server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v63/PopulateUuidColumnOfEventsTest/in_progress_events.sql12
55 files changed, 2977 insertions, 0 deletions
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationEngineModule.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationEngineModule.java
new file mode 100644
index 00000000000..69ca3930f5f
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationEngineModule.java
@@ -0,0 +1,49 @@
+/*
+ * 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.migration;
+
+import org.sonar.core.platform.Module;
+import org.sonar.server.platform.db.migration.engine.MigrationContainerPopulatorImpl;
+import org.sonar.server.platform.db.migration.engine.MigrationEngineImpl;
+import org.sonar.server.platform.db.migration.history.MigrationHistoryImpl;
+import org.sonar.server.platform.db.migration.step.MigrationStepRegistryImpl;
+import org.sonar.server.platform.db.migration.step.MigrationStepsProvider;
+
+/**
+ * Defines the components for the migration engine. This does not include the
+ * {@link org.sonar.server.platform.db.migration.version.DbVersion} classes which are bundled together in
+ * {@link org.sonar.server.platform.db.migration.version.DbVersionModule}.
+ */
+public class MigrationEngineModule extends Module {
+ @Override
+ protected void configureModule() {
+ add(
+ // migration steps
+ MigrationStepRegistryImpl.class,
+ new MigrationStepsProvider(),
+
+ // history
+ MigrationHistoryImpl.class,
+
+ // engine
+ MigrationContainerPopulatorImpl.class,
+ MigrationEngineImpl.class);
+ }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainer.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainer.java
new file mode 100644
index 00000000000..507a124ca1d
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainer.java
@@ -0,0 +1,42 @@
+/*
+ * 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.migration.engine;
+
+import org.sonar.core.platform.ContainerPopulator;
+
+/**
+ * A dedicated container used to run DB migrations where all components are lazily instantiated.
+ * <p>
+ * As a new container will be created for each run of DB migrations, components in this container can safely be
+ * stateful.
+ * </p>
+ * <p>
+ * Lazy instantiation is convenient to instantiate {@link org.sonar.server.platform.db.migration.step.MigrationStep}
+ * classes only they really are to be executed.
+ * </p>
+ */
+public interface MigrationContainer extends ContainerPopulator.Container {
+
+ /**
+ * Cleans up resources after migration has run.
+ * <strong>This method must never fail.</strong>
+ */
+ void cleanup();
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainerImpl.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainerImpl.java
new file mode 100644
index 00000000000..b998879eb85
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainerImpl.java
@@ -0,0 +1,70 @@
+/*
+ * 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.migration.engine;
+
+import org.picocontainer.ComponentAdapter;
+import org.picocontainer.ComponentMonitor;
+import org.picocontainer.DefaultPicoContainer;
+import org.picocontainer.MutablePicoContainer;
+import org.picocontainer.behaviors.OptInCaching;
+import org.picocontainer.lifecycle.ReflectionLifecycleStrategy;
+import org.picocontainer.monitors.NullComponentMonitor;
+import org.sonar.api.config.PropertyDefinitions;
+import org.sonar.core.platform.ComponentContainer;
+
+import static java.util.Objects.requireNonNull;
+
+public class MigrationContainerImpl extends ComponentContainer implements MigrationContainer {
+
+ public MigrationContainerImpl(ComponentContainer parent, MigrationContainerPopulator populator) {
+ super(createContainer(requireNonNull(parent)), parent.getComponentByType(PropertyDefinitions.class));
+
+ populateContainer(requireNonNull(populator));
+ startComponents();
+ }
+
+ private void populateContainer(MigrationContainerPopulator populator) {
+ populator.populateContainer(this);
+ }
+
+ /**
+ * Creates a PicContainer which extends the specified ComponentContainer <strong>but is not referenced in return</strong>.
+ */
+ private static MutablePicoContainer createContainer(ComponentContainer parent) {
+ ComponentMonitor componentMonitor = new NullComponentMonitor();
+ ReflectionLifecycleStrategy lifecycleStrategy = new ReflectionLifecycleStrategy(componentMonitor, "start", "stop", "close") {
+ @Override
+ public boolean isLazy(ComponentAdapter<?> adapter) {
+ return true;
+ }
+ };
+ return new DefaultPicoContainer(new OptInCaching(), lifecycleStrategy, parent.getPicoContainer(), componentMonitor);
+ }
+
+ @Override
+ public void cleanup() {
+ stopComponents(true);
+ }
+
+ @Override
+ public String toString() {
+ return "MigrationContainerImpl";
+ }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainerPopulator.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainerPopulator.java
new file mode 100644
index 00000000000..23de1b24068
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainerPopulator.java
@@ -0,0 +1,25 @@
+/*
+ * 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.migration.engine;
+
+import org.sonar.core.platform.ContainerPopulator;
+
+public interface MigrationContainerPopulator extends ContainerPopulator<MigrationContainer> {
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainerPopulatorImpl.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainerPopulatorImpl.java
new file mode 100644
index 00000000000..b5e81d24606
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainerPopulatorImpl.java
@@ -0,0 +1,45 @@
+/*
+ * 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.migration.engine;
+
+import org.sonar.server.platform.db.migration.step.MigrationStep;
+import org.sonar.server.platform.db.migration.step.MigrationSteps;
+import org.sonar.server.platform.db.migration.step.MigrationStepsExecutorImpl;
+
+/**
+ * Responsible for:
+ * <ul>
+ * <li>adding all the {@link MigrationStep} classes to the container after building it</li>
+ * <li>adding dependencies for them to the container if there aren't already available in parent container</li>
+ * <li>adding the {@link MigrationStepsExecutorImpl} to the container</li>
+ * </ul>
+ */
+public class MigrationContainerPopulatorImpl implements MigrationContainerPopulator {
+ @Override
+ public void populateContainer(MigrationContainer container) {
+ container.add(MigrationStepsExecutorImpl.class);
+ populateFromMigrationSteps(container);
+ }
+
+ private static void populateFromMigrationSteps(MigrationContainer container) {
+ MigrationSteps migrationSteps = container.getComponentByType(MigrationSteps.class);
+ migrationSteps.readAll().forEach(step -> container.add(step.getStepClass()));
+ }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationEngine.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationEngine.java
new file mode 100644
index 00000000000..51489a17122
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationEngine.java
@@ -0,0 +1,33 @@
+/*
+ * 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.migration.engine;
+
+/**
+ * This class is responsible for:
+ * <ul>
+ * <li>creating a dedicate container to run the migrations, populating it and starting it</li>
+ * <li>resolving the migration starting point</li>
+ * <li>starting the db migration execution</li>
+ * <li>stop the container and dispose of it</li>
+ * </ul>
+ */
+public interface MigrationEngine {
+ void execute();
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationEngineImpl.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationEngineImpl.java
new file mode 100644
index 00000000000..5a0af4a5870
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationEngineImpl.java
@@ -0,0 +1,57 @@
+/*
+ * 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.migration.engine;
+
+import java.util.Optional;
+import org.sonar.core.platform.ComponentContainer;
+import org.sonar.server.platform.db.migration.history.MigrationHistory;
+import org.sonar.server.platform.db.migration.step.MigrationSteps;
+import org.sonar.server.platform.db.migration.step.MigrationStepsExecutor;
+
+public class MigrationEngineImpl implements MigrationEngine {
+ private final MigrationHistory migrationHistory;
+ private final ComponentContainer serverContainer;
+ private final MigrationContainerPopulator populator;
+ private final MigrationSteps migrationSteps;
+
+ public MigrationEngineImpl(MigrationHistory migrationHistory, ComponentContainer serverContainer, MigrationContainerPopulator populator, MigrationSteps migrationSteps) {
+ this.migrationHistory = migrationHistory;
+ this.serverContainer = serverContainer;
+ this.populator = populator;
+ this.migrationSteps = migrationSteps;
+ }
+
+ @Override
+ public void execute() {
+ MigrationContainer migrationContainer = new MigrationContainerImpl(serverContainer, populator);
+
+ try {
+ MigrationStepsExecutor stepsExecutor = migrationContainer.getComponentByType(MigrationStepsExecutor.class);
+ Optional<Long> lastMigrationNumber = migrationHistory.getLastMigrationNumber();
+ if (lastMigrationNumber.isPresent()) {
+ stepsExecutor.execute(migrationSteps.readFrom(lastMigrationNumber.get() + 1));
+ } else {
+ stepsExecutor.execute(migrationSteps.readAll());
+ }
+ } finally {
+ migrationContainer.cleanup();
+ }
+ }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/package-info.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/package-info.java
new file mode 100644
index 00000000000..8b76f6afe22
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.migration.engine;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/history/MigrationHistory.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/history/MigrationHistory.java
new file mode 100644
index 00000000000..cc4edfaf402
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/history/MigrationHistory.java
@@ -0,0 +1,56 @@
+/*
+ * 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.migration.history;
+
+import java.util.Optional;
+import org.sonar.api.Startable;
+import org.sonar.server.platform.db.migration.step.RegisteredMigrationStep;
+
+/**
+ * This class is responsible for providing methods to read and write information from the persisted
+ * history.
+ * <p>
+ * This class assumes the Migration History table exists (see {@link MigrationHistoryTable}) and will
+ * fail at startup (see {@link #start()}) if it doesn't.
+ * </p>
+ */
+public interface MigrationHistory extends Startable {
+ /**
+ * @throws IllegalStateException if the Migration History table does not exist.
+ */
+ @Override
+ void start();
+
+ /**
+ * Returns the last execute migration number according to the persistence information.
+ *
+ * @return a long >= 0.
+ */
+ Optional<Long> getLastMigrationNumber();
+
+ /**
+ * Saves in persisted migration history the fact that the specified {@link RegisteredMigrationStep} has
+ * been successfully executed.
+ *
+ * @throws RuntimeException if the information can not be persisted (and committed).
+ */
+ void done(RegisteredMigrationStep dbMigration);
+
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/history/MigrationHistoryImpl.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/history/MigrationHistoryImpl.java
new file mode 100644
index 00000000000..9c00b983a8b
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/history/MigrationHistoryImpl.java
@@ -0,0 +1,84 @@
+/*
+ * 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.migration.history;
+
+import com.google.common.base.Throwables;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import org.sonar.db.DatabaseUtils;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.version.SchemaMigrationMapper;
+import org.sonar.server.platform.db.migration.step.RegisteredMigrationStep;
+
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * TODO make {@link org.sonar.db.version.DatabaseVersion} use this class
+ */
+public class MigrationHistoryImpl implements MigrationHistory {
+ private final DbClient dbClient;
+
+ public MigrationHistoryImpl(DbClient dbClient) {
+ this.dbClient = dbClient;
+ }
+
+ @Override
+ public void start() {
+ try (Connection connection = dbClient.getDatabase().getDataSource().getConnection()) {
+ checkState(DatabaseUtils.tableExists(MigrationHistoryTable.NAME, connection), "Migration history table is missing");
+ } catch (SQLException e) {
+ Throwables.propagate(e);
+ }
+ }
+
+ @Override
+ public void stop() {
+ // nothing to do
+ }
+
+ @Override
+ public Optional<Long> getLastMigrationNumber() {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ List<Integer> versions = getMapper(dbSession).selectVersions();
+
+ if (!versions.isEmpty()) {
+ Collections.sort(versions);
+ return Optional.of(versions.get(versions.size() - 1).longValue());
+ }
+ return Optional.empty();
+ }
+ }
+
+ @Override
+ public void done(RegisteredMigrationStep dbMigration) {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ getMapper(dbSession).insert(String.valueOf(dbMigration.getMigrationNumber()));
+ dbSession.commit();
+ }
+ }
+
+ private static SchemaMigrationMapper getMapper(DbSession dbSession) {
+ return dbSession.getMapper(SchemaMigrationMapper.class);
+ }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/InternalMigrationStepRegistry.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/InternalMigrationStepRegistry.java
new file mode 100644
index 00000000000..de0c450e2a9
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/InternalMigrationStepRegistry.java
@@ -0,0 +1,27 @@
+/*
+ * 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.migration.step;
+
+public interface InternalMigrationStepRegistry extends MigrationStepRegistry {
+ /**
+ * @throws IllegalStateException if the registry is empty
+ */
+ MigrationSteps build();
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationNumber.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationNumber.java
new file mode 100644
index 00000000000..93e5ba9841d
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationNumber.java
@@ -0,0 +1,33 @@
+/*
+ * 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.migration.step;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+public final class MigrationNumber {
+ private MigrationNumber() {
+ // prevents instantiation
+ }
+
+ public static void validate(long migrationNumber) {
+ checkArgument(migrationNumber >= 0, "Migration number must be >= 0");
+ }
+
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStep.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStep.java
new file mode 100644
index 00000000000..c04ed0f6362
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStep.java
@@ -0,0 +1,27 @@
+/*
+ * 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.migration.step;
+
+import java.sql.SQLException;
+
+public interface MigrationStep {
+
+ void execute() throws SQLException;
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepExecutionException.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepExecutionException.java
new file mode 100644
index 00000000000..6c9cc124485
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepExecutionException.java
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.step;
+
+import static java.util.Objects.requireNonNull;
+
+public class MigrationStepExecutionException extends RuntimeException {
+ private final transient RegisteredMigrationStep failingStep;
+
+ public MigrationStepExecutionException(RegisteredMigrationStep failingStep, Throwable cause) {
+ super(createMessage(failingStep), requireNonNull(cause, "cause can't be null"));
+ this.failingStep = failingStep;
+ }
+
+ private static String createMessage(RegisteredMigrationStep failingStep) {
+ check(failingStep);
+ return String.format("Execution of migration step %s failed", failingStep);
+ }
+
+ private static RegisteredMigrationStep check(RegisteredMigrationStep failingStep) {
+ return requireNonNull(failingStep, "RegisteredMigrationStep can't be null");
+ }
+
+ public RegisteredMigrationStep getFailingStep() {
+ return failingStep;
+ }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepRegistry.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepRegistry.java
new file mode 100644
index 00000000000..8c19a2bd2f4
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepRegistry.java
@@ -0,0 +1,29 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.step;
+
+public interface MigrationStepRegistry {
+ /**
+ *
+ * @throws IllegalArgumentException if migrationNumber is < 0.
+ * @throws IllegalStateException if a db migration is already registered for the specified migrationNumber
+ */
+ <T extends MigrationStep> MigrationStepRegistry add(long migrationNumber, String description, Class<T> stepClass);
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepRegistryImpl.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepRegistryImpl.java
new file mode 100644
index 00000000000..9310bcfbb65
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepRegistryImpl.java
@@ -0,0 +1,60 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.step;
+
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.sonar.core.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+import static org.sonar.server.platform.db.migration.step.MigrationNumber.validate;
+
+public class MigrationStepRegistryImpl implements InternalMigrationStepRegistry {
+ private final Map<Long, RegisteredMigrationStep> migrations = new HashMap<>();
+
+ @Override
+ public <T extends MigrationStep> MigrationStepRegistry add(long migrationNumber, String description, Class<T> stepClass) {
+ validate(migrationNumber);
+ requireNonNull(description, "description can't be null");
+ checkArgument(!description.isEmpty(), "description can't be empty");
+ requireNonNull(stepClass, "MigrationStep class can't be null");
+ checkState(!migrations.containsKey(migrationNumber), "A migration is already registered for migration number '%s'", migrationNumber);
+ this.migrations.put(migrationNumber, new RegisteredMigrationStep(migrationNumber, description, stepClass));
+ return this;
+ }
+
+ @Override
+ public MigrationSteps build() {
+ checkState(!migrations.isEmpty(), "Registry is empty");
+ return new MigrationStepsImpl(toOrderedList(this.migrations));
+ }
+
+ private static List<RegisteredMigrationStep> toOrderedList(Map<Long, RegisteredMigrationStep> migrations) {
+ return migrations.entrySet().stream()
+ .sorted(Comparator.comparingLong(Map.Entry::getKey))
+ .map(Map.Entry::getValue)
+ .collect(Collectors.toList(migrations.size()));
+ }
+
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationSteps.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationSteps.java
new file mode 100644
index 00000000000..94296f4d4cc
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationSteps.java
@@ -0,0 +1,39 @@
+/*
+ * 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.migration.step;
+
+import java.util.stream.Stream;
+
+public interface MigrationSteps {
+ /**
+ * @return the migration number of the last migration step.
+ */
+ long getMaxMigrationNumber();
+
+ /**
+ * Reads all migration steps in order of increasing migration number.
+ */
+ Stream<RegisteredMigrationStep> readAll();
+
+ /**
+ * Reads migration steps, in order of increasing migration number, from the specified migration number <strong>included</strong>.
+ */
+ Stream<RegisteredMigrationStep> readFrom(long migrationNumber);
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepsExecutor.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepsExecutor.java
new file mode 100644
index 00000000000..c52f9b61ba9
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepsExecutor.java
@@ -0,0 +1,38 @@
+/*
+ * 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.migration.step;
+
+import java.util.stream.Stream;
+
+/**
+ * Responsible for:
+ * <ul>
+ * <li>looping over all the {@link MigrationStep} to execute</li>
+ * <li>put INFO log between each {@link MigrationStep} for user information</li>
+ * <li>handle errors during the execution of {@link MigrationStep}</li>
+ * <li>update the content of table {@code SCHEMA_MIGRATION}</li>
+ * </ul>
+ */
+public interface MigrationStepsExecutor {
+ /**
+ * @throws MigrationStepExecutionException at the first failing migration step execution
+ */
+ void execute(Stream<RegisteredMigrationStep> steps);
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepsExecutorImpl.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepsExecutorImpl.java
new file mode 100644
index 00000000000..331b9287efa
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepsExecutorImpl.java
@@ -0,0 +1,88 @@
+/*
+ * 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.migration.step;
+
+import java.util.stream.Stream;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.core.util.logs.Profiler;
+import org.sonar.server.platform.db.migration.engine.MigrationContainer;
+import org.sonar.server.platform.db.migration.history.MigrationHistory;
+
+import static com.google.common.base.Preconditions.checkState;
+
+public class MigrationStepsExecutorImpl implements MigrationStepsExecutor {
+ private static final Logger LOGGER = Loggers.get("DbMigrations");
+ private static final String GLOBAL_START_MESSAGE = "Executing DB migrations...";
+ private static final String GLOBAL_END_MESSAGE = "Executed DB migrations: {}";
+ private static final String STEP_START_PATTERN = "{}...";
+ private static final String STEP_STOP_PATTERN = "{}: {}";
+
+ private final MigrationContainer migrationContainer;
+ private final MigrationHistory migrationHistory;
+
+ public MigrationStepsExecutorImpl(MigrationContainer migrationContainer, MigrationHistory migrationHistory) {
+ this.migrationContainer = migrationContainer;
+ this.migrationHistory = migrationHistory;
+ }
+
+ @Override
+ public void execute(Stream<RegisteredMigrationStep> steps) {
+ Profiler globalProfiler = Profiler.create(LOGGER);
+ globalProfiler.startInfo(GLOBAL_START_MESSAGE);
+ boolean allStepsExecuted = false;
+ try {
+ steps.forEachOrdered(this::execute);
+ allStepsExecuted = true;
+ } finally {
+ if (allStepsExecuted) {
+ globalProfiler.stopInfo(GLOBAL_END_MESSAGE, "success");
+ } else {
+ globalProfiler.stopError(GLOBAL_END_MESSAGE, "failure");
+ }
+ }
+ }
+
+ private void execute(RegisteredMigrationStep step) {
+ MigrationStep migrationStep = migrationContainer.getComponentByType(step.getStepClass());
+ checkState(migrationStep != null, "Can not find instance of " + step.getStepClass());
+
+ execute(step, migrationStep);
+ }
+
+ private void execute(RegisteredMigrationStep step, MigrationStep migrationStep) {
+ Profiler stepProfiler = Profiler.create(LOGGER);
+ stepProfiler.startInfo(STEP_START_PATTERN, step);
+ boolean done = false;
+ try {
+ migrationStep.execute();
+ migrationHistory.done(step);
+ done = true;
+ } catch (Exception e) {
+ throw new MigrationStepExecutionException(step, e);
+ } finally {
+ if (done) {
+ stepProfiler.stopInfo(STEP_STOP_PATTERN, step, "success");
+ } else {
+ stepProfiler.stopError(STEP_STOP_PATTERN, step, "failure");
+ }
+ }
+ }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepsImpl.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepsImpl.java
new file mode 100644
index 00000000000..fd07a47a320
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepsImpl.java
@@ -0,0 +1,69 @@
+/*
+ * 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.migration.step;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.ImmutableList.copyOf;
+import static java.util.Objects.requireNonNull;
+import static org.sonar.server.platform.db.migration.step.MigrationNumber.validate;
+
+class MigrationStepsImpl implements MigrationSteps {
+ private final List<RegisteredMigrationStep> steps;
+
+ MigrationStepsImpl(List<RegisteredMigrationStep> steps) {
+ requireNonNull(steps, "steps can't be null");
+ checkArgument(!steps.isEmpty(), "steps can't be empty");
+ this.steps = copyOf(steps);
+ }
+
+ @Override
+ public long getMaxMigrationNumber() {
+ return steps.get(steps.size() -1).getMigrationNumber();
+ }
+
+ @Override
+ public Stream<RegisteredMigrationStep> readAll() {
+ return steps.stream();
+ }
+
+ @Override
+ public Stream<RegisteredMigrationStep> readFrom(long migrationNumber) {
+ validate(migrationNumber);
+ int startingIndex = lookupIndexOfClosestTo(migrationNumber);
+ if (startingIndex < 0) {
+ return Stream.empty();
+ }
+ return steps.subList(startingIndex, steps.size()).stream();
+ }
+
+ private int lookupIndexOfClosestTo(long startingPoint) {
+ int index = 0;
+ for (RegisteredMigrationStep step : steps) {
+ if (step.getMigrationNumber() >= startingPoint) {
+ return index;
+ }
+ index++;
+ }
+ return -1;
+ }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepsProvider.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepsProvider.java
new file mode 100644
index 00000000000..a599af60917
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepsProvider.java
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.step;
+
+import java.util.Arrays;
+import org.picocontainer.injectors.ProviderAdapter;
+import org.sonar.server.platform.db.migration.version.DbVersion;
+
+/**
+ * This class is responsible for providing the {@link MigrationSteps} to be injected in classes that need it and
+ * ensures that there's only one such instance.
+ */
+public class MigrationStepsProvider extends ProviderAdapter {
+ private MigrationSteps migrationSteps;
+
+ public MigrationSteps provide(InternalMigrationStepRegistry migrationStepRegistry, DbVersion... dbVersions) {
+ if (migrationSteps == null) {
+ migrationSteps = buildMigrationSteps(migrationStepRegistry, dbVersions);
+ }
+ return migrationSteps;
+ }
+
+ private static MigrationSteps buildMigrationSteps(InternalMigrationStepRegistry migrationStepRegistry, DbVersion[] dbVersions) {
+ Arrays.stream(dbVersions).forEach(dbVersion -> dbVersion.addSteps(migrationStepRegistry));
+ return migrationStepRegistry.build();
+ }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/RegisteredMigrationStep.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/RegisteredMigrationStep.java
new file mode 100644
index 00000000000..67f3d1cafdb
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/RegisteredMigrationStep.java
@@ -0,0 +1,51 @@
+/*
+ * 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.migration.step;
+
+import static java.util.Objects.requireNonNull;
+
+public final class RegisteredMigrationStep {
+ private final long migrationNumber;
+ private final String description;
+ private final Class<? extends MigrationStep> stepClass;
+
+ public RegisteredMigrationStep(long migrationNumber, String description, Class<? extends MigrationStep> migration) {
+ this.migrationNumber = migrationNumber;
+ this.description = requireNonNull(description, "description can't be null");
+ this.stepClass = requireNonNull(migration, "MigrationStep class can't be null");
+ }
+
+ public long getMigrationNumber() {
+ return migrationNumber;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public Class<? extends MigrationStep> getStepClass() {
+ return stepClass;
+ }
+
+ @Override
+ public String toString() {
+ return "#" + migrationNumber + " '" + description + "'";
+ }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/package-info.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/package-info.java
new file mode 100644
index 00000000000..1e449f74624
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.migration.step;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/DbVersion.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/DbVersion.java
new file mode 100644
index 00000000000..971d399967e
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/DbVersion.java
@@ -0,0 +1,26 @@
+/*
+ * 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.migration.version;
+
+import org.sonar.server.platform.db.migration.step.MigrationStepRegistry;
+
+public interface DbVersion {
+ void addSteps(MigrationStepRegistry registry);
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/DbVersionModule.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/DbVersionModule.java
new file mode 100644
index 00000000000..3047464206d
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/DbVersionModule.java
@@ -0,0 +1,31 @@
+/*
+ * 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.migration.version;
+
+import org.sonar.core.platform.Module;
+import org.sonar.server.platform.db.migration.version.v63.DbVersion63;
+
+public class DbVersionModule extends Module {
+ @Override
+ protected void configureModule() {
+ add(DbVersion63.class);
+ }
+
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/package-info.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/package-info.java
new file mode 100644
index 00000000000..c31514b0f5d
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.migration.version;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v63/AddUuidToEvents.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v63/AddUuidToEvents.java
new file mode 100644
index 00000000000..9f4b53bdcf6
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v63/AddUuidToEvents.java
@@ -0,0 +1,46 @@
+/*
+ * 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.migration.version.v63;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.version.AddColumnsBuilder;
+import org.sonar.db.version.VarcharColumnDef;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+import static org.sonar.db.version.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+public class AddUuidToEvents extends DdlChange {
+
+ public AddUuidToEvents(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ VarcharColumnDef column = newVarcharColumnDefBuilder()
+ .setColumnName("uuid")
+ .setIsNullable(true)
+ .setLimit(40)
+ .build();
+ context.execute(new AddColumnsBuilder(getDialect(), "events").addColumn(column).build());
+ }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v63/DbVersion63.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v63/DbVersion63.java
new file mode 100644
index 00000000000..03800cd11dc
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v63/DbVersion63.java
@@ -0,0 +1,33 @@
+/*
+ * 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.migration.version.v63;
+
+import org.sonar.server.platform.db.migration.step.MigrationStepRegistry;
+import org.sonar.server.platform.db.migration.version.DbVersion;
+
+public class DbVersion63 implements DbVersion {
+ @Override
+ public void addSteps(MigrationStepRegistry registry) {
+ registry
+ .add(1500, "Add Events.UUID", AddUuidToEvents.class)
+ .add(1501, "Populate Events.UUID", PopulateUuidColumnOfEvents.class)
+ .add(1502, "Make Events.UUID not nullable", MakeUuidNotNullOnEvents.class);
+ }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v63/MakeUuidNotNullOnEvents.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v63/MakeUuidNotNullOnEvents.java
new file mode 100644
index 00000000000..972c7ef9b0b
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v63/MakeUuidNotNullOnEvents.java
@@ -0,0 +1,54 @@
+/*
+ * 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.migration.version.v63;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.version.AlterColumnsBuilder;
+import org.sonar.db.version.CreateIndexBuilder;
+import org.sonar.db.version.VarcharColumnDef;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+import static org.sonar.db.version.VarcharColumnDef.UUID_SIZE;
+import static org.sonar.db.version.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+public class MakeUuidNotNullOnEvents extends DdlChange {
+
+ private static final String TABLE = "events";
+
+ public MakeUuidNotNullOnEvents(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ VarcharColumnDef uuidColumn = newVarcharColumnDefBuilder().setColumnName("uuid").setLimit(UUID_SIZE).setIsNullable(false).build();
+ context.execute(new AlterColumnsBuilder(getDatabase().getDialect(), TABLE)
+ .updateColumn(uuidColumn)
+ .build());
+
+ context.execute(new CreateIndexBuilder(getDialect())
+ .setTable(TABLE)
+ .setName("events_uuid")
+ .setUnique(true)
+ .addColumn(uuidColumn)
+ .build());
+ }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v63/PopulateUuidColumnOfEvents.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v63/PopulateUuidColumnOfEvents.java
new file mode 100644
index 00000000000..f4cecbffbd6
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v63/PopulateUuidColumnOfEvents.java
@@ -0,0 +1,56 @@
+/*
+ * 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.migration.version.v63;
+
+import java.sql.SQLException;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.Database;
+import org.sonar.db.version.MassUpdate;
+import org.sonar.db.version.Select;
+import org.sonar.db.version.SqlStatement;
+import org.sonar.server.platform.db.migration.step.DataChange;
+
+public class PopulateUuidColumnOfEvents extends DataChange {
+
+ private final UuidFactory uuidFactory;
+
+ public PopulateUuidColumnOfEvents(Database db, UuidFactory uuidFactory) {
+ super(db);
+ this.uuidFactory = uuidFactory;
+ }
+
+ @Override
+ protected void execute(Context context) throws SQLException {
+ MassUpdate massUpdate = context.prepareMassUpdate();
+ massUpdate.select("SELECT e.id from events e where e.uuid is null");
+ massUpdate.update("UPDATE events SET uuid=? WHERE id=?");
+ massUpdate.rowPluralName("events");
+ massUpdate.execute(this::handle);
+ }
+
+ private boolean handle(Select.Row row, SqlStatement update) throws SQLException {
+ long id = row.getLong(1);
+ update.setString(1, uuidFactory.create());
+ update.setLong(2, id);
+ return true;
+ }
+
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v63/package-info.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v63/package-info.java
new file mode 100644
index 00000000000..86bf6ad3104
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v63/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.migration.version.v63;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/MigrationEngineModuleTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/MigrationEngineModuleTest.java
new file mode 100644
index 00000000000..fb5bb4451b5
--- /dev/null
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/MigrationEngineModuleTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.migration;
+
+import org.junit.Test;
+import org.sonar.core.platform.ComponentContainer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class MigrationEngineModuleTest {
+ private static final int COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER = 2;
+
+ private MigrationEngineModule underTest = new MigrationEngineModule();
+
+ @Test
+ public void verify_component_count() {
+ ComponentContainer container = new ComponentContainer();
+
+ underTest.configure(container);
+
+ assertThat(container.getPicoContainer().getComponentAdapters())
+ .hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 5);
+ }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/MigrationContainerImplTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/MigrationContainerImplTest.java
new file mode 100644
index 00000000000..82a9402fd64
--- /dev/null
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/MigrationContainerImplTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.migration.engine;
+
+import org.junit.Test;
+import org.picocontainer.Startable;
+import org.sonar.core.platform.ComponentContainer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class MigrationContainerImplTest {
+ private ComponentContainer parent = new ComponentContainer();
+ private MigrationContainerPopulator populator = new MigrationContainerPopulator() {
+ @Override
+ public void populateContainer(MigrationContainer container) {
+ container.add(StartCallCounter.class);
+ }
+ };
+
+ private MigrationContainerImpl underTest = new MigrationContainerImpl(parent, populator);
+
+ @Test
+ public void pico_container_of_migration_container_has_pico_container_of_specified_container_as_parent() {
+ assertThat(underTest.getPicoContainer().getParent()).isEqualTo(parent.getPicoContainer());
+ }
+
+ @Test
+ public void pico_container_of_parent_does_not_have_pico_container_of_migration_container_as_child() {
+ assertThat(parent.getPicoContainer().removeChildContainer(underTest.getPicoContainer())).isFalse();
+ }
+
+ @Test
+ public void pico_container_of_migration_container_is_started_in_constructor() {
+ assertThat(underTest.getPicoContainer().getLifecycleState().isStarted()).isTrue();
+ }
+
+ @Test
+ public void migration_container_lazily_instance_components() {
+ assertThat(StartCallCounter.startCalls).isEqualTo(0);
+
+ StartCallCounter startCallCounter = underTest.getComponentByType(StartCallCounter.class);
+
+ assertThat(startCallCounter).isNotNull();
+ assertThat(StartCallCounter.startCalls).isEqualTo(1);
+ }
+
+ @Test
+ public void cleanup_does_not_fail_even_if_stop_of_component_fails() {
+ MigrationContainerImpl underTest = new MigrationContainerImpl(parent, (container -> container.add(StopFailing.class)));
+
+ underTest.cleanup();
+ }
+
+ public static final class StartCallCounter implements Startable {
+ private static int startCalls = 0;
+
+ @Override
+ public void start() {
+ startCalls++;
+ }
+
+ @Override
+ public void stop() {
+ // do nothing
+ }
+ }
+
+ public static final class StopFailing implements Startable {
+ @Override
+ public void start() {
+ // do nothing
+ }
+
+ @Override
+ public void stop() {
+ throw new RuntimeException("Faking stop call failing");
+ }
+ }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/MigrationContainerPopulatorImplTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/MigrationContainerPopulatorImplTest.java
new file mode 100644
index 00000000000..a0cc4d54bfe
--- /dev/null
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/MigrationContainerPopulatorImplTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.migration.engine;
+
+import java.sql.SQLException;
+import java.util.stream.Stream;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.server.platform.db.migration.history.MigrationHistory;
+import org.sonar.server.platform.db.migration.step.MigrationStep;
+import org.sonar.server.platform.db.migration.step.MigrationSteps;
+import org.sonar.server.platform.db.migration.step.MigrationStepsExecutorImpl;
+import org.sonar.server.platform.db.migration.step.RegisteredMigrationStep;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class MigrationContainerPopulatorImplTest {
+ private MigrationContainer migrationContainer = new SimpleMigrationContainer();
+ private MigrationSteps migrationSteps = mock(MigrationSteps.class);
+ private MigrationContainerPopulatorImpl underTest = new MigrationContainerPopulatorImpl();
+
+ @Before
+ public void setUp() throws Exception {
+ migrationContainer.add(migrationSteps);
+ }
+
+ @Test
+ public void populateContainer_adds_MigrationStepsExecutorImpl() {
+ when(migrationSteps.readAll()).thenReturn(Stream.empty());
+
+ // add MigrationStepsExecutorImpl's dependencies
+ migrationContainer.add(mock(MigrationHistory.class));
+
+ underTest.populateContainer(migrationContainer);
+
+ assertThat(migrationContainer.getComponentByType(MigrationStepsExecutorImpl.class)).isNotNull();
+ }
+
+ @Test
+ public void populateContainer_adds_classes_of_all_steps_defined_in_MigrationSteps() {
+ when(migrationSteps.readAll()).thenReturn(Stream.of(
+ new RegisteredMigrationStep(1, "foo", MigrationStep1.class),
+ new RegisteredMigrationStep(2, "bar", MigrationStep2.class),
+ new RegisteredMigrationStep(3, "dor", MigrationStep3.class)
+ ));
+
+ underTest.populateContainer(migrationContainer);
+
+ assertThat(migrationContainer.getComponentsByType(MigrationStep1.class)).isNotNull();
+ assertThat(migrationContainer.getComponentsByType(MigrationStep2.class)).isNotNull();
+ assertThat(migrationContainer.getComponentsByType(MigrationStep3.class)).isNotNull();
+ }
+
+ private static abstract class NoopMigrationStep implements MigrationStep {
+ @Override
+ public void execute() throws SQLException {
+ throw new UnsupportedOperationException("execute not implemented");
+ }
+ }
+
+ public static final class MigrationStep1 extends NoopMigrationStep {
+
+ }
+
+ public static final class MigrationStep2 extends NoopMigrationStep {
+
+ }
+
+ public static final class MigrationStep3 extends NoopMigrationStep {
+
+ }
+
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/MigrationEngineImplTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/MigrationEngineImplTest.java
new file mode 100644
index 00000000000..7e334fb18d4
--- /dev/null
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/MigrationEngineImplTest.java
@@ -0,0 +1,74 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.engine;
+
+import java.util.Optional;
+import java.util.stream.Stream;
+import org.junit.Test;
+import org.sonar.core.platform.ComponentContainer;
+import org.sonar.server.platform.db.migration.history.MigrationHistory;
+import org.sonar.server.platform.db.migration.step.MigrationStep;
+import org.sonar.server.platform.db.migration.step.MigrationSteps;
+import org.sonar.server.platform.db.migration.step.MigrationStepsExecutor;
+import org.sonar.server.platform.db.migration.step.RegisteredMigrationStep;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class MigrationEngineImplTest {
+ private MigrationHistory migrationHistory = mock(MigrationHistory.class);
+ private ComponentContainer serverContainer = new ComponentContainer();
+ private MigrationStepsExecutor stepsExecutor = mock(MigrationStepsExecutor.class);
+ private MigrationContainerPopulator populator = new MigrationContainerPopulator() {
+ @Override
+ public void populateContainer(MigrationContainer container) {
+ container.add(stepsExecutor);
+ }
+ };
+ private MigrationSteps migrationSteps = mock(MigrationSteps.class);
+
+ private MigrationEngineImpl underTest = new MigrationEngineImpl(migrationHistory, serverContainer, populator, migrationSteps);
+
+ @Test
+ public void execute_execute_all_steps_of_there_is_no_last_migration_number() {
+ when(migrationHistory.getLastMigrationNumber()).thenReturn(Optional.empty());
+ Stream<RegisteredMigrationStep> steps = Stream.of(new RegisteredMigrationStep(1, "doo", MigrationStep.class));
+ when(migrationSteps.readAll()).thenReturn(steps);
+
+ underTest.execute();
+
+ verify(migrationSteps).readAll();
+ verify(stepsExecutor).execute(steps);
+ }
+
+ @Test
+ public void execute_execute_steps_from_last_migration_number_plus_1() {
+ when(migrationHistory.getLastMigrationNumber()).thenReturn(Optional.of(50L));
+ Stream<RegisteredMigrationStep> steps = Stream.of(new RegisteredMigrationStep(1, "doo", MigrationStep.class));
+ when(migrationSteps.readFrom(51)).thenReturn(steps);
+
+ underTest.execute();
+
+ verify(migrationSteps).readFrom(51);
+ verify(stepsExecutor).execute(steps);
+ }
+
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/SimpleMigrationContainer.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/SimpleMigrationContainer.java
new file mode 100644
index 00000000000..5b242bf9774
--- /dev/null
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/SimpleMigrationContainer.java
@@ -0,0 +1,30 @@
+/*
+ * 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.migration.engine;
+
+import org.sonar.core.platform.ComponentContainer;
+
+public final class SimpleMigrationContainer extends ComponentContainer implements MigrationContainer {
+
+ @Override
+ public void cleanup() {
+ stopComponents();
+ }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/history/MigrationHistoryImplTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/history/MigrationHistoryImplTest.java
new file mode 100644
index 00000000000..6ece014caf8
--- /dev/null
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/history/MigrationHistoryImplTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.migration.history;
+
+import java.util.Arrays;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.version.SchemaMigrationMapper;
+import org.sonar.server.platform.db.migration.step.MigrationStep;
+import org.sonar.server.platform.db.migration.step.RegisteredMigrationStep;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class MigrationHistoryImplTest {
+ @Rule
+ public DbTester dbTester = DbTester.create(System2.INSTANCE);
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private DbSession session = dbTester.getSession();
+ private SchemaMigrationMapper schemaMigrationMapper = session.getMapper(SchemaMigrationMapper.class);
+
+ private MigrationHistoryImpl underTest = new MigrationHistoryImpl(dbTester.getDbClient());
+
+ @Test
+ public void start_does_not_fail_if_table_history_exists() {
+ underTest.start();
+ }
+
+ @Test
+ public void getLastMigrationNumber_returns_empty_if_history_table_is_empty() {
+ assertThat(underTest.getLastMigrationNumber()).isEmpty();
+ }
+
+ @Test
+ public void getLastMigrationNumber_returns_last_version_assuming_version_are_only_number() {
+ insert("12", "5", "30", "8");
+
+ assertThat(underTest.getLastMigrationNumber()).contains(30L);
+ }
+
+ @Test
+ public void done_fails_with_NPE_if_argument_is_null() {
+ expectedException.expect(NullPointerException.class);
+
+ underTest.done(null);
+ }
+
+ @Test
+ public void done_adds_migration_number_to_table() {
+ underTest.done(new RegisteredMigrationStep(12, "aa", MigrationStep.class));
+
+ assertThat(underTest.getLastMigrationNumber()).contains(12L);
+ }
+
+ private void insert(String... versions) {
+ Arrays.stream(versions).forEach(version -> schemaMigrationMapper.insert(version));
+ session.commit();
+ }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/history/NoTableMigrationHistoryImplTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/history/NoTableMigrationHistoryImplTest.java
new file mode 100644
index 00000000000..b61a737352a
--- /dev/null
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/history/NoTableMigrationHistoryImplTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.migration.history;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+
+public class NoTableMigrationHistoryImplTest {
+ @Rule
+ public DbTester dbTester = DbTester.createForSchema(System2.INSTANCE, NoTableMigrationHistoryImplTest.class, "empty.sql");
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private MigrationHistoryImpl underTest = new MigrationHistoryImpl(dbTester.getDbClient());
+
+ @Test
+ public void start_fails_with_ISE_if_table_history_does_not_exist() throws SQLException {
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Migration history table is missing");
+
+ underTest.start();
+ }
+
+
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationNumberTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationNumberTest.java
new file mode 100644
index 00000000000..cb2b391f218
--- /dev/null
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationNumberTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.migration.step;
+
+import java.util.Random;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.test.TestUtils;
+
+public class MigrationNumberTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void constructor_is_private() {
+ TestUtils.hasOnlyPrivateConstructors(MigrationNumber.class);
+ }
+
+ @Test
+ public void validate_throws_IAE_if_argument_is_less_then_0() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Migration number must be >= 0");
+
+ MigrationNumber.validate(-(Math.abs(new Random().nextInt()) + 1));
+ }
+
+ @Test
+ public void validate_accepts_0() {
+ MigrationNumber.validate(0);
+ }
+
+ @Test
+ public void validate_accepts_any_positive_long() {
+ MigrationNumber.validate(Math.abs(new Random().nextInt()));
+ }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepExecutionExceptionTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepExecutionExceptionTest.java
new file mode 100644
index 00000000000..d77e083fdc4
--- /dev/null
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepExecutionExceptionTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.migration.step;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class MigrationStepExecutionExceptionTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private RegisteredMigrationStep step = new RegisteredMigrationStep(1, "foo", MigrationStep.class);
+ private MigrationStepExecutionException underTest = new MigrationStepExecutionException(
+ step, new IllegalArgumentException("some cause"));
+
+ @Test
+ public void MigrationStepExecutionException_is_unchecked() {
+ assertThat(RuntimeException.class.isAssignableFrom(MigrationStepExecutionException.class)).isTrue();
+ }
+
+ @Test
+ public void constructor_throws_NPE_if_step_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("RegisteredMigrationStep can't be null");
+
+ new MigrationStepExecutionException(null, new NullPointerException("Some cause"));
+ }
+
+ @Test
+ public void constructor_throws_NPE_if_cause_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("cause can't be null");
+
+ new MigrationStepExecutionException(new RegisteredMigrationStep(1, "foo", MigrationStep.class), null);
+ }
+
+ @Test
+ public void constructor_sets_exception_message_from_step_argument() {
+ assertThat(underTest.getMessage()).isEqualTo("Execution of migration step #1 'foo' failed");
+ }
+
+ @Test
+ public void getFailingStep_returns_constructor_argument() {
+ assertThat(underTest.getFailingStep()).isSameAs(step);
+ }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepRegistryImplTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepRegistryImplTest.java
new file mode 100644
index 00000000000..ec016716c19
--- /dev/null
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepRegistryImplTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.migration.step;
+
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Random;
+import java.util.stream.Collectors;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class MigrationStepRegistryImplTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private MigrationStepRegistryImpl underTest = new MigrationStepRegistryImpl();
+
+ @Test
+ public void add_fails_with_IAE_if_migrationNumber_is_less_than_0() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Migration number must be >= 0");
+
+ underTest.add(-Math.abs(new Random().nextLong() + 1), "sdsd", MigrationStep.class);
+ }
+
+ @Test
+ public void add_fails_with_NPE_if_description_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("description can't be null");
+
+ underTest.add(12, null, MigrationStep.class);
+ }
+
+ @Test
+ public void add_fails_with_IAE_if_description_is_empty() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("description can't be empty");
+
+ underTest.add(12, "", MigrationStep.class);
+ }
+
+ @Test
+ public void add_fails_with_NPE_is_migrationstep_class_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("MigrationStep class can't be null");
+
+ underTest.add(12, "sdsd", null);
+ }
+
+ @Test
+ public void add_fails_with_ISE_when_called_twice_with_same_migration_number() {
+ underTest.add(12, "dsd", MigrationStep.class);
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("A migration is already registered for migration number '12'");
+
+ underTest.add(12, "dfsdf", MigrationStep.class);
+ }
+
+ @Test
+ public void build_fails_with_ISE_if_registry_is_empty() {
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Registry is empty");
+
+ underTest.build();
+ }
+
+ @Test
+ public void build_returns_a_MigrationStepsImpl_with_all_steps_added_to_registry_ordered_by_migration_number() {
+ underTest.add(343, "sss", MigrationStep2.class);
+ underTest.add(5, "aazsa", MigrationStep1.class);
+ underTest.add(66, "bbb", MigrationStep3.class);
+ underTest.add(2, "aaaa", MigrationStep4.class);
+
+ MigrationSteps migrationSteps = underTest.build();
+ assertThat(migrationSteps).isInstanceOf(MigrationStepsImpl.class);
+ List<RegisteredMigrationStep> registeredMigrationSteps = migrationSteps.readAll().collect(Collectors.toList());
+ assertThat(registeredMigrationSteps).hasSize(4);
+ verify(registeredMigrationSteps.get(0), 2, "aaaa", MigrationStep4.class);
+ verify(registeredMigrationSteps.get(1), 5, "aazsa", MigrationStep1.class);
+ verify(registeredMigrationSteps.get(2), 66, "bbb", MigrationStep3.class);
+ verify(registeredMigrationSteps.get(3), 343, "sss", MigrationStep2.class);
+ }
+
+ private static void verify(RegisteredMigrationStep step, int migrationNUmber, String description, Class<? extends MigrationStep> stepClass) {
+ assertThat(step.getMigrationNumber()).isEqualTo(migrationNUmber);
+ assertThat(step.getDescription()).isEqualTo(description);
+ assertThat(step.getStepClass()).isEqualTo(stepClass);
+ }
+
+ private static abstract class NoopMigrationStep implements MigrationStep {
+ @Override
+ public void execute() throws SQLException {
+ throw new IllegalStateException("execute is not implemented");
+ }
+ }
+
+ private static class MigrationStep1 extends NoopMigrationStep {
+
+ }
+
+ private static class MigrationStep2 extends NoopMigrationStep {
+
+ }
+
+ private static class MigrationStep3 extends NoopMigrationStep {
+
+ }
+
+ private static class MigrationStep4 extends NoopMigrationStep {
+
+ }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepsExecutorImplTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepsExecutorImplTest.java
new file mode 100644
index 00000000000..f5a1a0cba07
--- /dev/null
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepsExecutorImplTest.java
@@ -0,0 +1,213 @@
+/*
+ * 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.migration.step;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Stream;
+import org.hamcrest.Matchers;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.server.platform.db.migration.engine.MigrationContainer;
+import org.sonar.server.platform.db.migration.engine.SimpleMigrationContainer;
+import org.sonar.server.platform.db.migration.history.MigrationHistory;
+
+import static com.google.common.base.Preconditions.checkState;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+import static org.mockito.Mockito.mock;
+
+public class MigrationStepsExecutorImplTest {
+ @Rule
+ public LogTester logTester = new LogTester();
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private MigrationContainer migrationContainer = new SimpleMigrationContainer();
+ private MigrationHistory migrationHistor = mock(MigrationHistory.class);
+ private MigrationStepsExecutorImpl underTest = new MigrationStepsExecutorImpl(migrationContainer, migrationHistor);
+
+ @Test
+ public void execute_does_not_fail_when_stream_is_empty_and_log_start_stop_INFO() {
+ underTest.execute(Stream.empty());
+
+ assertThat(logTester.logs()).hasSize(2);
+ assertLogLevel(LoggerLevel.INFO, "Executing DB migrations...", "Executed DB migrations: success | time=");
+ }
+
+ @Test
+ public void execute_fails_with_ISE_if_no_instance_of_computation_step_exist_in_container() {
+ Stream<RegisteredMigrationStep> steps = Stream.of(registeredStepOf(1, MigrationStep1.class));
+
+ try {
+ underTest.execute(steps);
+ fail("execute should have thrown a IllegalStateException");
+ } catch (IllegalStateException e) {
+ assertThat(e).hasMessage("Can not find instance of " + MigrationStep1.class);
+ } finally {
+ assertThat(logTester.logs()).hasSize(2);
+ assertLogLevel(LoggerLevel.INFO, "Executing DB migrations...");
+ assertLogLevel(LoggerLevel.ERROR, "Executed DB migrations: failure | time=");
+ }
+ }
+
+ private void assertLogLevel(LoggerLevel level, String... expected) {
+ List<String> logs = logTester.logs(level);
+ assertThat(logs).hasSize(expected.length);
+ Iterator<String> iterator = logs.iterator();
+ Arrays.stream(expected).forEachOrdered(log -> {
+ if (log.endsWith(" | time=")) {
+ assertThat(iterator.next()).startsWith(log);
+ } else {
+ assertThat(iterator.next()).isEqualTo(log);
+ }
+ });
+ }
+
+ @Test
+ public void execute_execute_the_instance_of_type_specified_in_step_in_stream_order() {
+ migrationContainer.add(MigrationStep1.class, MigrationStep2.class, MigrationStep3.class);
+
+ underTest.execute(Stream.of(
+ registeredStepOf(1, MigrationStep2.class),
+ registeredStepOf(2, MigrationStep1.class),
+ registeredStepOf(3, MigrationStep3.class)));
+
+ assertThat(SingleCallCheckerMigrationStep.calledSteps)
+ .containsExactly(MigrationStep2.class, MigrationStep1.class, MigrationStep3.class);
+ assertThat(logTester.logs()).hasSize(8);
+ assertLogLevel(LoggerLevel.INFO,
+ "Executing DB migrations...",
+ "#1 '1-MigrationStep2'...",
+ "#1 '1-MigrationStep2': success | time=",
+ "#2 '2-MigrationStep1'...",
+ "#2 '2-MigrationStep1': success | time=",
+ "#3 '3-MigrationStep3'...",
+ "#3 '3-MigrationStep3': success | time=",
+ "Executed DB migrations: success | time=");
+
+ assertThat(migrationContainer.getComponentByType(MigrationStep1.class).isCalled()).isTrue();
+ assertThat(migrationContainer.getComponentByType(MigrationStep2.class).isCalled()).isTrue();
+ assertThat(migrationContainer.getComponentByType(MigrationStep3.class).isCalled()).isTrue();
+ }
+
+ @Test
+ public void execute_throws_MigrationStepExecutionException_on_first_failing_step_execution_throws_SQLException() {
+ migrationContainer.add(MigrationStep2.class, SqlExceptionFailingMigrationStep.class, MigrationStep3.class);
+ Stream<RegisteredMigrationStep> steps = Stream.of(
+ registeredStepOf(1, MigrationStep2.class),
+ registeredStepOf(2, SqlExceptionFailingMigrationStep.class),
+ registeredStepOf(3, MigrationStep3.class));
+
+ try {
+ underTest.execute(steps);
+ fail("a MigrationStepExecutionException should have been thrown");
+ } catch (MigrationStepExecutionException e) {
+ assertThat(e).hasMessage("Execution of migration step #2 '2-SqlExceptionFailingMigrationStep' failed");
+ assertThat(e).hasCause(SqlExceptionFailingMigrationStep.THROWN_EXCEPTION);
+ } finally {
+ assertThat(logTester.logs()).hasSize(6);
+ assertLogLevel(LoggerLevel.INFO,
+ "Executing DB migrations...",
+ "#1 '1-MigrationStep2'...",
+ "#1 '1-MigrationStep2': success | time=",
+ "#2 '2-SqlExceptionFailingMigrationStep'...");
+ assertLogLevel(LoggerLevel.ERROR,
+ "#2 '2-SqlExceptionFailingMigrationStep': failure | time=",
+ "Executed DB migrations: failure | time=");
+ }
+ }
+
+ @Test
+ public void execute_throws_MigrationStepExecutionException_on_first_failing_step_execution_throws_any_exception() {
+ migrationContainer.add(MigrationStep2.class, RuntimeExceptionFailingMigrationStep.class, MigrationStep3.class);
+
+ Stream<RegisteredMigrationStep> steps = Stream.of(
+ registeredStepOf(1, MigrationStep2.class),
+ registeredStepOf(2, RuntimeExceptionFailingMigrationStep.class),
+ registeredStepOf(3, MigrationStep3.class));
+ expectedException.expect(MigrationStepExecutionException.class);
+ expectedException.expectMessage("Execution of migration step #2 '2-RuntimeExceptionFailingMigrationStep' failed");
+ expectedException.expectCause(Matchers.sameInstance(RuntimeExceptionFailingMigrationStep.THROWN_EXCEPTION));
+
+ underTest.execute(steps);
+ }
+
+ private static RegisteredMigrationStep registeredStepOf(int migrationNumber, Class<? extends MigrationStep> migrationStep1Class) {
+ return new RegisteredMigrationStep(migrationNumber, migrationNumber + "-" + migrationStep1Class.getSimpleName(), migrationStep1Class);
+ }
+
+ private static abstract class SingleCallCheckerMigrationStep implements MigrationStep {
+ private static List<Class<? extends MigrationStep>> calledSteps = new ArrayList<>();
+ private boolean called = false;
+
+ @Override
+ public void execute() throws SQLException {
+ checkState(!called, "execute must not be called twice");
+ this.called = true;
+ calledSteps.add(getClass());
+ }
+
+ public boolean isCalled() {
+ return called;
+ }
+
+ public static List<Class<? extends MigrationStep>> getCalledSteps() {
+ return calledSteps;
+ }
+ }
+
+ public static final class MigrationStep1 extends SingleCallCheckerMigrationStep {
+
+ }
+
+ public static final class MigrationStep2 extends SingleCallCheckerMigrationStep {
+
+ }
+
+ public static final class MigrationStep3 extends SingleCallCheckerMigrationStep {
+
+ }
+
+ public static class SqlExceptionFailingMigrationStep implements MigrationStep {
+ private static final SQLException THROWN_EXCEPTION = new SQLException("Faking SQL exception in MigrationStep#execute()");
+
+ @Override
+ public void execute() throws SQLException {
+ throw THROWN_EXCEPTION;
+ }
+ }
+
+ public static class RuntimeExceptionFailingMigrationStep implements MigrationStep {
+ private static final RuntimeException THROWN_EXCEPTION = new RuntimeException("Faking failing migration step");
+
+ @Override
+ public void execute() throws SQLException {
+ throw THROWN_EXCEPTION;
+ }
+ }
+
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepsImplTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepsImplTest.java
new file mode 100644
index 00000000000..0ac73e7223e
--- /dev/null
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepsImplTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.migration.step;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class MigrationStepsImplTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private MigrationStepsImpl underTest = new MigrationStepsImpl(Arrays.asList(
+ new RegisteredMigrationStep(1, "mmmmmm", MigrationStep.class),
+ new RegisteredMigrationStep(2, "sds", MigrationStep.class),
+ new RegisteredMigrationStep(8, "ss", MigrationStep.class)));
+ private MigrationStepsImpl unorderedSteps = new MigrationStepsImpl(Arrays.asList(
+ new RegisteredMigrationStep(2, "sds", MigrationStep.class),
+ new RegisteredMigrationStep(8, "ss", MigrationStep.class),
+ new RegisteredMigrationStep(1, "mmmmmm", MigrationStep.class)));
+
+ @Test
+ public void constructor_fails_with_NPE_if_argument_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("steps can't be null");
+
+ new MigrationStepsImpl(null);
+ }
+
+ @Test
+ public void constructor_fails_with_IAE_if_argument_is_empty() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("steps can't be empty");
+
+ new MigrationStepsImpl(Collections.emptyList());
+ }
+
+ @Test
+ public void constructor_fails_with_NPE_if_argument_contains_a_null() {
+ expectedException.expect(NullPointerException.class);
+
+ new MigrationStepsImpl(Arrays.asList(
+ new RegisteredMigrationStep(12, "sdsd", MigrationStep.class),
+ null,
+ new RegisteredMigrationStep(88, "q", MigrationStep.class)));
+ }
+
+ @Test
+ public void getMaxMigrationNumber_returns_migration_of_last_step_in_constructor_list_argument() {
+ assertThat(underTest.getMaxMigrationNumber()).isEqualTo(8L);
+ assertThat(unorderedSteps.getMaxMigrationNumber()).isEqualTo(1L);
+ }
+
+ @Test
+ public void readAll_iterates_over_all_steps_in_constructor_list_argument() {
+ verifySteamContainsNumbers(underTest.readAll(), 1L, 2L, 8L);
+ }
+
+ @Test
+ public void readFrom_throws_IAE_if_number_is_less_than_0() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Migration number must be >= 0");
+
+ underTest.readFrom(-1);
+ }
+
+ @Test
+ public void readFrom_returns_stream_of_sublist_from_the_first_migration_with_number_greater_or_equal_to_argument() {
+ verifySteamContainsNumbers(underTest.readFrom(1), 1L, 2L, 8L);
+ verifySteamContainsNumbers(underTest.readFrom(2), 2L, 8L);
+ verifySteamContainsNumbers(underTest.readFrom(3), 8L);
+ verifySteamContainsNumbers(underTest.readFrom(4), 8L);
+ verifySteamContainsNumbers(underTest.readFrom(5), 8L);
+ verifySteamContainsNumbers(underTest.readFrom(6), 8L);
+ verifySteamContainsNumbers(underTest.readFrom(7), 8L);
+ verifySteamContainsNumbers(underTest.readFrom(8), 8L);
+ }
+
+ @Test
+ public void readFrom_returns_an_empty_stream_if_argument_is_greater_than_biggest_migration_number() {
+ verifySteamContainsNumbers(underTest.readFrom(9));
+ verifySteamContainsNumbers(unorderedSteps.readFrom(9));
+ }
+
+ private static void verifySteamContainsNumbers(Stream<RegisteredMigrationStep> stream, Long... expectedMigrationNumbers) {
+ List<RegisteredMigrationStep> steps = stream.collect(Collectors.toList());
+ assertThat(steps).hasSize(expectedMigrationNumbers.length);
+ Iterator<RegisteredMigrationStep> iterator = steps.iterator();
+ Arrays.stream(expectedMigrationNumbers).forEach(expected -> assertThat(iterator.next().getMigrationNumber()).isEqualTo(expected));
+ }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepsProviderTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepsProviderTest.java
new file mode 100644
index 00000000000..c5e9a61891f
--- /dev/null
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepsProviderTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.migration.step;
+
+import java.util.Random;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.InOrder;
+import org.sonar.server.platform.db.migration.version.DbVersion;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+public class MigrationStepsProviderTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private InternalMigrationStepRegistry internalMigrationStepRegistry = mock(InternalMigrationStepRegistry.class);
+ private MigrationStepsProvider underTest = new MigrationStepsProvider();
+
+ @Test
+ public void provide_throws_ISE_with_registry_build_throws_ISE_because_it_is_empty() {
+ IllegalStateException expected = new IllegalStateException("faking ISE because registry is empty");
+ when(internalMigrationStepRegistry.build()).thenThrow(expected);
+
+ expectedException.expect(expected.getClass());
+ expectedException.expectMessage(expected.getMessage());
+
+ underTest.provide(internalMigrationStepRegistry);
+ }
+
+ @Test
+ public void provide_calls_DbVersion_addStep_in_order_and_only_once() {
+ DbVersion dbVersion1 = newMockFailingOnSecondBuildCall();
+ DbVersion dbVersion2 = newMockFailingOnSecondBuildCall();
+ DbVersion dbVersion3 = newMockFailingOnSecondBuildCall();
+ InOrder inOrder = inOrder(dbVersion1, dbVersion2, dbVersion3);
+ MigrationSteps expected = mock(MigrationSteps.class);
+ when(internalMigrationStepRegistry.build()).thenReturn(expected);
+
+ assertThat(underTest.provide(internalMigrationStepRegistry, dbVersion1, dbVersion2, dbVersion3))
+ .isSameAs(expected);
+
+ inOrder.verify(dbVersion1).addSteps(internalMigrationStepRegistry);
+ inOrder.verify(dbVersion2).addSteps(internalMigrationStepRegistry);
+ inOrder.verify(dbVersion3).addSteps(internalMigrationStepRegistry);
+ inOrder.verifyNoMoreInteractions();
+
+ // calling a second time with another argument, it's just ignored
+ DbVersion dbVersion4 = newMockFailingOnSecondBuildCall();
+ assertThat(underTest.provide(internalMigrationStepRegistry, dbVersion4)).isSameAs(expected);
+ verifyZeroInteractions(dbVersion4);
+ }
+
+ @Test
+ public void provide_always_returns_the_same_MigrationSteps_instance_and_calls_registry_build_only_once() {
+ MigrationSteps migrationSteps = mock(MigrationSteps.class);
+ when(internalMigrationStepRegistry.build())
+ .thenReturn(migrationSteps)
+ .thenThrow(new RuntimeException("method build should not be called twice"));
+
+ for (int i = 0; i < Math.abs(new Random().nextInt(50)) + 1; i++) {
+ assertThat(underTest.provide(internalMigrationStepRegistry)).isSameAs(migrationSteps);
+ }
+
+ }
+
+ private static DbVersion newMockFailingOnSecondBuildCall() {
+ DbVersion res = mock(DbVersion.class);
+ doNothing()
+ .doThrow(new RuntimeException("addStep should not be called twice"))
+ .when(res)
+ .addSteps(any(MigrationStepRegistry.class));
+ return res;
+ }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/RegisteredMigrationStepTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/RegisteredMigrationStepTest.java
new file mode 100644
index 00000000000..8029d7b4bb0
--- /dev/null
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/RegisteredMigrationStepTest.java
@@ -0,0 +1,59 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.step;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class RegisteredMigrationStepTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void constructor_throws_NPE_if_description_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("description can't be null");
+
+ new RegisteredMigrationStep(1, null, MigrationStep.class);
+ }
+
+ @Test
+ public void constructor_throws_NPE_if_MigrationStep_class_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("MigrationStep class can't be null");
+
+ new RegisteredMigrationStep(1, "", null);
+ }
+
+ @Test
+ public void verify_getters() {
+ RegisteredMigrationStep underTest = new RegisteredMigrationStep(3, "foo", MyMigrationStep.class);
+ assertThat(underTest.getMigrationNumber()).isEqualTo(3L);
+ assertThat(underTest.getDescription()).isEqualTo("foo");
+ assertThat(underTest.getStepClass()).isEqualTo(MyMigrationStep.class);
+ }
+
+ private static abstract class MyMigrationStep implements MigrationStep {
+
+ }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/DbVersionModuleTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/DbVersionModuleTest.java
new file mode 100644
index 00000000000..0ad182db9f6
--- /dev/null
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/DbVersionModuleTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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.migration.version;
+
+import org.junit.Test;
+import org.sonar.core.platform.ComponentContainer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DbVersionModuleTest {
+ private static final int COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER = 2;
+
+ private DbVersionModule underTest = new DbVersionModule();
+
+ @Test
+ public void verify_component_count() {
+ ComponentContainer container = new ComponentContainer();
+
+ underTest.configure(container);
+
+ assertThat(container.getPicoContainer().getComponentAdapters())
+ .hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 1);
+ }
+
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/DbVersionTestUtils.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/DbVersionTestUtils.java
new file mode 100644
index 00000000000..39b1a3b6376
--- /dev/null
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/DbVersionTestUtils.java
@@ -0,0 +1,67 @@
+/*
+ * 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.migration.version;
+
+import java.util.HashSet;
+import java.util.Set;
+import org.sonar.server.platform.db.migration.step.MigrationStep;
+import org.sonar.server.platform.db.migration.step.MigrationStepRegistry;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.platform.db.migration.step.MigrationNumber.validate;
+
+public class DbVersionTestUtils {
+
+ public static void verifyMinimumMigrationNumber(DbVersion underTest, int minimumMigrationNumber) {
+ TestMigrationStepRegistry registry = new TestMigrationStepRegistry() {
+ @Override
+ public <T extends MigrationStep> MigrationStepRegistry add(long migrationNumber, String description, Class<T> stepClass) {
+ super.add(migrationNumber, description, MigrationStep.class);
+
+ assertThat(migrationNumber).isGreaterThanOrEqualTo(minimumMigrationNumber);
+ return this;
+ }
+ };
+
+ underTest.addSteps(registry);
+
+ assertThat(registry.migrationNumbers).describedAs("No migration added to registry").isNotEmpty();
+ assertThat(registry.migrationNumbers.stream().sorted().findFirst().get()).isEqualTo(minimumMigrationNumber);
+ }
+
+ public static void verifyMigrationCount(DbVersion underTest, int migrationCount) {
+ TestMigrationStepRegistry registry = new TestMigrationStepRegistry();
+ underTest.addSteps(registry);
+ assertThat(registry.migrationNumbers).hasSize(migrationCount);
+ }
+
+ private static class TestMigrationStepRegistry implements MigrationStepRegistry {
+ private Set<Long> migrationNumbers = new HashSet<>();
+
+ @Override
+ public <T extends MigrationStep> MigrationStepRegistry add(long migrationNumber, String description, Class<T> stepClass) {
+ validate(migrationNumber);
+ assertThat(description).isNotEmpty();
+ assertThat(stepClass).isNotNull();
+ assertThat(migrationNumbers.add(migrationNumber)).isTrue();
+ return this;
+ }
+ }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v63/AddUuidToEventsTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v63/AddUuidToEventsTest.java
new file mode 100644
index 00000000000..9ccc01c861b
--- /dev/null
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v63/AddUuidToEventsTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.migration.version.v63;
+
+import java.sql.SQLException;
+import java.sql.Types;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+
+
+public class AddUuidToEventsTest {
+
+ @Rule
+ public final DbTester dbTester = DbTester.createForSchema(System2.INSTANCE, AddUuidToEventsTest.class, "previous-events.sql");
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private AddUuidToEvents underTest = new AddUuidToEvents(dbTester.database());
+
+ @Test
+ public void creates_table_on_empty_db() throws SQLException {
+ underTest.execute();
+
+ dbTester.assertColumnDefinition("events", "uuid", Types.VARCHAR, 40, true);
+ }
+
+ @Test
+ public void migration_is_not_reentrant() throws SQLException {
+ underTest.execute();
+
+ expectedException.expect(IllegalStateException.class);
+
+ underTest.execute();
+ }
+
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v63/DbVersion63Test.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v63/DbVersion63Test.java
new file mode 100644
index 00000000000..6209c1f2546
--- /dev/null
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v63/DbVersion63Test.java
@@ -0,0 +1,41 @@
+/*
+ * 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.migration.version.v63;
+
+import org.junit.Test;
+
+import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMigrationCount;
+import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMinimumMigrationNumber;
+
+public class DbVersion63Test {
+ private DbVersion63 underTest = new DbVersion63();
+
+ @Test
+ public void migrationNumber_starts_at_1500() {
+ verifyMinimumMigrationNumber(underTest, 1500);
+ }
+
+ @Test
+ public void verify_migration_count() {
+ verifyMigrationCount(underTest, 3);
+ }
+
+
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v63/MakeUuidNotNullOnEventsTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v63/MakeUuidNotNullOnEventsTest.java
new file mode 100644
index 00000000000..0e1d922f7b7
--- /dev/null
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v63/MakeUuidNotNullOnEventsTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.migration.version.v63;
+
+import java.sql.SQLException;
+import java.sql.Types;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+
+import static java.lang.String.valueOf;
+
+public class MakeUuidNotNullOnEventsTest {
+
+ private static final String TABLE_EVENTS = "events";
+
+ @Rule
+ public DbTester db = DbTester.createForSchema(System2.INSTANCE, MakeUuidNotNullOnEventsTest.class, "in_progress_events.sql");
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private MakeUuidNotNullOnEvents underTest = new MakeUuidNotNullOnEvents(db.database());
+
+ @Test
+ public void migration_sets_uuid_column_not_nullable_on_empty_table() throws SQLException {
+ underTest.execute();
+
+ verifyColumnDefinition();
+ }
+
+ @Test
+ public void migration_sets_uuid_column_not_nullable_on_populated_table() throws SQLException {
+ insertEvent(1, true);
+ insertEvent(2, true);
+
+ underTest.execute();
+
+ verifyColumnDefinition();
+ }
+
+ @Test
+ public void migration_fails_if_some_row_has_a_null_uuid() throws SQLException {
+ insertEvent(1, false);
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Fail to execute");
+
+ underTest.execute();
+ }
+
+ private void verifyColumnDefinition() {
+ db.assertColumnDefinition(TABLE_EVENTS, "uuid", Types.VARCHAR, 40, false);
+ }
+
+ private String insertEvent(long id, boolean hasUuid) {
+ String uuid = "uuid_" + id;
+ db.executeInsert(
+ TABLE_EVENTS,
+ "ID", valueOf(id),
+ "ANALYSIS_UUID", valueOf(id + 10),
+ "COMPONENT_UUID", valueOf(id + 100),
+ "CREATED_AT", id,
+ "EVENT_DATE", id,
+ "UUID", hasUuid ? "uuid_" + id : null);
+ return uuid;
+ }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v63/PopulateUuidColumnOfEventsTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v63/PopulateUuidColumnOfEventsTest.java
new file mode 100644
index 00000000000..e9369d46e71
--- /dev/null
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v63/PopulateUuidColumnOfEventsTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.migration.version.v63;
+
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.commons.lang.StringUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.UuidFactoryImpl;
+import org.sonar.db.DbTester;
+
+import static java.lang.String.valueOf;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class PopulateUuidColumnOfEventsTest {
+
+ private static final String TABLE_EVENTS = "events";
+
+ @Rule
+ public DbTester db = DbTester.createForSchema(System2.INSTANCE, PopulateUuidColumnOfEventsTest.class, "in_progress_events.sql");
+
+ private PopulateUuidColumnOfEvents underTest = new PopulateUuidColumnOfEvents(db.database(), UuidFactoryImpl.INSTANCE);
+
+ @Test
+ public void migration_has_no_effect_on_empty_tables() throws SQLException {
+ underTest.execute();
+
+ assertThat(db.countRowsOfTable(TABLE_EVENTS)).isEqualTo(0);
+ }
+
+ @Test
+ public void migration_generates_uuids() throws SQLException {
+ insertEvents(1);
+ insertEvents(2);
+ insertEvents(3);
+ db.commit();
+
+ underTest.execute();
+
+ verifyUuids(3);
+ }
+
+ @Test
+ public void migration_is_reentrant() throws SQLException {
+ insertEvents(1);
+
+ underTest.execute();
+ verifyUuids(1);
+
+ underTest.execute();
+ verifyUuids(1);
+ }
+
+ private void verifyUuids(int expectedCount) {
+ List<Map<String, Object>> rows = db.select("select uuid from events where uuid is not null");
+ Set<Object> uuids = rows.stream().map(cols -> cols.get("UUID")).filter(uuid -> StringUtils.isNotBlank((String) uuid)).collect(Collectors.toSet());
+ assertThat(uuids).hasSize(expectedCount);
+ }
+
+ private String insertEvents(long id) {
+ String uuid = "uuid_" + id;
+ db.executeInsert(
+ TABLE_EVENTS,
+ "ID", valueOf(id),
+ "COMPONENT_UUID", valueOf(id + 10),
+ "ANALYSIS_UUID", valueOf(id + 100),
+ "EVENT_DATE", 123456,
+ "CREATED_AT", 456789);
+ return uuid;
+ }
+
+}
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/history/NoTableMigrationHistoryImplTest/empty.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/history/NoTableMigrationHistoryImplTest/empty.sql
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/history/NoTableMigrationHistoryImplTest/empty.sql
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v63/AddUuidToEventsTest/previous-events.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v63/AddUuidToEventsTest/previous-events.sql
new file mode 100644
index 00000000000..b0712271a84
--- /dev/null
+++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v63/AddUuidToEventsTest/previous-events.sql
@@ -0,0 +1,11 @@
+CREATE TABLE "EVENTS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "NAME" VARCHAR(400),
+ "ANALYSIS_UUID" VARCHAR(50) NOT NULL,
+ "COMPONENT_UUID" VARCHAR(50),
+ "CATEGORY" VARCHAR(50),
+ "EVENT_DATE" BIGINT NOT NULL,
+ "CREATED_AT" BIGINT NOT NULL,
+ "DESCRIPTION" VARCHAR(4000),
+ "EVENT_DATA" VARCHAR(4000)
+);
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v63/MakeUuidNotNullOnEventsTest/in_progress_events.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v63/MakeUuidNotNullOnEventsTest/in_progress_events.sql
new file mode 100644
index 00000000000..d83b16dd817
--- /dev/null
+++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v63/MakeUuidNotNullOnEventsTest/in_progress_events.sql
@@ -0,0 +1,12 @@
+CREATE TABLE "EVENTS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "UUID" VARCHAR(40),
+ "NAME" VARCHAR(400),
+ "ANALYSIS_UUID" VARCHAR(50) NOT NULL,
+ "COMPONENT_UUID" VARCHAR(50),
+ "CATEGORY" VARCHAR(50),
+ "EVENT_DATE" BIGINT NOT NULL,
+ "CREATED_AT" BIGINT NOT NULL,
+ "DESCRIPTION" VARCHAR(4000),
+ "EVENT_DATA" VARCHAR(4000)
+);
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v63/PopulateUuidColumnOfEventsTest/in_progress_events.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v63/PopulateUuidColumnOfEventsTest/in_progress_events.sql
new file mode 100644
index 00000000000..d83b16dd817
--- /dev/null
+++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v63/PopulateUuidColumnOfEventsTest/in_progress_events.sql
@@ -0,0 +1,12 @@
+CREATE TABLE "EVENTS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "UUID" VARCHAR(40),
+ "NAME" VARCHAR(400),
+ "ANALYSIS_UUID" VARCHAR(50) NOT NULL,
+ "COMPONENT_UUID" VARCHAR(50),
+ "CATEGORY" VARCHAR(50),
+ "EVENT_DATE" BIGINT NOT NULL,
+ "CREATED_AT" BIGINT NOT NULL,
+ "DESCRIPTION" VARCHAR(4000),
+ "EVENT_DATA" VARCHAR(4000)
+);