]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8445 run DB migration of SQ 6.3 with Java
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Thu, 8 Dec 2016 17:25:20 +0000 (18:25 +0100)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Wed, 14 Dec 2016 11:11:52 +0000 (12:11 +0100)
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

77 files changed:
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationEngineModule.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainer.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainerImpl.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainerPopulator.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationContainerPopulatorImpl.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationEngine.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/MigrationEngineImpl.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/engine/package-info.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/history/MigrationHistory.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/history/MigrationHistoryImpl.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/InternalMigrationStepRegistry.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationNumber.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStep.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepExecutionException.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepRegistry.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepRegistryImpl.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationSteps.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepsExecutor.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepsExecutorImpl.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepsImpl.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/MigrationStepsProvider.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/RegisteredMigrationStep.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/step/package-info.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/DbVersion.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/DbVersionModule.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/package-info.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v63/AddUuidToEvents.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v63/DbVersion63.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v63/MakeUuidNotNullOnEvents.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v63/PopulateUuidColumnOfEvents.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v63/package-info.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/MigrationEngineModuleTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/MigrationContainerImplTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/MigrationContainerPopulatorImplTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/MigrationEngineImplTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/engine/SimpleMigrationContainer.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/history/MigrationHistoryImplTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/history/NoTableMigrationHistoryImplTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationNumberTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepExecutionExceptionTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepRegistryImplTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepsExecutorImplTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepsImplTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/MigrationStepsProviderTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/step/RegisteredMigrationStepTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/DbVersionModuleTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/DbVersionTestUtils.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v63/AddUuidToEventsTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v63/DbVersion63Test.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v63/MakeUuidNotNullOnEventsTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v63/PopulateUuidColumnOfEventsTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/history/NoTableMigrationHistoryImplTest/empty.sql [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v63/AddUuidToEventsTest/previous-events.sql [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v63/MakeUuidNotNullOnEventsTest/in_progress_events.sql [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v63/PopulateUuidColumnOfEventsTest/in_progress_events.sql [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/Platform.java
server/sonar-server/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationImpl.java
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelSafeMode.java
server/sonar-server/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationImplAsynchronousTest.java
server/sonar-server/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationImplConcurrentAccessTest.java
server/sonar-server/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationImplTest.java
server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1500_add_uuid_to_events.rb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1501_populate_uuid_of_events.rb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1502_make_uuid_not_null_on_events.rb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/lib/database_version.rb
sonar-db/src/main/java/org/sonar/db/version/MigrationStepModule.java
sonar-db/src/main/java/org/sonar/db/version/v63/AddUuidToEvents.java [deleted file]
sonar-db/src/main/java/org/sonar/db/version/v63/MakeUuidNotNullOnEvents.java [deleted file]
sonar-db/src/main/java/org/sonar/db/version/v63/PopulateUuidColumnOfEvents.java [deleted file]
sonar-db/src/main/java/org/sonar/db/version/v63/package-info.java [deleted file]
sonar-db/src/test/java/org/sonar/db/version/MigrationStepModuleTest.java
sonar-db/src/test/java/org/sonar/db/version/v63/AddUuidToEventsTest.java [deleted file]
sonar-db/src/test/java/org/sonar/db/version/v63/MakeUuidNotNullOnEventsTest.java [deleted file]
sonar-db/src/test/java/org/sonar/db/version/v63/PopulateUuidColumnOfEventsTest.java [deleted file]
sonar-db/src/test/resources/org/sonar/db/version/v63/AddUuidToEventsTest/previous-events.sql [deleted file]
sonar-db/src/test/resources/org/sonar/db/version/v63/MakeUuidNotNullOnEventsTest/in_progress_events.sql [deleted file]
sonar-db/src/test/resources/org/sonar/db/version/v63/PopulateUuidColumnOfEventsTest/in_progress_events.sql [deleted file]

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 (file)
index 0000000..69ca393
--- /dev/null
@@ -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 (file)
index 0000000..507a124
--- /dev/null
@@ -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 (file)
index 0000000..b998879
--- /dev/null
@@ -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 (file)
index 0000000..23de1b2
--- /dev/null
@@ -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 (file)
index 0000000..b5e81d2
--- /dev/null
@@ -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 (file)
index 0000000..51489a1
--- /dev/null
@@ -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 (file)
index 0000000..5a0af4a
--- /dev/null
@@ -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 (file)
index 0000000..8b76f6a
--- /dev/null
@@ -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 (file)
index 0000000..cc4edfa
--- /dev/null
@@ -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 (file)
index 0000000..9c00b98
--- /dev/null
@@ -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 (file)
index 0000000..de0c450
--- /dev/null
@@ -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 (file)
index 0000000..93e5ba9
--- /dev/null
@@ -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 (file)
index 0000000..c04ed0f
--- /dev/null
@@ -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 (file)
index 0000000..6c9cc12
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.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 (file)
index 0000000..8c19a2b
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.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 (file)
index 0000000..9310bcf
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.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 (file)
index 0000000..94296f4
--- /dev/null
@@ -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 (file)
index 0000000..c52f9b6
--- /dev/null
@@ -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 (file)
index 0000000..331b928
--- /dev/null
@@ -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 (file)
index 0000000..fd07a47
--- /dev/null
@@ -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 (file)
index 0000000..a599af6
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.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 (file)
index 0000000..67f3d1c
--- /dev/null
@@ -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 (file)
index 0000000..1e449f7
--- /dev/null
@@ -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 (file)
index 0000000..971d399
--- /dev/null
@@ -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 (file)
index 0000000..3047464
--- /dev/null
@@ -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 (file)
index 0000000..c31514b
--- /dev/null
@@ -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 (file)
index 0000000..9f4b53b
--- /dev/null
@@ -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 (file)
index 0000000..03800cd
--- /dev/null
@@ -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 (file)
index 0000000..972c7ef
--- /dev/null
@@ -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 (file)
index 0000000..f4cecbf
--- /dev/null
@@ -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 (file)
index 0000000..86bf6ad
--- /dev/null
@@ -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 (file)
index 0000000..fb5bb44
--- /dev/null
@@ -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 (file)
index 0000000..82a9402
--- /dev/null
@@ -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 (file)
index 0000000..a0cc4d5
--- /dev/null
@@ -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 (file)
index 0000000..7e334fb
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.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 (file)
index 0000000..5b242bf
--- /dev/null
@@ -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 (file)
index 0000000..6ece014
--- /dev/null
@@ -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 (file)
index 0000000..b61a737
--- /dev/null
@@ -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 (file)
index 0000000..cb2b391
--- /dev/null
@@ -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 (file)
index 0000000..d77e083
--- /dev/null
@@ -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 (file)
index 0000000..ec01671
--- /dev/null
@@ -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 (file)
index 0000000..f5a1a0c
--- /dev/null
@@ -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 (file)
index 0000000..0ac73e7
--- /dev/null
@@ -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 (file)
index 0000000..c5e9a61
--- /dev/null
@@ -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 (file)
index 0000000..8029d7b
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.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 (file)
index 0000000..0ad182d
--- /dev/null
@@ -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 (file)
index 0000000..39b1a3b
--- /dev/null
@@ -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 (file)
index 0000000..9ccc01c
--- /dev/null
@@ -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 (file)
index 0000000..6209c1f
--- /dev/null
@@ -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 (file)
index 0000000..0e1d922
--- /dev/null
@@ -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 (file)
index 0000000..e9369d4
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
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 (file)
index 0000000..b071227
--- /dev/null
@@ -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 (file)
index 0000000..d83b16d
--- /dev/null
@@ -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 (file)
index 0000000..d83b16d
--- /dev/null
@@ -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)
+);
index 68f4dadbd9100f8b38296ecc236dafd9a40d27b6..c1980a69c19745cb301fe0d13c1cb05c7defa8e5 100644 (file)
@@ -31,6 +31,7 @@ import org.sonar.api.utils.log.Loggers;
 import org.sonar.api.utils.log.Profiler;
 import org.sonar.core.platform.ComponentContainer;
 import org.sonar.db.version.DatabaseVersion;
+import org.sonar.server.platform.db.migration.engine.MigrationEngine;
 import org.sonar.server.platform.platformlevel.PlatformLevel;
 import org.sonar.server.platform.platformlevel.PlatformLevel1;
 import org.sonar.server.platform.platformlevel.PlatformLevel2;
@@ -39,6 +40,8 @@ import org.sonar.server.platform.platformlevel.PlatformLevel4;
 import org.sonar.server.platform.platformlevel.PlatformLevelSafeMode;
 import org.sonar.server.platform.platformlevel.PlatformLevelStartup;
 
+import static com.google.common.base.Preconditions.checkState;
+
 /**
  * @since 2.2
  */
@@ -94,6 +97,12 @@ public class Platform {
     }
   }
 
+  public void upgradeDb() {
+    checkState(isInSafeMode(), "Must be in safe mode");
+    MigrationEngine migrationEngine = currentLevel.getContainer().getComponentByType(MigrationEngine.class);
+    migrationEngine.execute();
+  }
+
   // Platform is injected in Pico, so do not rename this method "start"
   public void doStart() {
     doStart(Startup.ALL);
index 4770873e53ba56bc3eea9d658b4083c88b34dcfc..193f8fd6143cbfeb8e9ddec4fbc9834ac1403eb4 100644 (file)
@@ -26,6 +26,7 @@ import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.api.utils.log.Profiler;
 import org.sonar.server.platform.Platform;
+import org.sonar.server.platform.db.migration.engine.MigrationEngine;
 import org.sonar.server.ruby.RubyBridge;
 
 import static org.sonar.server.platform.db.migration.DatabaseMigrationState.Status;
@@ -37,11 +38,12 @@ public class DatabaseMigrationImpl implements DatabaseMigration {
 
   private static final Logger LOGGER = Loggers.get(DatabaseMigrationImpl.class);
 
-  private final RubyBridge rubyBridge;
   /**
    * ExecutorService implements threads management.
    */
   private final DatabaseMigrationExecutorService executorService;
+  private final RubyBridge rubyBridge;
+  private final MigrationEngine migrationEngine;
   private final Platform platform;
   private final MutableDatabaseMigrationState migrationState;
   /**
@@ -58,12 +60,13 @@ public class DatabaseMigrationImpl implements DatabaseMigration {
    */
   private final AtomicBoolean running = new AtomicBoolean(false);
 
-  public DatabaseMigrationImpl(RubyBridge rubyBridge, DatabaseMigrationExecutorService executorService, Platform platform,
-                               MutableDatabaseMigrationState migrationState) {
-    this.rubyBridge = rubyBridge;
+  public DatabaseMigrationImpl(DatabaseMigrationExecutorService executorService, MutableDatabaseMigrationState migrationState,
+    RubyBridge rubyBridge, MigrationEngine migrationEngine, Platform platform) {
     this.executorService = executorService;
-    this.platform = platform;
     this.migrationState = migrationState;
+    this.rubyBridge = rubyBridge;
+    this.migrationEngine = migrationEngine;
+    this.platform = platform;
   }
 
   @Override
@@ -120,6 +123,7 @@ public class DatabaseMigrationImpl implements DatabaseMigration {
     Profiler profiler = Profiler.createIfTrace(LOGGER);
     profiler.startTrace("Starting DB Migration");
     rubyBridge.databaseMigration().trigger();
+    migrationEngine.execute();
     profiler.stopTrace("DB Migration ended");
   }
 
index 5d86d638fce1e14af91332e88979dceb726424b5..c9043e0008df123e0100de6b70bc8a0f42751853 100644 (file)
@@ -22,6 +22,8 @@ package org.sonar.server.platform.platformlevel;
 import org.sonar.server.organization.NoopDefaultOrganizationCache;
 import org.sonar.server.platform.ServerImpl;
 import org.sonar.server.platform.db.migration.DatabaseMigrationImpl;
+import org.sonar.server.platform.db.migration.MigrationEngineModule;
+import org.sonar.server.platform.db.migration.version.DbVersionModule;
 import org.sonar.server.platform.web.WebPagesFilter;
 import org.sonar.server.platform.ws.DbMigrationStatusAction;
 import org.sonar.server.platform.ws.IndexAction;
@@ -63,5 +65,7 @@ public class PlatformLevelSafeMode extends PlatformLevel {
 
       NoopDefaultOrganizationCache.class);
     add(DatabaseMigrationImpl.class);
+    add(DbVersionModule.class);
+    addIfStartupLeader(MigrationEngineModule.class);
   }
 }
index d5904c95389c58d051e089a9dd98b50302f4f57d..eea53055a8a886803d4edce4a65425f854044002 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.server.platform.db.migration;
 
 import org.junit.Test;
 import org.sonar.server.platform.Platform;
+import org.sonar.server.platform.db.migration.engine.MigrationEngine;
 import org.sonar.server.ruby.RubyBridge;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -39,10 +40,11 @@ public class DatabaseMigrationImplAsynchronousTest {
       taskSuppliedForAsyncProcess = true;
     }
   };
+  private MutableDatabaseMigrationState migrationState = mock(MutableDatabaseMigrationState.class);
   private RubyBridge rubyBridge = mock(RubyBridge.class);
   private Platform platform = mock(Platform.class);
-  private MutableDatabaseMigrationState migrationState = mock(MutableDatabaseMigrationState.class);
-  private DatabaseMigrationImpl underTest = new DatabaseMigrationImpl(rubyBridge, executorService, platform, migrationState);
+  private MigrationEngine migrationEngine = mock(MigrationEngine.class);
+  private DatabaseMigrationImpl underTest = new DatabaseMigrationImpl(executorService, migrationState, rubyBridge, migrationEngine, platform);
 
   @Test
   public void testName() throws Exception {
index 75fe3d42753f6e6ae7e3b3fb32df4b3751ee8fe8..1b27e76ecd16c4e65262007c0d5c5d4125b568fa 100644 (file)
@@ -28,6 +28,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 import org.junit.After;
 import org.junit.Test;
 import org.sonar.server.platform.Platform;
+import org.sonar.server.platform.db.migration.engine.MigrationEngine;
 import org.sonar.server.ruby.RubyBridge;
 import org.sonar.server.ruby.RubyDatabaseMigration;
 import org.sonar.server.ruby.RubyRailsRoutes;
@@ -72,11 +73,12 @@ public class DatabaseMigrationImplConcurrentAccessTest {
       }
     }
   };
+  private MutableDatabaseMigrationState migrationState = mock(MutableDatabaseMigrationState.class);
   private RubyBridge rubyBridge = mock(RubyBridge.class);
   private Platform platform = mock(Platform.class);
   private RubyRailsRoutes railsRoutes = mock(RubyRailsRoutes.class);
-  private MutableDatabaseMigrationState migrationState = mock(MutableDatabaseMigrationState.class);
-  private DatabaseMigrationImpl underTest = new DatabaseMigrationImpl(rubyBridge, executorService, platform, migrationState);
+  private MigrationEngine migrationEngine = mock(MigrationEngine.class);
+  private DatabaseMigrationImpl underTest = new DatabaseMigrationImpl(executorService, migrationState, rubyBridge, migrationEngine, platform);
 
   @After
   public void tearDown() {
index a1a248a074e15d6d0f5ad33ad868e31e20abfbf9..3704d8c776158536f3c6aeacdd9e41c4d123be9b 100644 (file)
@@ -23,6 +23,7 @@ import java.util.Date;
 import org.junit.Test;
 import org.mockito.InOrder;
 import org.sonar.server.platform.Platform;
+import org.sonar.server.platform.db.migration.engine.MigrationEngine;
 import org.sonar.server.ruby.RubyBridge;
 import org.sonar.server.ruby.RubyDatabaseMigration;
 import org.sonar.server.ruby.RubyRailsRoutes;
@@ -49,14 +50,15 @@ public class DatabaseMigrationImplTest {
       command.run();
     }
   };
+  private MutableDatabaseMigrationState migrationState = new DatabaseMigrationStateImpl();
   private RubyBridge rubyBridge = mock(RubyBridge.class);
   private RubyDatabaseMigration rubyDatabaseMigration = mock(RubyDatabaseMigration.class);
   private RubyRailsRoutes rubyRailsRoutes = mock(RubyRailsRoutes.class);
   private Platform platform = mock(Platform.class);
-  private MutableDatabaseMigrationState migrationState = new DatabaseMigrationStateImpl();
-  private InOrder inOrder = inOrder(rubyDatabaseMigration, rubyBridge, rubyRailsRoutes, platform);
+  private MigrationEngine migrationEngine = mock(MigrationEngine.class);
+  private InOrder inOrder = inOrder(rubyDatabaseMigration, rubyBridge, rubyRailsRoutes, platform, migrationEngine);
 
-  private DatabaseMigrationImpl underTest = new DatabaseMigrationImpl(rubyBridge, executorService, platform, migrationState);
+  private DatabaseMigrationImpl underTest = new DatabaseMigrationImpl(executorService, migrationState, rubyBridge, migrationEngine, platform);
 
   @Test
   public void startit_calls_databasemigration_trigger_in_a_separate_thread() {
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1500_add_uuid_to_events.rb b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1500_add_uuid_to_events.rb
deleted file mode 100644 (file)
index 2f7fdd2..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2016 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-#
-# SonarQube 6.3
-#
-class AddUuidToEvents < ActiveRecord::Migration
-
-  def self.up
-    execute_java_migration('org.sonar.db.version.v63.AddUuidToEvents')
-  end
-end
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1501_populate_uuid_of_events.rb b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1501_populate_uuid_of_events.rb
deleted file mode 100644 (file)
index b155e25..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2016 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-#
-# SonarQube 6.3
-#
-class PopulateUuidOfEvents < ActiveRecord::Migration
-
-  def self.up
-    execute_java_migration('org.sonar.db.version.v63.PopulateUuidColumnOfEvents')
-  end
-end
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1502_make_uuid_not_null_on_events.rb b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1502_make_uuid_not_null_on_events.rb
deleted file mode 100644 (file)
index 8aee750..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2016 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 3 of the License, or (at your option) any later version.
-#
-# SonarQube is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-
-#
-# SonarQube 6.3
-#
-class MakeUuidNotNullOnEvents < ActiveRecord::Migration
-
-  def self.up
-    execute_java_migration('org.sonar.db.version.v63.MakeUuidNotNullOnEvents')
-  end
-end
index ebac46154421b59d425c567f35d746598231112a..52f032d0441de6694274f2c04cd2e3f4f09ad97b 100644 (file)
@@ -64,6 +64,7 @@ class DatabaseVersion
 
   def self.upgrade_and_start
     ActiveRecord::Migrator.migrate(migrations_path)
+    Java::OrgSonarServerPlatform::Platform.getInstance().upgradeDb()
     Java::OrgSonarServerPlatform::Platform.getInstance().doStart()
     load_java_web_services
   end
index 96be3391fe721f94c764c5a4d820f58cd30e164d..df4322c14b445e95d94899db9e0f8a0f8b7adafe 100644 (file)
@@ -141,9 +141,6 @@ import org.sonar.db.version.v62.PopulateOrganizationUuidOfGroups;
 import org.sonar.db.version.v62.PopulateOrganizationUuidOfPermissionTemplates;
 import org.sonar.db.version.v62.PopulateOrganizationUuidOfUserRoles;
 import org.sonar.db.version.v62.UpdateQualityGateConditionsOnCoverage;
-import org.sonar.db.version.v63.AddUuidToEvents;
-import org.sonar.db.version.v63.MakeUuidNotNullOnEvents;
-import org.sonar.db.version.v63.PopulateUuidColumnOfEvents;
 
 public class MigrationStepModule extends Module {
   @Override
@@ -293,11 +290,6 @@ public class MigrationStepModule extends Module {
       DropMeasureFiltersTables.class,
       DropIssueFiltersTables.class,
       CreateTableWebhookDeliveries.class,
-      IncludeOrganizationUuidInUniqueIndexOfGroupRoles.class,
-
-      // 6.3
-      AddUuidToEvents.class,
-      PopulateUuidColumnOfEvents.class,
-      MakeUuidNotNullOnEvents.class);
+      IncludeOrganizationUuidInUniqueIndexOfGroupRoles.class);
   }
 }
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v63/AddUuidToEvents.java b/sonar-db/src/main/java/org/sonar/db/version/v63/AddUuidToEvents.java
deleted file mode 100644 (file)
index 220ba52..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-package org.sonar.db.version.v63;
-
-import java.sql.SQLException;
-import org.sonar.db.Database;
-import org.sonar.db.version.AddColumnsBuilder;
-import org.sonar.db.version.DdlChange;
-import org.sonar.db.version.VarcharColumnDef;
-
-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/sonar-db/src/main/java/org/sonar/db/version/v63/MakeUuidNotNullOnEvents.java b/sonar-db/src/main/java/org/sonar/db/version/v63/MakeUuidNotNullOnEvents.java
deleted file mode 100644 (file)
index e8e3bc2..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.db.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.DdlChange;
-import org.sonar.db.version.VarcharColumnDef;
-
-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/sonar-db/src/main/java/org/sonar/db/version/v63/PopulateUuidColumnOfEvents.java b/sonar-db/src/main/java/org/sonar/db/version/v63/PopulateUuidColumnOfEvents.java
deleted file mode 100644 (file)
index 698d29f..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-package org.sonar.db.version.v63;
-
-import java.sql.SQLException;
-import org.sonar.core.util.UuidFactory;
-import org.sonar.db.Database;
-import org.sonar.db.version.BaseDataChange;
-import org.sonar.db.version.MassUpdate;
-import org.sonar.db.version.Select;
-import org.sonar.db.version.SqlStatement;
-
-public class PopulateUuidColumnOfEvents extends BaseDataChange {
-
-  private final UuidFactory uuidFactory;
-
-  public PopulateUuidColumnOfEvents(Database db, UuidFactory uuidFactory) {
-    super(db);
-    this.uuidFactory = uuidFactory;
-  }
-
-  @Override
-  public 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/sonar-db/src/main/java/org/sonar/db/version/v63/package-info.java b/sonar-db/src/main/java/org/sonar/db/version/v63/package-info.java
deleted file mode 100644 (file)
index 8908b7c..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-@ParametersAreNonnullByDefault
-package org.sonar.db.version.v63;
-
-import javax.annotation.ParametersAreNonnullByDefault;
-
index 579506d38b1e5652fad8f7ac6f36ffc83d48e8a8..0502ecf2ce8738e9b437d587e7245a6d0d7f384b 100644 (file)
@@ -29,6 +29,6 @@ public class MigrationStepModuleTest {
   public void verify_count_of_added_MigrationStep_types() {
     ComponentContainer container = new ComponentContainer();
     new MigrationStepModule().configure(container);
-    assertThat(container.size()).isEqualTo(126);
+    assertThat(container.size()).isEqualTo(123);
   }
 }
diff --git a/sonar-db/src/test/java/org/sonar/db/version/v63/AddUuidToEventsTest.java b/sonar-db/src/test/java/org/sonar/db/version/v63/AddUuidToEventsTest.java
deleted file mode 100644 (file)
index 71b9139..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-package org.sonar.db.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/sonar-db/src/test/java/org/sonar/db/version/v63/MakeUuidNotNullOnEventsTest.java b/sonar-db/src/test/java/org/sonar/db/version/v63/MakeUuidNotNullOnEventsTest.java
deleted file mode 100644 (file)
index 2813b7b..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-package org.sonar.db.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/sonar-db/src/test/java/org/sonar/db/version/v63/PopulateUuidColumnOfEventsTest.java b/sonar-db/src/test/java/org/sonar/db/version/v63/PopulateUuidColumnOfEventsTest.java
deleted file mode 100644 (file)
index 1c67cc9..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-package org.sonar.db.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/sonar-db/src/test/resources/org/sonar/db/version/v63/AddUuidToEventsTest/previous-events.sql b/sonar-db/src/test/resources/org/sonar/db/version/v63/AddUuidToEventsTest/previous-events.sql
deleted file mode 100644 (file)
index b071227..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-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/sonar-db/src/test/resources/org/sonar/db/version/v63/MakeUuidNotNullOnEventsTest/in_progress_events.sql b/sonar-db/src/test/resources/org/sonar/db/version/v63/MakeUuidNotNullOnEventsTest/in_progress_events.sql
deleted file mode 100644 (file)
index d83b16d..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-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/sonar-db/src/test/resources/org/sonar/db/version/v63/PopulateUuidColumnOfEventsTest/in_progress_events.sql b/sonar-db/src/test/resources/org/sonar/db/version/v63/PopulateUuidColumnOfEventsTest/in_progress_events.sql
deleted file mode 100644 (file)
index d83b16d..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-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)
-);