]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11310 add temporary columns to CE tables
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Wed, 26 Sep 2018 06:58:59 +0000 (08:58 +0200)
committerSonarTech <sonartech@sonarsource.com>
Thu, 4 Oct 2018 18:20:56 +0000 (20:20 +0200)
- add main_component_uuid temporary columns to CE_QUEUE
- add main_last_key and main_component_uuid columns to CE_ACTIVITY
- back to initial paradigm in Compute Engine: even for branches/PRs, the row in table PROJECTS a task belongs to is created in api/ce/submit
- add main component concept to CeTask
- improved consistency check when processing a report task to account for row in PROJECTS now being the one of the branche/PR
- stronger validation of characteristics passed to api/ce/submit
- add api/system/migrate_data for SonarCloud online data migration

86 files changed:
server/sonar-ce-common/src/main/java/org/sonar/ce/queue/CeQueue.java
server/sonar-ce-common/src/main/java/org/sonar/ce/queue/CeQueueImpl.java
server/sonar-ce-common/src/main/java/org/sonar/ce/queue/CeTaskSubmit.java
server/sonar-ce-common/src/test/java/org/sonar/ce/queue/CeQueueImplTest.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutor.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/BranchLoader.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/BranchPersisterImpl.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/dbmigration/PopulateFileSourceLineCount.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStep.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutorTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/BranchPersisterImplTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/dbmigration/PopulateFileSourceLineCountTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStepTest.java
server/sonar-ce-task/src/main/java/org/sonar/ce/task/CeTask.java
server/sonar-ce-task/src/test/java/org/sonar/ce/task/CeTaskComponentTest.java [new file with mode: 0644]
server/sonar-ce-task/src/test/java/org/sonar/ce/task/CeTaskTest.java
server/sonar-ce/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationExecutionListener.java
server/sonar-ce/src/main/java/org/sonar/ce/queue/InternalCeQueueImpl.java
server/sonar-ce/src/main/java/org/sonar/ce/taskprocessor/CeWorkerImpl.java
server/sonar-ce/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationExecutionListenerTest.java
server/sonar-ce/src/test/java/org/sonar/ce/queue/InternalCeQueueImplTest.java
server/sonar-ce/src/test/java/org/sonar/ce/taskprocessor/CeTaskProcessorRepositoryImplTest.java
server/sonar-ce/src/test/java/org/sonar/ce/taskprocessor/CeWorkerImplTest.java
server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl
server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeActivityDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeActivityDto.java
server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeActivityMapper.java
server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeQueueDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeQueueDto.java
server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeQueueMapper.java
server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskQuery.java
server/sonar-db-dao/src/main/java/org/sonar/db/ce/QueueCount.java
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java
server/sonar-db-dao/src/main/resources/org/sonar/db/ce/CeActivityMapper.xml
server/sonar-db-dao/src/main/resources/org/sonar/db/ce/CeQueueMapper.xml
server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeActivityDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeActivityDtoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeQueueDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeQueueDtoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeQueueTesting.java
server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeTaskQueryTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeActivity.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeQueue.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeTable.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/AddTmpLastKeyColumnsToCeActivity.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/DbVersion74.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeActivity.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeQueue.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpLastKeyColumnsToCeActivity.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeActivityTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeQueueTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/AddTmpLastKeyColumnsToCeActivityTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/DbVersion74Test.java
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeActivityTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeQueueTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpLastKeyColumnsToCeActivityTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/Row.java [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeActivityTest/ce_activity.sql [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeQueueTest/ce_queue.sql [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/AddTmpLastKeyColumnsToCeActivityTest/ce_activity.sql [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeActivityTest/ce_activity.sql [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeQueueTest/ce_queue.sql [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/PopulateTmpLastKeyColumnsToCeActivityTest/ce_activity.sql [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/ce/CeModule.java
server/sonar-server/src/main/java/org/sonar/server/ce/queue/BranchSupport.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/ce/queue/BranchSupportDelegate.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/ce/queue/ReportSubmitter.java
server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityAction.java
server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityStatusAction.java
server/sonar-server/src/main/java/org/sonar/server/ce/ws/ComponentAction.java
server/sonar-server/src/main/java/org/sonar/server/ce/ws/SubmitAction.java
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
server/sonar-server/src/main/java/org/sonar/server/platform/ws/MigrateDataAction.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/project/ws/UpdateVisibilityAction.java
server/sonar-server/src/main/java/org/sonar/server/user/AbstractUserSession.java
server/sonar-server/src/test/java/org/sonar/server/ce/queue/BranchReportSubmitterTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/ce/queue/BranchSupportTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/ce/queue/ReportSubmitterTest.java
server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityActionTest.java
server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityStatusActionTest.java
server/sonar-server/src/test/java/org/sonar/server/ce/ws/CancelActionTest.java
server/sonar-server/src/test/java/org/sonar/server/ce/ws/ComponentActionTest.java
server/sonar-server/src/test/java/org/sonar/server/ce/ws/SubmitActionTest.java
server/sonar-server/src/test/java/org/sonar/server/project/ws/UpdateVisibilityActionTest.java

index cb6810f5a86d43aeaea278aa1393fcd55b44c010..62596377a3fc94f4410b9d255efa14408670a265 100644 (file)
@@ -57,7 +57,7 @@ public interface CeQueue {
    * <p>
    * This method is equivalent to calling {@code massSubmit(Collections.singletonList(submission))}.
    *
-   * @return empty if {@code options} contains {@link SubmitOption#UNIQUE_QUEUE_PER_COMPONENT UNIQUE_QUEUE_PER_COMPONENT}
+   * @return empty if {@code options} contains {@link SubmitOption#UNIQUE_QUEUE_PER_MAIN_COMPONENT UNIQUE_QUEUE_PER_MAIN_COMPONENT}
    *         and there's already a queued task, otherwise the created task.
    */
   Optional<CeTask> submit(CeTaskSubmit submission, SubmitOption... options);
@@ -102,7 +102,7 @@ public interface CeQueue {
   WorkersPauseStatus getWorkersPauseStatus();
 
   enum SubmitOption {
-    UNIQUE_QUEUE_PER_COMPONENT
+    UNIQUE_QUEUE_PER_MAIN_COMPONENT
   }
 
   enum WorkersPauseStatus {
index 940c48eed66c1c09e7f0ff447b9b2d21909e3341..3722ace7b9652466f5cb02d0dba5032f7a7608f2 100644 (file)
@@ -28,9 +28,11 @@ import java.util.EnumSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 import javax.annotation.Nullable;
 import org.sonar.api.server.ServerSide;
 import org.sonar.api.utils.log.Loggers;
@@ -48,8 +50,11 @@ import org.sonar.server.organization.DefaultOrganizationProvider;
 import org.sonar.server.property.InternalProperties;
 
 import static com.google.common.base.Preconditions.checkState;
+import static java.util.Collections.emptyMap;
 import static java.util.Collections.singleton;
-import static org.sonar.ce.queue.CeQueue.SubmitOption.UNIQUE_QUEUE_PER_COMPONENT;
+import static java.util.Optional.of;
+import static java.util.Optional.ofNullable;
+import static org.sonar.ce.queue.CeQueue.SubmitOption.UNIQUE_QUEUE_PER_MAIN_COMPONENT;
 import static org.sonar.core.util.stream.MoreCollectors.toEnumSet;
 import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
 import static org.sonar.db.ce.CeQueueDto.Status.PENDING;
@@ -78,30 +83,45 @@ public class CeQueueImpl implements CeQueue {
   }
 
   @Override
-  public java.util.Optional<CeTask> submit(CeTaskSubmit submission, SubmitOption... options) {
+  public Optional<CeTask> submit(CeTaskSubmit submission, SubmitOption... options) {
     return submit(submission, toSet(options));
   }
 
-  private java.util.Optional<CeTask> submit(CeTaskSubmit submission, EnumSet<SubmitOption> submitOptions) {
+  private Optional<CeTask> submit(CeTaskSubmit submission, EnumSet<SubmitOption> submitOptions) {
     try (DbSession dbSession = dbClient.openSession(false)) {
-      if (submitOptions.contains(UNIQUE_QUEUE_PER_COMPONENT)
-        && submission.getComponentUuid() != null
-        && dbClient.ceQueueDao().countByStatusAndComponentUuid(dbSession, PENDING, submission.getComponentUuid()) > 0) {
-        return java.util.Optional.empty();
+      if (submitOptions.contains(UNIQUE_QUEUE_PER_MAIN_COMPONENT)
+        && submission.getComponent()
+          .map(component -> dbClient.ceQueueDao().countByStatusAndMainComponentUuid(dbSession, PENDING, component.getMainComponentUuid()) > 0)
+          .orElse(false)) {
+        return Optional.empty();
       }
       CeQueueDto taskDto = addToQueueInDb(dbSession, submission);
       dbSession.commit();
 
-      ComponentDto component = null;
-      String componentUuid = taskDto.getComponentUuid();
-      if (componentUuid != null) {
-        component = dbClient.componentDao().selectByUuid(dbSession, componentUuid).orElse(null);
+      Map<String, ComponentDto> componentsByUuid = loadComponentDtos(dbSession, taskDto);
+      if (componentsByUuid.isEmpty()) {
+        return of(convertToTask(taskDto, submission.getCharacteristics(), null, null));
       }
-      CeTask task = convertToTask(taskDto, submission.getCharacteristics(), component);
-      return java.util.Optional.of(task);
+
+      return of(convertToTask(taskDto, submission.getCharacteristics(),
+        ofNullable(taskDto.getComponentUuid()).map(componentsByUuid::get).orElse(null),
+        ofNullable(taskDto.getMainComponentUuid()).map(componentsByUuid::get).orElse(null)));
     }
   }
 
+  Map<String, ComponentDto> loadComponentDtos(DbSession dbSession, CeQueueDto taskDto) {
+    Set<String> componentUuids = Stream.of(taskDto.getComponentUuid(), taskDto.getMainComponentUuid())
+      .filter(Objects::nonNull)
+      .collect(MoreCollectors.toSet(2));
+    if (componentUuids.isEmpty()) {
+      return emptyMap();
+    }
+
+    return dbClient.componentDao().selectByUuids(dbSession, componentUuids)
+      .stream()
+      .collect(uniqueIndex(ComponentDto::uuid, 2));
+  }
+
   @Override
   public List<CeTask> massSubmit(Collection<CeTaskSubmit> submissions, SubmitOption... options) {
     if (submissions.isEmpty()) {
@@ -109,11 +129,11 @@ public class CeQueueImpl implements CeQueue {
     }
 
     try (DbSession dbSession = dbClient.openSession(false)) {
-      List<CeQueueDto> taskDto = submissions.stream()
+      List<CeQueueDto> taskDtos = submissions.stream()
         .filter(filterBySubmitOptions(options, submissions, dbSession))
         .map(submission -> addToQueueInDb(dbSession, submission))
         .collect(Collectors.toList());
-      List<CeTask> tasks = loadTasks(dbSession, taskDto);
+      List<CeTask> tasks = loadTasks(dbSession, taskDtos);
       dbSession.commit();
       return tasks;
     }
@@ -122,31 +142,34 @@ public class CeQueueImpl implements CeQueue {
   private Predicate<CeTaskSubmit> filterBySubmitOptions(SubmitOption[] options, Collection<CeTaskSubmit> submissions, DbSession dbSession) {
     EnumSet<SubmitOption> submitOptions = toSet(options);
 
-    if (submitOptions.contains(UNIQUE_QUEUE_PER_COMPONENT)) {
-      Set<String> componentUuids = submissions.stream()
-        .map(CeTaskSubmit::getComponentUuid)
-        .filter(Objects::nonNull)
+    if (submitOptions.contains(UNIQUE_QUEUE_PER_MAIN_COMPONENT)) {
+      Set<String> mainComponentUuids = submissions.stream()
+        .map(CeTaskSubmit::getComponent)
+        .filter(Optional::isPresent)
+        .map(Optional::get)
+        .map(CeTaskSubmit.Component::getMainComponentUuid)
         .collect(MoreCollectors.toSet(submissions.size()));
-      if (componentUuids.isEmpty()) {
+      if (mainComponentUuids.isEmpty()) {
         return t -> true;
       }
-      return new NoPendingTaskFilter(dbSession, componentUuids);
+      return new NoPendingTaskFilter(dbSession, mainComponentUuids);
     }
 
     return t -> true;
   }
 
   private class NoPendingTaskFilter implements Predicate<CeTaskSubmit> {
-    private final Map<String, Integer> queuedItemsByComponentUuid;
+    private final Map<String, Integer> queuedItemsByMainComponentUuid;
 
-    private NoPendingTaskFilter(DbSession dbSession, Set<String> componentUuids) {
-      queuedItemsByComponentUuid = dbClient.ceQueueDao().countByStatusAndComponentUuids(dbSession, PENDING, componentUuids);
+    private NoPendingTaskFilter(DbSession dbSession, Set<String> projectUuids) {
+      queuedItemsByMainComponentUuid = dbClient.ceQueueDao().countByStatusAndMainComponentUuids(dbSession, PENDING, projectUuids);
     }
 
     @Override
     public boolean test(CeTaskSubmit ceTaskSubmit) {
-      String componentUuid = ceTaskSubmit.getComponentUuid();
-      return componentUuid == null || queuedItemsByComponentUuid.getOrDefault(componentUuid, 0) == 0;
+      return ceTaskSubmit.getComponent()
+        .map(component -> queuedItemsByMainComponentUuid.getOrDefault(component.getMainComponentUuid(), 0) == 0)
+        .orElse(true);
     }
   }
 
@@ -167,7 +190,9 @@ public class CeQueueImpl implements CeQueue {
     CeQueueDto dto = new CeQueueDto();
     dto.setUuid(submission.getUuid());
     dto.setTaskType(submission.getType());
-    dto.setComponentUuid(submission.getComponentUuid());
+    submission.getComponent().ifPresent(component -> dto
+      .setComponentUuid(component.getUuid())
+      .setMainComponentUuid(component.getMainComponentUuid()));
     dto.setStatus(PENDING);
     dto.setSubmitterUuid(submission.getSubmitterUuid());
     dbClient.ceQueueDao().insert(dbSession, dto);
@@ -178,7 +203,7 @@ public class CeQueueImpl implements CeQueue {
   private List<CeTask> loadTasks(DbSession dbSession, List<CeQueueDto> dtos) {
     // load components, if defined
     Set<String> componentUuids = dtos.stream()
-      .map(CeQueueDto::getComponentUuid)
+      .flatMap(dto -> Stream.of(dto.getComponentUuid(), dto.getMainComponentUuid()))
       .filter(Objects::nonNull)
       .collect(Collectors.toSet());
     Map<String, ComponentDto> componentsByUuid = dbClient.componentDao()
@@ -194,13 +219,15 @@ public class CeQueueImpl implements CeQueue {
 
     List<CeTask> result = new ArrayList<>();
     for (CeQueueDto dto : dtos) {
-      ComponentDto component = null;
-      if (dto.getComponentUuid() != null) {
-        component = componentsByUuid.get(dto.getComponentUuid());
-      }
+      ComponentDto component = ofNullable(dto.getComponentUuid())
+        .map(componentsByUuid::get)
+        .orElse(null);
+      ComponentDto mainComponent = ofNullable(dto.getMainComponentUuid())
+        .map(componentsByUuid::get)
+        .orElse(null);
       Map<String, String> characteristics = characteristicsByTaskUuid.get(dto.getUuid()).stream()
         .collect(uniqueIndex(CeTaskCharacteristicDto::getKey, CeTaskCharacteristicDto::getValue));
-      result.add(convertToTask(dto, characteristics, component));
+      result.add(convertToTask(dto, characteristics, component, mainComponent));
     }
     return result;
   }
@@ -272,7 +299,7 @@ public class CeQueueImpl implements CeQueue {
   @Override
   public WorkersPauseStatus getWorkersPauseStatus() {
     try (DbSession dbSession = dbClient.openSession(false)) {
-      java.util.Optional<String> propValue = dbClient.internalPropertiesDao().selectByKey(dbSession, InternalProperties.COMPUTE_ENGINE_PAUSE);
+      Optional<String> propValue = dbClient.internalPropertiesDao().selectByKey(dbSession, InternalProperties.COMPUTE_ENGINE_PAUSE);
       if (!propValue.isPresent() || !propValue.get().equals("true")) {
         return WorkersPauseStatus.RESUMED;
       }
@@ -284,18 +311,26 @@ public class CeQueueImpl implements CeQueue {
     }
   }
 
-  CeTask convertToTask(CeQueueDto taskDto, Map<String, String> characteristics, @Nullable ComponentDto component) {
+  CeTask convertToTask(CeQueueDto taskDto, Map<String, String> characteristics, @Nullable ComponentDto component, @Nullable ComponentDto mainComponent) {
     CeTask.Builder builder = new CeTask.Builder()
       .setUuid(taskDto.getUuid())
       .setType(taskDto.getTaskType())
       .setSubmitterUuid(taskDto.getSubmitterUuid())
-      .setComponentUuid(taskDto.getComponentUuid())
       .setCharacteristics(characteristics);
 
+    String componentUuid = taskDto.getComponentUuid();
     if (component != null) {
+      builder.setComponent(new CeTask.Component(component.uuid(), component.getDbKey(), component.name()));
       builder.setOrganizationUuid(component.getOrganizationUuid());
-      builder.setComponentKey(component.getDbKey());
-      builder.setComponentName(component.name());
+    } else if (componentUuid != null) {
+      builder.setComponent(new CeTask.Component(componentUuid, null, null));
+    }
+
+    String mainComponentUuid = taskDto.getMainComponentUuid();
+    if (mainComponent != null) {
+      builder.setMainComponent(new CeTask.Component(mainComponent.uuid(), mainComponent.getDbKey(), mainComponent.name()));
+    } else if (mainComponentUuid != null) {
+      builder.setMainComponent(new CeTask.Component(mainComponentUuid, null, null));
     }
 
     // FIXME this should be set from the CeQueueDto
index 7ac4669e990ba3908255b81b97143ccd125577e6..9aaae90ea81ae609d413a08038a91e17a375cda8 100644 (file)
 package org.sonar.ce.queue;
 
 import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
+import org.sonar.db.component.ComponentDto;
 
+import static com.google.common.base.MoreObjects.firstNonNull;
 import static com.google.common.base.Strings.emptyToNull;
+import static com.google.common.base.Strings.nullToEmpty;
 import static java.util.Collections.unmodifiableMap;
 import static java.util.Objects.requireNonNull;
 
@@ -33,16 +38,16 @@ public final class CeTaskSubmit {
 
   private final String uuid;
   private final String type;
-  private final String componentUuid;
+  private final Component component;
   private final String submitterUuid;
   private final Map<String, String> characteristics;
 
   private CeTaskSubmit(Builder builder) {
-    this.uuid = requireNonNull(emptyToNull(builder.uuid));
-    this.type = requireNonNull(emptyToNull(builder.type));
-    this.componentUuid = emptyToNull(builder.componentUuid);
-    this.submitterUuid = emptyToNull(builder.submitterUuid);
-    this.characteristics = unmodifiableMap(requireNonNull(builder.characteristics));
+    this.uuid = requireNonNull(builder.uuid);
+    this.type = requireNonNull(builder.type);
+    this.component = builder.component;
+    this.submitterUuid = builder.submitterUuid;
+    this.characteristics = unmodifiableMap(builder.characteristics);
   }
 
   public String getType() {
@@ -53,9 +58,8 @@ public final class CeTaskSubmit {
     return uuid;
   }
 
-  @CheckForNull
-  public String getComponentUuid() {
-    return componentUuid;
+  public Optional<Component> getComponent() {
+    return Optional.ofNullable(component);
   }
 
   @CheckForNull
@@ -70,12 +74,12 @@ public final class CeTaskSubmit {
   public static final class Builder {
     private final String uuid;
     private String type;
-    private String componentUuid;
+    private Component component;
     private String submitterUuid;
     private Map<String, String> characteristics = null;
 
     public Builder(String uuid) {
-      this.uuid = uuid;
+      this.uuid = emptyToNull(uuid);
     }
 
     public String getUuid() {
@@ -83,12 +87,12 @@ public final class CeTaskSubmit {
     }
 
     public Builder setType(String s) {
-      this.type = s;
+      this.type = emptyToNull(s);
       return this;
     }
 
-    public Builder setComponentUuid(@Nullable String s) {
-      this.componentUuid = s;
+    public Builder setComponent(@Nullable Component component) {
+      this.component = component;
       return this;
     }
 
@@ -103,7 +107,58 @@ public final class CeTaskSubmit {
     }
 
     public CeTaskSubmit build() {
+      requireNonNull(uuid, "uuid can't be null");
+      requireNonNull(type, "type can't be null");
+      requireNonNull(characteristics, "characteristics can't be null");
       return new CeTaskSubmit(this);
     }
   }
+
+  public static class Component {
+    private String uuid;
+    private String mainComponentUuid;
+
+    public Component(String uuid, String mainComponentUuid) {
+      this.uuid = requireNonNull(nullToEmpty(uuid), "uuid can't be null");
+      this.mainComponentUuid = requireNonNull(nullToEmpty(mainComponentUuid), "mainComponentUuid can't be null");
+    }
+
+    public static Component fromDto(ComponentDto dto) {
+      String uuid = dto.uuid();
+      return new Component(uuid, firstNonNull(dto.getMainBranchProjectUuid(), uuid));
+    }
+
+    public String getUuid() {
+      return uuid;
+    }
+
+    public String getMainComponentUuid() {
+      return mainComponentUuid;
+    }
+
+    @Override
+    public String toString() {
+      return "Component{" +
+        "uuid='" + uuid + '\'' +
+        ", mainComponentUuid='" + mainComponentUuid + '\'' +
+        '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      Component component = (Component) o;
+      return uuid.equals(component.uuid) && mainComponentUuid.equals(component.mainComponentUuid);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(uuid, mainComponentUuid);
+    }
+  }
 }
index 9d185fcaf3dcb6b660de3e517bc5a419aeb268da..b48c0ff9cc6615318b765e9b742c7666ca026320 100644 (file)
@@ -30,6 +30,7 @@ import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.sonar.api.utils.System2;
 import org.sonar.api.utils.internal.TestSystem2;
+import org.sonar.ce.queue.CeTaskSubmit.Component;
 import org.sonar.ce.task.CeTask;
 import org.sonar.core.util.UuidFactory;
 import org.sonar.core.util.UuidFactoryFast;
@@ -49,8 +50,9 @@ import static java.util.Arrays.asList;
 import static java.util.Collections.emptyMap;
 import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
 import static org.hamcrest.Matchers.startsWith;
-import static org.sonar.ce.queue.CeQueue.SubmitOption.UNIQUE_QUEUE_PER_COMPONENT;
+import static org.sonar.ce.queue.CeQueue.SubmitOption.UNIQUE_QUEUE_PER_MAIN_COMPONENT;
 
 public class CeQueueImplTest {
 
@@ -72,7 +74,9 @@ public class CeQueueImplTest {
 
   @Test
   public void submit_returns_task_populated_from_CeTaskSubmit_and_creates_CeQueue_row() {
-    CeTaskSubmit taskSubmit = createTaskSubmit(CeTaskTypes.REPORT, "PROJECT_1", "submitter uuid");
+    String componentUuid = randomAlphabetic(3);
+    String mainComponentUuid = randomAlphabetic(4);
+    CeTaskSubmit taskSubmit = createTaskSubmit(CeTaskTypes.REPORT, new Component(componentUuid, mainComponentUuid), "submitter uuid");
 
     CeTask task = underTest.submit(taskSubmit);
 
@@ -83,7 +87,7 @@ public class CeQueueImplTest {
   @Test
   public void submit_populates_component_name_and_key_of_CeTask_if_component_exists() {
     ComponentDto componentDto = insertComponent(ComponentTesting.newPrivateProjectDto(db.organizations().insert(), "PROJECT_1"));
-    CeTaskSubmit taskSubmit = createTaskSubmit(CeTaskTypes.REPORT, componentDto.uuid(), null);
+    CeTaskSubmit taskSubmit = createTaskSubmit(CeTaskTypes.REPORT, Component.fromDto(componentDto), null);
 
     CeTask task = underTest.submit(taskSubmit);
 
@@ -100,11 +104,11 @@ public class CeQueueImplTest {
   }
 
   @Test
-  public void submit_with_UNIQUE_QUEUE_PER_COMPONENT_creates_task_without_component_when_there_is_a_pending_task_without_component() {
+  public void submit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_without_component_when_there_is_a_pending_task_without_component() {
     CeTaskSubmit taskSubmit = createTaskSubmit("no_component");
     CeQueueDto dto = insertPendingInQueue(null);
 
-    Optional<CeTask> task = underTest.submit(taskSubmit, UNIQUE_QUEUE_PER_COMPONENT);
+    Optional<CeTask> task = underTest.submit(taskSubmit, UNIQUE_QUEUE_PER_MAIN_COMPONENT);
 
     assertThat(task).isNotEmpty();
     assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
@@ -113,13 +117,13 @@ public class CeQueueImplTest {
   }
 
   @Test
-  public void submit_with_UNIQUE_QUEUE_PER_COMPONENT_creates_task_when_there_is_a_pending_task_for_another_component() {
-    String componentUuid = randomAlphabetic(5);
-    String otherComponentUuid = randomAlphabetic(6);
-    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", componentUuid, null);
-    CeQueueDto dto = insertPendingInQueue(otherComponentUuid);
+  public void submit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_when_there_is_a_pending_task_for_another_main_component() {
+    String mainComponentUuid = randomAlphabetic(5);
+    String otherMainComponentUuid = randomAlphabetic(6);
+    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
+    CeQueueDto dto = insertPendingInQueue(newComponent(otherMainComponentUuid));
 
-    Optional<CeTask> task = underTest.submit(taskSubmit, UNIQUE_QUEUE_PER_COMPONENT);
+    Optional<CeTask> task = underTest.submit(taskSubmit, UNIQUE_QUEUE_PER_MAIN_COMPONENT);
 
     assertThat(task).isNotEmpty();
     assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
@@ -128,12 +132,12 @@ public class CeQueueImplTest {
   }
 
   @Test
-  public void submit_with_UNIQUE_QUEUE_PER_COMPONENT_does_not_create_task_when_there_is_one_pending_task_for_component() {
-    String componentUuid = randomAlphabetic(5);
-    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", componentUuid, null);
-    CeQueueDto dto = insertPendingInQueue(componentUuid);
+  public void submit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_does_not_create_task_when_there_is_one_pending_task_for_same_main_component() {
+    String mainComponentUuid = randomAlphabetic(5);
+    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
+    CeQueueDto dto = insertPendingInQueue(newComponent(mainComponentUuid));
 
-    Optional<CeTask> task = underTest.submit(taskSubmit, UNIQUE_QUEUE_PER_COMPONENT);
+    Optional<CeTask> task = underTest.submit(taskSubmit, UNIQUE_QUEUE_PER_MAIN_COMPONENT);
 
     assertThat(task).isEmpty();
     assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
@@ -142,15 +146,15 @@ public class CeQueueImplTest {
   }
 
   @Test
-  public void submit_with_UNIQUE_QUEUE_PER_COMPONENT_does_not_create_task_when_there_is_many_pending_task_for_component() {
-    String componentUuid = randomAlphabetic(5);
-    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", componentUuid, null);
+  public void submit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_does_not_create_task_when_there_is_many_pending_task_for_same_main_component() {
+    String mainComponentUuid = randomAlphabetic(5);
+    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
     String[] uuids = IntStream.range(0, 2 + new Random().nextInt(5))
-      .mapToObj(i -> insertPendingInQueue(componentUuid))
+      .mapToObj(i -> insertPendingInQueue(newComponent(mainComponentUuid)))
       .map(CeQueueDto::getUuid)
       .toArray(String[]::new);
 
-    Optional<CeTask> task = underTest.submit(taskSubmit, UNIQUE_QUEUE_PER_COMPONENT);
+    Optional<CeTask> task = underTest.submit(taskSubmit, UNIQUE_QUEUE_PER_MAIN_COMPONENT);
 
     assertThat(task).isEmpty();
     assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
@@ -159,10 +163,10 @@ public class CeQueueImplTest {
   }
 
   @Test
-  public void submit_without_UNIQUE_QUEUE_PER_COMPONENT_creates_task_when_there_is_one_pending_task_for_component() {
-    String componentUuid = randomAlphabetic(5);
-    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", componentUuid, null);
-    CeQueueDto dto = insertPendingInQueue(componentUuid);
+  public void submit_without_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_when_there_is_one_pending_task_for_same_main_component() {
+    String mainComponentUuid = randomAlphabetic(5);
+    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
+    CeQueueDto dto = insertPendingInQueue(newComponent(mainComponentUuid));
 
     CeTask task = underTest.submit(taskSubmit);
 
@@ -172,11 +176,11 @@ public class CeQueueImplTest {
   }
 
   @Test
-  public void submit_without_UNIQUE_QUEUE_PER_COMPONENT_creates_task_when_there_is_many_pending_task_for_component() {
-    String componentUuid = randomAlphabetic(5);
-    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", componentUuid, null);
+  public void submit_without_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_when_there_is_many_pending_task_for_same_main_component() {
+    String mainComponentUuid = randomAlphabetic(5);
+    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
     String[] uuids = IntStream.range(0, 2 + new Random().nextInt(5))
-      .mapToObj(i -> insertPendingInQueue(componentUuid))
+      .mapToObj(i -> insertPendingInQueue(newComponent(mainComponentUuid)))
       .map(CeQueueDto::getUuid)
       .toArray(String[]::new);
 
@@ -191,7 +195,8 @@ public class CeQueueImplTest {
 
   @Test
   public void massSubmit_returns_tasks_for_each_CeTaskSubmit_populated_from_CeTaskSubmit_and_creates_CeQueue_row_for_each() {
-    CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, "PROJECT_1", "submitter uuid");
+    String mainComponentUuid = randomAlphabetic(10);
+    CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, newComponent(mainComponentUuid), "submitter uuid");
     CeTaskSubmit taskSubmit2 = createTaskSubmit("some type");
 
     List<CeTask> tasks = underTest.massSubmit(asList(taskSubmit1, taskSubmit2));
@@ -204,10 +209,10 @@ public class CeQueueImplTest {
   }
 
   @Test
-  public void massSubmit_populates_component_name_and_key_of_CeTask_if_component_exists() {
+  public void massSubmit_populates_component_name_and_key_of_CeTask_if_project_exists() {
     ComponentDto componentDto1 = insertComponent(ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization(), "PROJECT_1"));
-    CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, componentDto1.uuid(), null);
-    CeTaskSubmit taskSubmit2 = createTaskSubmit("something", "non existing component uuid", null);
+    CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, Component.fromDto(componentDto1), null);
+    CeTaskSubmit taskSubmit2 = createTaskSubmit("something", newComponent(randomAlphabetic(12)), null);
 
     List<CeTask> tasks = underTest.massSubmit(asList(taskSubmit1, taskSubmit2));
 
@@ -217,11 +222,26 @@ public class CeQueueImplTest {
   }
 
   @Test
-  public void massSubmit_with_UNIQUE_QUEUE_PER_COMPONENT_creates_task_without_component_when_there_is_a_pending_task_without_component() {
+  public void massSubmit_populates_component_name_and_key_of_CeTask_if_project_and_branch_exists() {
+    ComponentDto project = insertComponent(ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization(), "PROJECT_1"));
+    ComponentDto branch1 = db.components().insertProjectBranch(project);
+    ComponentDto branch2 = db.components().insertProjectBranch(project);
+    CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, Component.fromDto(branch1), null);
+    CeTaskSubmit taskSubmit2 = createTaskSubmit("something", Component.fromDto(branch2), null);
+
+    List<CeTask> tasks = underTest.massSubmit(asList(taskSubmit1, taskSubmit2));
+
+    assertThat(tasks).hasSize(2);
+    verifyCeTask(taskSubmit1, tasks.get(0), branch1, project);
+    verifyCeTask(taskSubmit2, tasks.get(1), branch2, project);
+  }
+
+  @Test
+  public void massSubmit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_without_component_when_there_is_a_pending_task_without_component() {
     CeTaskSubmit taskSubmit = createTaskSubmit("no_component");
     CeQueueDto dto = insertPendingInQueue(null);
 
-    List<CeTask> tasks = underTest.massSubmit(of(taskSubmit), UNIQUE_QUEUE_PER_COMPONENT);
+    List<CeTask> tasks = underTest.massSubmit(of(taskSubmit), UNIQUE_QUEUE_PER_MAIN_COMPONENT);
 
     assertThat(tasks).hasSize(1);
     assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
@@ -230,13 +250,13 @@ public class CeQueueImplTest {
   }
 
   @Test
-  public void massSubmit_with_UNIQUE_QUEUE_PER_COMPONENT_creates_task_when_there_is_a_pending_task_for_another_component() {
-    String componentUuid = randomAlphabetic(5);
-    String otherComponentUuid = randomAlphabetic(6);
-    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", componentUuid, null);
-    CeQueueDto dto = insertPendingInQueue(otherComponentUuid);
+  public void massSubmit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_when_there_is_a_pending_task_for_another_main_component() {
+    String mainComponentUuid = randomAlphabetic(5);
+    String otherMainComponentUuid = randomAlphabetic(6);
+    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
+    CeQueueDto dto = insertPendingInQueue(newComponent(otherMainComponentUuid));
 
-    List<CeTask> tasks = underTest.massSubmit(of(taskSubmit), UNIQUE_QUEUE_PER_COMPONENT);
+    List<CeTask> tasks = underTest.massSubmit(of(taskSubmit), UNIQUE_QUEUE_PER_MAIN_COMPONENT);
 
     assertThat(tasks).hasSize(1);
     assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
@@ -245,12 +265,12 @@ public class CeQueueImplTest {
   }
 
   @Test
-  public void massSubmit_with_UNIQUE_QUEUE_PER_COMPONENT_does_not_create_task_when_there_is_one_pending_task_for_component() {
-    String componentUuid = randomAlphabetic(5);
-    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", componentUuid, null);
-    CeQueueDto dto = insertPendingInQueue(componentUuid);
+  public void massSubmit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_does_not_create_task_when_there_is_one_pending_task_for_same_main_component() {
+    String mainComponentUuid = randomAlphabetic(5);
+    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
+    CeQueueDto dto = insertPendingInQueue(newComponent(mainComponentUuid));
 
-    List<CeTask> tasks = underTest.massSubmit(of(taskSubmit), UNIQUE_QUEUE_PER_COMPONENT);
+    List<CeTask> tasks = underTest.massSubmit(of(taskSubmit), UNIQUE_QUEUE_PER_MAIN_COMPONENT);
 
     assertThat(tasks).isEmpty();
     assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
@@ -259,15 +279,15 @@ public class CeQueueImplTest {
   }
 
   @Test
-  public void massSubmit_with_UNIQUE_QUEUE_PER_COMPONENT_does_not_create_task_when_there_is_many_pending_task_for_component() {
-    String componentUuid = randomAlphabetic(5);
-    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", componentUuid, null);
+  public void massSubmit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_does_not_create_task_when_there_is_many_pending_task_for_same_main_component() {
+    String mainComponentUuid = randomAlphabetic(5);
+    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
     String[] uuids = IntStream.range(0, 2 + new Random().nextInt(5))
-      .mapToObj(i -> insertPendingInQueue(componentUuid))
+      .mapToObj(i -> insertPendingInQueue(newComponent(mainComponentUuid)))
       .map(CeQueueDto::getUuid)
       .toArray(String[]::new);
 
-    List<CeTask> tasks = underTest.massSubmit(of(taskSubmit), UNIQUE_QUEUE_PER_COMPONENT);
+    List<CeTask> tasks = underTest.massSubmit(of(taskSubmit), UNIQUE_QUEUE_PER_MAIN_COMPONENT);
 
     assertThat(tasks).isEmpty();
     assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
@@ -276,10 +296,10 @@ public class CeQueueImplTest {
   }
 
   @Test
-  public void massSubmit_without_UNIQUE_QUEUE_PER_COMPONENT_creates_task_when_there_is_one_pending_task_for_component() {
-    String componentUuid = randomAlphabetic(5);
-    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", componentUuid, null);
-    CeQueueDto dto = insertPendingInQueue(componentUuid);
+  public void massSubmit_without_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_when_there_is_one_pending_task_for_other_main_component() {
+    String mainComponentUuid = randomAlphabetic(5);
+    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
+    CeQueueDto dto = insertPendingInQueue(newComponent(mainComponentUuid));
 
     List<CeTask> tasks = underTest.massSubmit(of(taskSubmit));
 
@@ -290,11 +310,11 @@ public class CeQueueImplTest {
   }
 
   @Test
-  public void massSubmit_without_UNIQUE_QUEUE_PER_COMPONENT_creates_task_when_there_is_many_pending_task_for_component() {
-    String componentUuid = randomAlphabetic(5);
-    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", componentUuid, null);
+  public void massSubmit_without_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_when_there_is_many_pending_task_for_other_main_component() {
+    String mainComponentUuid = randomAlphabetic(5);
+    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
     String[] uuids = IntStream.range(0, 2 + new Random().nextInt(5))
-      .mapToObj(i -> insertPendingInQueue(componentUuid))
+      .mapToObj(i -> insertPendingInQueue(newComponent(mainComponentUuid)))
       .map(CeQueueDto::getUuid)
       .toArray(String[]::new);
 
@@ -309,30 +329,33 @@ public class CeQueueImplTest {
   }
 
   @Test
-  public void massSubmit_with_UNIQUE_QUEUE_PER_COMPONENT_creates_tasks_depending_on_whether_there_is_pending_task_for_component() {
-    String componentUuid1 = randomAlphabetic(5);
-    String componentUuid2 = randomAlphabetic(6);
-    String componentUuid3 = randomAlphabetic(7);
-    String componentUuid4 = randomAlphabetic(8);
-    String componentUuid5 = randomAlphabetic(9);
-    CeTaskSubmit taskSubmit1 = createTaskSubmit("with_one_pending", componentUuid1, null);
-    CeQueueDto dto1 = insertPendingInQueue(componentUuid1);
-    CeTaskSubmit taskSubmit2 = createTaskSubmit("no_pending", componentUuid2, null);
-    CeTaskSubmit taskSubmit3 = createTaskSubmit("with_many_pending", componentUuid3, null);
+  public void massSubmit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_tasks_depending_on_whether_there_is_pending_task_for_same_main_component() {
+    String mainComponentUuid1 = randomAlphabetic(5);
+    String mainComponentUuid2 = randomAlphabetic(6);
+    String mainComponentUuid3 = randomAlphabetic(7);
+    String mainComponentUuid4 = randomAlphabetic(8);
+    String mainComponentUuid5 = randomAlphabetic(9);
+    CeTaskSubmit taskSubmit1 = createTaskSubmit("with_one_pending", newComponent(mainComponentUuid1), null);
+    CeQueueDto dto1 = insertPendingInQueue(newComponent(mainComponentUuid1));
+    Component componentForMainComponentUuid2 = newComponent(mainComponentUuid2);
+    CeTaskSubmit taskSubmit2 = createTaskSubmit("no_pending", componentForMainComponentUuid2, null);
+    CeTaskSubmit taskSubmit3 = createTaskSubmit("with_many_pending", newComponent(mainComponentUuid3), null);
     String[] uuids3 = IntStream.range(0, 2 + new Random().nextInt(5))
-      .mapToObj(i -> insertPendingInQueue(componentUuid3))
+      .mapToObj(i -> insertPendingInQueue(newComponent(mainComponentUuid3)))
       .map(CeQueueDto::getUuid)
       .toArray(String[]::new);
-    CeTaskSubmit taskSubmit4 = createTaskSubmit("no_pending_2", componentUuid4, null);
-    CeTaskSubmit taskSubmit5 = createTaskSubmit("with_pending_2", componentUuid5, null);
-    CeQueueDto dto5 = insertPendingInQueue(componentUuid5);
+    Component componentForMainComponentUuid4 = newComponent(mainComponentUuid4);
+    CeTaskSubmit taskSubmit4 = createTaskSubmit("no_pending_2", componentForMainComponentUuid4, null);
+    CeTaskSubmit taskSubmit5 = createTaskSubmit("with_pending_2", newComponent(mainComponentUuid5), null);
+    CeQueueDto dto5 = insertPendingInQueue(newComponent(mainComponentUuid5));
 
-    List<CeTask> tasks = underTest.massSubmit(of(taskSubmit1, taskSubmit2, taskSubmit3, taskSubmit4, taskSubmit5), UNIQUE_QUEUE_PER_COMPONENT);
+    List<CeTask> tasks = underTest.massSubmit(of(taskSubmit1, taskSubmit2, taskSubmit3, taskSubmit4, taskSubmit5), UNIQUE_QUEUE_PER_MAIN_COMPONENT);
 
     assertThat(tasks)
       .hasSize(2)
-      .extracting(CeTask::getComponentUuid)
-      .containsOnly(componentUuid2, componentUuid4);
+      .extracting(task -> task.getComponent().get().getUuid(),task -> task.getMainComponent().get().getUuid())
+      .containsOnly(tuple(componentForMainComponentUuid2.getUuid(), componentForMainComponentUuid2.getMainComponentUuid()),
+        tuple(componentForMainComponentUuid4.getUuid(), componentForMainComponentUuid4.getMainComponentUuid()));
     assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
       .extracting(CeQueueDto::getUuid)
       .hasSize(1 + uuids3.length + 1 + tasks.size())
@@ -344,7 +367,7 @@ public class CeQueueImplTest {
 
   @Test
   public void cancel_pending() {
-    CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
+    CeTask task = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
     CeQueueDto queueDto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), task.getUuid()).get();
 
     underTest.cancel(db.getSession(), queueDto);
@@ -356,7 +379,7 @@ public class CeQueueImplTest {
 
   @Test
   public void fail_to_cancel_if_in_progress() {
-    submit(CeTaskTypes.REPORT, "PROJECT_1");
+    submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(11)));
     CeQueueDto ceQueueDto = db.getDbClient().ceQueueDao().peek(session, WORKER_UUID).get();
 
     expectedException.expect(IllegalStateException.class);
@@ -367,9 +390,9 @@ public class CeQueueImplTest {
 
   @Test
   public void cancelAll_pendings_but_not_in_progress() {
-    CeTask inProgressTask = submit(CeTaskTypes.REPORT, "PROJECT_1");
-    CeTask pendingTask1 = submit(CeTaskTypes.REPORT, "PROJECT_2");
-    CeTask pendingTask2 = submit(CeTaskTypes.REPORT, "PROJECT_3");
+    CeTask inProgressTask = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
+    CeTask pendingTask1 = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(13)));
+    CeTask pendingTask2 = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(14)));
 
     db.getDbClient().ceQueueDao().peek(session, WORKER_UUID);
 
@@ -386,7 +409,7 @@ public class CeQueueImplTest {
 
   @Test
   public void pauseWorkers_marks_workers_as_paused_if_zero_tasks_in_progress() {
-    submit(CeTaskTypes.REPORT, "PROJECT_1");
+    submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
     // task is pending
 
     assertThat(underTest.getWorkersPauseStatus()).isEqualTo(CeQueue.WorkersPauseStatus.RESUMED);
@@ -397,7 +420,7 @@ public class CeQueueImplTest {
 
   @Test
   public void pauseWorkers_marks_workers_as_pausing_if_some_tasks_in_progress() {
-    submit(CeTaskTypes.REPORT, "PROJECT_1");
+    submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
     db.getDbClient().ceQueueDao().peek(session, WORKER_UUID);
     // task is in-progress
 
@@ -418,7 +441,7 @@ public class CeQueueImplTest {
 
   @Test
   public void resumeWorkers_resumes_pausing_workers() {
-    submit(CeTaskTypes.REPORT, "PROJECT_1");
+    submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
     db.getDbClient().ceQueueDao().peek(session, WORKER_UUID);
     // task is in-progress
 
@@ -439,21 +462,37 @@ public class CeQueueImplTest {
   }
 
   private void verifyCeTask(CeTaskSubmit taskSubmit, CeTask task, @Nullable ComponentDto componentDto) {
+    verifyCeTask(taskSubmit, task, componentDto, componentDto);
+  }
+
+  private void verifyCeTask(CeTaskSubmit taskSubmit, CeTask task, @Nullable ComponentDto componentDto, @Nullable ComponentDto mainComponentDto) {
     if (componentDto == null) {
       assertThat(task.getOrganizationUuid()).isEqualTo(defaultOrganizationProvider.get().getUuid());
     } else {
       assertThat(task.getOrganizationUuid()).isEqualTo(componentDto.getOrganizationUuid());
     }
     assertThat(task.getUuid()).isEqualTo(taskSubmit.getUuid());
-    assertThat(task.getComponentUuid()).isEqualTo(task.getComponentUuid());
-    assertThat(task.getType()).isEqualTo(taskSubmit.getType());
-    if (componentDto == null) {
-      assertThat(task.getComponentKey()).isNull();
-      assertThat(task.getComponentName()).isNull();
+    if (componentDto != null) {
+      CeTask.Component component = task.getComponent().get();
+      assertThat(component.getUuid()).isEqualTo(componentDto.uuid());
+      assertThat(component.getKey()).contains(componentDto.getDbKey());
+      assertThat(component.getName()).contains(componentDto.name());
+    } else if (taskSubmit.getComponent().isPresent()) {
+      assertThat(task.getComponent()).contains(new CeTask.Component(taskSubmit.getComponent().get().getUuid(), null, null));
+    } else {
+      assertThat(task.getComponent()).isEmpty();
+    }
+    if (mainComponentDto != null) {
+      CeTask.Component component = task.getMainComponent().get();
+      assertThat(component.getUuid()).isEqualTo(mainComponentDto.uuid());
+      assertThat(component.getKey()).contains(mainComponentDto.getDbKey());
+      assertThat(component.getName()).contains(mainComponentDto.name());
+    } else if (taskSubmit.getComponent().isPresent()) {
+      assertThat(task.getMainComponent()).contains(new CeTask.Component(taskSubmit.getComponent().get().getMainComponentUuid(), null, null));
     } else {
-      assertThat(task.getComponentKey()).isEqualTo(componentDto.getDbKey());
-      assertThat(task.getComponentName()).isEqualTo(componentDto.name());
+      assertThat(task.getMainComponent()).isEmpty();
     }
+    assertThat(task.getType()).isEqualTo(taskSubmit.getType());
     assertThat(task.getSubmitterUuid()).isEqualTo(taskSubmit.getSubmitterUuid());
   }
 
@@ -461,23 +500,30 @@ public class CeQueueImplTest {
     Optional<CeQueueDto> queueDto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), taskSubmit.getUuid());
     assertThat(queueDto.isPresent()).isTrue();
     assertThat(queueDto.get().getTaskType()).isEqualTo(taskSubmit.getType());
-    assertThat(queueDto.get().getComponentUuid()).isEqualTo(taskSubmit.getComponentUuid());
+    Optional<Component> component = taskSubmit.getComponent();
+    if (component.isPresent()) {
+      assertThat(queueDto.get().getComponentUuid()).isEqualTo(component.get().getUuid());
+      assertThat(queueDto.get().getMainComponentUuid()).isEqualTo(component.get().getMainComponentUuid());
+    } else {
+      assertThat(queueDto.get().getComponentUuid()).isNull();
+      assertThat(queueDto.get().getComponentUuid()).isNull();
+    }
     assertThat(queueDto.get().getSubmitterUuid()).isEqualTo(taskSubmit.getSubmitterUuid());
     assertThat(queueDto.get().getCreatedAt()).isEqualTo(1_450_000_000_000L);
   }
 
-  private CeTask submit(String reportType, String componentUuid) {
-    return underTest.submit(createTaskSubmit(reportType, componentUuid, null));
+  private CeTask submit(String reportType, Component component) {
+    return underTest.submit(createTaskSubmit(reportType, component, null));
   }
 
   private CeTaskSubmit createTaskSubmit(String type) {
     return createTaskSubmit(type, null, null);
   }
 
-  private CeTaskSubmit createTaskSubmit(String type, @Nullable String componentUuid, @Nullable String submitterUuid) {
+  private CeTaskSubmit createTaskSubmit(String type, @Nullable Component component, @Nullable String submitterUuid) {
     return underTest.prepareSubmit()
       .setType(type)
-      .setComponentUuid(componentUuid)
+      .setComponent(component)
       .setSubmitterUuid(submitterUuid)
       .setCharacteristics(emptyMap())
       .build();
@@ -489,14 +535,22 @@ public class CeQueueImplTest {
     return componentDto;
   }
 
-  private CeQueueDto insertPendingInQueue(@Nullable String componentUuid) {
+  private CeQueueDto insertPendingInQueue(@Nullable Component component) {
     CeQueueDto dto = new CeQueueDto()
       .setUuid(UuidFactoryFast.getInstance().create())
       .setTaskType("some type")
-      .setComponentUuid(componentUuid)
       .setStatus(CeQueueDto.Status.PENDING);
+    if (component != null) {
+      dto.setComponentUuid(component.getUuid())
+        .setMainComponentUuid(component.getMainComponentUuid());
+    }
     db.getDbClient().ceQueueDao().insert(db.getSession(), dto);
     db.commit();
     return dto;
   }
+
+  private static int newComponentIdGenerator = new Random().nextInt(8_999_333);
+  private static Component newComponent(String mainComponentUuid) {
+    return new Component("uuid_" + newComponentIdGenerator++, mainComponentUuid);
+  }
 }
index 3c6f89c7c85c03889b971b89cb69996e6935c98d..fbd3db946d2eb1b4edde9d30b93554042d3e4bd3 100644 (file)
@@ -148,10 +148,12 @@ public class PostProjectAnalysisTasksExecutor implements ComputationStepExecutor
   }
 
   private static Project createProject(org.sonar.ce.task.CeTask ceTask) {
-    return new ProjectImpl(
-      ceTask.getComponentUuid(),
-      ceTask.getComponentKey(),
-      ceTask.getComponentName());
+    return ceTask.getMainComponent()
+      .map(c -> new ProjectImpl(
+        c.getUuid(),
+        c.getKey().orElseThrow(() -> new IllegalStateException("Missing project key")),
+        c.getName().orElseThrow(() -> new IllegalStateException("Missing project name"))))
+      .orElseThrow(() -> new IllegalStateException("Report processed for a task of a deleted component"));
   }
 
   @CheckForNull
index 9341e29a09d762dbf93aec8812fac3a3d9f7e751..e6629463e6ca8ac80079ed449566e4ca99e45a76 100644 (file)
@@ -21,10 +21,11 @@ package org.sonar.ce.task.projectanalysis.component;
 
 import javax.annotation.Nullable;
 import org.sonar.api.utils.MessageException;
-import org.sonar.scanner.protocol.output.ScannerReport;
 import org.sonar.ce.task.projectanalysis.analysis.MutableAnalysisMetadataHolder;
+import org.sonar.scanner.protocol.output.ScannerReport;
 
 import static org.apache.commons.lang.StringUtils.trimToNull;
+import static org.sonar.scanner.protocol.output.ScannerReport.Metadata.BranchType.UNSET;
 
 public class BranchLoader {
   private final MutableAnalysisMetadataHolder metadataHolder;
@@ -47,10 +48,22 @@ public class BranchLoader {
       throw MessageException.of("Properties sonar.branch and sonar.branch.name can't be set together");
     }
 
+    if (delegate == null && hasBranchProperties(metadata)) {
+      throw MessageException.of("Current edition does not support branch feature");
+    }
+
     if (delegate != null && deprecatedBranch == null) {
       delegate.load(metadata);
     } else {
       metadataHolder.setBranch(new DefaultBranchImpl(deprecatedBranch));
     }
   }
+
+  private static boolean hasBranchProperties(ScannerReport.Metadata metadata) {
+    return !metadata.getBranchName().isEmpty()
+      || !metadata.getPullRequestKey().isEmpty()
+      || !metadata.getMergeBranchName().isEmpty()
+      || metadata.getBranchType() != UNSET;
+  }
+
 }
index 8af513738c01cc331046a6f2f5d0f14580e2728b..6efa7d0d9a1e9f713666a939a38f3e76744d0800 100644 (file)
  */
 package org.sonar.ce.task.projectanalysis.component;
 
-import java.util.Date;
-import java.util.Optional;
 import javax.annotation.Nullable;
-import org.sonar.api.utils.System2;
 import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
 import org.sonar.ce.task.projectanalysis.analysis.Branch;
 import org.sonar.db.DbClient;
@@ -32,18 +29,16 @@ import org.sonar.db.component.BranchType;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.protobuf.DbProjectBranches;
 
-import static org.sonar.db.component.ComponentDto.UUID_PATH_OF_ROOT;
-import static org.sonar.db.component.ComponentDto.UUID_PATH_SEPARATOR;
-
+/**
+ * Creates or updates the data in table {@code PROJECT_BRANCHES} for the current root.
+ */
 public class BranchPersisterImpl implements BranchPersister {
   private final DbClient dbClient;
-  private final System2 system2;
   private final TreeRootHolder treeRootHolder;
   private final AnalysisMetadataHolder analysisMetadataHolder;
 
-  public BranchPersisterImpl(DbClient dbClient, System2 system2, TreeRootHolder treeRootHolder, AnalysisMetadataHolder analysisMetadataHolder) {
+  public BranchPersisterImpl(DbClient dbClient, TreeRootHolder treeRootHolder, AnalysisMetadataHolder analysisMetadataHolder) {
     this.dbClient = dbClient;
-    this.system2 = system2;
     this.treeRootHolder = treeRootHolder;
     this.analysisMetadataHolder = analysisMetadataHolder;
   }
@@ -52,32 +47,14 @@ public class BranchPersisterImpl implements BranchPersister {
     Branch branch = analysisMetadataHolder.getBranch();
     String branchUuid = treeRootHolder.getRoot().getUuid();
 
-    Optional<ComponentDto> branchComponentDtoOpt = dbClient.componentDao().selectByUuid(dbSession, branchUuid);
-
-    ComponentDto branchComponentDto;
-    if (branch.isMain()) {
-      checkState(branchComponentDtoOpt.isPresent(), "Project has been deleted by end-user during analysis");
-      branchComponentDto = branchComponentDtoOpt.get();
-    } else {
-      // inserts new row in table projects if it's the first time branch is analyzed
-      branchComponentDto = branchComponentDtoOpt.orElseGet(() -> insertIntoProjectsTable(dbSession, branchUuid));
-    }
+    ComponentDto branchComponentDto = dbClient.componentDao().selectByUuid(dbSession, branchUuid)
+      .orElseThrow(() -> new IllegalStateException("Component has been deleted by end-user during analysis"));
 
     // insert or update in table project_branches
     dbClient.branchDao().upsert(dbSession, toBranchDto(branchComponentDto, branch));
   }
 
-  private static void checkState(boolean condition, String msg) {
-    if (!condition) {
-      throw new IllegalStateException(msg);
-    }
-  }
-
-  private static <T> T firstNonNull(@Nullable T first, T second) {
-    return (first != null) ? first : second;
-  }
-
-  private BranchDto toBranchDto(ComponentDto componentDto, Branch branch) {
+  protected BranchDto toBranchDto(ComponentDto componentDto, Branch branch) {
     BranchDto dto = new BranchDto();
     dto.setUuid(componentDto.uuid());
 
@@ -103,19 +80,8 @@ public class BranchPersisterImpl implements BranchPersister {
     return dto;
   }
 
-  private ComponentDto insertIntoProjectsTable(DbSession dbSession, String branchUuid) {
-    String mainBranchProjectUuid = analysisMetadataHolder.getProject().getUuid();
-    ComponentDto project = dbClient.componentDao().selectOrFailByUuid(dbSession, mainBranchProjectUuid);
-    ComponentDto branchDto = project.copy();
-    branchDto.setUuid(branchUuid);
-    branchDto.setProjectUuid(branchUuid);
-    branchDto.setRootUuid(branchUuid);
-    branchDto.setUuidPath(UUID_PATH_OF_ROOT);
-    branchDto.setModuleUuidPath(UUID_PATH_SEPARATOR + branchUuid + UUID_PATH_SEPARATOR);
-    branchDto.setMainBranchProjectUuid(mainBranchProjectUuid);
-    branchDto.setDbKey(treeRootHolder.getRoot().getDbKey());
-    branchDto.setCreatedAt(new Date(system2.now()));
-    dbClient.componentDao().insert(dbSession, branchDto);
-    return branchDto;
+  private static <T> T firstNonNull(@Nullable T first, T second) {
+    return (first != null) ? first : second;
   }
+
 }
index 6de4c30557a7722a25427ed081ba974575ce12fb..80963133fc5f33e7138d0338ddd350c8fb29a345 100644 (file)
@@ -41,18 +41,19 @@ public class PopulateFileSourceLineCount extends DataChange implements ProjectAn
 
   @Override
   protected void execute(Context context) throws SQLException {
+    String componentUuid = ceTask.getComponent().get().getUuid();
     Long unInitializedFileSources = context.prepareSelect("select count(1) from file_sources where line_count = ? and project_uuid = ?")
       .setInt(1, LINE_COUNT_NOT_POPULATED)
-      .setString(2, ceTask.getComponentUuid())
+      .setString(2, componentUuid)
       .get(row -> row.getLong(1));
 
     if (unInitializedFileSources != null && unInitializedFileSources > 0) {
       MassUpdate massUpdate = context.prepareMassUpdate();
       massUpdate.select("select id,line_hashes from file_sources where line_count = ? and project_uuid = ?")
         .setInt(1, LINE_COUNT_NOT_POPULATED)
-        .setString(2, ceTask.getComponentUuid());
+        .setString(2, componentUuid);
       massUpdate.update("update file_sources set line_count = ? where id = ?");
-      massUpdate.rowPluralName("line counts of sources of project " + ceTask.getComponentUuid());
+      massUpdate.rowPluralName("line counts of sources of project " + componentUuid);
       massUpdate.execute(PopulateFileSourceLineCount::handle);
     }
   }
index 9a0c3b1592ec071222d6da0431754df6927cea69..99709304ebdb0a49d68d9aa9dd250b6b1cc7f48b 100644 (file)
@@ -104,30 +104,40 @@ public class LoadReportAnalysisMetadataHolderStep implements ComputationStep {
    */
   private Runnable loadProject(ScannerReport.Metadata reportMetadata, Organization organization) {
     String reportProjectKey = projectKeyFromReport(reportMetadata);
-    String componentKey = ceTask.getComponentKey();
-    if (componentKey == null) {
-      throw MessageException.of(format(
+    CeTask.Component mainComponent = mandatoryComponent(ceTask.getMainComponent());
+    String mainComponentKey = mainComponent.getKey()
+      .orElseThrow(() -> MessageException.of(format(
+        "Compute Engine task main component key is null. Project with UUID %s must have been deleted since report was uploaded. Can not proceed.",
+        mainComponent.getUuid())));
+    CeTask.Component component = mandatoryComponent(ceTask.getComponent());
+    String componentKey = component.getKey()
+      .orElseThrow(() -> MessageException.of(format(
         "Compute Engine task component key is null. Project with UUID %s must have been deleted since report was uploaded. Can not proceed.",
-        ceTask.getComponentUuid()));
-    }
+        component.getUuid())));
     ComponentDto dto = toProject(reportProjectKey);
+
     analysisMetadata.setProject(Project.from(dto));
     return () -> {
-      if (!componentKey.equals(reportProjectKey)) {
+      if (!mainComponentKey.equals(reportProjectKey)) {
         throw MessageException.of(format(
           "ProjectKey in report (%s) is not consistent with projectKey under which the report has been submitted (%s)",
           reportProjectKey,
-          componentKey));
+          mainComponentKey));
       }
       if (!dto.getOrganizationUuid().equals(organization.getUuid())) {
         throw MessageException.of(format("Project is not in the expected organization: %s", organization.getKey()));
       }
-      if (dto.getMainBranchProjectUuid() != null) {
-        throw MessageException.of("Project should not reference a branch");
+      if (componentKey.equals(mainComponentKey) && dto.getMainBranchProjectUuid() != null) {
+        throw MessageException.of("Component should not reference a branch");
       }
     };
   }
 
+  private static CeTask.Component mandatoryComponent(Optional<CeTask.Component> mainComponent) {
+    return mainComponent
+      .orElseThrow(() -> new IllegalStateException("component missing on ce task"));
+  }
+
   private Organization loadOrganization(ScannerReport.Metadata reportMetadata) {
     try (DbSession dbSession = dbClient.openSession(false)) {
       Organization organization = toOrganization(dbSession, ceTask.getOrganizationUuid());
index fabd35b9dbc1991a7d3b07820f828cbe9fb6579e..af0eafd471e7e122a7369c2054adbe53b7cca64e 100644 (file)
@@ -87,13 +87,13 @@ public class PostProjectAnalysisTasksExecutorTest {
   private String organizationName = organizationUuid + "_name";
   private System2 system2 = mock(System2.class);
   private ArgumentCaptor<PostProjectAnalysisTask.ProjectAnalysis> projectAnalysisArgumentCaptor = ArgumentCaptor.forClass(PostProjectAnalysisTask.ProjectAnalysis.class);
+  private CeTask.Component component = new CeTask.Component("component uuid", "component key", "component name");
   private CeTask ceTask = new CeTask.Builder()
     .setOrganizationUuid(organizationUuid)
     .setType("type")
     .setUuid("uuid")
-    .setComponentKey("component key")
-    .setComponentName("component name")
-    .setComponentUuid("component uuid")
+    .setComponent(component)
+    .setMainComponent(component)
     .build();
   private PostProjectAnalysisTask postProjectAnalysisTask = mock(PostProjectAnalysisTask.class);
   private PostProjectAnalysisTasksExecutor underTest = new PostProjectAnalysisTasksExecutor(
@@ -203,9 +203,9 @@ public class PostProjectAnalysisTasksExecutorTest {
     verify(postProjectAnalysisTask).finished(projectAnalysisArgumentCaptor.capture());
 
     Project project = projectAnalysisArgumentCaptor.getValue().getProject();
-    assertThat(project.getUuid()).isEqualTo(ceTask.getComponentUuid());
-    assertThat(project.getKey()).isEqualTo(ceTask.getComponentKey());
-    assertThat(project.getName()).isEqualTo(ceTask.getComponentName());
+    assertThat(project.getUuid()).isEqualTo(ceTask.getComponent().get().getUuid());
+    assertThat(project.getKey()).isEqualTo(ceTask.getComponent().get().getKey().get());
+    assertThat(project.getName()).isEqualTo(ceTask.getComponent().get().getName().get());
   }
 
   @Test
index 237b7b3efc13d5199d74ad10dcf17e39993df774..7cfcbd5c116ebb943b293d228e37d28bfe1859a6 100644 (file)
  */
 package org.sonar.ce.task.projectanalysis.component;
 
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
 import java.util.Optional;
+import javax.annotation.Nullable;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
 import org.sonar.api.utils.System2;
 import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
 import org.sonar.ce.task.projectanalysis.analysis.Branch;
 import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchDto;
 import org.sonar.db.component.BranchType;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.protobuf.DbProjectBranches;
 import org.sonar.server.project.Project;
 
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT;
 import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
+import static org.sonar.db.component.BranchType.LONG;
+import static org.sonar.db.component.BranchType.PULL_REQUEST;
 
+@RunWith(DataProviderRunner.class)
 public class BranchPersisterImplTest {
   private final static Component MAIN = builder(PROJECT, 1).setUuid("PROJECT_UUID").setKey("PROJECT_KEY").build();
   private final static Component BRANCH = builder(PROJECT, 1).setUuid("BRANCH_UUID").setKey("BRANCH_KEY").build();
@@ -51,66 +62,133 @@ public class BranchPersisterImplTest {
   @Rule
   public ExpectedException exception = ExpectedException.none();
 
-  BranchPersister underTest = new BranchPersisterImpl(dbTester.getDbClient(), System2.INSTANCE, treeRootHolder, analysisMetadataHolder);
+  BranchPersister underTest = new BranchPersisterImpl(dbTester.getDbClient(), treeRootHolder, analysisMetadataHolder);
 
   @Test
-  public void fail_if_no_component_for_main_branches() {
-    analysisMetadataHolder.setBranch(createBranch(BranchType.LONG, true, "master"));
+  public void persist_fails_with_ISE_if_no_component_for_main_branches() {
+    analysisMetadataHolder.setBranch(createBranch(LONG, true, "master"));
     treeRootHolder.setRoot(MAIN);
 
-    exception.expect(IllegalStateException.class);
-    exception.expectMessage("Project has been deleted by end-user during analysis");
+    expectMissingComponentISE();
+
+    underTest.persist(dbTester.getSession());
+  }
+
+  @Test
+  public void persist_fails_with_ISE_if_no_component_for_long_branches() {
+    analysisMetadataHolder.setBranch(createBranch(LONG, false, "foo"));
+    treeRootHolder.setRoot(BRANCH);
+
+    expectMissingComponentISE();
 
     underTest.persist(dbTester.getSession());
   }
 
   @Test
-  public void persist_secondary_branch() {
-    analysisMetadataHolder.setBranch(createBranch(BranchType.LONG, false, "branch"));
+  public void persist_fails_with_ISE_if_no_component_for_short_branches() {
+    analysisMetadataHolder.setBranch(createBranch(BranchType.SHORT, false, "foo"));
     treeRootHolder.setRoot(BRANCH);
 
-    // add main branch in project table and in metadata
-    ComponentDto dto = ComponentTesting.newPrivateProjectDto(dbTester.organizations().insert(), MAIN.getUuid()).setDbKey(MAIN.getDbKey());
-    analysisMetadataHolder.setProject(Project.from(dto));
-    dbTester.getDbClient().componentDao().insert(dbTester.getSession(), dto);
+    expectMissingComponentISE();
+
+    underTest.persist(dbTester.getSession());
+  }
+
+  @Test
+  public void persist_fails_with_ISE_if_no_component_for_pull_request() {
+    analysisMetadataHolder.setBranch(createBranch(BranchType.PULL_REQUEST, false, "12"));
+    treeRootHolder.setRoot(BRANCH);
+
+    expectMissingComponentISE();
+
+    underTest.persist(dbTester.getSession());
+  }
+
+  @Test
+  @UseDataProvider("nullOrNotNullString")
+  public void persist_creates_row_in_PROJECTS_BRANCHES_for_long_branch(@Nullable String mergeBranchUuid) {
+    String branchName = "branch";
+
+    // add project and branch in table PROJECTS
+    ComponentDto mainComponent = ComponentTesting.newPrivateProjectDto(dbTester.organizations().insert(), MAIN.getUuid()).setDbKey(MAIN.getKey());
+    ComponentDto component = ComponentTesting.newProjectBranch(mainComponent, new BranchDto().setUuid(BRANCH.getUuid()).setKey(BRANCH.getKey()).setBranchType(LONG));
+    dbTester.getDbClient().componentDao().insert(dbTester.getSession(), mainComponent, component);
+    dbTester.commit();
+    // set project in metadata
+    treeRootHolder.setRoot(BRANCH);
+    analysisMetadataHolder.setBranch(createBranch(LONG, false, branchName, mergeBranchUuid));
+    analysisMetadataHolder.setProject(Project.from(mainComponent));
 
-    // this should add new columns in project and project_branches
     underTest.persist(dbTester.getSession());
 
     dbTester.getSession().commit();
 
     assertThat(dbTester.countRowsOfTable("projects")).isEqualTo(2);
-    assertThat(dbTester.countRowsOfTable("project_branches")).isEqualTo(1);
+    Optional<BranchDto> branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH.getUuid());
+    assertThat(branchDto).isPresent();
+    assertThat(branchDto.get().getBranchType()).isEqualTo(LONG);
+    assertThat(branchDto.get().getKey()).isEqualTo(branchName);
+    assertThat(branchDto.get().getMergeBranchUuid()).isEqualTo(mergeBranchUuid);
+    assertThat(branchDto.get().getProjectUuid()).isEqualTo(MAIN.getUuid());
+    assertThat(branchDto.get().getPullRequestData()).isNull();
+  }
+
+  @DataProvider
+  public static Object[][] nullOrNotNullString() {
+    return new Object[][] {
+      {null},
+      {randomAlphabetic(12)}
+    };
   }
 
   @Test
-  public void persist_pull_request_data() {
+  @UseDataProvider("nullOrNotNullString")
+  public void persist_creates_row_in_PROJECTS_BRANCHES_for_pull_request(@Nullable String mergeBranchUuid) {
     String pullRequestId = "pr-123";
-    analysisMetadataHolder.setBranch(createBranch(BranchType.PULL_REQUEST, false, pullRequestId));
-    analysisMetadataHolder.setPullRequestKey(pullRequestId);
-    treeRootHolder.setRoot(BRANCH);
 
-    // add main branch in project table and in metadata
-    ComponentDto dto = ComponentTesting.newPrivateProjectDto(dbTester.organizations().insert(), MAIN.getUuid()).setDbKey(MAIN.getDbKey());
-    analysisMetadataHolder.setProject(Project.from(dto));
-    dbTester.getDbClient().componentDao().insert(dbTester.getSession(), dto);
+    // add project and branch in table PROJECTS
+    ComponentDto mainComponent = ComponentTesting.newPrivateProjectDto(dbTester.organizations().insert(), MAIN.getUuid()).setDbKey(MAIN.getKey());
+    ComponentDto component = ComponentTesting.newProjectBranch(mainComponent, new BranchDto().setUuid(BRANCH.getUuid()).setKey(BRANCH.getKey()).setBranchType(PULL_REQUEST));
+    dbTester.getDbClient().componentDao().insert(dbTester.getSession(), mainComponent, component);
+    dbTester.commit();
+    // set project in metadata
+    treeRootHolder.setRoot(BRANCH);
+    analysisMetadataHolder.setBranch(createBranch(PULL_REQUEST, false, pullRequestId, mergeBranchUuid));
+    analysisMetadataHolder.setProject(Project.from(mainComponent));
+    analysisMetadataHolder.setPullRequestKey(pullRequestId);
 
-    // this should add new columns in project and project_branches
     underTest.persist(dbTester.getSession());
 
     dbTester.getSession().commit();
 
     assertThat(dbTester.countRowsOfTable("projects")).isEqualTo(2);
-    assertThat(dbTester.countRowsOfTable("project_branches")).isEqualTo(1);
-    assertThat(dbTester.countSql("select count(*) from project_branches where pull_request_binary is not null")).isEqualTo(1);
+    Optional<BranchDto> branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH.getUuid());
+    assertThat(branchDto).isPresent();
+    assertThat(branchDto.get().getBranchType()).isEqualTo(PULL_REQUEST);
+    assertThat(branchDto.get().getKey()).isEqualTo(pullRequestId);
+    assertThat(branchDto.get().getMergeBranchUuid()).isEqualTo(mergeBranchUuid);
+    assertThat(branchDto.get().getProjectUuid()).isEqualTo(MAIN.getUuid());
+    assertThat(branchDto.get().getPullRequestData()).isEqualTo(DbProjectBranches.PullRequestData.newBuilder()
+      .setBranch(pullRequestId)
+      .setTitle(pullRequestId)
+      .build());
   }
 
   private static Branch createBranch(BranchType type, boolean isMain, String name) {
+    return createBranch(type, isMain, name, null);
+  }
+
+  private static Branch createBranch(BranchType type, boolean isMain, String name, @Nullable String mergeBranchUuid) {
     Branch branch = mock(Branch.class);
     when(branch.getType()).thenReturn(type);
     when(branch.getName()).thenReturn(name);
     when(branch.isMain()).thenReturn(isMain);
-    when(branch.getMergeBranchUuid()).thenReturn(Optional.empty());
+    when(branch.getMergeBranchUuid()).thenReturn(Optional.ofNullable(mergeBranchUuid));
     return branch;
   }
+
+  private void expectMissingComponentISE() {
+    exception.expect(IllegalStateException.class);
+    exception.expectMessage("Component has been deleted by end-user during analysis");
+  }
 }
index 2b103c4b3a91d13d87273f5b4aeb062d4a08dafa..502d6aa0e0fb6a449d7feaf8791e2c74feb211b8 100644 (file)
@@ -23,6 +23,7 @@ import com.tngtech.java.junit.dataprovider.DataProvider;
 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
 import com.tngtech.java.junit.dataprovider.UseDataProvider;
 import java.sql.SQLException;
+import java.util.Optional;
 import java.util.Random;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
@@ -57,6 +58,9 @@ public class PopulateFileSourceLineCountTest {
 
   @Test
   public void execute_has_no_effect_on_empty_table() throws SQLException {
+    String projectUuid = randomAlphanumeric(4);
+    when(ceTask.getComponent()).thenReturn(newComponent(projectUuid));
+
     underTest.execute();
   }
 
@@ -65,7 +69,7 @@ public class PopulateFileSourceLineCountTest {
   public void execute_populates_line_count_of_any_type(String type) throws SQLException {
     String projectUuid = randomAlphanumeric(4);
     String fileUuid = randomAlphanumeric(5);
-    when(ceTask.getComponentUuid()).thenReturn(projectUuid);
+    when(ceTask.getComponent()).thenReturn(newComponent(projectUuid));
     int lineCount = 1 + random.nextInt(15);
     insertUnpopulatedFileSource(projectUuid, fileUuid, type, lineCount);
     assertThat(getLineCountByFileUuid(fileUuid)).isEqualTo(LINE_COUNT_NOT_POPULATED);
@@ -86,7 +90,7 @@ public class PopulateFileSourceLineCountTest {
     int lineCountFile2 = 50 + random.nextInt(15);
     int lineCountFile3 = 150 + random.nextInt(15);
 
-    when(ceTask.getComponentUuid()).thenReturn(projectUuid);
+    when(ceTask.getComponent()).thenReturn(newComponent(projectUuid));
     insertPopulatedFileSource(projectUuid, fileUuid1, type, lineCountFile1);
     int badLineCountFile2 = insertInconsistentPopulatedFileSource(projectUuid, fileUuid2, type, lineCountFile2);
     insertUnpopulatedFileSource(projectUuid, fileUuid3, type, lineCountFile3);
@@ -111,7 +115,7 @@ public class PopulateFileSourceLineCountTest {
     int lineCountFile1 = 100 + random.nextInt(15);
     int lineCountFile2 = 30 + random.nextInt(15);
 
-    when(ceTask.getComponentUuid()).thenReturn(projectUuid1);
+    when(ceTask.getComponent()).thenReturn(newComponent(projectUuid1));
     insertUnpopulatedFileSource(projectUuid1, fileUuid1, type, lineCountFile1);
     insertUnpopulatedFileSource(projectUuid2, fileUuid2, type, lineCountFile2);
 
@@ -127,7 +131,7 @@ public class PopulateFileSourceLineCountTest {
     String projectUuid = randomAlphanumeric(4);
     String fileUuid1 = randomAlphanumeric(5);
 
-    when(ceTask.getComponentUuid()).thenReturn(projectUuid);
+    when(ceTask.getComponent()).thenReturn(newComponent(projectUuid));
     insertFileSource(projectUuid, fileUuid1, type, null, LINE_COUNT_NOT_POPULATED);
 
     underTest.execute();
@@ -141,7 +145,7 @@ public class PopulateFileSourceLineCountTest {
     String projectUuid = randomAlphanumeric(4);
     String fileUuid1 = randomAlphanumeric(5);
 
-    when(ceTask.getComponentUuid()).thenReturn(projectUuid);
+    when(ceTask.getComponent()).thenReturn(newComponent(projectUuid));
     insertFileSource(projectUuid, fileUuid1, type, "", LINE_COUNT_NOT_POPULATED);
 
     underTest.execute();
@@ -204,4 +208,8 @@ public class PopulateFileSourceLineCountTest {
       "UPDATED_AT", 1_222_333L);
     db.commit();
   }
+
+  private static Optional<CeTask.Component> newComponent(String projectUuid) {
+    return Optional.of(new CeTask.Component(projectUuid, "key_" + projectUuid, "name_" + projectUuid));
+  }
 }
index 41e3a4f8b73019471867aef4dfe653f9f6fefeb4..3bf4c6fa7fe4d4c8a7db221d305a2b2dae0d243b 100644 (file)
@@ -25,6 +25,7 @@ import com.tngtech.java.junit.dataprovider.UseDataProvider;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Optional;
 import org.assertj.core.api.Assertions;
 import org.junit.Before;
 import org.junit.Rule;
@@ -165,10 +166,44 @@ public class LoadReportAnalysisMetadataHolderStepTest {
     assertThat(analysisMetadataHolder.isCrossProjectDuplicationEnabled()).isEqualTo(false);
   }
 
+  @Test
+  public void execute_fails_with_ISE_if_component_is_null_in_CE_task() {
+    CeTask res = mock(CeTask.class);
+    when(res.getComponent()).thenReturn(Optional.empty());
+    when(res.getOrganizationUuid()).thenReturn(defaultOrganizationProvider.get().getUuid());
+    reportReader.setMetadata(ScannerReport.Metadata.newBuilder().build());
+
+    ComputationStep underTest = createStep(res);
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("component missing on ce task");
+
+    underTest.execute(new TestComputationStepContext());
+  }
+
+  @Test
+  public void execute_fails_with_MessageException_if_main_projectKey_is_null_in_CE_task() {
+    CeTask res = mock(CeTask.class);
+    Optional<CeTask.Component> component = Optional.of(new CeTask.Component("main_prj_uuid", null, null));
+    when(res.getComponent()).thenReturn(component);
+    when(res.getMainComponent()).thenReturn(component);
+    when(res.getOrganizationUuid()).thenReturn(defaultOrganizationProvider.get().getUuid());
+    reportReader.setMetadata(ScannerReport.Metadata.newBuilder().build());
+
+    ComputationStep underTest = createStep(res);
+
+    expectedException.expect(MessageException.class);
+    expectedException.expectMessage("Compute Engine task main component key is null. Project with UUID main_prj_uuid must have been deleted since report was uploaded. Can not proceed.");
+
+    underTest.execute(new TestComputationStepContext());
+  }
+
   @Test
   public void execute_fails_with_MessageException_if_projectKey_is_null_in_CE_task() {
     CeTask res = mock(CeTask.class);
-    when(res.getComponentUuid()).thenReturn("prj_uuid");
+    Optional<CeTask.Component> component = Optional.of(new CeTask.Component("prj_uuid", null, null));
+    when(res.getComponent()).thenReturn(component);
+    when(res.getMainComponent()).thenReturn(Optional.of(new CeTask.Component("main_prj_uuid", "main_prj_key", null)));
     when(res.getOrganizationUuid()).thenReturn(defaultOrganizationProvider.get().getUuid());
     reportReader.setMetadata(ScannerReport.Metadata.newBuilder().build());
 
@@ -407,8 +442,10 @@ public class LoadReportAnalysisMetadataHolderStepTest {
 
   private CeTask createCeTask(String projectKey, String organizationUuid) {
     CeTask res = mock(CeTask.class);
+    Optional<CeTask.Component> component = Optional.of(new CeTask.Component(projectKey + "_uuid", projectKey, projectKey + "_name"));
     when(res.getOrganizationUuid()).thenReturn(organizationUuid);
-    when(res.getComponentKey()).thenReturn(projectKey);
+    when(res.getComponent()).thenReturn(component);
+    when(res.getMainComponent()).thenReturn(component);
     return res;
   }
 
index d71f9d036d0e43e81b479d393310b34d57cf41bf..3ea246612fac97c697c1b0884a22d6611ecd76c7 100644 (file)
@@ -22,10 +22,13 @@ package org.sonar.ce.task;
 import com.google.common.base.MoreObjects;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Strings.emptyToNull;
 import static java.util.Collections.emptyMap;
 import static java.util.Collections.unmodifiableMap;
@@ -37,9 +40,8 @@ public class CeTask {
   private final String organizationUuid;
   private final String type;
   private final String uuid;
-  private final String componentUuid;
-  private final String componentKey;
-  private final String componentName;
+  private final Component component;
+  private final Component mainComponent;
   private final String submitterUuid;
   private final Map<String, String> characteristics;
 
@@ -47,9 +49,10 @@ public class CeTask {
     this.organizationUuid = requireNonNull(emptyToNull(builder.organizationUuid), "organizationUuid can't be null nor empty");
     this.uuid = requireNonNull(emptyToNull(builder.uuid), "uuid can't be null nor empty");
     this.type = requireNonNull(emptyToNull(builder.type), "type can't be null nor empty");
-    this.componentUuid = emptyToNull(builder.componentUuid);
-    this.componentKey = emptyToNull(builder.componentKey);
-    this.componentName = emptyToNull(builder.componentName);
+    checkArgument((builder.component == null) == (builder.mainComponent == null),
+      "None or both component and main component must be non null");
+    this.component = builder.component;
+    this.mainComponent = builder.mainComponent;
     this.submitterUuid = emptyToNull(builder.submitterUuid);
     if (builder.characteristics == null) {
       this.characteristics = emptyMap();
@@ -70,19 +73,12 @@ public class CeTask {
     return type;
   }
 
-  @CheckForNull
-  public String getComponentUuid() {
-    return componentUuid;
-  }
-
-  @CheckForNull
-  public String getComponentKey() {
-    return componentKey;
+  public Optional<Component> getComponent() {
+    return Optional.ofNullable(component);
   }
 
-  @CheckForNull
-  public String getComponentName() {
-    return componentName;
+  public Optional<Component> getMainComponent() {
+    return Optional.ofNullable(mainComponent);
   }
 
   @CheckForNull
@@ -100,9 +96,8 @@ public class CeTask {
       .add("organizationUuid", organizationUuid)
       .add("type", type)
       .add("uuid", uuid)
-      .add("componentUuid", componentUuid)
-      .add("componentKey", componentKey)
-      .add("componentName", componentName)
+      .add("component", component)
+      .add("mainComponent", mainComponent)
       .add("submitterUuid", submitterUuid)
       .toString();
   }
@@ -128,9 +123,8 @@ public class CeTask {
     private String organizationUuid;
     private String uuid;
     private String type;
-    private String componentUuid;
-    private String componentKey;
-    private String componentName;
+    private Component component;
+    private Component mainComponent;
     private String submitterUuid;
     private Map<String, String> characteristics;
 
@@ -154,18 +148,13 @@ public class CeTask {
       return this;
     }
 
-    public Builder setComponentUuid(@Nullable String componentUuid) {
-      this.componentUuid = componentUuid;
-      return this;
-    }
-
-    public Builder setComponentKey(@Nullable String s) {
-      this.componentKey = s;
+    public Builder setComponent(@Nullable Component component) {
+      this.component = component;
       return this;
     }
 
-    public Builder setComponentName(@Nullable String s) {
-      this.componentName = s;
+    public Builder setMainComponent(@Nullable Component mainComponent) {
+      this.mainComponent = mainComponent;
       return this;
     }
 
@@ -183,4 +172,58 @@ public class CeTask {
       return new CeTask(this);
     }
   }
+
+  public static final class Component {
+    private final String uuid;
+    @CheckForNull
+    private final String key;
+    @CheckForNull
+    private final String name;
+
+    public Component(String uuid, @Nullable String key, @Nullable String name) {
+      this.uuid = requireNonNull(emptyToNull(uuid), "uuid can't be null nor empty");
+      this.key = emptyToNull(key);
+      this.name = emptyToNull(name);
+    }
+
+    public String getUuid() {
+      return uuid;
+    }
+
+    public Optional<String> getKey() {
+      return Optional.ofNullable(key);
+    }
+
+    public Optional<String> getName() {
+      return Optional.ofNullable(name);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      Component component = (Component) o;
+      return Objects.equals(uuid, component.uuid) &&
+        Objects.equals(key, component.key) &&
+        Objects.equals(name, component.name);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(uuid, key, name);
+    }
+
+    @Override
+    public String toString() {
+      return "Component{" +
+        "uuid='" + uuid + '\'' +
+        ", key='" + key + '\'' +
+        ", name='" + name + '\'' +
+        '}';
+    }
+  }
 }
diff --git a/server/sonar-ce-task/src/test/java/org/sonar/ce/task/CeTaskComponentTest.java b/server/sonar-ce-task/src/test/java/org/sonar/ce/task/CeTaskComponentTest.java
new file mode 100644 (file)
index 0000000..556b9f7
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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.ce.task;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@RunWith(DataProviderRunner.class)
+public class CeTaskComponentTest {
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Test
+  @UseDataProvider("nullOrEmpty")
+  public void constructor_fails_with_NPE_if_uuid_is_null_or_empty(String str) {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("uuid can't be null nor empty");
+
+    new CeTask.Component(str, "foo", "bar");
+  }
+
+  @Test
+  @UseDataProvider("nullOrEmpty")
+  public void constructor_considers_empty_as_null_and_accept_it_for_key(String str) {
+    CeTask.Component underTest = new CeTask.Component("foo", str, "bar");
+
+    assertThat(underTest.getKey()).isEmpty();
+  }
+
+  @Test
+  @UseDataProvider("nullOrEmpty")
+  public void constructor_considers_empty_as_null_and_accept_it_for_name(String str) {
+    CeTask.Component underTest = new CeTask.Component("foo", "bar", str);
+
+    assertThat(underTest.getName()).isEmpty();
+  }
+
+  @Test
+  public void equals_is_based_on_all_fields() {
+    String uuid = randomAlphabetic(2);
+    String key = randomAlphabetic(3);
+    String name = randomAlphabetic(4);
+    String somethingElse = randomAlphabetic(5);
+    CeTask.Component underTest = new CeTask.Component(uuid, key, name);
+
+    assertThat(underTest).isEqualTo(underTest);
+    assertThat(underTest).isEqualTo(new CeTask.Component(uuid, key, name));
+    assertThat(underTest).isNotEqualTo(null);
+    assertThat(underTest).isNotEqualTo(new Object());
+    assertThat(underTest).isNotEqualTo(new CeTask.Component(somethingElse, key, name));
+    assertThat(underTest).isNotEqualTo(new CeTask.Component(uuid, somethingElse, name));
+    assertThat(underTest).isNotEqualTo(new CeTask.Component(uuid, key, somethingElse));
+    assertThat(underTest).isNotEqualTo(new CeTask.Component(uuid, key, null));
+  }
+
+  @Test
+  public void hashcode_is_based_on_all_fields() {
+    String uuid = randomAlphabetic(2);
+    String key = randomAlphabetic(3);
+    String name = randomAlphabetic(4);
+    String somethingElse = randomAlphabetic(5);
+    CeTask.Component underTest = new CeTask.Component(uuid, key, name);
+
+    assertThat(underTest.hashCode()).isEqualTo(underTest.hashCode());
+    assertThat(underTest.hashCode()).isEqualTo(new CeTask.Component(uuid, key, name).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new Object().hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new CeTask.Component(somethingElse, key, name).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new CeTask.Component(uuid, somethingElse, name).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new CeTask.Component(uuid, key, somethingElse).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new CeTask.Component(uuid, key, null).hashCode());
+  }
+
+  @DataProvider
+  public static Object[][] nullOrEmpty() {
+    return new Object[][] {
+      {null},
+      {""},
+    };
+  }
+}
index dbb26c47ca34cf8583db6e4cc6c2dc7fae50ce03..a72e0b39b5930da4ceef1a03404a548f34ddd0ec 100644 (file)
 package org.sonar.ce.task;
 
 import com.google.common.collect.ImmutableMap;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.entry;
 
+@RunWith(DataProviderRunner.class)
 public class CeTaskTest {
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
@@ -91,15 +96,40 @@ public class CeTaskTest {
     underTest.build();
   }
 
+  @Test
+  @UseDataProvider("oneAndOnlyOneOfComponentAndMainComponent")
+  public void build_fails_with_IAE_if_only_one_of_component_and_main_component_is_non_null(CeTask.Component component, CeTask.Component mainComponent) {
+    underTest.setOrganizationUuid("org1");
+    underTest.setType("TYPE_1");
+    underTest.setUuid("UUID_1");
+    underTest.setComponent(component);
+    underTest.setMainComponent(mainComponent);
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("None or both component and main component must be non null");
+    
+    underTest.build();
+  }
+
+  @DataProvider
+  public static Object[][] oneAndOnlyOneOfComponentAndMainComponent() {
+    CeTask.Component component = new CeTask.Component("COMPONENT_UUID_1", "COMPONENT_KEY_1", "The component");
+    return new Object[][] {
+      {component, null},
+      {null, component}
+    };
+  }
+
   @Test
   public void verify_getters() {
+    CeTask.Component component = new CeTask.Component("COMPONENT_UUID_1", "COMPONENT_KEY_1", "The component");
+    CeTask.Component mainComponent = new CeTask.Component("MAIN_COMPONENT_UUID_1", "MAIN_COMPONENT_KEY_1", "The main component");
     underTest.setOrganizationUuid("org1");
     underTest.setType("TYPE_1");
     underTest.setUuid("UUID_1");
     underTest.setSubmitterUuid("LOGIN_1");
-    underTest.setComponentKey("COMPONENT_KEY_1");
-    underTest.setComponentUuid("COMPONENT_UUID_1");
-    underTest.setComponentName("The component");
+    underTest.setComponent(component);
+    underTest.setMainComponent(mainComponent);
     underTest.setCharacteristics(ImmutableMap.of("k1", "v1", "k2", "v2"));
 
     CeTask task = underTest.build();
@@ -108,25 +138,11 @@ public class CeTaskTest {
     assertThat(task.getUuid()).isEqualTo("UUID_1");
     assertThat(task.getType()).isEqualTo("TYPE_1");
     assertThat(task.getSubmitterUuid()).isEqualTo("LOGIN_1");
-    assertThat(task.getComponentKey()).isEqualTo("COMPONENT_KEY_1");
-    assertThat(task.getComponentUuid()).isEqualTo("COMPONENT_UUID_1");
-    assertThat(task.getComponentName()).isEqualTo("The component");
+    assertThat(task.getComponent()).contains(component);
+    assertThat(task.getMainComponent()).contains(mainComponent);
     assertThat(task.getCharacteristics()).containsExactly(entry("k1", "v1"), entry("k2", "v2"));
   }
 
-  @Test
-  public void empty_in_component_properties_is_considered_as_null() {
-    CeTask ceTask = underTest.setOrganizationUuid("org1").setUuid("uuid").setType("type")
-      .setComponentKey("")
-      .setComponentName("")
-      .setComponentUuid("")
-      .build();
-
-    assertThat(ceTask.getComponentKey()).isNull();
-    assertThat(ceTask.getComponentName()).isNull();
-    assertThat(ceTask.getComponentUuid()).isNull();
-  }
-
   @Test
   public void empty_in_submitterLogin_is_considered_as_null() {
     CeTask ceTask = underTest.setOrganizationUuid("org1").setUuid("uuid").setType("type")
index db4c94834ccbf198a4712b2a7cb3b2b03f9ad764..e414da45fc01d1a30e46ad60646407f478f973f1 100644 (file)
@@ -64,7 +64,7 @@ public class ReportAnalysisFailureNotificationExecutionListener implements CeWor
     if (status == CeActivityDto.Status.SUCCESS) {
       return;
     }
-    String projectUuid = ceTask.getComponentUuid();
+    String projectUuid = ceTask.getComponent().map(CeTask.Component::getUuid).orElse(null);
     if (!CeTaskTypes.REPORT.equals(ceTask.getType()) || projectUuid == null) {
       return;
     }
index 61c801947bccaea085330569e3cf435186240311..eb6928877d3e1d274ca8c75f47745614caf99906 100644 (file)
@@ -53,6 +53,7 @@ import static com.google.common.base.Preconditions.checkArgument;
 import static java.lang.String.format;
 import static java.util.Collections.singletonList;
 import static java.util.Objects.requireNonNull;
+import static java.util.Optional.ofNullable;
 import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
 
 @ComputeEngineSide
@@ -90,15 +91,13 @@ public class InternalCeQueueImpl extends CeQueueImpl implements InternalCeQueue
       Optional<CeQueueDto> opt = ceQueueDao.peek(dbSession, workerUuid);
       if (opt.isPresent()) {
         CeQueueDto taskDto = opt.get();
-        ComponentDto component = null;
-        String componentUuid = taskDto.getComponentUuid();
-        if (componentUuid != null) {
-          component = dbClient.componentDao().selectByUuid(dbSession, componentUuid).orElse(null);
-        }
+        Map<String, ComponentDto> componentsByUuid = loadComponentDtos(dbSession, taskDto);
         Map<String, String> characteristics = dbClient.ceTaskCharacteristicsDao().selectByTaskUuids(dbSession, singletonList(taskDto.getUuid())).stream()
           .collect(uniqueIndex(CeTaskCharacteristicDto::getKey, CeTaskCharacteristicDto::getValue));
 
-        CeTask task = convertToTask(taskDto, characteristics, component);
+        CeTask task = convertToTask(taskDto, characteristics,
+          ofNullable(taskDto.getComponentUuid()).map(componentsByUuid::get).orElse(null),
+          ofNullable(taskDto.getMainComponentUuid()).map(componentsByUuid::get).orElse(null));
         queueStatus.addInProgress();
         return Optional.of(task);
       }
index e49748b73d9ff797e50e6cefa2345ed2bd456ff0..ba71928c6588d178c1baa8b2d15c64fafa965110 100644 (file)
@@ -180,7 +180,7 @@ public class CeWorkerImpl implements CeWorker {
   private static Profiler startLogProfiler(CeTask task) {
     Profiler profiler = Profiler.create(LOG)
       .logTimeLast(true)
-      .addContext("project", task.getComponentKey())
+      .addContext("project", task.getMainComponent().flatMap(CeTask.Component::getKey).orElse(null))
       .addContext("type", task.getType());
     for (Map.Entry<String, String> characteristic : task.getCharacteristics().entrySet()) {
       profiler.addContext(characteristic.getKey(), characteristic.getValue());
index fd341a467dd7a048ee203a9665abd638151436cc..69cd28130967f74933f8c487f9344cf47d1cb622 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.ce.notification;
 
 import java.util.Arrays;
+import java.util.Optional;
 import java.util.Random;
 import javax.annotation.Nullable;
 import org.junit.Before;
@@ -121,7 +122,7 @@ public class ReportAnalysisFailureNotificationExecutionListenerTest {
   public void onEnd_has_no_effect_if_there_is_no_subscriber_for_ReportAnalysisFailureNotification_type() {
     String componentUuid = randomAlphanumeric(6);
     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
-    when(ceTaskMock.getComponentUuid()).thenReturn(componentUuid);
+    when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
     when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.TYPE)))
       .thenReturn(false);
 
@@ -134,7 +135,7 @@ public class ReportAnalysisFailureNotificationExecutionListenerTest {
   public void onEnd_fails_with_RowNotFoundException_if_component_does_not_exist_in_DB() {
     String componentUuid = randomAlphanumeric(6);
     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
-    when(ceTaskMock.getComponentUuid()).thenReturn(componentUuid);
+    when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
     when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.TYPE)))
       .thenReturn(true);
 
@@ -160,7 +161,7 @@ public class ReportAnalysisFailureNotificationExecutionListenerTest {
     Arrays.asList(module, directory, file, view, subView, projectCopy, application)
       .forEach(component -> {
         try {
-          when(ceTaskMock.getComponentUuid()).thenReturn(component.uuid());
+          when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(component.uuid(), null, null)));
           when(notificationService.hasProjectSubscribersForTypes(component.uuid(), singleton(ReportAnalysisFailureNotification.TYPE)))
             .thenReturn(true);
 
@@ -180,7 +181,7 @@ public class ReportAnalysisFailureNotificationExecutionListenerTest {
     String taskUuid = randomAlphanumeric(6);
     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
     when(ceTaskMock.getUuid()).thenReturn(taskUuid);
-    when(ceTaskMock.getComponentUuid()).thenReturn(componentUuid);
+    when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
     when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.TYPE)))
       .thenReturn(true);
     dbTester.components().insertPrivateProject(s -> s.setUuid(componentUuid));
@@ -293,7 +294,7 @@ public class ReportAnalysisFailureNotificationExecutionListenerTest {
   private ComponentDto initMocksToPassConditions(String taskUuid, int createdAt, @Nullable Long executedAt) {
     ComponentDto project = random.nextBoolean() ? dbTester.components().insertPrivateProject() : dbTester.components().insertPublicProject();
     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
-    when(ceTaskMock.getComponentUuid()).thenReturn(project.uuid());
+    when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(project.uuid(), null, null)));
     when(ceTaskMock.getUuid()).thenReturn(taskUuid);
     when(notificationService.hasProjectSubscribersForTypes(project.uuid(), singleton(ReportAnalysisFailureNotification.TYPE)))
       .thenReturn(true);
index 2b7229ee4d26f83933c99edd3ffbabfe74af6bd0..82678dd86996f65b235e9ac6b876c79a9d91de2f 100644 (file)
@@ -99,7 +99,7 @@ public class InternalCeQueueImplTest {
 
   @Test
   public void submit_returns_task_populated_from_CeTaskSubmit_and_creates_CeQueue_row() {
-    CeTaskSubmit taskSubmit = createTaskSubmit(CeTaskTypes.REPORT, "PROJECT_1", "rob");
+    CeTaskSubmit taskSubmit = createTaskSubmit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"), "rob");
     CeTask task = underTest.submit(taskSubmit);
 
     verifyCeTask(taskSubmit, task, null);
@@ -108,8 +108,8 @@ public class InternalCeQueueImplTest {
 
   @Test
   public void submit_populates_component_name_and_key_of_CeTask_if_component_exists() {
-    ComponentDto componentDto = insertComponent(newComponentDto("PROJECT_1"));
-    CeTaskSubmit taskSubmit = createTaskSubmit(CeTaskTypes.REPORT, componentDto.uuid(), null);
+    ComponentDto componentDto = insertComponent(newProjectDto("PROJECT_1"));
+    CeTaskSubmit taskSubmit = createTaskSubmit(CeTaskTypes.REPORT, componentDto, null);
 
     CeTask task = underTest.submit(taskSubmit);
 
@@ -127,7 +127,7 @@ public class InternalCeQueueImplTest {
 
   @Test
   public void massSubmit_returns_tasks_for_each_CeTaskSubmit_populated_from_CeTaskSubmit_and_creates_CeQueue_row_for_each() {
-    CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, "PROJECT_1", "rob");
+    CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"), "rob");
     CeTaskSubmit taskSubmit2 = createTaskSubmit("some type");
 
     List<CeTask> tasks = underTest.massSubmit(asList(taskSubmit1, taskSubmit2));
@@ -141,9 +141,9 @@ public class InternalCeQueueImplTest {
 
   @Test
   public void massSubmit_populates_component_name_and_key_of_CeTask_if_component_exists() {
-    ComponentDto componentDto1 = insertComponent(newComponentDto("PROJECT_1"));
-    CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, componentDto1.uuid(), null);
-    CeTaskSubmit taskSubmit2 = createTaskSubmit("something", "non existing component uuid", null);
+    ComponentDto componentDto1 = insertComponent(newProjectDto("PROJECT_1"));
+    CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, componentDto1, null);
+    CeTaskSubmit taskSubmit2 = createTaskSubmit("something", newProjectDto("non existing component uuid"), null);
 
     List<CeTask> tasks = underTest.massSubmit(asList(taskSubmit1, taskSubmit2));
 
@@ -162,7 +162,7 @@ public class InternalCeQueueImplTest {
 
   @Test
   public void test_remove() {
-    CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
     Optional<CeTask> peek = underTest.peek(WORKER_UUID_1);
     underTest.remove(peek.get(), CeActivityDto.Status.SUCCESS, null, null);
 
@@ -196,7 +196,7 @@ public class InternalCeQueueImplTest {
 
   @Test
   public void remove_does_not_set_analysisUuid_in_CeActivity_when_CeTaskResult_has_no_analysis_uuid() {
-    CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
     Optional<CeTask> peek = underTest.peek(WORKER_UUID_1);
     underTest.remove(peek.get(), CeActivityDto.Status.SUCCESS, newTaskResult(null), null);
 
@@ -208,7 +208,7 @@ public class InternalCeQueueImplTest {
 
   @Test
   public void remove_sets_analysisUuid_in_CeActivity_when_CeTaskResult_has_analysis_uuid() {
-    CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
 
     Optional<CeTask> peek = underTest.peek(WORKER_UUID_2);
     underTest.remove(peek.get(), CeActivityDto.Status.SUCCESS, newTaskResult(AN_ANALYSIS_UUID), null);
@@ -223,7 +223,7 @@ public class InternalCeQueueImplTest {
   public void remove_saves_error_message_and_stacktrace_when_exception_is_provided() {
     Throwable error = new NullPointerException("Fake NPE to test persistence to DB");
 
-    CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
     Optional<CeTask> peek = underTest.peek(WORKER_UUID_1);
     underTest.remove(peek.get(), CeActivityDto.Status.FAILED, null, error);
 
@@ -239,7 +239,7 @@ public class InternalCeQueueImplTest {
   public void remove_saves_error_when_TypedMessageException_is_provided() {
     Throwable error = new TypedExceptionImpl("aType", "aMessage");
 
-    CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
     Optional<CeTask> peek = underTest.peek(WORKER_UUID_1);
     underTest.remove(peek.get(), CeActivityDto.Status.FAILED, null, error);
 
@@ -253,7 +253,7 @@ public class InternalCeQueueImplTest {
   public void remove_updates_queueStatus_success_even_if_task_does_not_exist_in_DB() {
     CEQueueStatus queueStatus = mock(CEQueueStatus.class);
 
-    CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
     db.getDbClient().ceQueueDao().deleteByUuid(db.getSession(), task.getUuid());
     db.commit();
 
@@ -271,7 +271,7 @@ public class InternalCeQueueImplTest {
   public void remove_updates_queueStatus_failure_even_if_task_does_not_exist_in_DB() {
     CEQueueStatus queueStatusMock = mock(CEQueueStatus.class);
 
-    CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
     db.getDbClient().ceQueueDao().deleteByUuid(db.getSession(), task.getUuid());
     db.commit();
     InternalCeQueueImpl underTest = new InternalCeQueueImpl(system2, db.getDbClient(), null, queueStatusMock, null, null);
@@ -288,7 +288,7 @@ public class InternalCeQueueImplTest {
   public void cancelWornOuts_does_not_update_queueStatus() {
     CEQueueStatus queueStatusMock = mock(CEQueueStatus.class);
 
-    CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
     db.executeUpdateSql("update ce_queue set status = 'PENDING', started_at = 123 where uuid = '" + task.getUuid() + "'");
     db.commit();
     InternalCeQueueImpl underTest = new InternalCeQueueImpl(system2, db.getDbClient(), null, queueStatusMock, null, null);
@@ -334,7 +334,7 @@ public class InternalCeQueueImplTest {
 
   @Test
   public void fail_to_remove_if_not_in_queue() {
-    CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
     underTest.remove(task, CeActivityDto.Status.SUCCESS, null, null);
 
     expectedException.expect(IllegalStateException.class);
@@ -344,13 +344,32 @@ public class InternalCeQueueImplTest {
 
   @Test
   public void test_peek() {
-    CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
 
     Optional<CeTask> peek = underTest.peek(WORKER_UUID_1);
     assertThat(peek.isPresent()).isTrue();
     assertThat(peek.get().getUuid()).isEqualTo(task.getUuid());
     assertThat(peek.get().getType()).isEqualTo(CeTaskTypes.REPORT);
-    assertThat(peek.get().getComponentUuid()).isEqualTo("PROJECT_1");
+    assertThat(peek.get().getComponent()).contains(new CeTask.Component("PROJECT_1", null, null));
+    assertThat(peek.get().getMainComponent()).contains(peek.get().getComponent().get());
+
+    // no more pending tasks
+    peek = underTest.peek(WORKER_UUID_2);
+    assertThat(peek.isPresent()).isFalse();
+  }
+
+  @Test
+  public void peek_populates_name_and_key_for_existing_component_and_main_component() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto branch = db.components().insertProjectBranch(project);
+    CeTask task = submit(CeTaskTypes.REPORT, branch);
+
+    Optional<CeTask> peek = underTest.peek(WORKER_UUID_1);
+    assertThat(peek.isPresent()).isTrue();
+    assertThat(peek.get().getUuid()).isEqualTo(task.getUuid());
+    assertThat(peek.get().getType()).isEqualTo(CeTaskTypes.REPORT);
+    assertThat(peek.get().getComponent()).contains(new CeTask.Component(branch.uuid(), branch.getDbKey(), branch.name()));
+    assertThat(peek.get().getMainComponent()).contains(new CeTask.Component(project.uuid(), project.getDbKey(), project.name()));
 
     // no more pending tasks
     peek = underTest.peek(WORKER_UUID_2);
@@ -359,7 +378,7 @@ public class InternalCeQueueImplTest {
 
   @Test
   public void peek_is_paused_then_resumed() {
-    CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
     underTest.pauseWorkers();
 
     Optional<CeTask> peek = underTest.peek(WORKER_UUID_1);
@@ -385,7 +404,7 @@ public class InternalCeQueueImplTest {
 
   @Test
   public void peek_nothing_if_application_status_stopping() {
-    submit(CeTaskTypes.REPORT, "PROJECT_1");
+    submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
     when(computeEngineStatus.getStatus()).thenReturn(STOPPING);
 
     Optional<CeTask> peek = underTest.peek(WORKER_UUID_1);
@@ -468,7 +487,7 @@ public class InternalCeQueueImplTest {
 
   @Test
   public void cancel_pending() {
-    CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
     CeQueueDto queueDto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), task.getUuid()).get();
 
     underTest.cancel(db.getSession(), queueDto);
@@ -480,7 +499,7 @@ public class InternalCeQueueImplTest {
 
   @Test
   public void fail_to_cancel_if_in_progress() {
-    CeTask task = submit(CeTaskTypes.REPORT, "PROJECT_1");
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
     underTest.peek(WORKER_UUID_2);
     CeQueueDto queueDto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), task.getUuid()).get();
 
@@ -492,9 +511,9 @@ public class InternalCeQueueImplTest {
 
   @Test
   public void cancelAll_pendings_but_not_in_progress() {
-    CeTask inProgressTask = submit(CeTaskTypes.REPORT, "PROJECT_1");
-    CeTask pendingTask1 = submit(CeTaskTypes.REPORT, "PROJECT_2");
-    CeTask pendingTask2 = submit(CeTaskTypes.REPORT, "PROJECT_3");
+    CeTask inProgressTask = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
+    CeTask pendingTask1 = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_2"));
+    CeTask pendingTask2 = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_3"));
     underTest.peek(WORKER_UUID_2);
 
     int canceledCount = underTest.cancelAll();
@@ -608,14 +627,16 @@ public class InternalCeQueueImplTest {
       assertThat(task.getOrganizationUuid()).isEqualTo(componentDto.getOrganizationUuid());
     }
     assertThat(task.getUuid()).isEqualTo(taskSubmit.getUuid());
-    assertThat(task.getComponentUuid()).isEqualTo(task.getComponentUuid());
     assertThat(task.getType()).isEqualTo(taskSubmit.getType());
-    if (componentDto == null) {
-      assertThat(task.getComponentKey()).isNull();
-      assertThat(task.getComponentName()).isNull();
+    if (componentDto != null) {
+      CeTask.Component component = task.getComponent().get();
+      assertThat(component.getUuid()).isEqualTo(componentDto.uuid());
+      assertThat(component.getKey()).contains(componentDto.getDbKey());
+      assertThat(component.getName()).contains(componentDto.name());
+    } else if (taskSubmit.getComponent().isPresent()) {
+      assertThat(task.getComponent()).contains(new CeTask.Component(taskSubmit.getComponent().get().getUuid(), null, null));
     } else {
-      assertThat(task.getComponentKey()).isEqualTo(componentDto.getDbKey());
-      assertThat(task.getComponentName()).isEqualTo(componentDto.name());
+      assertThat(task.getComponent()).isEmpty();
     }
     assertThat(task.getSubmitterUuid()).isEqualTo(taskSubmit.getSubmitterUuid());
   }
@@ -625,30 +646,39 @@ public class InternalCeQueueImplTest {
     assertThat(queueDto.isPresent()).isTrue();
     CeQueueDto dto = queueDto.get();
     assertThat(dto.getTaskType()).isEqualTo(taskSubmit.getType());
-    assertThat(dto.getComponentUuid()).isEqualTo(taskSubmit.getComponentUuid());
+    Optional<CeTaskSubmit.Component> component = taskSubmit.getComponent();
+    if (component.isPresent()) {
+      assertThat(dto.getMainComponentUuid()).isEqualTo(component.get().getMainComponentUuid());
+      assertThat(dto.getComponentUuid()).isEqualTo(component.get().getUuid());
+    } else {
+      assertThat(dto.getMainComponentUuid()).isNull();
+      assertThat(dto.getComponentUuid()).isNull();
+    }
     assertThat(dto.getSubmitterUuid()).isEqualTo(taskSubmit.getSubmitterUuid());
     assertThat(dto.getCreatedAt()).isEqualTo(dto.getUpdatedAt()).isNotNull();
   }
 
-  private ComponentDto newComponentDto(String uuid) {
+  private ComponentDto newProjectDto(String uuid) {
     return ComponentTesting.newPublicProjectDto(db.getDefaultOrganization(), uuid).setName("name_" + uuid).setDbKey("key_" + uuid);
   }
 
-  private CeTask submit(String reportType, String componentUuid) {
-    return underTest.submit(createTaskSubmit(reportType, componentUuid, null));
+  private CeTask submit(String reportType, ComponentDto componentDto) {
+    return underTest.submit(createTaskSubmit(reportType, componentDto, null));
   }
 
   private CeTaskSubmit createTaskSubmit(String type) {
     return createTaskSubmit(type, null, null);
   }
 
-  private CeTaskSubmit createTaskSubmit(String type, @Nullable String componentUuid, @Nullable String submitterUuid) {
-    return underTest.prepareSubmit()
+  private CeTaskSubmit createTaskSubmit(String type, @Nullable ComponentDto componentDto, @Nullable String submitterUuid) {
+    CeTaskSubmit.Builder builder = underTest.prepareSubmit()
       .setType(type)
-      .setComponentUuid(componentUuid)
       .setSubmitterUuid(submitterUuid)
-      .setCharacteristics(emptyMap())
-      .build();
+      .setCharacteristics(emptyMap());
+    if (componentDto != null) {
+      builder.setComponent(CeTaskSubmit.Component.fromDto(componentDto));
+    }
+    return builder.build();
   }
 
   private CeTaskResult newTaskResult(@Nullable String analysisUuid) {
index acd806917121d47439779a49291a2845a166016a..57524a13fa65a34f9a3c84baf9959e0de0cc7741 100644 (file)
@@ -107,13 +107,13 @@ public class CeTaskProcessorRepositoryImplTest {
   }
 
   private static CeTask createCeTask(String ceTaskType, String key) {
+    CeTask.Component component = new CeTask.Component("uuid_" + key, key, "name_" + key);
     return new CeTask.Builder()
       .setOrganizationUuid("org1")
       .setType(ceTaskType)
       .setUuid("task_uuid_" + key)
-      .setComponentKey(key)
-      .setComponentUuid("uuid_" + key)
-      .setComponentName("name_" + key)
+      .setComponent(component)
+      .setMainComponent(component)
       .build();
   }
 
index b207eef455659ec5e8c21ef85d2a088ab1bde3d9..5a1166a16c665420aca3c4dc84b3c162ae6373dd 100644 (file)
@@ -554,10 +554,12 @@ public class CeWorkerImplTest {
     for (int i = 0; i < characteristics.length; i += 2) {
       characteristicMap.put(characteristics[i], characteristics[i + 1]);
     }
+    CeTask.Component component = new CeTask.Component("PROJECT_1", null, null);
     return new CeTask.Builder()
       .setOrganizationUuid("org1")
       .setUuid("TASK_1").setType(CeTaskTypes.REPORT)
-      .setComponentUuid("PROJECT_1")
+      .setComponent(component)
+      .setMainComponent(component)
       .setSubmitterUuid(submitterLogin)
       .setCharacteristics(characteristicMap)
       .build();
index 3bae61a4f50542e5c6a6a6ca8b5ded0f8cd68838..ed0e309f56e610ad3fcac17e6eca39db0cf68798 100644 (file)
@@ -669,6 +669,8 @@ CREATE TABLE "CE_QUEUE" (
   "UUID" VARCHAR(40) NOT NULL,
   "TASK_TYPE" VARCHAR(15) NOT NULL,
   "COMPONENT_UUID" VARCHAR(40) NULL,
+  "TMP_COMPONENT_UUID" VARCHAR(40) NULL,
+  "TMP_MAIN_COMPONENT_UUID" VARCHAR(40) NULL,
   "STATUS" VARCHAR(15) NOT NULL,
   "SUBMITTER_UUID" VARCHAR(255) NULL,
   "WORKER_UUID" VARCHAR(40) NULL,
@@ -679,6 +681,8 @@ CREATE TABLE "CE_QUEUE" (
 );
 CREATE UNIQUE INDEX "CE_QUEUE_UUID" ON "CE_QUEUE" ("UUID");
 CREATE INDEX "CE_QUEUE_COMPONENT_UUID" ON "CE_QUEUE" ("COMPONENT_UUID");
+CREATE INDEX "CE_QUEUE_TMP_CPNT_UUID" ON "CE_QUEUE" ("TMP_COMPONENT_UUID");
+CREATE INDEX "CE_QUEUE_TMP_MAIN_CPNT_UUID" ON "CE_QUEUE" ("TMP_MAIN_COMPONENT_UUID");
 CREATE INDEX "CE_QUEUE_STATUS" ON "CE_QUEUE" ("STATUS");
 
 
@@ -687,10 +691,16 @@ CREATE TABLE "CE_ACTIVITY" (
   "UUID" VARCHAR(40) NOT NULL,
   "TASK_TYPE" VARCHAR(15) NOT NULL,
   "COMPONENT_UUID" VARCHAR(40) NULL,
+  "TMP_COMPONENT_UUID" VARCHAR(40) NULL,
+  "TMP_MAIN_COMPONENT_UUID" VARCHAR(40) NULL,
   "ANALYSIS_UUID" VARCHAR(50) NULL,
   "STATUS" VARCHAR(15) NOT NULL,
   "IS_LAST" BOOLEAN NOT NULL,
   "IS_LAST_KEY" VARCHAR(55) NOT NULL,
+  "TMP_IS_LAST" BOOLEAN,
+  "TMP_IS_LAST_KEY" VARCHAR(55),
+  "TMP_MAIN_IS_LAST" BOOLEAN,
+  "TMP_MAIN_IS_LAST_KEY" VARCHAR(55),
   "SUBMITTER_UUID" VARCHAR(255) NULL,
   "WORKER_UUID" VARCHAR(40) NULL,
   "EXECUTION_COUNT" INTEGER NOT NULL,
@@ -706,8 +716,14 @@ CREATE TABLE "CE_ACTIVITY" (
 );
 CREATE UNIQUE INDEX "CE_ACTIVITY_UUID" ON "CE_ACTIVITY" ("UUID");
 CREATE INDEX "CE_ACTIVITY_COMPONENT_UUID" ON "CE_ACTIVITY" ("COMPONENT_UUID");
+CREATE INDEX "CE_ACTIVITY_TMP_CPNT_UUID" ON "CE_ACTIVITY" ("TMP_COMPONENT_UUID");
+CREATE INDEX "CE_ACTIVITY_TMP_MAIN_CPNT_UUID" ON "CE_ACTIVITY" ("TMP_MAIN_COMPONENT_UUID");
 CREATE INDEX "CE_ACTIVITY_ISLASTKEY" ON "CE_ACTIVITY" ("IS_LAST_KEY");
 CREATE INDEX "CE_ACTIVITY_ISLAST_STATUS" ON "CE_ACTIVITY" ("IS_LAST", "STATUS");
+CREATE INDEX "CE_ACTIVITY_T_ISLAST_KEY" ON "CE_ACTIVITY" ("TMP_IS_LAST_KEY");
+CREATE INDEX "CE_ACTIVITY_T_ISLAST" ON "CE_ACTIVITY" ("TMP_IS_LAST", "STATUS");
+CREATE INDEX "CE_ACTIVITY_T_MAIN_ISLAST_KEY" ON "CE_ACTIVITY" ("TMP_MAIN_IS_LAST_KEY");
+CREATE INDEX "CE_ACTIVITY_T_MAIN_ISLAST" ON "CE_ACTIVITY" ("TMP_MAIN_IS_LAST", "STATUS");
 
 
 CREATE TABLE "CE_TASK_CHARACTERISTICS" (
index 4cd6ada6da02f760f96db8a11edc1c984f37f79c..6791b86ee08d41fedf034619fa790def2a0fd5b5 100644 (file)
@@ -46,11 +46,12 @@ public class CeActivityDao implements Dao {
   public void insert(DbSession dbSession, CeActivityDto dto) {
     dto.setCreatedAt(system2.now());
     dto.setUpdatedAt(system2.now());
-    dto.setIsLast(dto.getStatus() != CeActivityDto.Status.CANCELED);
+    boolean isLast = dto.getStatus() != CeActivityDto.Status.CANCELED;
+    dto.setIsLast(isLast);
 
     CeActivityMapper ceActivityMapper = mapper(dbSession);
-    if (dto.getIsLast()) {
-      ceActivityMapper.updateIsLastToFalseForLastKey(dto.getIsLastKey(), dto.getUpdatedAt());
+    if (isLast) {
+      ceActivityMapper.clearIsLast(dto.getIsLastKey(), dto.getMainIsLastKey(), dto.getUpdatedAt());
     }
     ceActivityMapper.insert(dto);
   }
@@ -67,15 +68,15 @@ public class CeActivityDao implements Dao {
    * Ordered by id desc -> newest to oldest
    */
   public List<CeActivityDto> selectByQuery(DbSession dbSession, CeTaskQuery query, Pagination pagination) {
-    if (query.isShortCircuitedByComponentUuids()) {
+    if (query.isShortCircuitedByMainComponentUuids()) {
       return Collections.emptyList();
     }
 
     return mapper(dbSession).selectByQuery(query, pagination);
   }
 
-  public int countLastByStatusAndComponentUuid(DbSession dbSession, CeActivityDto.Status status, @Nullable String componentUuid) {
-    return mapper(dbSession).countLastByStatusAndComponentUuid(status, componentUuid);
+  public int countLastByStatusAndMainComponentUuid(DbSession dbSession, CeActivityDto.Status status, @Nullable String mainComponentUuid) {
+    return mapper(dbSession).countLastByStatusAndMainComponentUuid(status, mainComponentUuid);
   }
 
   private static CeActivityMapper mapper(DbSession dbSession) {
index 19be3c4951a35a44fb519af2574f2b4a26b6a3ee..af3e004aa750f6403b80c88a1b470e8a4cbce6ad 100644 (file)
@@ -23,6 +23,7 @@ import com.google.common.base.Strings;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static java.lang.String.format;
@@ -36,12 +37,28 @@ public class CeActivityDto {
   }
 
   private String uuid;
+  /**
+   * Can be {@code null} when task is not associated to any data in table PROJECTS, but must always be non {@code null}
+   * at the same time as {@link #mainComponentUuid}.
+   * <p>
+   * The component uuid of a any component (project or not) is its own UUID.
+   */
   private String componentUuid;
+  /**
+   * Can be {@code null} when task is not associated to any data in table PROJECTS, but must always be non {@code null}
+   * at the same time as {@link #componentUuid}.
+   * <p>
+   * The main component uuid of the main branch of project is its own UUID. For other branches of a project, it is the
+   * project UUID of the main branch of that project ({@link ComponentDto#getMainBranchProjectUuid()}).
+   */
+  private String mainComponentUuid;
   private String analysisUuid;
   private Status status;
   private String taskType;
   private boolean isLast;
   private String isLastKey;
+  private boolean mainIsLast;
+  private String mainIsLastKey;
   private String submitterUuid;
   private String workerUuid;
   private long submittedAt;
@@ -91,7 +108,9 @@ public class CeActivityDto {
     this.uuid = queueDto.getUuid();
     this.taskType = queueDto.getTaskType();
     this.componentUuid = queueDto.getComponentUuid();
+    this.mainComponentUuid = queueDto.getMainComponentUuid();
     this.isLastKey = format("%s%s", taskType, Strings.nullToEmpty(componentUuid));
+    this.mainIsLastKey = format("%s%s", taskType, Strings.nullToEmpty(mainComponentUuid));
     this.submitterUuid = queueDto.getSubmitterUuid();
     this.workerUuid = queueDto.getWorkerUuid();
     this.submittedAt = queueDto.getCreatedAt();
@@ -102,8 +121,8 @@ public class CeActivityDto {
     return uuid;
   }
 
-  public CeActivityDto setUuid(String s) {
-    checkArgument(s.length() <= 40, "Value is too long for column CE_ACTIVITY.UUID: %s", s);
+  public CeActivityDto setUuid(@Nullable String s) {
+    validateUuid(s, "UUID");
     this.uuid = s;
     return this;
   }
@@ -123,11 +142,26 @@ public class CeActivityDto {
   }
 
   public CeActivityDto setComponentUuid(@Nullable String s) {
-    checkArgument(s == null || s.length() <= 40, "Value is too long for column CE_ACTIVITY.COMPONENT_UUID: %s", s);
+    validateUuid(s, "COMPONENT_UUID");
     this.componentUuid = s;
     return this;
   }
 
+  @CheckForNull
+  public String getMainComponentUuid() {
+    return mainComponentUuid;
+  }
+
+  public CeActivityDto setMainComponentUuid(@Nullable String s) {
+    validateUuid(s, "MAIN_COMPONENT_UUID");
+    this.mainComponentUuid = s;
+    return this;
+  }
+
+  private static void validateUuid(@Nullable String s, String columnName) {
+    checkArgument(s == null || s.length() <= 40, "Value is too long for column CE_ACTIVITY.%s: %s", columnName, s);
+  }
+
   public Status getStatus() {
     return status;
   }
@@ -143,6 +177,7 @@ public class CeActivityDto {
 
   CeActivityDto setIsLast(boolean b) {
     this.isLast = b;
+    this.mainIsLast = b;
     return this;
   }
 
@@ -150,6 +185,14 @@ public class CeActivityDto {
     return isLastKey;
   }
 
+  public boolean getMainIsLast() {
+    return mainIsLast;
+  }
+
+  public String getMainIsLastKey() {
+    return mainIsLastKey;
+  }
+
   @CheckForNull
   public String getSubmitterUuid() {
     return submitterUuid;
@@ -277,11 +320,14 @@ public class CeActivityDto {
     return "CeActivityDto{" +
       "uuid='" + uuid + '\'' +
       ", componentUuid='" + componentUuid + '\'' +
+      ", mainComponentUuid='" + mainComponentUuid + '\'' +
       ", analysisUuid='" + analysisUuid + '\'' +
       ", status=" + status +
       ", taskType='" + taskType + '\'' +
       ", isLast=" + isLast +
       ", isLastKey='" + isLastKey + '\'' +
+      ", mainIsLast=" + mainIsLast +
+      ", mainIsLastKey='" + mainIsLastKey + '\'' +
       ", submitterUuid='" + submitterUuid + '\'' +
       ", workerUuid='" + workerUuid + '\'' +
       ", submittedAt=" + submittedAt +
index 838f1d022f1c49672e22b0f62b8f19b857c4efdc..bc36fd2d9dd8602a166e3128c48efdb4e361b6a5 100644 (file)
@@ -30,17 +30,15 @@ public interface CeActivityMapper {
   @CheckForNull
   CeActivityDto selectByUuid(@Param("uuid") String uuid);
 
-  List<CeActivityDto> selectByComponentUuid(@Param("componentUuid") String componentUuid);
-
   List<CeActivityDto> selectByQuery(@Param("query") CeTaskQuery query, @Param("pagination") Pagination pagination);
 
   List<CeActivityDto> selectOlderThan(@Param("beforeDate") long beforeDate);
 
-  int countLastByStatusAndComponentUuid(@Param("status") CeActivityDto.Status status, @Nullable @Param("componentUuid") String componentUuid);
+  int countLastByStatusAndMainComponentUuid(@Param("status") CeActivityDto.Status status, @Nullable @Param("mainComponentUuid") String mainComponentUuid);
 
   void insert(CeActivityDto dto);
 
-  void updateIsLastToFalseForLastKey(@Param("isLastKey") String isLastKey, @Param("updatedAt") long updatedAt);
+  void clearIsLast(@Param("isLastKey") String isLastKey, @Param("mainIsLastKey") String mainIsLastKey, @Param("updatedAt") long updatedAt);
 
   void deleteByUuids(@Param("uuids") List<String> uuids);
 }
index 3aabaa68ca42a063e528ade68ac7d437961d9a59..02fab58ce363435d67d0e979bad4512b6dc4ee4a 100644 (file)
@@ -55,7 +55,7 @@ public class CeQueueDao implements Dao {
   }
 
   public List<CeQueueDto> selectByQueryInDescOrder(DbSession dbSession, CeTaskQuery query, int pageSize) {
-    if (query.isShortCircuitedByComponentUuids()
+    if (query.isShortCircuitedByMainComponentUuids()
       || query.isOnlyCurrents()
       || query.getMaxExecutedAt() != null) {
       return emptyList();
@@ -65,7 +65,7 @@ public class CeQueueDao implements Dao {
   }
 
   public int countByQuery(DbSession dbSession, CeTaskQuery query) {
-    if (query.isShortCircuitedByComponentUuids()
+    if (query.isShortCircuitedByMainComponentUuids()
       || query.isOnlyCurrents()
       || query.getMaxExecutedAt() != null) {
       return 0;
@@ -77,8 +77,8 @@ public class CeQueueDao implements Dao {
   /**
    * Ordered by ascending id: oldest to newest
    */
-  public List<CeQueueDto> selectByComponentUuid(DbSession session, String componentUuid) {
-    return mapper(session).selectByComponentUuid(componentUuid);
+  public List<CeQueueDto> selectByMainComponentUuid(DbSession session, String projectUuid) {
+    return mapper(session).selectByMainComponentUuid(projectUuid);
   }
 
   public Optional<CeQueueDto> selectByUuid(DbSession session, String uuid) {
@@ -131,30 +131,30 @@ public class CeQueueDao implements Dao {
   }
 
   public int countByStatus(DbSession dbSession, CeQueueDto.Status status) {
-    return mapper(dbSession).countByStatusAndComponentUuid(status, null);
+    return mapper(dbSession).countByStatusAndMainComponentUuid(status, null);
   }
 
-  public int countByStatusAndComponentUuid(DbSession dbSession, CeQueueDto.Status status, @Nullable String componentUuid) {
-    return mapper(dbSession).countByStatusAndComponentUuid(status, componentUuid);
+  public int countByStatusAndMainComponentUuid(DbSession dbSession, CeQueueDto.Status status, @Nullable String mainComponentUuid) {
+    return mapper(dbSession).countByStatusAndMainComponentUuid(status, mainComponentUuid);
   }
 
   /**
-   * Counts entries in the queue with the specified status for each specified component uuid.
+   * Counts entries in the queue with the specified status for each specified main component uuid.
    *
-   * The returned map doesn't contain any entry for component uuid for which there is no entry in the queue (ie.
+   * The returned map doesn't contain any entry for main component uuids for which there is no entry in the queue (ie.
    * all entries have a value >= 0).
    */
-  public Map<String, Integer> countByStatusAndComponentUuids(DbSession dbSession, CeQueueDto.Status status, Set<String> componentUuids) {
-    if (componentUuids.isEmpty()) {
+  public Map<String, Integer> countByStatusAndMainComponentUuids(DbSession dbSession, CeQueueDto.Status status, Set<String> projectUuids) {
+    if (projectUuids.isEmpty()) {
       return emptyMap();
     }
 
     ImmutableMap.Builder<String, Integer> builder = ImmutableMap.builder();
     executeLargeUpdates(
-      componentUuids,
+      projectUuids,
       uuids -> {
-        List<QueueCount> i = mapper(dbSession).countByStatusAndComponentUuids(status, componentUuids);
-        i.forEach(o -> builder.put(o.getComponentUuid(), o.getTotal()));
+        List<QueueCount> i = mapper(dbSession).countByStatusAndMainComponentUuids(status, projectUuids);
+        i.forEach(o -> builder.put(o.getMainComponentUuid(), o.getTotal()));
       });
     return builder.build();
   }
index b31c0bece83573aa04f03b1787a0d542541e1c3c..32362261c14809b0206e4e051222838526ec59c6 100644 (file)
@@ -21,8 +21,11 @@ package org.sonar.db.ce;
 
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
+import org.sonar.db.component.ComponentDto;
 
+import static com.google.common.base.MoreObjects.firstNonNull;
 import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
 
 public class CeQueueDto {
 
@@ -32,7 +35,21 @@ public class CeQueueDto {
 
   private String uuid;
   private String taskType;
+  /**
+   * Can be {@code null} when task is not associated to any data in table PROJECTS, but must always be non {@code null}
+   * at the same time as {@link #mainComponentUuid}.
+   * <p>
+   * The component uuid of a any component (project or not) is its own UUID.
+   */
   private String componentUuid;
+  /**
+   * Can be {@code null} when task is not associated to any data in table PROJECTS, but must always be non {@code null}
+   * at the same time as {@link #componentUuid}.
+   * <p>
+   * The main component uuid of the main branch of project is its own UUID. For other branches of a project, it is the
+   * project UUID of the main branch of that project ({@link ComponentDto#getMainBranchProjectUuid()}).
+   */
+  private String mainComponentUuid;
   private Status status;
   private String submitterUuid;
   /**
@@ -48,22 +65,52 @@ public class CeQueueDto {
   }
 
   public CeQueueDto setUuid(String s) {
-    checkArgument(s.length() <= 40, "Value of UUID is too long: %s", s);
+    checkUuid(s, "UUID");
     this.uuid = s;
     return this;
   }
 
+  /**
+   * Helper methods which sets both {@link #componentUuid} and {@link #mainComponentUuid} from the specified
+   * {@link ComponentDto}.
+   */
+  public CeQueueDto setComponent(@Nullable ComponentDto component) {
+    if (component == null) {
+      this.componentUuid = null;
+      this.mainComponentUuid = null;
+    } else {
+      this.componentUuid = requireNonNull(component.uuid());
+      this.mainComponentUuid = firstNonNull(component.getMainBranchProjectUuid(), component.uuid());
+    }
+    return this;
+  }
+
   @CheckForNull
   public String getComponentUuid() {
     return componentUuid;
   }
 
   public CeQueueDto setComponentUuid(@Nullable String s) {
-    checkArgument(s == null || s.length() <= 40, "Value of component UUID is too long: %s", s);
+    checkUuid(s, "COMPONENT_UUID");
     this.componentUuid = s;
     return this;
   }
 
+  @CheckForNull
+  public String getMainComponentUuid() {
+    return mainComponentUuid;
+  }
+
+  public CeQueueDto setMainComponentUuid(@Nullable String s) {
+    checkUuid(s, "MAIN_COMPONENT_UUID");
+    this.mainComponentUuid = s;
+    return this;
+  }
+
+  private static void checkUuid(@Nullable String s, String columnName) {
+    checkArgument(s == null || s.length() <= 40, "Value is too long for column CE_QUEUE.%s: %s", columnName, s);
+  }
+
   public Status getStatus() {
     return status;
   }
@@ -101,8 +148,9 @@ public class CeQueueDto {
   /**
    * Accessed by MyBatis through reflexion. Field is otherwise read-only.
    */
-  private  void setWorkerUuid(@Nullable String workerUuid) {
+  protected CeQueueDto setWorkerUuid(@Nullable String workerUuid) {
     this.workerUuid = workerUuid;
+    return this;
   }
 
   @CheckForNull
@@ -113,8 +161,9 @@ public class CeQueueDto {
   /**
    * Accessed by MyBatis through reflexion. Field is otherwise read-only.
    */
-  private void setStartedAt(@Nullable Long l) {
+  protected CeQueueDto setStartedAt(@Nullable Long l) {
     this.startedAt = l;
+    return this;
   }
 
   public long getCreatedAt() {
@@ -141,6 +190,7 @@ public class CeQueueDto {
       "uuid='" + uuid + '\'' +
       ", taskType='" + taskType + '\'' +
       ", componentUuid='" + componentUuid + '\'' +
+      ", mainComponentUuid='" + mainComponentUuid + '\'' +
       ", status=" + status +
       ", submitterLogin='" + submitterUuid + '\'' +
       ", workerUuid='" + workerUuid + '\'' +
index 190d1f5818d4be8a08ef48b5bc15a2c63b58b0cf..3ee7f9ebeb0325ed098791d5fcefe5438b072937 100644 (file)
@@ -29,7 +29,7 @@ import org.sonar.db.Pagination;
 
 public interface CeQueueMapper {
 
-  List<CeQueueDto> selectByComponentUuid(@Param("componentUuid") String componentUuid);
+  List<CeQueueDto> selectByMainComponentUuid(@Param("mainComponentUuid") String mainComponentUuid);
 
   List<CeQueueDto> selectAllInAscOrder();
 
@@ -62,9 +62,9 @@ public interface CeQueueMapper {
    */
   void resetAllInProgressTasks(@Param("updatedAt") long updatedAt);
 
-  int countByStatusAndComponentUuid(@Param("status") CeQueueDto.Status status, @Nullable @Param("componentUuid") String componentUuid);
+  int countByStatusAndMainComponentUuid(@Param("status") CeQueueDto.Status status, @Nullable @Param("mainComponentUuid") String mainComponentUuid);
 
-  List<QueueCount> countByStatusAndComponentUuids(@Param("status") CeQueueDto.Status status, @Param("componentUuids") Set<String> componentUuids);
+  List<QueueCount> countByStatusAndMainComponentUuids(@Param("status") CeQueueDto.Status status, @Param("mainComponentUuids") Set<String> mainComponentUuids);
 
   void insert(CeQueueDto dto);
 
index f559bfa56f83f21e9c32cbbc96fc02fd6130c4e9..99e80085838f884a266f08e79e8540ee1df8b8b6 100644 (file)
@@ -36,31 +36,31 @@ public class CeTaskQuery {
 
   private boolean onlyCurrents = false;
   // SONAR-7681 a public implementation of List must be used in MyBatis - potential concurrency exceptions otherwise
-  private ArrayList<String> componentUuids;
+  private ArrayList<String> mainComponentUuids;
   private ArrayList<String> statuses;
   private String type;
   private Long minSubmittedAt;
   private Long maxExecutedAt;
 
   @CheckForNull
-  public List<String> getComponentUuids() {
-    return componentUuids;
+  public List<String> getMainComponentUuids() {
+    return mainComponentUuids;
   }
 
-  public CeTaskQuery setComponentUuids(@Nullable List<String> l) {
-    this.componentUuids = l == null ? null : newArrayList(l);
+  public CeTaskQuery setMainComponentUuids(@Nullable List<String> l) {
+    this.mainComponentUuids = l == null ? null : newArrayList(l);
     return this;
   }
 
-  public boolean isShortCircuitedByComponentUuids() {
-    return componentUuids != null && (componentUuids.isEmpty() || componentUuids.size() > MAX_COMPONENT_UUIDS);
+  public boolean isShortCircuitedByMainComponentUuids() {
+    return mainComponentUuids != null && (mainComponentUuids.isEmpty() || mainComponentUuids.size() > MAX_COMPONENT_UUIDS);
   }
 
-  public CeTaskQuery setComponentUuid(@Nullable String s) {
+  public CeTaskQuery setMainComponentUuid(@Nullable String s) {
     if (s == null) {
-      this.componentUuids = null;
+      this.mainComponentUuids = null;
     } else {
-      this.componentUuids = newArrayList(s);
+      this.mainComponentUuids = newArrayList(s);
     }
     return this;
   }
index 5b948c38df9a5d273828eb4bc1e1566c26872403..f73c9699ca866a2fbde38e2fb2f8906f21960ac6 100644 (file)
@@ -21,11 +21,11 @@ package org.sonar.db.ce;
 
 public class QueueCount {
   // set by reflection by MyBatis
-  private String componentUuid;
+  private String mainComponentUuid;
   private int total;
 
-  public String getComponentUuid() {
-    return componentUuid;
+  public String getMainComponentUuid() {
+    return mainComponentUuid;
   }
 
   public int getTotal() {
index 413ddf5dfe084dd018c12ec215d6aaec5bdb160c..411a98dd614b6008386af2c870938ab5295e670a 100644 (file)
@@ -112,7 +112,7 @@ public class ComponentDao implements Dao {
   /**
    * @throws IllegalArgumentException if parameter query#getComponentIds() has more than {@link org.sonar.db.DatabaseUtils#PARTITION_SIZE_FOR_ORACLE} values
    * @throws IllegalArgumentException if parameter query#getComponentKeys() has more than {@link org.sonar.db.DatabaseUtils#PARTITION_SIZE_FOR_ORACLE} values
-   * @throws IllegalArgumentException if parameter query#getComponentUuids() has more than {@link org.sonar.db.DatabaseUtils#PARTITION_SIZE_FOR_ORACLE} values
+   * @throws IllegalArgumentException if parameter query#getMainComponentUuids() has more than {@link org.sonar.db.DatabaseUtils#PARTITION_SIZE_FOR_ORACLE} values
    */
   public List<ComponentDto> selectByQuery(DbSession dbSession, String organizationUuid, ComponentQuery query, int offset, int limit) {
     requireNonNull(organizationUuid, "organizationUuid can't be null");
@@ -130,7 +130,7 @@ public class ComponentDao implements Dao {
   /**
    * @throws IllegalArgumentException if parameter query#getComponentIds() has more than {@link org.sonar.db.DatabaseUtils#PARTITION_SIZE_FOR_ORACLE} values
    * @throws IllegalArgumentException if parameter query#getComponentKeys() has more than {@link org.sonar.db.DatabaseUtils#PARTITION_SIZE_FOR_ORACLE} values
-   * @throws IllegalArgumentException if parameter query#getComponentUuids() has more than {@link org.sonar.db.DatabaseUtils#PARTITION_SIZE_FOR_ORACLE} values
+   * @throws IllegalArgumentException if parameter query#getMainComponentUuids() has more than {@link org.sonar.db.DatabaseUtils#PARTITION_SIZE_FOR_ORACLE} values
    */
   public int countByQuery(DbSession session, String organizationUuid, ComponentQuery query) {
     requireNonNull(organizationUuid, "organizationUuid can't be null");
index 6c4779f6e93190f632afee58826dcb3324a5d83f..69c6504dead4efe931f8a6004ac5955420e9a270 100644 (file)
@@ -19,7 +19,8 @@
     ca.id,
     ca.uuid,
     ca.task_type as taskType,
-    ca.component_uuid as componentUuid,
+    ca.tmp_component_uuid as componentUuid,
+    ca.tmp_main_component_uuid as mainComponentUuid,
     ca.analysis_uuid as analysisUuid,
     ca.status as status,
     ca.submitter_uuid as submitterUuid,
     ca.executed_at as executedAt,
     ca.created_at as createdAt,
     ca.updated_at as updatedAt,
-    ca.is_last as isLast,
-    ca.is_last_key as isLastKey,
+    ca.tmp_is_last as isLast,
+    ca.tmp_is_last_key as isLastKey,
+    ca.tmp_main_is_last as mainIsLast,
+    ca.tmp_main_is_last_key as mainIsLastKey,
     ca.execution_time_ms as executionTimeMs,
     ca.error_message as errorMessage,
     ca.error_type as errorType,
     left outer join ce_scanner_context csc on csc.task_uuid = ca.uuid
     <where>
       <if test="query.onlyCurrents">
-        and ca.is_last=${_true}
+        and ca.tmp_main_is_last=${_true}
       </if>
-      <if test="query.componentUuids != null and query.componentUuids.size()>0">
-        and ca.component_uuid in
-        <foreach collection="query.componentUuids" open="(" close=")" item="cUuid" separator=",">
+      <if test="query.mainComponentUuids != null and query.mainComponentUuids.size()>0">
+        and ca.tmp_main_component_uuid in
+        <foreach collection="query.mainComponentUuids" open="(" close=")" item="cUuid" separator=",">
           #{cUuid,jdbcType=VARCHAR}
         </foreach>
       </if>
       ca.created_at &lt; #{beforeDate,jdbcType=BIGINT}
   </select>
   
-  <select id="countLastByStatusAndComponentUuid" resultType="int">
+  <select id="countLastByStatusAndMainComponentUuid" resultType="int">
     select
       count(1)
     from
       ce_activity
     where
-      status=#{status,jdbcType=VARCHAR}
-      and is_last=${_true}
-      <if test="componentUuid!=null">
-        and component_uuid=#{componentUuid,jdbcType=VARCHAR}
+      tmp_main_is_last=${_true}
+      and status=#{status,jdbcType=VARCHAR}
+      <if test="mainComponentUuid!=null">
+        and tmp_main_component_uuid=#{mainComponentUuid,jdbcType=VARCHAR}
       </if>
   </select>
 
   <insert id="insert" parameterType="org.sonar.db.ce.CeActivityDto" useGeneratedKeys="false">
     insert into ce_activity (
       uuid,
+      tmp_component_uuid,
+      tmp_main_component_uuid,
       component_uuid,
       analysis_uuid,
       status,
       task_type,
+      tmp_is_last,
+      tmp_is_last_key,
+      tmp_main_is_last,
+      tmp_main_is_last_key,
       is_last,
       is_last_key,
       submitter_uuid,
     values (
       #{uuid,jdbcType=VARCHAR},
       #{componentUuid,jdbcType=VARCHAR},
+      #{mainComponentUuid,jdbcType=VARCHAR},
+      #{mainComponentUuid,jdbcType=VARCHAR},
       #{analysisUuid,jdbcType=VARCHAR},
       #{status,jdbcType=VARCHAR},
       #{taskType,jdbcType=VARCHAR},
       #{isLast,jdbcType=BOOLEAN},
       #{isLastKey,jdbcType=VARCHAR},
+      #{mainIsLast,jdbcType=BOOLEAN},
+      #{mainIsLastKey,jdbcType=VARCHAR},
+      #{mainIsLast,jdbcType=BOOLEAN},
+      #{mainIsLastKey,jdbcType=VARCHAR},
       #{submitterUuid,jdbcType=VARCHAR},
       #{submittedAt,jdbcType=BIGINT},
       #{workerUuid,jdbcType=VARCHAR},
     )
   </insert>
 
-  <update id="updateIsLastToFalseForLastKey" parameterType="map">
-    update ce_activity
-    set is_last=${_false},
-    updated_at=#{updatedAt,jdbcType=BIGINT}
-    where is_last=${_true} and is_last_key=#{isLastKey,jdbcType=VARCHAR}
+  <update id="clearIsLast" parameterType="map">
+    update ce_activity set
+      tmp_is_last=${_false},
+      tmp_main_is_last=${_false},
+      is_last=${_false},
+      updated_at=#{updatedAt,jdbcType=BIGINT}
+    where
+      (tmp_is_last=${_true} and tmp_is_last_key=#{isLastKey,jdbcType=VARCHAR})
+      or
+      (tmp_main_is_last=${_true} and tmp_main_is_last_key=#{mainIsLastKey,jdbcType=VARCHAR})
   </update>
 
   <delete id="deleteByUuids" parameterType="string">
index de6508b2133c487f14e0de4723cbe315c5a8d490..6eab90a67f8e79c2032a8912af04782cb039082a 100644 (file)
@@ -6,7 +6,8 @@
   <sql id="columns">
     cq.uuid,
     cq.task_type as taskType,
-    cq.component_uuid as componentUuid,
+    cq.tmp_component_uuid as componentUuid,
+    cq.tmp_main_component_uuid as mainComponentUuid,
     cq.status as status,
     cq.submitter_uuid as submitterUuid,
     cq.worker_uuid as workerUuid,
       cq.uuid=#{uuid,jdbcType=VARCHAR}
   </select>
 
-  <select id="countByStatusAndComponentUuid" parameterType="map" resultType="int">
+  <select id="countByStatusAndMainComponentUuid" parameterType="map" resultType="int">
     select
       count(1)
     from
       ce_queue
     where
       status=#{status,jdbcType=VARCHAR}
-      <if test="componentUuid!=null">
-        and component_uuid=#{componentUuid,jdbcType=VARCHAR}
+      <if test="mainComponentUuid!=null">
+        and tmp_main_component_uuid=#{mainComponentUuid,jdbcType=VARCHAR}
       </if>
   </select>
 
-  <select id="countByStatusAndComponentUuids" resultType="org.sonar.db.ce.QueueCount">
+  <select id="countByStatusAndMainComponentUuids" resultType="org.sonar.db.ce.QueueCount">
     select
-      component_uuid as componentUuid,
+      tmp_main_component_uuid as mainComponentUuid,
       count(1) as total
     from
       ce_queue
     where
       status=#{status,jdbcType=VARCHAR}
-      and component_uuid in
-      <foreach collection="componentUuids" open="(" close=")" item="cUuid" separator=",">
-        #{cUuid,jdbcType=VARCHAR}
+      and tmp_main_component_uuid in
+      <foreach collection="mainComponentUuids" open="(" close=")" item="mainComponentUuid" separator=",">
+        #{mainComponentUuid,jdbcType=VARCHAR}
       </foreach>
-    group by component_uuid
+    group by tmp_main_component_uuid
   </select>
 
   <select id="countAll" resultType="int">
       ce_queue
   </select>
 
-  <select id="selectByComponentUuid" parameterType="String" resultType="org.sonar.db.ce.CeQueueDto">
+  <select id="selectByMainComponentUuid" parameterType="String" resultType="org.sonar.db.ce.CeQueueDto">
     select
       <include refid="columns"/>
     from
       ce_queue cq
     where
-      cq.component_uuid=#{componentUuid,jdbcType=VARCHAR}
+      cq.tmp_main_component_uuid=#{mainComponentUuid,jdbcType=VARCHAR}
     <include refid="orderByDateAndId"/>
   </select>
 
     from
       ce_queue cq
     <where>
-      <if test="query.componentUuids != null and query.componentUuids.size()>0">
-        and cq.component_uuid in
-        <foreach collection="query.componentUuids" open="(" close=")" item="cUuid" separator=",">
-          #{cUuid,jdbcType=VARCHAR}
+      <if test="query.mainComponentUuids != null and query.mainComponentUuids.size()>0">
+        and cq.tmp_main_component_uuid in
+        <foreach collection="query.mainComponentUuids" open="(" close=")" item="mainComponentUuid" separator=",">
+          #{mainComponentUuid,jdbcType=VARCHAR}
         </foreach>
       </if>
       <if test="query.statuses != null">
         from
           ce_queue cq2
         where
-          cq.component_uuid=cq2.component_uuid
+          cq.tmp_main_component_uuid=cq2.tmp_main_component_uuid
           and cq2.status &lt;&gt; 'PENDING'
       )
   </sql>
     (
       uuid,
       task_type,
+      tmp_component_uuid,
       component_uuid,
+      tmp_main_component_uuid,
       status,
       submitter_uuid,
       execution_count,
       #{uuid,jdbcType=VARCHAR},
       #{taskType,jdbcType=VARCHAR},
       #{componentUuid,jdbcType=VARCHAR},
+      #{mainComponentUuid,jdbcType=VARCHAR},
+      #{mainComponentUuid,jdbcType=VARCHAR},
       #{status,jdbcType=VARCHAR},
       #{submitterUuid,jdbcType=VARCHAR},
       0,
index 5051ad485c5a0796cb69b6ae4a07ca2696b445b2..1cca92d67d31e506ba2cdb3979d84d8099eed9f2 100644 (file)
   <delete id="deleteCeScannerContextOfCeActivityByProjectUuid">
     delete from ce_scanner_context
     where
-      task_uuid in (select uuid from ce_activity where component_uuid=#{projectUuid,jdbcType=VARCHAR})
+      task_uuid in (select uuid from ce_activity where tmp_main_component_uuid=#{projectUuid,jdbcType=VARCHAR})
   </delete>
 
   <delete id="deleteCeTaskCharacteristicsOfCeActivityByProjectUuid">
     delete from ce_task_characteristics
     where
-      task_uuid in (select uuid from ce_activity where component_uuid=#{projectUuid,jdbcType=VARCHAR})
+      task_uuid in (select uuid from ce_activity where tmp_main_component_uuid=#{projectUuid,jdbcType=VARCHAR})
   </delete>
 
   <delete id="deleteCeTaskInputOfCeActivityByProjectUuid">
     delete from ce_task_input
     where
-      task_uuid in (select uuid from ce_activity where component_uuid=#{projectUuid,jdbcType=VARCHAR})
+      task_uuid in (select uuid from ce_activity where tmp_main_component_uuid=#{projectUuid,jdbcType=VARCHAR})
   </delete>
 
   <delete id="deleteCeActivityByProjectUuid">
-      delete from ce_activity where component_uuid=#{projectUuid,jdbcType=VARCHAR}
+      delete from ce_activity where tmp_main_component_uuid=#{projectUuid,jdbcType=VARCHAR}
   </delete>
 
   <delete id="deleteCeScannerContextOfCeQueueByProjectUuid">
     delete from ce_scanner_context
     where
-      task_uuid in (select uuid from ce_queue where component_uuid=#{projectUuid,jdbcType=VARCHAR})
+      task_uuid in (select uuid from ce_queue where tmp_main_component_uuid=#{projectUuid,jdbcType=VARCHAR})
   </delete>
 
   <delete id="deleteCeTaskCharacteristicsOfCeQueueByProjectUuid">
     delete from ce_task_characteristics
     where
-      task_uuid in (select uuid from ce_queue where component_uuid=#{projectUuid,jdbcType=VARCHAR})
+      task_uuid in (select uuid from ce_queue where tmp_main_component_uuid=#{projectUuid,jdbcType=VARCHAR})
   </delete>
 
   <delete id="deleteCeTaskInputOfCeQueueByProjectUuid">
     delete from ce_task_input
     where
-      task_uuid in (select uuid from ce_queue where component_uuid=#{projectUuid,jdbcType=VARCHAR})
+      task_uuid in (select uuid from ce_queue where tmp_main_component_uuid=#{projectUuid,jdbcType=VARCHAR})
   </delete>
 
   <delete id="deleteCeQueueByProjectUuid">
-    delete from ce_queue where component_uuid=#{projectUuid,jdbcType=VARCHAR}
+    delete from ce_queue where tmp_main_component_uuid=#{projectUuid,jdbcType=VARCHAR}
   </delete>
 
   <delete id="deleteWebhooksByProjectUuid">
index f24e8b5a14a8fda992b7ed5342f73fe98ee83e87..ff8226a5235f52876c6b3fe2fc88a9532219dd5b 100644 (file)
@@ -26,6 +26,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 import org.junit.Rule;
 import org.junit.Test;
 import org.sonar.api.utils.internal.TestSystem2;
@@ -37,6 +38,7 @@ import org.sonar.db.Pagination;
 
 import static java.util.Collections.singleton;
 import static java.util.Collections.singletonList;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.sonar.db.Pagination.forPage;
 import static org.sonar.db.ce.CeActivityDto.Status.FAILED;
@@ -47,6 +49,10 @@ import static org.sonar.db.ce.CeTaskTypes.REPORT;
 
 public class CeActivityDaoTest {
 
+  private static final String MAINCOMPONENT_1 = randomAlphabetic(12);
+  private static final String MAINCOMPONENT_2 = randomAlphabetic(13);
+  private static final String COMPONENT_1 = randomAlphabetic(14);
+
   private TestSystem2 system2 = new TestSystem2().setNow(1_450_000_000_000L);
 
   @Rule
@@ -57,19 +63,22 @@ public class CeActivityDaoTest {
 
   @Test
   public void test_insert() {
-    CeActivityDto inserted = insert("TASK_1", REPORT, "PROJECT_1", CeActivityDto.Status.SUCCESS);
+    CeActivityDto inserted = insert("TASK_1", REPORT, COMPONENT_1, MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS);
 
     Optional<CeActivityDto> saved = underTest.selectByUuid(db.getSession(), "TASK_1");
     assertThat(saved).isPresent();
     CeActivityDto dto = saved.get();
     assertThat(dto.getUuid()).isEqualTo("TASK_1");
-    assertThat(dto.getComponentUuid()).isEqualTo("PROJECT_1");
+    assertThat(dto.getMainComponentUuid()).isEqualTo(MAINCOMPONENT_1);
+    assertThat(dto.getComponentUuid()).isEqualTo(COMPONENT_1);
     assertThat(dto.getStatus()).isEqualTo(CeActivityDto.Status.SUCCESS);
     assertThat(dto.getSubmitterUuid()).isEqualTo("submitter uuid");
     assertThat(dto.getSubmittedAt()).isEqualTo(1_450_000_000_000L);
     assertThat(dto.getWorkerUuid()).isEqualTo("worker uuid");
     assertThat(dto.getIsLast()).isTrue();
-    assertThat(dto.getIsLastKey()).isEqualTo("REPORTPROJECT_1");
+    assertThat(dto.getMainIsLast()).isTrue();
+    assertThat(dto.getIsLastKey()).isEqualTo("REPORT" + COMPONENT_1);
+    assertThat(dto.getMainIsLastKey()).isEqualTo("REPORT" + MAINCOMPONENT_1);
     assertThat(dto.getCreatedAt()).isEqualTo(1_450_000_000_000L);
     assertThat(dto.getStartedAt()).isEqualTo(1_500_000_000_000L);
     assertThat(dto.getExecutedAt()).isEqualTo(1_500_000_000_500L);
@@ -84,7 +93,7 @@ public class CeActivityDaoTest {
 
   @Test
   public void test_insert_of_errorMessage_of_1_000_chars() {
-    CeActivityDto dto = createActivityDto("TASK_1", REPORT, "PROJECT_1", CeActivityDto.Status.FAILED)
+    CeActivityDto dto = createActivityDto("TASK_1", REPORT, COMPONENT_1, MAINCOMPONENT_1, CeActivityDto.Status.FAILED)
       .setErrorMessage(Strings.repeat("x", 1_000));
     underTest.insert(db.getSession(), dto);
 
@@ -95,7 +104,7 @@ public class CeActivityDaoTest {
   @Test
   public void test_insert_of_errorMessage_of_1_001_chars_is_truncated_to_1000() {
     String expected = Strings.repeat("x", 1_000);
-    CeActivityDto dto = createActivityDto("TASK_1", REPORT, "PROJECT_1", CeActivityDto.Status.FAILED)
+    CeActivityDto dto = createActivityDto("TASK_1", REPORT, COMPONENT_1, MAINCOMPONENT_1, CeActivityDto.Status.FAILED)
       .setErrorMessage(expected + "y");
     underTest.insert(db.getSession(), dto);
 
@@ -105,7 +114,7 @@ public class CeActivityDaoTest {
 
   @Test
   public void test_insert_error_message_and_stacktrace() {
-    CeActivityDto dto = createActivityDto("TASK_1", REPORT, "PROJECT_1", CeActivityDto.Status.FAILED)
+    CeActivityDto dto = createActivityDto("TASK_1", REPORT, COMPONENT_1, MAINCOMPONENT_1, CeActivityDto.Status.FAILED)
       .setErrorStacktrace("error stack");
     underTest.insert(db.getSession(), dto);
 
@@ -118,7 +127,7 @@ public class CeActivityDaoTest {
 
   @Test
   public void test_insert_error_message_only() {
-    CeActivityDto dto = createActivityDto("TASK_1", REPORT, "PROJECT_1", CeActivityDto.Status.FAILED);
+    CeActivityDto dto = createActivityDto("TASK_1", REPORT, COMPONENT_1, MAINCOMPONENT_1, CeActivityDto.Status.FAILED);
     underTest.insert(db.getSession(), dto);
 
     Optional<CeActivityDto> saved = underTest.selectByUuid(db.getSession(), "TASK_1");
@@ -129,22 +138,22 @@ public class CeActivityDaoTest {
 
   @Test
   public void insert_must_set_relevant_is_last_field() {
-    // only a single task on PROJECT_1 -> is_last=true
-    insert("TASK_1", REPORT, "PROJECT_1", CeActivityDto.Status.SUCCESS);
+    // only a single task on MAINCOMPONENT_1 -> is_last=true
+    insert("TASK_1", REPORT, MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS);
     assertThat(underTest.selectByUuid(db.getSession(), "TASK_1").get().getIsLast()).isTrue();
 
-    // only a single task on PROJECT_2 -> is_last=true
-    insert("TASK_2", REPORT, "PROJECT_2", CeActivityDto.Status.SUCCESS);
+    // only a single task on MAINCOMPONENT_2 -> is_last=true
+    insert("TASK_2", REPORT, MAINCOMPONENT_2, CeActivityDto.Status.SUCCESS);
     assertThat(underTest.selectByUuid(db.getSession(), "TASK_2").get().getIsLast()).isTrue();
 
-    // two tasks on PROJECT_1, the most recent one is TASK_3
-    insert("TASK_3", REPORT, "PROJECT_1", FAILED);
+    // two tasks on MAINCOMPONENT_1, the most recent one is TASK_3
+    insert("TASK_3", REPORT, MAINCOMPONENT_1, FAILED);
     assertThat(underTest.selectByUuid(db.getSession(), "TASK_1").get().getIsLast()).isFalse();
     assertThat(underTest.selectByUuid(db.getSession(), "TASK_2").get().getIsLast()).isTrue();
     assertThat(underTest.selectByUuid(db.getSession(), "TASK_3").get().getIsLast()).isTrue();
 
     // inserting a cancelled task does not change the last task
-    insert("TASK_4", REPORT, "PROJECT_1", CeActivityDto.Status.CANCELED);
+    insert("TASK_4", REPORT, MAINCOMPONENT_1, CeActivityDto.Status.CANCELED);
     assertThat(underTest.selectByUuid(db.getSession(), "TASK_1").get().getIsLast()).isFalse();
     assertThat(underTest.selectByUuid(db.getSession(), "TASK_2").get().getIsLast()).isTrue();
     assertThat(underTest.selectByUuid(db.getSession(), "TASK_3").get().getIsLast()).isTrue();
@@ -153,9 +162,9 @@ public class CeActivityDaoTest {
 
   @Test
   public void test_selectByQuery() {
-    insert("TASK_1", REPORT, "PROJECT_1", CeActivityDto.Status.SUCCESS);
-    insert("TASK_2", REPORT, "PROJECT_1", FAILED);
-    insert("TASK_3", REPORT, "PROJECT_2", CeActivityDto.Status.SUCCESS);
+    insert("TASK_1", REPORT, MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS);
+    insert("TASK_2", REPORT, MAINCOMPONENT_1, FAILED);
+    insert("TASK_3", REPORT, MAINCOMPONENT_2, CeActivityDto.Status.SUCCESS);
     insert("TASK_4", "views", null, CeActivityDto.Status.SUCCESS);
 
     // no filters
@@ -164,7 +173,7 @@ public class CeActivityDaoTest {
     assertThat(dtos).extracting("uuid").containsExactly("TASK_4", "TASK_3", "TASK_2", "TASK_1");
 
     // select by component uuid
-    query = new CeTaskQuery().setComponentUuid("PROJECT_1");
+    query = new CeTaskQuery().setMainComponentUuid(MAINCOMPONENT_1);
     dtos = underTest.selectByQuery(db.getSession(), query, forPage(1).andSize(100));
     assertThat(dtos).extracting("uuid").containsExactly("TASK_2", "TASK_1");
 
@@ -182,17 +191,17 @@ public class CeActivityDaoTest {
     assertThat(dtos).extracting("uuid").containsExactly("TASK_4");
 
     // select by multiple conditions
-    query = new CeTaskQuery().setType(REPORT).setOnlyCurrents(true).setComponentUuid("PROJECT_1");
+    query = new CeTaskQuery().setType(REPORT).setOnlyCurrents(true).setMainComponentUuid(MAINCOMPONENT_1);
     dtos = underTest.selectByQuery(db.getSession(), query, forPage(1).andSize(100));
     assertThat(dtos).extracting("uuid").containsExactly("TASK_2");
   }
 
   @Test
   public void selectByQuery_does_not_populate_errorStacktrace_field() {
-    insert("TASK_1", REPORT, "PROJECT_1", FAILED);
-    underTest.insert(db.getSession(), createActivityDto("TASK_2", REPORT, "PROJECT_1", FAILED).setErrorStacktrace("some stack"));
+    insert("TASK_1", REPORT, MAINCOMPONENT_1, FAILED);
+    underTest.insert(db.getSession(), createActivityDto("TASK_2", REPORT, COMPONENT_1, MAINCOMPONENT_1, FAILED).setErrorStacktrace("some stack"));
 
-    List<CeActivityDto> dtos = underTest.selectByQuery(db.getSession(), new CeTaskQuery().setComponentUuid("PROJECT_1"), forPage(1).andSize(100));
+    List<CeActivityDto> dtos = underTest.selectByQuery(db.getSession(), new CeTaskQuery().setMainComponentUuid(MAINCOMPONENT_1), forPage(1).andSize(100));
 
     assertThat(dtos)
       .hasSize(2)
@@ -201,21 +210,21 @@ public class CeActivityDaoTest {
 
   @Test
   public void selectByQuery_populates_hasScannerContext_flag() {
-    insert("TASK_1", REPORT, "PROJECT_1", SUCCESS);
-    CeActivityDto dto2 = insert("TASK_2", REPORT, "PROJECT_2", SUCCESS);
+    insert("TASK_1", REPORT, MAINCOMPONENT_1, SUCCESS);
+    CeActivityDto dto2 = insert("TASK_2", REPORT, MAINCOMPONENT_2, SUCCESS);
     insertScannerContext(dto2.getUuid());
 
-    CeActivityDto dto = underTest.selectByQuery(db.getSession(), new CeTaskQuery().setComponentUuid("PROJECT_1"), forPage(1).andSize(100)).iterator().next();
+    CeActivityDto dto = underTest.selectByQuery(db.getSession(), new CeTaskQuery().setMainComponentUuid(MAINCOMPONENT_1), forPage(1).andSize(100)).iterator().next();
     assertThat(dto.isHasScannerContext()).isFalse();
-    dto = underTest.selectByQuery(db.getSession(), new CeTaskQuery().setComponentUuid("PROJECT_2"), forPage(1).andSize(100)).iterator().next();
+    dto = underTest.selectByQuery(db.getSession(), new CeTaskQuery().setMainComponentUuid(MAINCOMPONENT_2), forPage(1).andSize(100)).iterator().next();
     assertThat(dto.isHasScannerContext()).isTrue();
   }
 
   @Test
   public void selectByQuery_is_paginated_and_return_results_sorted_from_last_to_first() {
-    insert("TASK_1", REPORT, "PROJECT_1", CeActivityDto.Status.SUCCESS);
-    insert("TASK_2", REPORT, "PROJECT_1", CeActivityDto.Status.FAILED);
-    insert("TASK_3", REPORT, "PROJECT_2", CeActivityDto.Status.SUCCESS);
+    insert("TASK_1", REPORT, MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS);
+    insert("TASK_2", REPORT, MAINCOMPONENT_1, CeActivityDto.Status.FAILED);
+    insert("TASK_3", REPORT, MAINCOMPONENT_2, CeActivityDto.Status.SUCCESS);
     insert("TASK_4", "views", null, CeActivityDto.Status.SUCCESS);
 
     assertThat(selectPageOfUuids(forPage(1).andSize(1))).containsExactly("TASK_4");
@@ -229,10 +238,10 @@ public class CeActivityDaoTest {
 
   @Test
   public void selectByQuery_no_results_if_shortcircuited_by_component_uuids() {
-    insert("TASK_1", REPORT, "PROJECT_1", CeActivityDto.Status.SUCCESS);
+    insert("TASK_1", REPORT, MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS);
 
     CeTaskQuery query = new CeTaskQuery();
-    query.setComponentUuids(Collections.emptyList());
+    query.setMainComponentUuids(Collections.emptyList());
     assertThat(underTest.selectByQuery(db.getSession(), query, forPage(1).andSize(1))).isEmpty();
   }
 
@@ -291,8 +300,8 @@ public class CeActivityDaoTest {
 
   @Test
   public void selectOlderThan_does_not_populate_errorStacktrace() {
-    insert("TASK_1", REPORT, "PROJECT_1", FAILED);
-    underTest.insert(db.getSession(), createActivityDto("TASK_2", REPORT, "PROJECT_1", FAILED).setErrorStacktrace("some stack"));
+    insert("TASK_1", REPORT, MAINCOMPONENT_1, FAILED);
+    underTest.insert(db.getSession(), createActivityDto("TASK_2", REPORT, COMPONENT_1, MAINCOMPONENT_1, FAILED).setErrorStacktrace("some stack"));
 
     List<CeActivityDto> dtos = underTest.selectOlderThan(db.getSession(), system2.now() + 1_000_000L);
 
@@ -303,9 +312,9 @@ public class CeActivityDaoTest {
 
   @Test
   public void deleteByUuids() {
-    insert("TASK_1", "REPORT", "COMPONENT1", CeActivityDto.Status.SUCCESS);
-    insert("TASK_2", "REPORT", "COMPONENT1", CeActivityDto.Status.SUCCESS);
-    insert("TASK_3", "REPORT", "COMPONENT1", CeActivityDto.Status.SUCCESS);
+    insert("TASK_1", "REPORT", MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS);
+    insert("TASK_2", "REPORT", MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS);
+    insert("TASK_3", "REPORT", MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS);
 
     underTest.deleteByUuids(db.getSession(), ImmutableSet.of("TASK_1", "TASK_3"));
     assertThat(underTest.selectByUuid(db.getSession(), "TASK_1").isPresent()).isFalse();
@@ -315,7 +324,7 @@ public class CeActivityDaoTest {
 
   @Test
   public void deleteByUuids_does_nothing_if_uuid_does_not_exist() {
-    insert("TASK_1", "REPORT", "COMPONENT1", CeActivityDto.Status.SUCCESS);
+    insert("TASK_1", "REPORT", MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS);
 
     // must not fail
     underTest.deleteByUuids(db.getSession(), singleton("TASK_2"));
@@ -324,33 +333,38 @@ public class CeActivityDaoTest {
   }
 
   @Test
-  public void count_last_by_status_and_component_uuid() {
-    insert("TASK_1", CeTaskTypes.REPORT, "COMPONENT1", CeActivityDto.Status.SUCCESS);
+  public void count_last_by_status_and_main_component_uuid() {
+    insert("TASK_1", CeTaskTypes.REPORT, MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS);
     // component 2
-    insert("TASK_2", CeTaskTypes.REPORT, "COMPONENT2", CeActivityDto.Status.SUCCESS);
+    insert("TASK_2", CeTaskTypes.REPORT, MAINCOMPONENT_2, CeActivityDto.Status.SUCCESS);
     // status failed
-    insert("TASK_3", CeTaskTypes.REPORT, "COMPONENT1", CeActivityDto.Status.FAILED);
+    insert("TASK_3", CeTaskTypes.REPORT, MAINCOMPONENT_1, CeActivityDto.Status.FAILED);
     // status canceled
-    insert("TASK_4", CeTaskTypes.REPORT, "COMPONENT1", CeActivityDto.Status.CANCELED);
-    insert("TASK_5", CeTaskTypes.REPORT, "COMPONENT1", CeActivityDto.Status.SUCCESS);
+    insert("TASK_4", CeTaskTypes.REPORT, MAINCOMPONENT_1, CeActivityDto.Status.CANCELED);
+    insert("TASK_5", CeTaskTypes.REPORT, MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS);
     db.commit();
 
-    assertThat(underTest.countLastByStatusAndComponentUuid(dbSession, SUCCESS, "COMPONENT1")).isEqualTo(1);
-    assertThat(underTest.countLastByStatusAndComponentUuid(dbSession, SUCCESS, null)).isEqualTo(2);
+    assertThat(underTest.countLastByStatusAndMainComponentUuid(dbSession, SUCCESS, MAINCOMPONENT_1)).isEqualTo(1);
+    assertThat(underTest.countLastByStatusAndMainComponentUuid(dbSession, SUCCESS, null)).isEqualTo(2);
+  }
+
+  private CeActivityDto insert(String uuid, String type, @Nullable String mainComponentUuid, CeActivityDto.Status status) {
+    return insert(uuid, type, mainComponentUuid, mainComponentUuid, status);
   }
 
-  private CeActivityDto insert(String uuid, String type, String componentUuid, CeActivityDto.Status status) {
-    CeActivityDto dto = createActivityDto(uuid, type, componentUuid, status);
+  private CeActivityDto insert(String uuid, String type, String componentUuid, @Nullable String mainComponentUuid, CeActivityDto.Status status) {
+    CeActivityDto dto = createActivityDto(uuid, type, componentUuid, mainComponentUuid, status);
     underTest.insert(db.getSession(), dto);
     return dto;
   }
 
-  private CeActivityDto createActivityDto(String uuid, String type, String componentUuid, CeActivityDto.Status status) {
+  private CeActivityDto createActivityDto(String uuid, String type, @Nullable String componentUuid, @Nullable String mainComponentUuid, CeActivityDto.Status status) {
     CeQueueDto creating = new CeQueueDto();
     creating.setUuid(uuid);
     creating.setStatus(PENDING);
     creating.setTaskType(type);
     creating.setComponentUuid(componentUuid);
+    creating.setMainComponentUuid(mainComponentUuid);
     creating.setSubmitterUuid("submitter uuid");
     creating.setCreatedAt(1_300_000_000_000L);
 
index d5991d9a70d780ac13716cca305cc2dd71919b00..1035ea80d9fce082edd58c02f8a2a9b64641084c 100644 (file)
@@ -23,7 +23,9 @@ import com.tngtech.java.junit.dataprovider.DataProvider;
 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
 import com.tngtech.java.junit.dataprovider.UseDataProvider;
 import java.util.Random;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 import org.junit.runner.RunWith;
 
 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
@@ -31,8 +33,79 @@ import static org.assertj.core.api.Assertions.assertThat;
 
 @RunWith(DataProviderRunner.class)
 public class CeActivityDtoTest {
+  private static final String STR_40_CHARS = "0123456789012345678901234567890123456789";
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
   private CeActivityDto underTest = new CeActivityDto();
 
+  @Test
+  public void constructor_from_CeQueueDto_populates_fields() {
+    long now = new Random().nextLong();
+    CeQueueDto ceQueueDto = new CeQueueDto()
+      .setUuid(randomAlphanumeric(10))
+      .setTaskType(randomAlphanumeric(11))
+      .setComponentUuid(randomAlphanumeric(12))
+      .setMainComponentUuid(randomAlphanumeric(13))
+      .setSubmitterUuid(randomAlphanumeric(14))
+      .setWorkerUuid(randomAlphanumeric(15))
+      .setCreatedAt(now + 9_999)
+      .setStartedAt(now + 865);
+
+    CeActivityDto underTest = new CeActivityDto(ceQueueDto);
+
+    assertThat(underTest.getUuid()).isEqualTo(ceQueueDto.getUuid());
+    assertThat(underTest.getTaskType()).isEqualTo(ceQueueDto.getTaskType());
+    assertThat(underTest.getComponentUuid()).isEqualTo(ceQueueDto.getComponentUuid());
+    assertThat(underTest.getMainComponentUuid()).isEqualTo(ceQueueDto.getMainComponentUuid());
+    assertThat(underTest.getIsLastKey()).isEqualTo(ceQueueDto.getTaskType() + ceQueueDto.getComponentUuid());
+    assertThat(underTest.getIsLast()).isFalse();
+    assertThat(underTest.getMainIsLastKey()).isEqualTo(ceQueueDto.getTaskType() + ceQueueDto.getMainComponentUuid());
+    assertThat(underTest.getMainIsLast()).isFalse();
+    assertThat(underTest.getSubmitterUuid()).isEqualTo(ceQueueDto.getSubmitterUuid());
+    assertThat(underTest.getWorkerUuid()).isEqualTo(ceQueueDto.getWorkerUuid());
+    assertThat(underTest.getSubmittedAt()).isEqualTo(ceQueueDto.getCreatedAt());
+    assertThat(underTest.getStartedAt()).isEqualTo(ceQueueDto.getStartedAt());
+    assertThat(underTest.getStatus()).isNull();
+  }
+
+  @Test
+  public void setComponentUuid_accepts_null_empty_and_string_40_chars_or_less() {
+    underTest.setComponentUuid(null);
+    underTest.setComponentUuid("");
+    underTest.setComponentUuid("bar");
+    underTest.setComponentUuid(STR_40_CHARS);
+  }
+
+  @Test
+  public void setComponentUuid_throws_IAE_if_value_is_41_chars() {
+    String str_41_chars = STR_40_CHARS + "a";
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Value is too long for column CE_ACTIVITY.COMPONENT_UUID: " + str_41_chars);
+
+    underTest.setComponentUuid(str_41_chars);
+  }
+
+  @Test
+  public void setMainComponentUuid_accepts_null_empty_and_string_40_chars_or_less() {
+    underTest.setMainComponentUuid(null);
+    underTest.setMainComponentUuid("");
+    underTest.setMainComponentUuid("bar");
+    underTest.setMainComponentUuid(STR_40_CHARS);
+  }
+
+  @Test
+  public void setMainComponentUuid_throws_IAE_if_value_is_41_chars() {
+    String str_41_chars = STR_40_CHARS + "a";
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Value is too long for column CE_ACTIVITY.MAIN_COMPONENT_UUID: " + str_41_chars);
+
+    underTest.setMainComponentUuid(str_41_chars);
+  }
+
   @Test
   @UseDataProvider("stringsWithChar0")
   public void setStacktrace_filters_out_char_zero(String withChar0, String expected) {
index eaab9cb25ef9f3e1921cb079a4c4b401e8b63b77..23907c739adda3a9b7b56f3832a2ff38217dfb43 100644 (file)
@@ -27,6 +27,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Random;
 import java.util.stream.Stream;
 import javax.annotation.Nullable;
 import org.junit.Rule;
@@ -54,8 +55,8 @@ public class CeQueueDaoTest {
   private static final long INIT_TIME = 1_450_000_000_000L;
   private static final String TASK_UUID_1 = "TASK_1";
   private static final String TASK_UUID_2 = "TASK_2";
-  private static final String COMPONENT_UUID_1 = "PROJECT_1";
-  private static final String COMPONENT_UUID_2 = "PROJECT_2";
+  private static final String MAIN_COMPONENT_UUID_1 = "PROJECT_1";
+  private static final String MAIN_COMPONENT_UUID_2 = "PROJECT_2";
   private static final String TASK_UUID_3 = "TASK_3";
   private static final String SELECT_QUEUE_UUID_AND_STATUS_QUERY = "select uuid,status from ce_queue";
   private static final String SUBMITTER_LOGIN = "submitter uuid";
@@ -79,7 +80,7 @@ public class CeQueueDaoTest {
     long now = 1_334_333L;
     CeQueueDto dto = new CeQueueDto()
       .setTaskType(CeTaskTypes.REPORT)
-      .setComponentUuid(COMPONENT_UUID_1)
+      .setComponentUuid(MAIN_COMPONENT_UUID_1)
       .setStatus(PENDING)
       .setSubmitterUuid(SUBMITTER_LOGIN);
 
@@ -99,7 +100,7 @@ public class CeQueueDaoTest {
         CeQueueDto saved = underTest.selectByUuid(db.getSession(), uuid).get();
         assertThat(saved.getUuid()).isEqualTo(uuid);
         assertThat(saved.getTaskType()).isEqualTo(CeTaskTypes.REPORT);
-        assertThat(saved.getComponentUuid()).isEqualTo(COMPONENT_UUID_1);
+        assertThat(saved.getComponentUuid()).isEqualTo(MAIN_COMPONENT_UUID_1);
         assertThat(saved.getStatus()).isEqualTo(PENDING);
         assertThat(saved.getSubmitterUuid()).isEqualTo(SUBMITTER_LOGIN);
         assertThat(saved.getWorkerUuid()).isNull();
@@ -110,7 +111,7 @@ public class CeQueueDaoTest {
     CeQueueDto saved = underTest.selectByUuid(db.getSession(), uuid4).get();
     assertThat(saved.getUuid()).isEqualTo(uuid4);
     assertThat(saved.getTaskType()).isEqualTo(CeTaskTypes.REPORT);
-    assertThat(saved.getComponentUuid()).isEqualTo(COMPONENT_UUID_1);
+    assertThat(saved.getComponentUuid()).isEqualTo(MAIN_COMPONENT_UUID_1);
     assertThat(saved.getStatus()).isEqualTo(PENDING);
     assertThat(saved.getSubmitterUuid()).isEqualTo(SUBMITTER_LOGIN);
     assertThat(saved.getWorkerUuid()).isNull();
@@ -121,13 +122,14 @@ public class CeQueueDaoTest {
 
   @Test
   public void test_selectByUuid() {
-    insertPending(TASK_UUID_1, COMPONENT_UUID_1);
+    CeQueueDto ceQueueDto = insertPending(TASK_UUID_1, MAIN_COMPONENT_UUID_1);
 
     assertThat(underTest.selectByUuid(db.getSession(), "TASK_UNKNOWN").isPresent()).isFalse();
     CeQueueDto saved = underTest.selectByUuid(db.getSession(), TASK_UUID_1).get();
     assertThat(saved.getUuid()).isEqualTo(TASK_UUID_1);
     assertThat(saved.getTaskType()).isEqualTo(CeTaskTypes.REPORT);
-    assertThat(saved.getComponentUuid()).isEqualTo(COMPONENT_UUID_1);
+    assertThat(saved.getMainComponentUuid()).isEqualTo(MAIN_COMPONENT_UUID_1);
+    assertThat(saved.getComponentUuid()).isEqualTo(ceQueueDto.getComponentUuid());
     assertThat(saved.getStatus()).isEqualTo(PENDING);
     assertThat(saved.getSubmitterUuid()).isEqualTo("henri");
     assertThat(saved.getWorkerUuid()).isNull();
@@ -137,20 +139,20 @@ public class CeQueueDaoTest {
   }
 
   @Test
-  public void test_selectByComponentUuid() {
-    insertPending(TASK_UUID_1, COMPONENT_UUID_1);
-    insertPending(TASK_UUID_2, COMPONENT_UUID_1);
+  public void test_selectByMainComponentUuid() {
+    insertPending(TASK_UUID_1, MAIN_COMPONENT_UUID_1);
+    insertPending(TASK_UUID_2, MAIN_COMPONENT_UUID_1);
     insertPending(TASK_UUID_3, "PROJECT_2");
 
-    assertThat(underTest.selectByComponentUuid(db.getSession(), "UNKNOWN")).isEmpty();
-    assertThat(underTest.selectByComponentUuid(db.getSession(), COMPONENT_UUID_1)).extracting("uuid").containsOnly(TASK_UUID_1, TASK_UUID_2);
-    assertThat(underTest.selectByComponentUuid(db.getSession(), "PROJECT_2")).extracting("uuid").containsOnly(TASK_UUID_3);
+    assertThat(underTest.selectByMainComponentUuid(db.getSession(), "UNKNOWN")).isEmpty();
+    assertThat(underTest.selectByMainComponentUuid(db.getSession(), MAIN_COMPONENT_UUID_1)).extracting("uuid").containsOnly(TASK_UUID_1, TASK_UUID_2);
+    assertThat(underTest.selectByMainComponentUuid(db.getSession(), "PROJECT_2")).extracting("uuid").containsOnly(TASK_UUID_3);
   }
 
   @Test
   public void test_selectAllInAscOrder() {
-    insertPending(TASK_UUID_1, COMPONENT_UUID_1);
-    insertPending(TASK_UUID_2, COMPONENT_UUID_1);
+    insertPending(TASK_UUID_1, MAIN_COMPONENT_UUID_1);
+    insertPending(TASK_UUID_2, MAIN_COMPONENT_UUID_1);
     insertPending(TASK_UUID_3, "PROJECT_2");
 
     assertThat(underTest.selectAllInAscOrder(db.getSession())).extracting("uuid").containsOnly(TASK_UUID_1, TASK_UUID_2, TASK_UUID_3);
@@ -186,8 +188,8 @@ public class CeQueueDaoTest {
 
   @Test
   public void test_delete() {
-    insertPending(TASK_UUID_1, COMPONENT_UUID_1);
-    insertPending(TASK_UUID_2, COMPONENT_UUID_1);
+    insertPending(TASK_UUID_1, MAIN_COMPONENT_UUID_1);
+    insertPending(TASK_UUID_2, MAIN_COMPONENT_UUID_1);
 
     int deletedCount = underTest.deleteByUuid(db.getSession(), "UNKNOWN");
     assertThat(deletedCount).isEqualTo(0);
@@ -204,8 +206,8 @@ public class CeQueueDaoTest {
 
   @Test
   public void test_delete_with_expected_status() {
-    insertPending(TASK_UUID_1, COMPONENT_UUID_1);
-    insertInProgress(TASK_UUID_2, COMPONENT_UUID_1);
+    insertPending(TASK_UUID_1, MAIN_COMPONENT_UUID_1);
+    insertInProgress(TASK_UUID_2, MAIN_COMPONENT_UUID_1);
 
     int deletedCount = underTest.deleteByUuid(db.getSession(), "UNKNOWN", null);
     assertThat(deletedCount).isEqualTo(0);
@@ -340,15 +342,15 @@ public class CeQueueDaoTest {
     assertThat(underTest.peek(db.getSession(), WORKER_UUID_1).isPresent()).isFalse();
 
     // not pending, but in progress
-    makeInProgress(WORKER_UUID_1, 2_232_222L, insertPending(TASK_UUID_1, COMPONENT_UUID_1));
+    makeInProgress(WORKER_UUID_1, 2_232_222L, insertPending(TASK_UUID_1, MAIN_COMPONENT_UUID_1));
     assertThat(underTest.peek(db.getSession(), WORKER_UUID_1).isPresent()).isFalse();
   }
 
   @Test
   public void peek_oldest_pending() {
-    insertPending(TASK_UUID_1, COMPONENT_UUID_1);
+    insertPending(TASK_UUID_1, MAIN_COMPONENT_UUID_1);
     system2.setNow(INIT_TIME + 3_000_000);
-    insertPending(TASK_UUID_2, COMPONENT_UUID_2);
+    insertPending(TASK_UUID_2, MAIN_COMPONENT_UUID_2);
 
     assertThat(db.countRowsOfTable("ce_queue")).isEqualTo(2);
     verifyCeQueueStatuses(TASK_UUID_1, PENDING, TASK_UUID_2, PENDING);
@@ -374,15 +376,16 @@ public class CeQueueDaoTest {
   }
 
   @Test
-  public void do_not_peek_multiple_tasks_on_same_project_at_the_same_time() {
+  public void do_not_peek_multiple_tasks_on_same_main_component_at_the_same_time() {
     // two pending tasks on the same project
-    insertPending(TASK_UUID_1, COMPONENT_UUID_1);
+    insertPending(TASK_UUID_1, MAIN_COMPONENT_UUID_1);
     system2.setNow(INIT_TIME + 3_000_000);
-    insertPending(TASK_UUID_2, COMPONENT_UUID_1);
+    insertPending(TASK_UUID_2, MAIN_COMPONENT_UUID_1);
 
     Optional<CeQueueDto> peek = underTest.peek(db.getSession(), WORKER_UUID_1);
     assertThat(peek).isPresent();
     assertThat(peek.get().getUuid()).isEqualTo(TASK_UUID_1);
+    assertThat(peek.get().getMainComponentUuid()).isEqualTo(MAIN_COMPONENT_UUID_1);
     assertThat(peek.get().getWorkerUuid()).isEqualTo(WORKER_UUID_1);
     verifyCeQueueStatuses(TASK_UUID_1, IN_PROGRESS, TASK_UUID_2, PENDING);
 
@@ -401,42 +404,42 @@ public class CeQueueDaoTest {
   public void select_by_query() {
     // task status not in query
     insertPending(newCeQueueDto(TASK_UUID_1)
-      .setComponentUuid(COMPONENT_UUID_1)
+      .setMainComponentUuid(MAIN_COMPONENT_UUID_1)
       .setStatus(IN_PROGRESS)
       .setTaskType(CeTaskTypes.REPORT)
       .setCreatedAt(100_000L));
 
     // too early
     insertPending(newCeQueueDto(TASK_UUID_3)
-      .setComponentUuid(COMPONENT_UUID_1)
+      .setMainComponentUuid(MAIN_COMPONENT_UUID_1)
       .setStatus(PENDING)
       .setTaskType(CeTaskTypes.REPORT)
       .setCreatedAt(90_000L));
 
     // task type not in query
     insertPending(newCeQueueDto("TASK_4")
-      .setComponentUuid("PROJECT_2")
+      .setMainComponentUuid("PROJECT_2")
       .setStatus(PENDING)
       .setTaskType("ANOTHER_TYPE")
       .setCreatedAt(100_000L));
 
     // correct
     insertPending(newCeQueueDto(TASK_UUID_2)
-      .setComponentUuid(COMPONENT_UUID_1)
+      .setMainComponentUuid(MAIN_COMPONENT_UUID_1)
       .setStatus(PENDING)
       .setTaskType(CeTaskTypes.REPORT)
       .setCreatedAt(100_000L));
 
     // correct submitted later
     insertPending(newCeQueueDto("TASK_5")
-      .setComponentUuid(COMPONENT_UUID_1)
+      .setMainComponentUuid(MAIN_COMPONENT_UUID_1)
       .setStatus(PENDING)
       .setTaskType(CeTaskTypes.REPORT)
       .setCreatedAt(120_000L));
 
     // select by component uuid, status, task type and minimum submitted at
     CeTaskQuery query = new CeTaskQuery()
-      .setComponentUuids(newArrayList(COMPONENT_UUID_1, "PROJECT_2"))
+      .setMainComponentUuids(newArrayList(MAIN_COMPONENT_UUID_1, "PROJECT_2"))
       .setStatuses(singletonList(PENDING.name()))
       .setType(CeTaskTypes.REPORT)
       .setMinSubmittedAt(100_000L);
@@ -451,7 +454,7 @@ public class CeQueueDaoTest {
   @Test
   public void select_by_query_returns_empty_list_when_only_current() {
     insertPending(newCeQueueDto(TASK_UUID_1)
-      .setComponentUuid(COMPONENT_UUID_1)
+      .setComponentUuid(MAIN_COMPONENT_UUID_1)
       .setStatus(IN_PROGRESS)
       .setTaskType(CeTaskTypes.REPORT)
       .setCreatedAt(100_000L));
@@ -468,7 +471,7 @@ public class CeQueueDaoTest {
   @Test
   public void select_by_query_returns_empty_list_when_max_submitted_at() {
     insertPending(newCeQueueDto(TASK_UUID_1)
-      .setComponentUuid(COMPONENT_UUID_1)
+      .setComponentUuid(MAIN_COMPONENT_UUID_1)
       .setStatus(IN_PROGRESS)
       .setTaskType(CeTaskTypes.REPORT)
       .setCreatedAt(100_000L));
@@ -483,14 +486,14 @@ public class CeQueueDaoTest {
   }
 
   @Test
-  public void select_by_query_returns_empty_list_when_empty_list_of_component_uuid() {
+  public void select_by_query_returns_empty_list_when_empty_list_of_main_component_uuid() {
     insertPending(newCeQueueDto(TASK_UUID_1)
-      .setComponentUuid(COMPONENT_UUID_1)
+      .setComponentUuid(MAIN_COMPONENT_UUID_1)
       .setStatus(IN_PROGRESS)
       .setTaskType(CeTaskTypes.REPORT)
       .setCreatedAt(100_000L));
 
-    CeTaskQuery query = new CeTaskQuery().setComponentUuids(Collections.emptyList());
+    CeTaskQuery query = new CeTaskQuery().setMainComponentUuids(Collections.emptyList());
 
     List<CeQueueDto> result = underTest.selectByQueryInDescOrder(db.getSession(), query, 1_000);
     int total = underTest.countByQuery(db.getSession(), query);
@@ -500,57 +503,57 @@ public class CeQueueDaoTest {
   }
 
   @Test
-  public void count_by_status_and_component_uuid() {
+  public void count_by_status_and_main_component_uuid() {
     // task retrieved in the queue
     insertPending(newCeQueueDto(TASK_UUID_1)
-      .setComponentUuid(COMPONENT_UUID_1)
+      .setMainComponentUuid(MAIN_COMPONENT_UUID_1)
       .setStatus(IN_PROGRESS)
       .setTaskType(CeTaskTypes.REPORT)
       .setCreatedAt(100_000L));
     // on component uuid 2, not returned
     insertPending(newCeQueueDto(TASK_UUID_2)
-      .setComponentUuid(COMPONENT_UUID_2)
+      .setMainComponentUuid(MAIN_COMPONENT_UUID_2)
       .setStatus(IN_PROGRESS)
       .setTaskType(CeTaskTypes.REPORT)
       .setCreatedAt(100_000L));
     // pending status, not returned
     insertPending(newCeQueueDto(TASK_UUID_3)
-      .setComponentUuid(COMPONENT_UUID_1)
+      .setMainComponentUuid(MAIN_COMPONENT_UUID_1)
       .setStatus(PENDING)
       .setTaskType(CeTaskTypes.REPORT)
       .setCreatedAt(100_000L));
 
-    assertThat(underTest.countByStatusAndComponentUuid(db.getSession(), IN_PROGRESS, COMPONENT_UUID_1)).isEqualTo(1);
+    assertThat(underTest.countByStatusAndMainComponentUuid(db.getSession(), IN_PROGRESS, MAIN_COMPONENT_UUID_1)).isEqualTo(1);
     assertThat(underTest.countByStatus(db.getSession(), IN_PROGRESS)).isEqualTo(2);
   }
 
   @Test
-  public void count_by_status_and_component_uuids() {
+  public void count_by_status_and_main_component_uuids() {
     // task retrieved in the queue
     insertPending(newCeQueueDto(TASK_UUID_1)
-      .setComponentUuid(COMPONENT_UUID_1)
+      .setMainComponentUuid(MAIN_COMPONENT_UUID_1)
       .setStatus(IN_PROGRESS)
       .setTaskType(CeTaskTypes.REPORT)
       .setCreatedAt(100_000L));
     // on component uuid 2, not returned
     insertPending(newCeQueueDto(TASK_UUID_2)
-      .setComponentUuid(COMPONENT_UUID_2)
+      .setMainComponentUuid(MAIN_COMPONENT_UUID_2)
       .setStatus(IN_PROGRESS)
       .setTaskType(CeTaskTypes.REPORT)
       .setCreatedAt(100_000L));
     // pending status, not returned
     insertPending(newCeQueueDto(TASK_UUID_3)
-      .setComponentUuid(COMPONENT_UUID_1)
+      .setMainComponentUuid(MAIN_COMPONENT_UUID_1)
       .setStatus(PENDING)
       .setTaskType(CeTaskTypes.REPORT)
       .setCreatedAt(100_000L));
 
-    assertThat(underTest.countByStatusAndComponentUuids(db.getSession(), IN_PROGRESS, ImmutableSet.of())).isEmpty();
-    assertThat(underTest.countByStatusAndComponentUuids(db.getSession(), IN_PROGRESS, ImmutableSet.of("non existing component uuid"))).isEmpty();
-    assertThat(underTest.countByStatusAndComponentUuids(db.getSession(), IN_PROGRESS, ImmutableSet.of(COMPONENT_UUID_1, COMPONENT_UUID_2)))
-      .containsOnly(entry(COMPONENT_UUID_1, 1), entry(COMPONENT_UUID_2, 1));
-    assertThat(underTest.countByStatusAndComponentUuids(db.getSession(), PENDING, ImmutableSet.of(COMPONENT_UUID_1, COMPONENT_UUID_2)))
-      .containsOnly(entry(COMPONENT_UUID_1, 1));
+    assertThat(underTest.countByStatusAndMainComponentUuids(db.getSession(), IN_PROGRESS, ImmutableSet.of())).isEmpty();
+    assertThat(underTest.countByStatusAndMainComponentUuids(db.getSession(), IN_PROGRESS, ImmutableSet.of("non existing component uuid"))).isEmpty();
+    assertThat(underTest.countByStatusAndMainComponentUuids(db.getSession(), IN_PROGRESS, ImmutableSet.of(MAIN_COMPONENT_UUID_1, MAIN_COMPONENT_UUID_2)))
+      .containsOnly(entry(MAIN_COMPONENT_UUID_1, 1), entry(MAIN_COMPONENT_UUID_2, 1));
+    assertThat(underTest.countByStatusAndMainComponentUuids(db.getSession(), PENDING, ImmutableSet.of(MAIN_COMPONENT_UUID_1, MAIN_COMPONENT_UUID_2)))
+      .containsOnly(entry(MAIN_COMPONENT_UUID_1, 1));
     assertThat(underTest.countByStatus(db.getSession(), IN_PROGRESS)).isEqualTo(2);
   }
 
@@ -570,11 +573,13 @@ public class CeQueueDaoTest {
     return dto;
   }
 
-  private CeQueueDto insertPending(String uuid, String componentUuid) {
+  private int pendingComponentUuidGenerator = new Random().nextInt(200);
+  private CeQueueDto insertPending(String uuid, String mainComponentUuid) {
     CeQueueDto dto = new CeQueueDto();
     dto.setUuid(uuid);
     dto.setTaskType(CeTaskTypes.REPORT);
-    dto.setComponentUuid(componentUuid);
+    dto.setMainComponentUuid(mainComponentUuid);
+    dto.setComponentUuid("uuid_" + pendingComponentUuidGenerator++);
     dto.setStatus(PENDING);
     dto.setSubmitterUuid("henri");
     underTest.insert(db.getSession(), dto);
index b93c6c90fd766e17e54dadf3f48cc1f56bf7eb47..10e57d12133e3860e2971ccc35f4d61116c1a639 100644 (file)
@@ -47,11 +47,29 @@ public class CeQueueDtoTest {
     String str_41_chars = STR_40_CHARS + "a";
 
     expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Value of component UUID is too long: " + str_41_chars);
+    expectedException.expectMessage("Value is too long for column CE_QUEUE.COMPONENT_UUID: " + str_41_chars);
 
     underTest.setComponentUuid(str_41_chars);
   }
 
+  @Test
+  public void setMainComponentUuid_accepts_null_empty_and_string_40_chars_or_less() {
+    underTest.setMainComponentUuid(null);
+    underTest.setMainComponentUuid("");
+    underTest.setMainComponentUuid("bar");
+    underTest.setMainComponentUuid(STR_40_CHARS);
+  }
+
+  @Test
+  public void setMainComponentUuid_throws_IAE_if_value_is_41_chars() {
+    String str_41_chars = STR_40_CHARS + "a";
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Value is too long for column CE_QUEUE.MAIN_COMPONENT_UUID: " + str_41_chars);
+
+    underTest.setMainComponentUuid(str_41_chars);
+  }
+
   @Test
   public void setTaskType_throws_NPE_if_argument_is_null() {
     expectedException.expect(NullPointerException.class);
index 16330122d55202cc5f46bf126fcff538cfa343dd..a32c733358be45236d16a3d946000af12e7d7f9e 100644 (file)
@@ -38,6 +38,7 @@ public class CeQueueTesting {
     return new CeQueueDto()
       .setUuid(uuid)
       .setComponentUuid(randomAlphanumeric(40))
+      .setMainComponentUuid(randomAlphanumeric(39))
       .setStatus(CeQueueDto.Status.PENDING)
       .setTaskType(CeTaskTypes.REPORT)
       .setSubmitterUuid(randomAlphanumeric(255))
index 8322735f85f625a64380d0d4fc542f80f074de52..c95622292efbf02a3126692ff63f56e8e44fb6ea 100644 (file)
@@ -33,22 +33,22 @@ public class CeTaskQueryTest {
 
   @Test
   public void no_filter_on_component_uuids_by_default() {
-    assertThat(underTest.getComponentUuids()).isNull();
-    assertThat(underTest.isShortCircuitedByComponentUuids()).isFalse();
+    assertThat(underTest.getMainComponentUuids()).isNull();
+    assertThat(underTest.isShortCircuitedByMainComponentUuids()).isFalse();
   }
 
   @Test
   public void filter_on_component_uuid() {
-    underTest.setComponentUuid("UUID1");
-    assertThat(underTest.getComponentUuids()).containsOnly("UUID1");
-    assertThat(underTest.isShortCircuitedByComponentUuids()).isFalse();
+    underTest.setMainComponentUuid("UUID1");
+    assertThat(underTest.getMainComponentUuids()).containsOnly("UUID1");
+    assertThat(underTest.isShortCircuitedByMainComponentUuids()).isFalse();
   }
 
   @Test
   public void filter_on_multiple_component_uuids() {
-    underTest.setComponentUuids(asList("UUID1", "UUID2"));
-    assertThat(underTest.getComponentUuids()).containsOnly("UUID1", "UUID2");
-    assertThat(underTest.isShortCircuitedByComponentUuids()).isFalse();
+    underTest.setMainComponentUuids(asList("UUID1", "UUID2"));
+    assertThat(underTest.getMainComponentUuids()).containsOnly("UUID1", "UUID2");
+    assertThat(underTest.isShortCircuitedByMainComponentUuids()).isFalse();
   }
 
   /**
@@ -57,9 +57,9 @@ public class CeTaskQueryTest {
    */
   @Test
   public void short_circuited_if_empty_component_uuid_filter() {
-    underTest.setComponentUuids(Collections.emptyList());
-    assertThat(underTest.getComponentUuids()).isEmpty();
-    assertThat(underTest.isShortCircuitedByComponentUuids()).isTrue();
+    underTest.setMainComponentUuids(Collections.emptyList());
+    assertThat(underTest.getMainComponentUuids()).isEmpty();
+    assertThat(underTest.isShortCircuitedByMainComponentUuids()).isTrue();
   }
 
   /**
@@ -72,8 +72,8 @@ public class CeTaskQueryTest {
     for (int i = 0; i < CeTaskQuery.MAX_COMPONENT_UUIDS + 2; i++) {
       uuids.add(String.valueOf(i));
     }
-    underTest.setComponentUuids(uuids);
-    assertThat(underTest.getComponentUuids()).hasSize(CeTaskQuery.MAX_COMPONENT_UUIDS + 2);
-    assertThat(underTest.isShortCircuitedByComponentUuids()).isTrue();
+    underTest.setMainComponentUuids(uuids);
+    assertThat(underTest.getMainComponentUuids()).hasSize(CeTaskQuery.MAX_COMPONENT_UUIDS + 2);
+    assertThat(underTest.isShortCircuitedByMainComponentUuids()).isTrue();
   }
 }
index a6247601c0b013fee9df99c7c76f37ce4a1fd6fb..eb060efb539ee384bd4e0be9c85522232adc7d4d 100644 (file)
@@ -63,6 +63,7 @@ import org.sonar.db.source.FileSourceDto;
 import org.sonar.db.webhook.WebhookDeliveryLiteDto;
 import org.sonar.db.webhook.WebhookDto;
 
+import static com.google.common.base.MoreObjects.firstNonNull;
 import static java.util.Arrays.asList;
 import static java.util.Collections.emptyList;
 import static java.util.Collections.singletonList;
@@ -905,6 +906,7 @@ public class PurgeDaoTest {
     queueDto.setUuid(Uuids.create());
     queueDto.setTaskType(REPORT);
     queueDto.setComponentUuid(component.uuid());
+    queueDto.setMainComponentUuid(firstNonNull(component.getMainBranchProjectUuid(), component.uuid()));
     queueDto.setSubmitterUuid("submitter uuid");
     queueDto.setCreatedAt(1_300_000_000_000L);
     queueDto.setStatus(status);
@@ -929,6 +931,7 @@ public class PurgeDaoTest {
       .setUuid(UuidFactoryFast.getInstance().create())
       .setTaskType("foo")
       .setComponentUuid(project.uuid())
+      .setMainComponentUuid(firstNonNull(project.getMainBranchProjectUuid(), project.uuid()))
       .setStatus(Status.PENDING)
       .setCreatedAt(1_2323_222L)
       .setUpdatedAt(1_2323_222L);
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeActivity.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeActivity.java
new file mode 100644 (file)
index 0000000..f394c33
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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.v74;
+
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.SupportsBlueGreen;
+
+@SupportsBlueGreen
+public class AddTmpColumnsToCeActivity extends AddTmpColumnsToCeTable {
+
+  public AddTmpColumnsToCeActivity(Database db) {
+    super(db, "ce_activity");
+  }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeQueue.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeQueue.java
new file mode 100644 (file)
index 0000000..0cb1366
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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.v74;
+
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.SupportsBlueGreen;
+
+@SupportsBlueGreen
+public class AddTmpColumnsToCeQueue extends AddTmpColumnsToCeTable {
+
+  public AddTmpColumnsToCeQueue(Database db) {
+    super(db, "ce_queue");
+  }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeTable.java
new file mode 100644 (file)
index 0000000..3a25818
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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.v74;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.def.VarcharColumnDef;
+import org.sonar.server.platform.db.migration.sql.AddColumnsBuilder;
+import org.sonar.server.platform.db.migration.sql.CreateIndexBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+abstract class AddTmpColumnsToCeTable extends DdlChange {
+  private static final VarcharColumnDef COLUMN_TMP_COMPONENT_UUID = newVarcharColumnDefBuilder()
+    .setColumnName("tmp_component_uuid")
+    .setLimit(VarcharColumnDef.UUID_SIZE)
+    .setIsNullable(true)
+    .build();
+  private static final VarcharColumnDef COLUMN_TMP_MAIN_COMPONENT_UUID = newVarcharColumnDefBuilder()
+    .setColumnName("tmp_main_component_uuid")
+    .setLimit(VarcharColumnDef.UUID_SIZE)
+    .setIsNullable(true)
+    .build();
+  private final String tableName;
+
+  public AddTmpColumnsToCeTable(Database db, String tableName) {
+    super(db);
+    this.tableName = tableName;
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    context.execute(new AddColumnsBuilder(getDialect(), tableName)
+      .addColumn(COLUMN_TMP_MAIN_COMPONENT_UUID)
+      .addColumn(COLUMN_TMP_COMPONENT_UUID)
+      .build());
+
+    // create indexes
+    context.execute(new CreateIndexBuilder(getDialect())
+      .setTable(tableName)
+      .setName(tableName + "_tmp_cpnt_uuid")
+      .addColumn(COLUMN_TMP_COMPONENT_UUID)
+      .setUnique(false)
+      .build());
+    context.execute(new CreateIndexBuilder(getDialect())
+      .setTable(tableName)
+      .setName(tableName + "_tmp_main_cpnt_uuid")
+      .addColumn(COLUMN_TMP_MAIN_COMPONENT_UUID)
+      .setUnique(false)
+      .build());
+  }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/AddTmpLastKeyColumnsToCeActivity.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/AddTmpLastKeyColumnsToCeActivity.java
new file mode 100644 (file)
index 0000000..6398091
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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.v74;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.SupportsBlueGreen;
+import org.sonar.server.platform.db.migration.def.BooleanColumnDef;
+import org.sonar.server.platform.db.migration.def.VarcharColumnDef;
+import org.sonar.server.platform.db.migration.sql.AddColumnsBuilder;
+import org.sonar.server.platform.db.migration.sql.CreateIndexBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+import static org.sonar.server.platform.db.migration.def.BooleanColumnDef.newBooleanColumnDefBuilder;
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.UUID_SIZE;
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+@SupportsBlueGreen
+public class AddTmpLastKeyColumnsToCeActivity extends DdlChange {
+  private static final String TABLE_NAME = "ce_activity";
+  private static final int TASK_TYPE_COLUMN_SIZE = 15;
+  private static final BooleanColumnDef COLUMN_TMP_IS_LAST = newBooleanColumnDefBuilder()
+    .setColumnName("tmp_is_last")
+    .setIsNullable(true)
+    .build();
+  private static final VarcharColumnDef COLUMN_TMP_IS_LAST_KEY = newVarcharColumnDefBuilder()
+    .setColumnName("tmp_is_last_key")
+    .setLimit(UUID_SIZE + TASK_TYPE_COLUMN_SIZE)
+    .setIsNullable(true)
+    .build();
+  private static final BooleanColumnDef COLUMN_TMP_MAIN_IS_LAST = newBooleanColumnDefBuilder()
+    .setColumnName("tmp_main_is_last")
+    .setIsNullable(true)
+    .build();
+  private static final VarcharColumnDef COLUMN_TMP_MAIN_IS_LAST_KEY = newVarcharColumnDefBuilder()
+    .setColumnName("tmp_main_is_last_key")
+    .setLimit(UUID_SIZE + TASK_TYPE_COLUMN_SIZE)
+    .setIsNullable(true)
+    .build();
+  private static final VarcharColumnDef COLUMN_STATUS = newVarcharColumnDefBuilder()
+    .setColumnName("status")
+    .setLimit(15)
+    .setIsNullable(false)
+    .build();
+
+  public AddTmpLastKeyColumnsToCeActivity(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    context.execute(new AddColumnsBuilder(getDialect(), TABLE_NAME)
+      .addColumn(COLUMN_TMP_IS_LAST)
+      .addColumn(COLUMN_TMP_IS_LAST_KEY)
+      .addColumn(COLUMN_TMP_MAIN_IS_LAST)
+      .addColumn(COLUMN_TMP_MAIN_IS_LAST_KEY)
+      .build());
+
+    // create indexes
+    context.execute(new CreateIndexBuilder(getDialect())
+      .setTable(TABLE_NAME)
+      .setName(TABLE_NAME + "_t_islast_key")
+      .addColumn(COLUMN_TMP_IS_LAST_KEY)
+      .setUnique(false)
+      .build());
+    context.execute(new CreateIndexBuilder(getDialect())
+      .setTable(TABLE_NAME)
+      .setName(TABLE_NAME + "_t_islast")
+      .addColumn(COLUMN_TMP_IS_LAST)
+      .addColumn(COLUMN_STATUS)
+      .setUnique(false)
+      .build());
+    context.execute(new CreateIndexBuilder(getDialect())
+      .setTable(TABLE_NAME)
+      .setName(TABLE_NAME + "_t_main_islast_key")
+      .addColumn(COLUMN_TMP_MAIN_IS_LAST_KEY)
+      .setUnique(false)
+      .build());
+    context.execute(new CreateIndexBuilder(getDialect())
+      .setTable(TABLE_NAME)
+      .setName(TABLE_NAME + "_t_main_islast")
+      .addColumn(COLUMN_TMP_MAIN_IS_LAST)
+      .addColumn(COLUMN_STATUS)
+      .setUnique(false)
+      .build());
+
+  }
+}
index 4d45e1d0765974f9adab878e90537da2ca9f5be0..82b34413d0ba4fc6f60513113424d3ebe3439e5b 100644 (file)
@@ -32,6 +32,12 @@ public class DbVersion74 implements DbVersion {
       .add(2302, "Populate IS_AD_HOC in RULES", PopulateIsAdHocOnRules.class)
       .add(2303, "Set IS_EXTERNAL and IS_AD_HOC not nullable in RULES", SetIsExternalAndIsAdHocNotNullableInRules.class)
       .add(2304, "Add ad hoc related columns in RULES_METADATA", AddAdHocColumnsInInRulesMetadata.class)
+      .add(2305, "Add CE_QUEUE.MAIN_COMPONENT_UUID 1/5", AddTmpColumnsToCeQueue.class)
+      .add(2306, "Add CE_ACTIVITY.MAIN_COMPONENT_UUID 1/5", AddTmpColumnsToCeActivity.class)
+      .add(2307, "Populate CE_QUEUE.MAIN_COMPONENT_UUID 2/5", PopulateTmpColumnsToCeQueue.class)
+      .add(2308, "Populate CE_ACTIVITY.MAIN_COMPONENT_UUID 2/5", PopulateTmpColumnsToCeActivity.class)
+      .add(2309, "Add CE_ACTIVITY.MAIN_LAST_KEY 1/6", AddTmpLastKeyColumnsToCeActivity.class)
+      .add(2310, "Populate CE_ACTIVITY.MAIN_LAST_KEY 2/6", PopulateTmpLastKeyColumnsToCeActivity.class)
     ;
   }
 }
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeActivity.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeActivity.java
new file mode 100644 (file)
index 0000000..bcf08d5
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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.v74;
+
+import java.sql.SQLException;
+import org.sonar.api.config.Configuration;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.SupportsBlueGreen;
+import org.sonar.server.platform.db.migration.step.DataChange;
+import org.sonar.server.platform.db.migration.step.MassUpdate;
+import org.sonar.server.platform.db.migration.step.Select;
+import org.sonar.server.platform.db.migration.step.SqlStatement;
+
+@SupportsBlueGreen
+public class PopulateTmpColumnsToCeActivity extends DataChange {
+  private final Configuration configuration;
+
+  public PopulateTmpColumnsToCeActivity(Database db, Configuration configuration) {
+    super(db);
+    this.configuration = configuration;
+  }
+
+  @Override
+  protected void execute(Context context) throws SQLException {
+    if (configuration.getBoolean("sonar.sonarcloud.enabled").orElse(false)) {
+      // data migration will be done in background so that interruption of service
+      // is reduced during upgrade
+      return;
+    }
+
+    // activity of long and short branches
+    populateCeActivityTmpColumns(context, "Archived tasks of branches",
+      "select" +
+        "  cea.uuid, p.uuid, cea.component_uuid" +
+        " from ce_activity cea" +
+        " inner join projects mp on mp.uuid = cea.component_uuid" +
+        " inner join ce_task_characteristics ctc1 on ctc1.task_uuid = cea.uuid and ctc1.kee = 'branchType'" +
+        " inner join ce_task_characteristics ctc2 on ctc2.task_uuid = cea.uuid and ctc2.kee = 'branch'" +
+        " inner join projects p on p.kee = concat(mp.kee, ':BRANCH:', ctc2.text_value)" +
+        " where" +
+        "  cea.component_uuid is not null" +
+        "  and (cea.tmp_component_uuid is null or cea.tmp_main_component_uuid is null)");
+
+    // activity of PRs
+    populateCeActivityTmpColumns(context, "Archived tasks of PRs",
+      "select" +
+        "  cea.uuid, p.uuid, cea.component_uuid" +
+        " from ce_activity cea" +
+        " inner join projects mp on mp.uuid = cea.component_uuid " +
+        " inner join ce_task_characteristics ctc1 on ctc1.task_uuid = cea.uuid and ctc1.kee = 'pullRequest'" +
+        " inner join projects p on p.kee = concat(mp.kee, ':PULL_REQUEST:', ctc1.text_value)" +
+        " where" +
+        "  cea.component_uuid is not null" +
+        "  and (cea.tmp_component_uuid is null or cea.tmp_main_component_uuid is null)");
+
+    // all activities which tmp columns are not populated yet (will include main and deprecated branches)
+    // both tmp columns will be set to CE_ACTIVITY.COMPONENT_UUID
+    // do not join on PROJECTS to also catch orphans
+    populateCeActivityTmpColumns(context, "Archived tasks of main and deprecated branches",
+      "select" +
+        "  cea.uuid, cea.component_uuid, cea.component_uuid" +
+        " from ce_activity cea" +
+        " where" +
+        "  cea.component_uuid is not null" +
+        "  and (cea.tmp_component_uuid is null or cea.tmp_main_component_uuid is null)");
+  }
+
+  private static void populateCeActivityTmpColumns(Context context, String rowPluralName, String sql) throws SQLException {
+    MassUpdate massUpdate = context.prepareMassUpdate();
+    massUpdate.select(sql);
+    massUpdate.update("update ce_activity set tmp_component_uuid=?, tmp_main_component_uuid=? where uuid=?");
+    massUpdate.rowPluralName(rowPluralName);
+    massUpdate.execute(PopulateTmpColumnsToCeActivity::handleUpdate);
+  }
+
+  private static boolean handleUpdate(Select.Row row, SqlStatement update) throws SQLException {
+    String uuid = row.getString(1);
+    String componentUuuid = row.getString(2);
+    String mainComponentUuuid = row.getString(3);
+
+    update.setString(1, componentUuuid);
+    update.setString(2, mainComponentUuuid);
+    update.setString(3, uuid);
+
+    return true;
+  }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeQueue.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeQueue.java
new file mode 100644 (file)
index 0000000..e19e274
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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.v74;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.SupportsBlueGreen;
+import org.sonar.server.platform.db.migration.step.DataChange;
+import org.sonar.server.platform.db.migration.step.MassUpdate;
+import org.sonar.server.platform.db.migration.step.Select;
+import org.sonar.server.platform.db.migration.step.SqlStatement;
+
+@SupportsBlueGreen
+public class PopulateTmpColumnsToCeQueue extends DataChange {
+  public PopulateTmpColumnsToCeQueue(Database db) {
+    super(db);
+  }
+
+  @Override
+  protected void execute(Context context) throws SQLException {
+    // queued task of long and short branches which have already been analyzed at least once
+    populateCeQueueTmpColumns(context, "queued tasks of branches",
+      "select" +
+        "  cq.uuid, p.uuid, cq.component_uuid" +
+        " from ce_queue cq" +
+        " inner join projects mp on mp.uuid = cq.component_uuid" +
+        " inner join ce_task_characteristics ctc1 on ctc1.task_uuid = cq.uuid and ctc1.kee = 'branchType'" +
+        " inner join ce_task_characteristics ctc2 on ctc2.task_uuid = cq.uuid and ctc2.kee = 'branch'" +
+        " inner join projects p on p.kee = concat(mp.kee, ':BRANCH:', ctc2.text_value)" +
+        " where" +
+        "  cq.component_uuid is not null" +
+        "  and (cq.tmp_component_uuid is null or cq.tmp_main_component_uuid is null)");
+
+    // queued task of pull request which have already been analyzed at least once
+    populateCeQueueTmpColumns(context, "queued tasks of PRs",
+      " select" +
+        "  cq.uuid, p.uuid, cq.component_uuid" +
+        " from ce_queue cq" +
+        " inner join projects mp on mp.uuid = cq.component_uuid " +
+        " inner join ce_task_characteristics ctc1 on ctc1.task_uuid = cq.uuid and ctc1.kee = 'pullRequest'" +
+        " inner join projects p on p.kee = concat(mp.kee, ':PULL_REQUEST:', ctc1.text_value)" +
+        " where" +
+        "  cq.component_uuid is not null" +
+        "  and (cq.tmp_component_uuid is null or cq.tmp_main_component_uuid is null)");
+
+    // queued task of long and short branches which have never been analyzed must be deleted
+    deleteFromCeQueue(context, "queued tasks of never analyzed branches",
+      "select" +
+        "  cq.uuid" +
+        " from ce_queue cq" +
+        " inner join projects mp on mp.uuid = cq.component_uuid" +
+        " inner join ce_task_characteristics ctc1 on ctc1.task_uuid = cq.uuid and ctc1.kee = 'branchType'" +
+        " inner join ce_task_characteristics ctc2 on ctc2.task_uuid = cq.uuid and ctc2.kee = 'branch'" +
+        " where" +
+        "  cq.component_uuid is not null" +
+        "  and (cq.tmp_component_uuid is null or cq.tmp_main_component_uuid is null)" +
+        "  and not exists (select 1 from projects p where p.kee = concat(mp.kee, ':BRANCH:', ctc2.text_value))");
+
+    // queued of pull request which have never been analyzed must be deleted
+    deleteFromCeQueue(context, "queued tasks of never analyzed PRs",
+      "select" +
+        "  cq.uuid" +
+        " from ce_queue cq" +
+        " inner join projects mp on mp.uuid = cq.component_uuid " +
+        " inner join ce_task_characteristics ctc1 on ctc1.task_uuid = cq.uuid and ctc1.kee = 'pullRequest'" +
+        " where" +
+        "  cq.component_uuid is not null" +
+        "  and (cq.tmp_component_uuid is null or cq.tmp_main_component_uuid is null)" +
+        "  and not exists (select 1 from projects p where p.kee = concat(mp.kee, ':PULL_REQUEST:', ctc1.text_value))");
+
+    // all queue which tmp columns are not populated yet (will include main and deprecated branches)
+    // both tmp columns will be set to CE_QUEUE.COMPONENT_UUID
+    // do not join on PROJECTS to also catch orphans (there are many for branch and PRs due to SONAR-10642)
+    populateCeQueueTmpColumns(context, "queued tasks of main and deprecated branches",
+      "select" +
+        "  cq.uuid, cq.component_uuid, cq.component_uuid" +
+        " from ce_queue cq" +
+        " where" +
+        " cq.component_uuid is not null" +
+        " and (cq.tmp_component_uuid is null or cq.tmp_main_component_uuid is null)");
+  }
+
+  private static void populateCeQueueTmpColumns(Context context, String pluralName, String selectSQL) throws SQLException {
+    MassUpdate massUpdate = context.prepareMassUpdate();
+    massUpdate.select(selectSQL);
+    massUpdate.update("update ce_queue set tmp_component_uuid=?, tmp_main_component_uuid=? where uuid=?");
+    massUpdate.rowPluralName(pluralName);
+    massUpdate.execute(PopulateTmpColumnsToCeQueue::handleUpdate);
+  }
+
+  private static boolean handleUpdate(Select.Row row, SqlStatement update) throws SQLException {
+    String uuid = row.getString(1);
+    String componentUuuid = row.getString(2);
+    String mainComponentUuuid = row.getString(3);
+
+    update.setString(1, componentUuuid);
+    update.setString(2, mainComponentUuuid);
+    update.setString(3, uuid);
+
+    return true;
+  }
+
+  private static void deleteFromCeQueue(Context context, String pluralName, String sql) throws SQLException {
+    MassUpdate massUpdate = context.prepareMassUpdate();
+    massUpdate.select(sql);
+    massUpdate.update("delete from ce_queue where uuid = ?");
+    massUpdate.rowPluralName(pluralName);
+    massUpdate.execute(PopulateTmpColumnsToCeQueue::handleDelete);
+  }
+
+  private static boolean handleDelete(Select.Row row, SqlStatement update) throws SQLException {
+    String uuid = row.getString(1);
+
+    update.setString(1, uuid);
+
+    return true;
+  }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpLastKeyColumnsToCeActivity.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpLastKeyColumnsToCeActivity.java
new file mode 100644 (file)
index 0000000..81b9340
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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.v74;
+
+import java.sql.SQLException;
+import org.sonar.api.config.Configuration;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.SupportsBlueGreen;
+import org.sonar.server.platform.db.migration.step.DataChange;
+import org.sonar.server.platform.db.migration.step.MassUpdate;
+import org.sonar.server.platform.db.migration.step.Select;
+import org.sonar.server.platform.db.migration.step.SqlStatement;
+
+@SupportsBlueGreen
+public class PopulateTmpLastKeyColumnsToCeActivity extends DataChange {
+  private final Configuration configuration;
+
+  public PopulateTmpLastKeyColumnsToCeActivity(Database db, Configuration configuration) {
+    super(db);
+    this.configuration = configuration;
+  }
+
+  @Override
+  protected void execute(Context context) throws SQLException {
+    if (configuration.getBoolean("sonar.sonarcloud.enabled").orElse(false)) {
+      // data migration will be done in background so that interruption of service
+      // is reduced during upgrade
+      return;
+    }
+
+    MassUpdate massUpdate = context.prepareMassUpdate();
+    massUpdate.select("select" +
+      "  cea.uuid, cea.task_type, cea.component_uuid, cea.tmp_component_uuid, cea.tmp_main_component_uuid" +
+      " from ce_activity cea" +
+      " where" +
+      "  cea.tmp_is_last is null" +
+      "  or cea.tmp_is_last_key is null" +
+      "  or cea.tmp_main_is_last is null" +
+      "  or cea.tmp_main_is_last_key is null");
+    massUpdate.update("update ce_activity" +
+      " set" +
+      "  tmp_is_last=?" +
+      "  ,tmp_is_last_key=?" +
+      "  ,tmp_main_is_last=?" +
+      "  ,tmp_main_is_last_key=?" +
+      " where uuid=?");
+    massUpdate.rowPluralName("rows of ce_activity");
+    massUpdate.execute(PopulateTmpLastKeyColumnsToCeActivity::handleUpdate);
+  }
+
+  private static boolean handleUpdate(Select.Row row, SqlStatement update) throws SQLException {
+    String uuid = row.getString(1);
+    String taskType = row.getString(2);
+    String oldComponentUuid = row.getString(3);
+    String componentUuuid = row.getString(4);
+    String mainComponentUuuid = row.getString(5);
+
+    update.setBoolean(1, false);
+    update.setString(2, oldComponentUuid == null ? taskType : (taskType + componentUuuid));
+    update.setBoolean(3, false);
+    update.setString(4, oldComponentUuid == null ? taskType : (taskType + mainComponentUuuid));
+    update.setString(5, uuid);
+
+    return true;
+  }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeActivityTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeActivityTest.java
new file mode 100644 (file)
index 0000000..f0f7036
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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.v74;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.db.CoreDbTester;
+
+import static java.sql.Types.VARCHAR;
+
+public class AddTmpColumnsToCeActivityTest {
+  @Rule
+  public final CoreDbTester db = CoreDbTester.createForSchema(AddTmpColumnsToCeActivityTest.class, "ce_activity.sql");
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private AddTmpColumnsToCeActivity underTest = new AddTmpColumnsToCeActivity(db.database());
+
+  @Test
+  public void columns_and_indexes_are_added_to_table() throws SQLException {
+    underTest.execute();
+
+    db.assertColumnDefinition("ce_activity", "tmp_component_uuid", VARCHAR, 40, true);
+    db.assertColumnDefinition("ce_activity", "tmp_main_component_uuid", VARCHAR, 40, true);
+    db.assertIndex("ce_activity", "ce_activity_tmp_cpnt_uuid", "tmp_component_uuid");
+    db.assertIndex("ce_activity", "ce_activity_tmp_main_cpnt_uuid", "tmp_main_component_uuid");
+  }
+
+  @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/v74/AddTmpColumnsToCeQueueTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeQueueTest.java
new file mode 100644 (file)
index 0000000..97b062a
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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.v74;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.db.CoreDbTester;
+
+import static java.sql.Types.VARCHAR;
+
+public class AddTmpColumnsToCeQueueTest {
+  @Rule
+  public final CoreDbTester db = CoreDbTester.createForSchema(AddTmpColumnsToCeQueueTest.class, "ce_queue.sql");
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private AddTmpColumnsToCeQueue underTest = new AddTmpColumnsToCeQueue(db.database());
+
+  @Test
+  public void columns_and_indexes_are_added_to_table() throws SQLException {
+    underTest.execute();
+
+    db.assertColumnDefinition("ce_queue", "tmp_component_uuid", VARCHAR, 40, true);
+    db.assertColumnDefinition("ce_queue", "tmp_main_component_uuid", VARCHAR, 40, true);
+    db.assertIndex("ce_queue", "ce_queue_tmp_cpnt_uuid", "tmp_component_uuid");
+    db.assertIndex("ce_queue", "ce_queue_tmp_main_cpnt_uuid", "tmp_main_component_uuid");
+  }
+
+  @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/v74/AddTmpLastKeyColumnsToCeActivityTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/AddTmpLastKeyColumnsToCeActivityTest.java
new file mode 100644 (file)
index 0000000..de71958
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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.v74;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.db.CoreDbTester;
+
+import static java.sql.Types.BOOLEAN;
+import static java.sql.Types.VARCHAR;
+
+public class AddTmpLastKeyColumnsToCeActivityTest {
+  @Rule
+  public final CoreDbTester db = CoreDbTester.createForSchema(AddTmpLastKeyColumnsToCeActivityTest.class, "ce_activity.sql");
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private AddTmpLastKeyColumnsToCeActivity underTest = new AddTmpLastKeyColumnsToCeActivity(db.database());
+
+  @Test
+  public void columns_and_indexes_are_added_to_table() throws SQLException {
+    underTest.execute();
+
+    db.assertColumnDefinition("ce_activity", "tmp_is_last", BOOLEAN, null, true);
+    db.assertColumnDefinition("ce_activity", "tmp_is_last_key", VARCHAR, 55, true);
+    db.assertColumnDefinition("ce_activity", "tmp_main_is_last", BOOLEAN, null, true);
+    db.assertColumnDefinition("ce_activity", "tmp_main_is_last_key", VARCHAR, 55, true);
+    db.assertIndex("ce_activity", "ce_activity_t_islast_key", "tmp_is_last_key");
+    db.assertIndex("ce_activity", "ce_activity_t_main_islast", "tmp_main_is_last", "status");
+    db.assertIndex("ce_activity", "ce_activity_t_main_islast_key", "tmp_main_is_last_key");
+    db.assertIndex("ce_activity", "ce_activity_t_main_islast", "tmp_main_is_last", "status");
+  }
+
+  @Test
+  public void migration_is_not_reentrant() throws SQLException {
+    underTest.execute();
+
+    expectedException.expect(IllegalStateException.class);
+
+    underTest.execute();
+  }
+
+}
index 28dc704bf77b6bf205e8f8edfb78261f69c1da1d..c0ba8282e1c681e73c9f39e732e97a58c37d0090 100644 (file)
@@ -35,6 +35,6 @@ public class DbVersion74Test {
 
   @Test
   public void verify_migration_count() {
-    verifyMigrationCount(underTest, 5);
+    verifyMigrationCount(underTest, 11);
   }
 }
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeActivityTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeActivityTest.java
new file mode 100644 (file)
index 0000000..298ae54
--- /dev/null
@@ -0,0 +1,233 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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.v74;
+
+import com.google.common.collect.ImmutableMap;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Random;
+import java.util.stream.Stream;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.db.CoreDbTester;
+
+import static java.util.Arrays.stream;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@RunWith(DataProviderRunner.class)
+public class PopulateTmpColumnsToCeActivityTest {
+  private static final Map<String, String> NO_CHARACTERISTICS = Collections.emptyMap();
+
+  @Rule
+  public CoreDbTester db = CoreDbTester.createForSchema(PopulateTmpColumnsToCeActivityTest.class, "ce_activity.sql");
+
+  private MapSettings settings = new MapSettings();
+  private PopulateTmpColumnsToCeActivity underTest = new PopulateTmpColumnsToCeActivity(db.database(), settings.asConfig());
+
+  @Test
+  public void execute_has_no_effect_on_empty_table() throws SQLException {
+    underTest.execute();
+
+    assertThat(rowsInCeActivity()).isEmpty();
+  }
+
+  @Test
+  public void execute_has_no_effect_on_empty_table_on_sonarcloud() throws SQLException {
+    settings.setProperty("sonar.sonarcloud.enabled", true);
+
+    underTest.execute();
+
+    assertThat(rowsInCeActivity()).isEmpty();
+  }
+
+  @Test
+  @UseDataProvider("characteriticsOfMainBranchesAndPr")
+  public void execute_populates_tmp_columns_with_component_uuid_if_task_has_no_row_in_PROJECTS(Map<String, String> characteristics) throws SQLException {
+    Row[] notUpdatedRows = Stream.of(
+      // not updated because no component_uuid
+      new Row(newUuid(), null, null, null),
+      new Row(newUuid(), null, randomAlphabetic(2), null),
+      new Row(newUuid(), null, randomAlphabetic(3), randomAlphabetic(4)),
+      new Row(newUuid(), null, null, randomAlphabetic(5)),
+      // not updated because both target fields are already set (re-entrance)
+      new Row(newUuid(), randomAlphabetic(14), randomAlphabetic(6), randomAlphabetic(7)))
+      .toArray(Row[]::new);
+    Row[] updatedRows = {
+      new Row(newUuid(), randomAlphabetic(12), null, null),
+      new Row(newUuid(), randomAlphabetic(13), randomAlphabetic(5), null),
+      new Row(newUuid(), randomAlphabetic(14), null, randomAlphabetic(6)),
+    };
+    stream(notUpdatedRows).forEach(row -> insertCeActivity(row, characteristics));
+    stream(updatedRows).forEach(row -> insertCeActivity(row, characteristics));
+
+    underTest.execute();
+
+    assertThat(rowsInCeActivity())
+      .hasSize(notUpdatedRows.length + updatedRows.length)
+      .contains(notUpdatedRows)
+      .contains(stream(updatedRows)
+        .map(row -> new Row(row.taskUuid, row.componentUuid, row.componentUuid, row.componentUuid))
+        .toArray(Row[]::new));
+  }
+
+  @DataProvider
+  public static Object[][] characteriticsOfMainBranchesAndPr() {
+    return new Object[][] {
+      {NO_CHARACTERISTICS},
+      {branchCharacteristics("LONG", randomAlphabetic(15))},
+      {branchCharacteristics("SHORT", randomAlphabetic(16))},
+      {branchCharacteristics(randomAlphabetic(17), randomAlphabetic(18))},
+      {prCharacteristics(randomAlphabetic(19))},
+    };
+  }
+
+  @Test
+  public void execute_populates_tmp_columns_with_component_uuid_for_existing_main_branch() throws SQLException {
+    String mainComponentUuid = randomAlphabetic(2);
+    insertProjects(mainComponentUuid, randomAlphabetic(3));
+    String taskUuid = insertCeActivity(new Row(newUuid(), mainComponentUuid, null, null), NO_CHARACTERISTICS);
+
+    underTest.execute();
+
+    assertThat(rowsInCeActivity())
+      .containsOnly(new Row(taskUuid, mainComponentUuid, mainComponentUuid, mainComponentUuid));
+  }
+
+  @Test
+  public void execute_deletes_populates_branches_of_task_without_row_in_PROJECTS_with_COMPONENT_UUID_and_those_with_row_in_PROJECTS_by_KEE() throws SQLException {
+    String mainComponentUuid = randomAlphabetic(2);
+    String mainComponentKey = randomAlphabetic(3);
+    String branchUuid = randomAlphabetic(4);
+    String branchType1 = randomAlphabetic(5);
+    String branchName1 = randomAlphabetic(6);
+    String branchType2 = randomAlphabetic(7);
+    String branchName2 = randomAlphabetic(8);
+    insertProjects(mainComponentUuid, mainComponentKey);
+    insertProjects(branchUuid, mainComponentKey + ":BRANCH:" + branchName2);
+    String orphanTaskUuid = insertCeActivity(new Row(newUuid(), mainComponentUuid, null, null), branchCharacteristics(branchType1, branchName1));
+    String regularTaskUuid = insertCeActivity(new Row(newUuid(), mainComponentUuid, null, null), branchCharacteristics(branchType2, branchName2));
+
+    underTest.execute();
+
+    assertThat(rowsInCeActivity())
+      .containsOnly(
+        new Row(orphanTaskUuid, mainComponentUuid, mainComponentUuid, mainComponentUuid),
+        new Row(regularTaskUuid, mainComponentUuid, branchUuid, mainComponentUuid));
+  }
+
+  @Test
+  public void execute_deletes_populates_prs_of_task_without_row_in_PROJECTS_with_COMPONENT_UUID_and_those_with_row_in_PROJECTS_by_KEE() throws SQLException {
+    String mainComponentUuid = randomAlphabetic(2);
+    String mainComponentKey = randomAlphabetic(3);
+    String prUuid = randomAlphabetic(4);
+    String prName1 = randomAlphabetic(6);
+    String prName2 = randomAlphabetic(8);
+    insertProjects(mainComponentUuid, mainComponentKey);
+    insertProjects(prUuid, mainComponentKey + ":PULL_REQUEST:" + prName2);
+    String orphanTaskUuid = insertCeActivity(new Row(newUuid(), mainComponentUuid, null, null), prCharacteristics(prName1));
+    String regularTaskUuid = insertCeActivity(new Row(newUuid(), mainComponentUuid, null, null), prCharacteristics(prName2));
+
+    underTest.execute();
+
+    assertThat(rowsInCeActivity())
+      .containsOnly(
+        new Row(orphanTaskUuid, mainComponentUuid, mainComponentUuid, mainComponentUuid),
+        new Row(regularTaskUuid, mainComponentUuid, prUuid, mainComponentUuid));
+  }
+
+  private Stream<Row> rowsInCeActivity() {
+    return db.select("select" +
+      " uuid as \"UUID\", component_uuid as \"COMPONENT_UUID\", tmp_component_uuid as \"TMP_COMPONENT_UUID\", tmp_main_component_UUID as \"TMP_MAIN_COMPONENT_UUID\"" +
+      " from ce_activity")
+      .stream()
+      .map(row -> new Row(
+        (String) row.get("UUID"),
+        (String) row.get("COMPONENT_UUID"),
+        (String) row.get("TMP_COMPONENT_UUID"),
+        (String) row.get("TMP_MAIN_COMPONENT_UUID")));
+  }
+
+  private String insertCeActivity(Row row, Map<String, String> characteristics) {
+    String uuid = insertCeActivity(row.taskUuid, row.componentUuid, row.tmpComponentUuid, row.tmpMainComponentUuid);
+    characteristics.forEach((key, value) -> insertCeCharacteristic(uuid, key, value));
+    return uuid;
+  }
+
+  private String insertCeActivity(String uuid, @Nullable String componentUuid, @Nullable String tmpComponentUuid, @Nullable String tmpMainComponentUuid) {
+    Random random = new Random();
+    db.executeInsert("ce_activity",
+      "UUID", uuid,
+      "TASK_TYPE", randomAlphabetic(6),
+      "COMPONENT_UUID", componentUuid,
+      "TMP_COMPONENT_UUID", tmpComponentUuid,
+      "TMP_MAIN_COMPONENT_UUID", tmpMainComponentUuid,
+      "STATUS", randomAlphabetic(7),
+      "IS_LAST", random.nextBoolean(),
+      "IS_LAST_KEY", random.nextBoolean(),
+      "EXECUTION_COUNT", random.nextInt(500),
+      "SUBMITTED_AT", (long) random.nextInt(500),
+      "CREATED_AT", (long) random.nextInt(500),
+      "UPDATED_AT", (long) random.nextInt(500));
+    return uuid;
+  }
+
+  private void insertCeCharacteristic(String taskUuid, String key, String value) {
+    db.executeInsert(
+      "ce_task_characteristics",
+      "UUID", newUuid(),
+      "TASK_UUID", taskUuid,
+      "KEE", key,
+      "TEXT_VALUE", value);
+  }
+
+  private void insertProjects(String uuid, String key) {
+    db.executeInsert(
+      "PROJECTS",
+      "UUID", uuid,
+      "KEE", key,
+      "ORGANIZATION_UUID", "org_" + uuid,
+      "ROOT_UUID", uuid + "_root",
+      "UUID_PATH", uuid + "_path",
+      "PROJECT_UUID", uuid + "_project",
+      "PRIVATE", new Random().nextBoolean());
+  }
+
+  private int uuidGenerator = new Random().nextInt(9000);
+
+  private String newUuid() {
+    return "uuid_" + uuidGenerator++;
+  }
+
+  private static Map<String, String> branchCharacteristics(String branchType, String branchName) {
+    return ImmutableMap.of("branchType", branchType, "branch", branchName);
+  }
+
+  private static Map<String, String> prCharacteristics(String prName) {
+    return ImmutableMap.of("pullRequest", prName);
+  }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeQueueTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeQueueTest.java
new file mode 100644 (file)
index 0000000..866eaa4
--- /dev/null
@@ -0,0 +1,216 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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.v74;
+
+import com.google.common.collect.ImmutableMap;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Random;
+import java.util.stream.Stream;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.sonar.db.CoreDbTester;
+
+import static java.util.Arrays.stream;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@RunWith(DataProviderRunner.class)
+public class PopulateTmpColumnsToCeQueueTest {
+  private static final Map<String, String> NO_CHARACTERISTICS = Collections.emptyMap();
+
+  @Rule
+  public CoreDbTester db = CoreDbTester.createForSchema(PopulateTmpColumnsToCeQueueTest.class, "ce_queue.sql");
+
+  private PopulateTmpColumnsToCeQueue underTest = new PopulateTmpColumnsToCeQueue(db.database());
+
+  @Test
+  public void no_action_on_empty_table() throws SQLException {
+    underTest.execute();
+
+    assertThat(db.countRowsOfTable("ce_queue")).isZero();
+  }
+
+  @Test
+  @UseDataProvider("characteriticsOfMainBranchesAndPr")
+  public void execute_populates_tmp_columns_with_component_uuid_if_task_has_no_row_in_PROJECTS(Map<String, String> characteristics) throws SQLException {
+    Row[] notUpdatedRows = Stream.of(
+      // not updated because no component_uuid
+      new Row(newUuid(), null, null, null),
+      new Row(newUuid(), null, randomAlphabetic(2), null),
+      new Row(newUuid(), null, randomAlphabetic(3), randomAlphabetic(4)),
+      new Row(newUuid(), null, null, randomAlphabetic(5)),
+      // not updated because both target fields are already set (re-entrance)
+      new Row(newUuid(), randomAlphabetic(14), randomAlphabetic(6), randomAlphabetic(7)))
+      .toArray(Row[]::new);
+    Row[] updatedRows = {
+      new Row(newUuid(), randomAlphabetic(12), null, null),
+      new Row(newUuid(), randomAlphabetic(13), randomAlphabetic(5), null),
+      new Row(newUuid(), randomAlphabetic(14), null, randomAlphabetic(6)),
+    };
+    stream(notUpdatedRows).forEach(row -> insertCeQueue(row, characteristics));
+    stream(updatedRows).forEach(row -> insertCeQueue(row, characteristics));
+
+    underTest.execute();
+
+    assertThat(rowsInCeQueue())
+      .hasSize(notUpdatedRows.length + updatedRows.length)
+      .contains(notUpdatedRows)
+      .contains(stream(updatedRows)
+        .map(row -> new Row(row.taskUuid, row.componentUuid, row.componentUuid, row.componentUuid))
+        .toArray(Row[]::new));
+  }
+
+  @DataProvider
+  public static Object[][] characteriticsOfMainBranchesAndPr() {
+    return new Object[][] {
+      {NO_CHARACTERISTICS},
+      {branchCharacteristics("LONG", randomAlphabetic(15))},
+      {branchCharacteristics("SHORT", randomAlphabetic(16))},
+      {branchCharacteristics(randomAlphabetic(17), randomAlphabetic(18))},
+      {prCharacteristics(randomAlphabetic(19))},
+    };
+  }
+
+  @Test
+  public void execute_populates_tmp_columns_with_component_uuid_for_existing_main_branch() throws SQLException {
+    String mainComponentUuid = randomAlphabetic(2);
+    insertProjects(mainComponentUuid, randomAlphabetic(3));
+    String taskUuid = insertCeQueue(new Row(newUuid(), mainComponentUuid, null, null), NO_CHARACTERISTICS);
+
+    underTest.execute();
+
+    assertThat(rowsInCeQueue())
+      .containsOnly(new Row(taskUuid, mainComponentUuid, mainComponentUuid, mainComponentUuid));
+  }
+
+  @Test
+  public void execute_deletes_tasks_of_branches_without_row_in_PROJECTS_and_populates_others_matching_row_in_PROJECTS_by_KEE() throws SQLException {
+    String mainComponentUuid = randomAlphabetic(2);
+    String mainComponentKey = randomAlphabetic(3);
+    String branchUuid = randomAlphabetic(4);
+    String branchType1 = randomAlphabetic(5);
+    String branchName1 = randomAlphabetic(6);
+    String branchType2 = randomAlphabetic(7);
+    String branchName2 = randomAlphabetic(8);
+    insertProjects(mainComponentUuid, mainComponentKey);
+    insertProjects(branchUuid, mainComponentKey + ":BRANCH:" + branchName2);
+    String deletedTaskUuid = insertCeQueue(new Row(newUuid(), mainComponentUuid, null, null), branchCharacteristics(branchType1, branchName1));
+    String updatedTaskUuid = insertCeQueue(new Row(newUuid(), mainComponentUuid, null, null), branchCharacteristics(branchType2, branchName2));
+
+    underTest.execute();
+
+    assertThat(rowsInCeQueue())
+      .containsOnly(new Row(updatedTaskUuid, mainComponentUuid, branchUuid, mainComponentUuid));
+  }
+
+  @Test
+  public void execute_deletes_tasks_of_prs_without_row_in_PROJECTS_and_populates_others_matching_row_in_PROJECTS_by_KEE() throws SQLException {
+    String mainComponentUuid = randomAlphabetic(2);
+    String mainComponentKey = randomAlphabetic(3);
+    String prUuid = randomAlphabetic(4);
+    String prName1 = randomAlphabetic(6);
+    String prName2 = randomAlphabetic(8);
+    insertProjects(mainComponentUuid, mainComponentKey);
+    insertProjects(prUuid, mainComponentKey + ":PULL_REQUEST:" + prName2);
+    String deletedTaskUuid = insertCeQueue(new Row(newUuid(), mainComponentUuid, null, null), prCharacteristics(prName1));
+    String updatedTaskUuid = insertCeQueue(new Row(newUuid(), mainComponentUuid, null, null), prCharacteristics(prName2));
+
+    underTest.execute();
+
+    assertThat(rowsInCeQueue())
+      .containsOnly(new Row(updatedTaskUuid, mainComponentUuid, prUuid, mainComponentUuid));
+  }
+
+  private Stream<Row> rowsInCeQueue() {
+    return db.select("select" +
+      " uuid as \"UUID\", component_uuid as \"COMPONENT_UUID\", tmp_component_uuid as \"TMP_COMPONENT_UUID\", tmp_main_component_UUID as \"TMP_MAIN_COMPONENT_UUID\"" +
+      " from ce_queue")
+      .stream()
+      .map(row -> new Row(
+        (String) row.get("UUID"),
+        (String) row.get("COMPONENT_UUID"),
+        (String) row.get("TMP_COMPONENT_UUID"),
+        (String) row.get("TMP_MAIN_COMPONENT_UUID")));
+  }
+
+  private String insertCeQueue(Row row, Map<String, String> characteristics) {
+    String uuid = insertCeQueue(row.taskUuid, row.componentUuid, row.tmpComponentUuid, row.tmpMainComponentUuid);
+    characteristics.forEach((key, value) -> insertCeCharacteristic(uuid, key, value));
+    return uuid;
+  }
+
+  private String insertCeQueue(String uuid, @Nullable String componentUuid, @Nullable String tmpComponentUuid, @Nullable String tmpMainComponentUuid) {
+    Random random = new Random();
+    db.executeInsert("ce_queue",
+      "UUID", uuid,
+      "TASK_TYPE", randomAlphabetic(6),
+      "COMPONENT_UUID", componentUuid,
+      "TMP_COMPONENT_UUID", tmpComponentUuid,
+      "TMP_MAIN_COMPONENT_UUID", tmpMainComponentUuid,
+      "STATUS", randomAlphabetic(7),
+      "EXECUTION_COUNT", random.nextInt(500),
+      "CREATED_AT", (long) random.nextInt(500),
+      "UPDATED_AT", (long) random.nextInt(500));
+    return uuid;
+  }
+
+  private void insertCeCharacteristic(String taskUuid, String key, String value) {
+    db.executeInsert(
+      "ce_task_characteristics",
+      "UUID", newUuid(),
+      "TASK_UUID", taskUuid,
+      "KEE", key,
+      "TEXT_VALUE", value);
+  }
+
+  private void insertProjects(String uuid, String key) {
+    db.executeInsert(
+      "PROJECTS",
+      "UUID", uuid,
+      "KEE", key,
+      "ORGANIZATION_UUID", "org_" + uuid,
+      "ROOT_UUID", uuid + "_root",
+      "UUID_PATH", uuid + "_path",
+      "PROJECT_UUID", uuid + "_project",
+      "PRIVATE", new Random().nextBoolean());
+  }
+
+  private int uuidGenerator = new Random().nextInt(9000);
+
+  private String newUuid() {
+    return "uuid_" + uuidGenerator++;
+  }
+
+  private static Map<String, String> branchCharacteristics(String branchType, String branchName) {
+    return ImmutableMap.of("branchType", branchType, "branch", branchName);
+  }
+
+  private static Map<String, String> prCharacteristics(String prName) {
+    return ImmutableMap.of("pullRequest", prName);
+  }
+
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpLastKeyColumnsToCeActivityTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/PopulateTmpLastKeyColumnsToCeActivityTest.java
new file mode 100644 (file)
index 0000000..ae4bd59
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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.v74;
+
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Random;
+import javax.annotation.Nullable;
+import org.assertj.core.api.AbstractIterableAssert;
+import org.assertj.core.api.ObjectAssert;
+import org.assertj.core.groups.Tuple;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.core.util.UuidFactoryFast;
+import org.sonar.db.CoreDbTester;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+
+public class PopulateTmpLastKeyColumnsToCeActivityTest {
+
+  @Rule
+  public CoreDbTester db = CoreDbTester.createForSchema(PopulateTmpLastKeyColumnsToCeActivityTest.class, "ce_activity.sql");
+
+  private MapSettings settings = new MapSettings();
+  private PopulateTmpLastKeyColumnsToCeActivity underTest = new PopulateTmpLastKeyColumnsToCeActivity(db.database(), settings.asConfig());
+
+  @Test
+  public void execute_has_no_effect_on_empty_table() throws SQLException {
+    underTest.execute();
+
+    assertThat(db.countRowsOfTable("ce_activity")).isZero();
+  }
+
+  @Test
+  public void execute_populate_tmp_last_key_columns_from_type_and_component_uuid_columns() throws SQLException {
+    String type = randomAlphabetic(6);
+    String oldComponentUuid = randomAlphabetic(7);
+    String tmpComponentUuid = randomAlphabetic(8);
+    String tmpMainComponentUuid = randomAlphabetic(9);
+
+    String taskWithComponentUuid = insertCeActivity(type, oldComponentUuid, tmpComponentUuid, tmpMainComponentUuid);
+    String taskWithInconsistentComponentUuid = insertCeActivity(type, null, tmpComponentUuid, tmpMainComponentUuid);
+    String taskNoComponentUuid = insertCeActivity(type, null, null, null);
+
+    underTest.execute();
+
+    assertThatTmpLastKeyAndMainLastKeyOf(taskWithComponentUuid).containsOnly(tuple(type + tmpComponentUuid, type + tmpMainComponentUuid));
+    assertThatTmpLastKeyAndMainLastKeyOf(taskWithInconsistentComponentUuid).containsOnly(tuple(type, type));
+    assertThatTmpLastKeyAndMainLastKeyOf(taskNoComponentUuid).containsOnly(tuple(type, type));
+
+    assertThatTmpIsLastAndMainIsLastOf(taskWithComponentUuid).containsOnly(tuple(false, false));
+    assertThatTmpIsLastAndMainIsLastOf(taskWithInconsistentComponentUuid).containsOnly(tuple(false, false));
+    assertThatTmpIsLastAndMainIsLastOf(taskNoComponentUuid).containsOnly(tuple(false, false));
+  }
+  @Test
+  public void execute_is_reentrant() throws SQLException {
+    execute_populate_tmp_last_key_columns_from_type_and_component_uuid_columns();
+
+    underTest.execute();
+  }
+
+  private String insertCeActivity(String type,
+    @Nullable String oldComponentUuid,
+    @Nullable String tmpComponentUuid, @Nullable String tmpMainComponentUuid) {
+    checkArgument((tmpComponentUuid == null) == (tmpMainComponentUuid == null));
+
+    String uuid = UuidFactoryFast.getInstance().create();
+
+    Random random = new Random();
+    db.executeInsert(
+      "ce_activity",
+      "UUID", uuid,
+      "TASK_TYPE", type,
+      "COMPONENT_UUID", oldComponentUuid,
+      "TMP_COMPONENT_UUID", tmpComponentUuid,
+      "TMP_MAIN_COMPONENT_UUID", tmpMainComponentUuid,
+      "STATUS", randomAlphabetic(5),
+      "IS_LAST", random.nextBoolean(),
+      "IS_LAST_KEY", randomAlphabetic(12),
+      "EXECUTION_COUNT", random.nextInt(10),
+      "SUBMITTED_AT", (long) random.nextInt(5_999),
+      "CREATED_AT", (long) random.nextInt(5_999),
+      "UPDATED_AT", (long) random.nextInt(5_999));
+
+    return uuid;
+  }
+
+  private AbstractIterableAssert<?, List<? extends Tuple>, Tuple, ObjectAssert<Tuple>> assertThatTmpLastKeyAndMainLastKeyOf(String uuid) {
+    return assertThat(db.select("select tmp_is_last_key as \"LAST_KEY\", tmp_main_is_last_key as \"MAIN_LAST_KEY\" from ce_activity where uuid='" + uuid + "'"))
+      .extracting(t -> (String) t.get("LAST_KEY"), t -> (String) t.get("MAIN_LAST_KEY"));
+  }
+
+  private AbstractIterableAssert<?, List<? extends Tuple>, Tuple, ObjectAssert<Tuple>> assertThatTmpIsLastAndMainIsLastOf(String uuid) {
+    return assertThat(db.select("select tmp_is_last as \"LAST\", tmp_main_is_last as \"MAIN_LAST\" from ce_activity where uuid='" + uuid + "'"))
+      .extracting(t -> (Boolean) t.get("LAST"), t -> (Boolean) t.get("MAIN_LAST"));
+  }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/Row.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v74/Row.java
new file mode 100644 (file)
index 0000000..5ba7719
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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.v74;
+
+import java.util.Objects;
+import javax.annotation.Nullable;
+
+final class Row {
+  final String taskUuid;
+  final String componentUuid;
+  final String tmpComponentUuid;
+  final String tmpMainComponentUuid;
+
+  Row(String taskUuid, @Nullable String componentUuid, @Nullable String tmpComponentUuid, @Nullable String tmpMainComponentUuid) {
+    this.taskUuid = taskUuid;
+    this.componentUuid = componentUuid;
+    this.tmpComponentUuid = tmpComponentUuid;
+    this.tmpMainComponentUuid = tmpMainComponentUuid;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    Row row = (Row) o;
+    return Objects.equals(taskUuid, row.taskUuid) &&
+      Objects.equals(componentUuid, row.componentUuid) &&
+      Objects.equals(tmpComponentUuid, row.tmpComponentUuid) &&
+      Objects.equals(tmpMainComponentUuid, row.tmpMainComponentUuid);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(taskUuid, componentUuid, tmpComponentUuid, tmpMainComponentUuid);
+  }
+
+  @Override
+  public String toString() {
+    return "Row{" +
+      "uuid='" + taskUuid + '\'' +
+      ", componentUuid='" + componentUuid + '\'' +
+      ", tmpComponentUuid='" + tmpComponentUuid + '\'' +
+      ", tmpMainComponentUuid='" + tmpMainComponentUuid + '\'' +
+      '}';
+  }
+}
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeActivityTest/ce_activity.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeActivityTest/ce_activity.sql
new file mode 100644 (file)
index 0000000..540c11a
--- /dev/null
@@ -0,0 +1,26 @@
+CREATE TABLE "CE_ACTIVITY" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "UUID" VARCHAR(40) NOT NULL,
+  "TASK_TYPE" VARCHAR(15) NOT NULL,
+  "COMPONENT_UUID" VARCHAR(40) NULL,
+  "ANALYSIS_UUID" VARCHAR(50) NULL,
+  "STATUS" VARCHAR(15) NOT NULL,
+  "IS_LAST" BOOLEAN NOT NULL,
+  "IS_LAST_KEY" VARCHAR(55) NOT NULL,
+  "SUBMITTER_UUID" VARCHAR(255) NULL,
+  "WORKER_UUID" VARCHAR(40) NULL,
+  "EXECUTION_COUNT" INTEGER NOT NULL,
+  "SUBMITTED_AT" BIGINT NOT NULL,
+  "STARTED_AT" BIGINT NULL,
+  "EXECUTED_AT" BIGINT NULL,
+  "CREATED_AT" BIGINT NOT NULL,
+  "UPDATED_AT" BIGINT NOT NULL,
+  "EXECUTION_TIME_MS" BIGINT NULL,
+  "ERROR_MESSAGE" VARCHAR(1000),
+  "ERROR_STACKTRACE" CLOB,
+  "ERROR_TYPE" VARCHAR(20)
+);
+CREATE UNIQUE INDEX "CE_ACTIVITY_UUID" ON "CE_ACTIVITY" ("UUID");
+CREATE INDEX "CE_ACTIVITY_COMPONENT_UUID" ON "CE_ACTIVITY" ("COMPONENT_UUID");
+CREATE INDEX "CE_ACTIVITY_ISLASTKEY" ON "CE_ACTIVITY" ("IS_LAST_KEY");
+CREATE INDEX "CE_ACTIVITY_ISLAST_STATUS" ON "CE_ACTIVITY" ("IS_LAST", "STATUS");
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeQueueTest/ce_queue.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/AddTmpColumnsToCeQueueTest/ce_queue.sql
new file mode 100644 (file)
index 0000000..b128253
--- /dev/null
@@ -0,0 +1,16 @@
+CREATE TABLE "CE_QUEUE" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "UUID" VARCHAR(40) NOT NULL,
+  "TASK_TYPE" VARCHAR(15) NOT NULL,
+  "COMPONENT_UUID" VARCHAR(40) NULL,
+  "STATUS" VARCHAR(15) NOT NULL,
+  "SUBMITTER_UUID" VARCHAR(255) NULL,
+  "WORKER_UUID" VARCHAR(40) NULL,
+  "EXECUTION_COUNT" INTEGER NOT NULL,
+  "STARTED_AT" BIGINT NULL,
+  "CREATED_AT" BIGINT NOT NULL,
+  "UPDATED_AT" BIGINT NOT NULL
+);
+CREATE UNIQUE INDEX "CE_QUEUE_UUID" ON "CE_QUEUE" ("UUID");
+CREATE INDEX "CE_QUEUE_COMPONENT_UUID" ON "CE_QUEUE" ("COMPONENT_UUID");
+CREATE INDEX "CE_QUEUE_STATUS" ON "CE_QUEUE" ("STATUS");
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/AddTmpLastKeyColumnsToCeActivityTest/ce_activity.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/AddTmpLastKeyColumnsToCeActivityTest/ce_activity.sql
new file mode 100644 (file)
index 0000000..fb0d730
--- /dev/null
@@ -0,0 +1,30 @@
+CREATE TABLE "CE_ACTIVITY" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "UUID" VARCHAR(40) NOT NULL,
+  "TASK_TYPE" VARCHAR(15) NOT NULL,
+  "COMPONENT_UUID" VARCHAR(40) NULL,
+  "TMP_COMPONENT_UUID" VARCHAR(40) NULL,
+  "TMP_MAIN_COMPONENT_UUID" VARCHAR(40) NULL,
+  "ANALYSIS_UUID" VARCHAR(50) NULL,
+  "STATUS" VARCHAR(15) NOT NULL,
+  "IS_LAST" BOOLEAN NOT NULL,
+  "IS_LAST_KEY" VARCHAR(55) NOT NULL,
+  "SUBMITTER_UUID" VARCHAR(255) NULL,
+  "WORKER_UUID" VARCHAR(40) NULL,
+  "EXECUTION_COUNT" INTEGER NOT NULL,
+  "SUBMITTED_AT" BIGINT NOT NULL,
+  "STARTED_AT" BIGINT NULL,
+  "EXECUTED_AT" BIGINT NULL,
+  "CREATED_AT" BIGINT NOT NULL,
+  "UPDATED_AT" BIGINT NOT NULL,
+  "EXECUTION_TIME_MS" BIGINT NULL,
+  "ERROR_MESSAGE" VARCHAR(1000),
+  "ERROR_STACKTRACE" CLOB,
+  "ERROR_TYPE" VARCHAR(20)
+);
+CREATE UNIQUE INDEX "CE_ACTIVITY_UUID" ON "CE_ACTIVITY" ("UUID");
+CREATE INDEX "CE_ACTIVITY_COMPONENT_UUID" ON "CE_ACTIVITY" ("COMPONENT_UUID");
+CREATE INDEX "CE_ACTIVITY_TMP_CMPT_UUID" ON "CE_ACTIVITY" ("TMP_COMPONENT_UUID");
+CREATE INDEX "CE_ACTIVITY_TMP_MAIN_CMPT_UUID" ON "CE_ACTIVITY" ("TMP_MAIN_COMPONENT_UUID");
+CREATE INDEX "CE_ACTIVITY_ISLASTKEY" ON "CE_ACTIVITY" ("IS_LAST_KEY");
+CREATE INDEX "CE_ACTIVITY_ISLAST_STATUS" ON "CE_ACTIVITY" ("IS_LAST", "STATUS");
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeActivityTest/ce_activity.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeActivityTest/ce_activity.sql
new file mode 100644 (file)
index 0000000..68242f6
--- /dev/null
@@ -0,0 +1,87 @@
+CREATE TABLE "CE_ACTIVITY" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "UUID" VARCHAR(40) NOT NULL,
+  "TASK_TYPE" VARCHAR(15) NOT NULL,
+  "COMPONENT_UUID" VARCHAR(40) NULL,
+  "TMP_COMPONENT_UUID" VARCHAR(40) NULL,
+  "TMP_MAIN_COMPONENT_UUID" VARCHAR(40) NULL,
+  "ANALYSIS_UUID" VARCHAR(50) NULL,
+  "STATUS" VARCHAR(15) NOT NULL,
+  "IS_LAST" BOOLEAN NOT NULL,
+  "IS_LAST_KEY" VARCHAR(55) NOT NULL,
+  "SUBMITTER_UUID" VARCHAR(255) NULL,
+  "WORKER_UUID" VARCHAR(40) NULL,
+  "EXECUTION_COUNT" INTEGER NOT NULL,
+  "SUBMITTED_AT" BIGINT NOT NULL,
+  "STARTED_AT" BIGINT NULL,
+  "EXECUTED_AT" BIGINT NULL,
+  "CREATED_AT" BIGINT NOT NULL,
+  "UPDATED_AT" BIGINT NOT NULL,
+  "EXECUTION_TIME_MS" BIGINT NULL,
+  "ERROR_MESSAGE" VARCHAR(1000),
+  "ERROR_STACKTRACE" CLOB,
+  "ERROR_TYPE" VARCHAR(20)
+);
+CREATE UNIQUE INDEX "CE_ACTIVITY_UUID" ON "CE_ACTIVITY" ("UUID");
+CREATE INDEX "CE_ACTIVITY_COMPONENT_UUID" ON "CE_ACTIVITY" ("COMPONENT_UUID");
+CREATE INDEX "CE_ACTIVITY_TMP_CMPT_UUID" ON "CE_ACTIVITY" ("TMP_COMPONENT_UUID");
+CREATE INDEX "CE_ACTIVITY_TMP_MAIN_CMPT_UUID" ON "CE_ACTIVITY" ("TMP_MAIN_COMPONENT_UUID");
+CREATE INDEX "CE_ACTIVITY_ISLASTKEY" ON "CE_ACTIVITY" ("IS_LAST_KEY");
+CREATE INDEX "CE_ACTIVITY_ISLAST_STATUS" ON "CE_ACTIVITY" ("IS_LAST", "STATUS");
+
+CREATE TABLE "CE_TASK_CHARACTERISTICS" (
+  "UUID" VARCHAR(40) NOT NULL,
+  "TASK_UUID" VARCHAR(40) NOT NULL,
+  "KEE" VARCHAR(50) NOT NULL,
+  "TEXT_VALUE" VARCHAR(4000),
+
+  CONSTRAINT "PK_CE_TASK_CHARACTERISTICS" PRIMARY KEY ("UUID")
+);
+CREATE INDEX "CE_TASK_CHARACTERISTICS_TASK_UUID" ON "CE_TASK_CHARACTERISTICS" ("TASK_UUID");
+
+CREATE TABLE "PROJECTS" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "ORGANIZATION_UUID" VARCHAR(40) NOT NULL,
+  "KEE" VARCHAR(400),
+  "UUID" VARCHAR(50) NOT NULL,
+  "UUID_PATH" VARCHAR(1500) NOT NULL,
+  "ROOT_UUID" VARCHAR(50) NOT NULL,
+  "PROJECT_UUID" VARCHAR(50) NOT NULL,
+  "MODULE_UUID" VARCHAR(50),
+  "MODULE_UUID_PATH" VARCHAR(1500),
+  "MAIN_BRANCH_PROJECT_UUID" VARCHAR(50),
+  "NAME" VARCHAR(2000),
+  "DESCRIPTION" VARCHAR(2000),
+  "PRIVATE" BOOLEAN NOT NULL,
+  "TAGS" VARCHAR(500),
+  "ENABLED" BOOLEAN NOT NULL DEFAULT TRUE,
+  "SCOPE" VARCHAR(3),
+  "QUALIFIER" VARCHAR(10),
+  "DEPRECATED_KEE" VARCHAR(400),
+  "PATH" VARCHAR(2000),
+  "LANGUAGE" VARCHAR(20),
+  "COPY_COMPONENT_UUID" VARCHAR(50),
+  "LONG_NAME" VARCHAR(2000),
+  "DEVELOPER_UUID" VARCHAR(50),
+  "CREATED_AT" TIMESTAMP,
+  "AUTHORIZATION_UPDATED_AT" BIGINT,
+  "B_CHANGED" BOOLEAN,
+  "B_COPY_COMPONENT_UUID" VARCHAR(50),
+  "B_DESCRIPTION" VARCHAR(2000),
+  "B_ENABLED" BOOLEAN,
+  "B_UUID_PATH" VARCHAR(1500),
+  "B_LANGUAGE" VARCHAR(20),
+  "B_LONG_NAME" VARCHAR(500),
+  "B_MODULE_UUID" VARCHAR(50),
+  "B_MODULE_UUID_PATH" VARCHAR(1500),
+  "B_NAME" VARCHAR(500),
+  "B_PATH" VARCHAR(2000),
+  "B_QUALIFIER" VARCHAR(10)
+);
+CREATE INDEX "PROJECTS_ORGANIZATION" ON "PROJECTS" ("ORGANIZATION_UUID");
+CREATE UNIQUE INDEX "PROJECTS_KEE" ON "PROJECTS" ("KEE");
+CREATE INDEX "PROJECTS_ROOT_UUID" ON "PROJECTS" ("ROOT_UUID");
+CREATE UNIQUE INDEX "PROJECTS_UUID" ON "PROJECTS" ("UUID");
+CREATE INDEX "PROJECTS_PROJECT_UUID" ON "PROJECTS" ("PROJECT_UUID");
+CREATE INDEX "PROJECTS_MODULE_UUID" ON "PROJECTS" ("MODULE_UUID");
+CREATE INDEX "PROJECTS_QUALIFIER" ON "PROJECTS" ("QUALIFIER");
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeQueueTest/ce_queue.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/PopulateTmpColumnsToCeQueueTest/ce_queue.sql
new file mode 100644 (file)
index 0000000..96cd2ea
--- /dev/null
@@ -0,0 +1,77 @@
+CREATE TABLE "CE_QUEUE" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "UUID" VARCHAR(40) NOT NULL,
+  "TASK_TYPE" VARCHAR(15) NOT NULL,
+  "COMPONENT_UUID" VARCHAR(40) NULL,
+  "TMP_COMPONENT_UUID" VARCHAR(40) NULL,
+  "TMP_MAIN_COMPONENT_UUID" VARCHAR(40) NULL,
+  "STATUS" VARCHAR(15) NOT NULL,
+  "SUBMITTER_UUID" VARCHAR(255) NULL,
+  "WORKER_UUID" VARCHAR(40) NULL,
+  "EXECUTION_COUNT" INTEGER NOT NULL,
+  "STARTED_AT" BIGINT NULL,
+  "CREATED_AT" BIGINT NOT NULL,
+  "UPDATED_AT" BIGINT NOT NULL
+);
+CREATE UNIQUE INDEX "CE_QUEUE_UUID" ON "CE_QUEUE" ("UUID");
+CREATE INDEX "CE_QUEUE_COMPONENT_UUID" ON "CE_QUEUE" ("COMPONENT_UUID");
+CREATE INDEX "CE_QUEUE_TMP_COMPONENT_UUID" ON "CE_QUEUE" ("TMP_COMPONENT_UUID");
+CREATE INDEX "CE_QUEUE_TMP_MAIN_CMPT_UUID" ON "CE_QUEUE" ("TMP_MAIN_COMPONENT_UUID");
+CREATE INDEX "CE_QUEUE_STATUS" ON "CE_QUEUE" ("STATUS");
+
+CREATE TABLE "CE_TASK_CHARACTERISTICS" (
+  "UUID" VARCHAR(40) NOT NULL,
+  "TASK_UUID" VARCHAR(40) NOT NULL,
+  "KEE" VARCHAR(50) NOT NULL,
+  "TEXT_VALUE" VARCHAR(4000),
+
+  CONSTRAINT "PK_CE_TASK_CHARACTERISTICS" PRIMARY KEY ("UUID")
+);
+CREATE INDEX "CE_TASK_CHARACTERISTICS_TASK_UUID" ON "CE_TASK_CHARACTERISTICS" ("TASK_UUID");
+
+CREATE TABLE "PROJECTS" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "ORGANIZATION_UUID" VARCHAR(40) NOT NULL,
+  "KEE" VARCHAR(400),
+  "UUID" VARCHAR(50) NOT NULL,
+  "UUID_PATH" VARCHAR(1500) NOT NULL,
+  "ROOT_UUID" VARCHAR(50) NOT NULL,
+  "PROJECT_UUID" VARCHAR(50) NOT NULL,
+  "MODULE_UUID" VARCHAR(50),
+  "MODULE_UUID_PATH" VARCHAR(1500),
+  "MAIN_BRANCH_PROJECT_UUID" VARCHAR(50),
+  "NAME" VARCHAR(2000),
+  "DESCRIPTION" VARCHAR(2000),
+  "PRIVATE" BOOLEAN NOT NULL,
+  "TAGS" VARCHAR(500),
+  "ENABLED" BOOLEAN NOT NULL DEFAULT TRUE,
+  "SCOPE" VARCHAR(3),
+  "QUALIFIER" VARCHAR(10),
+  "DEPRECATED_KEE" VARCHAR(400),
+  "PATH" VARCHAR(2000),
+  "LANGUAGE" VARCHAR(20),
+  "COPY_COMPONENT_UUID" VARCHAR(50),
+  "LONG_NAME" VARCHAR(2000),
+  "DEVELOPER_UUID" VARCHAR(50),
+  "CREATED_AT" TIMESTAMP,
+  "AUTHORIZATION_UPDATED_AT" BIGINT,
+  "B_CHANGED" BOOLEAN,
+  "B_COPY_COMPONENT_UUID" VARCHAR(50),
+  "B_DESCRIPTION" VARCHAR(2000),
+  "B_ENABLED" BOOLEAN,
+  "B_UUID_PATH" VARCHAR(1500),
+  "B_LANGUAGE" VARCHAR(20),
+  "B_LONG_NAME" VARCHAR(500),
+  "B_MODULE_UUID" VARCHAR(50),
+  "B_MODULE_UUID_PATH" VARCHAR(1500),
+  "B_NAME" VARCHAR(500),
+  "B_PATH" VARCHAR(2000),
+  "B_QUALIFIER" VARCHAR(10)
+);
+CREATE INDEX "PROJECTS_ORGANIZATION" ON "PROJECTS" ("ORGANIZATION_UUID");
+CREATE UNIQUE INDEX "PROJECTS_KEE" ON "PROJECTS" ("KEE");
+CREATE INDEX "PROJECTS_ROOT_UUID" ON "PROJECTS" ("ROOT_UUID");
+CREATE UNIQUE INDEX "PROJECTS_UUID" ON "PROJECTS" ("UUID");
+CREATE INDEX "PROJECTS_PROJECT_UUID" ON "PROJECTS" ("PROJECT_UUID");
+CREATE INDEX "PROJECTS_MODULE_UUID" ON "PROJECTS" ("MODULE_UUID");
+CREATE INDEX "PROJECTS_QUALIFIER" ON "PROJECTS" ("QUALIFIER");
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/PopulateTmpLastKeyColumnsToCeActivityTest/ce_activity.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v74/PopulateTmpLastKeyColumnsToCeActivityTest/ce_activity.sql
new file mode 100644 (file)
index 0000000..b4e110b
--- /dev/null
@@ -0,0 +1,38 @@
+CREATE TABLE "CE_ACTIVITY" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "UUID" VARCHAR(40) NOT NULL,
+  "TASK_TYPE" VARCHAR(15) NOT NULL,
+  "COMPONENT_UUID" VARCHAR(40) NULL,
+  "TMP_COMPONENT_UUID" VARCHAR(40) NULL,
+  "TMP_MAIN_COMPONENT_UUID" VARCHAR(40) NULL,
+  "ANALYSIS_UUID" VARCHAR(50) NULL,
+  "STATUS" VARCHAR(15) NOT NULL,
+  "IS_LAST" BOOLEAN NOT NULL,
+  "IS_LAST_KEY" VARCHAR(55) NOT NULL,
+  "TMP_IS_LAST" BOOLEAN,
+  "TMP_IS_LAST_KEY" VARCHAR(55),
+  "TMP_MAIN_IS_LAST" BOOLEAN,
+  "TMP_MAIN_IS_LAST_KEY" VARCHAR(55),
+  "SUBMITTER_UUID" VARCHAR(255) NULL,
+  "WORKER_UUID" VARCHAR(40) NULL,
+  "EXECUTION_COUNT" INTEGER NOT NULL,
+  "SUBMITTED_AT" BIGINT NOT NULL,
+  "STARTED_AT" BIGINT NULL,
+  "EXECUTED_AT" BIGINT NULL,
+  "CREATED_AT" BIGINT NOT NULL,
+  "UPDATED_AT" BIGINT NOT NULL,
+  "EXECUTION_TIME_MS" BIGINT NULL,
+  "ERROR_MESSAGE" VARCHAR(1000),
+  "ERROR_STACKTRACE" CLOB,
+  "ERROR_TYPE" VARCHAR(20)
+);
+CREATE UNIQUE INDEX "CE_ACTIVITY_UUID" ON "CE_ACTIVITY" ("UUID");
+CREATE INDEX "CE_ACTIVITY_COMPONENT_UUID" ON "CE_ACTIVITY" ("COMPONENT_UUID");
+CREATE INDEX "CE_ACTIVITY_TMP_CPNT_UUID" ON "CE_ACTIVITY" ("TMP_COMPONENT_UUID");
+CREATE INDEX "CE_ACTIVITY_TMP_MAIN_CPNT_UUID" ON "CE_ACTIVITY" ("TMP_MAIN_COMPONENT_UUID");
+CREATE INDEX "CE_ACTIVITY_ISLASTKEY" ON "CE_ACTIVITY" ("IS_LAST_KEY");
+CREATE INDEX "CE_ACTIVITY_ISLAST_STATUS" ON "CE_ACTIVITY" ("IS_LAST", "STATUS");
+CREATE INDEX "CE_ACTIVITY_T_ISLAST_KEY" ON "CE_ACTIVITY" ("TMP_IS_LAST_KEY");
+CREATE INDEX "CE_ACTIVITY_T_ISLAST" ON "CE_ACTIVITY" ("TMP_IS_LAST", "STATUS");
+CREATE INDEX "CE_ACTIVITY_T_MAIN_ISLAST_KEY" ON "CE_ACTIVITY" ("TMP_MAIN_IS_LAST_KEY");
+CREATE INDEX "CE_ACTIVITY_T_MAIN_ISLAST" ON "CE_ACTIVITY" ("TMP_MAIN_IS_LAST", "STATUS");
index 66ee97b146583231fbd591b09d94bebad9de3c40..f5b0e3b5791b6bab483d5c0a0e9af822007d1d37 100644 (file)
@@ -24,6 +24,7 @@ import org.sonar.ce.task.log.CeTaskLogging;
 import org.sonar.ce.task.projectanalysis.taskprocessor.ReportTaskProcessor;
 import org.sonar.core.platform.Module;
 import org.sonar.server.ce.http.CeHttpClientImpl;
+import org.sonar.server.ce.queue.BranchSupport;
 import org.sonar.server.ce.queue.ReportSubmitter;
 
 public class CeModule extends Module {
@@ -34,6 +35,7 @@ public class CeModule extends Module {
 
       // Queue
       CeQueueImpl.class,
+      BranchSupport.class,
       ReportSubmitter.class,
 
       // Core tasks processors
diff --git a/server/sonar-server/src/main/java/org/sonar/server/ce/queue/BranchSupport.java b/server/sonar-server/src/main/java/org/sonar/server/ce/queue/BranchSupport.java
new file mode 100644 (file)
index 0000000..647f3e4
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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.ce.queue;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.api.server.ServerSide;
+import org.sonar.core.component.ComponentKeys;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.organization.OrganizationDto;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Branch code for {@link ReportSubmitter}.
+ * <p>
+ * Does not support branches (except deprecated branch feature provided by "sonar.branch") unless an implementation of
+ * {@link BranchSupportDelegate} is available.
+ */
+@ServerSide
+public class BranchSupport {
+  @CheckForNull
+  private final BranchSupportDelegate delegate;
+
+  /**
+   * Constructor called by Pico when no implementation of {@link BranchSupportDelegate} is available.
+   */
+  public BranchSupport() {
+    this(null);
+  }
+
+  public BranchSupport(@Nullable BranchSupportDelegate delegate) {
+    this.delegate = delegate;
+  }
+
+  ComponentKey createComponentKey(String projectKey, @Nullable String deprecatedBranch, Map<String, String> characteristics) {
+    if (characteristics.isEmpty()) {
+      return new ComponentKeyImpl(projectKey, deprecatedBranch, ComponentKeys.createKey(projectKey, deprecatedBranch));
+    } else {
+      checkState(delegate != null, "Current edition does not support branch feature");
+    }
+
+    checkArgument(deprecatedBranch == null, "Deprecated branch feature can't be used at the same time as new branch support");
+    return delegate.createComponentKey(projectKey, characteristics);
+  }
+
+  ComponentDto createBranchComponent(DbSession dbSession, ComponentKey componentKey, OrganizationDto organization, ComponentDto mainComponentDto) {
+    checkState(delegate != null, "Current edition does not support branch feature");
+
+    return delegate.createBranchComponent(dbSession, componentKey, organization, mainComponentDto);
+  }
+
+  public interface ComponentKey {
+    String getKey();
+
+    String getDbKey();
+
+    Optional<String> getDeprecatedBranchName();
+
+    Optional<String> getBranchName();
+
+    Optional<String> getPullRequestKey();
+
+    boolean isMainBranch();
+
+    boolean isDeprecatedBranch();
+
+    /**
+     * @return the {@link ComponentKey} of the main branch for this component.
+     *         If this component is the main branch (ie. {@link #isMainBranch()} returns true), this method returns
+     *         {@code this}.
+     */
+    ComponentKey getMainBranchComponentKey();
+  }
+
+  private static final class ComponentKeyImpl implements ComponentKey {
+    private final String key;
+    private final String dbKey;
+    @CheckForNull
+    private final String deprecatedBranchName;
+
+    public ComponentKeyImpl(String key, @Nullable String deprecatedBranchName, String dbKey) {
+      this.key = key;
+      this.deprecatedBranchName = deprecatedBranchName;
+      this.dbKey = dbKey;
+    }
+
+    @Override
+    public String getKey() {
+      return key;
+    }
+
+    @Override
+    public String getDbKey() {
+      return dbKey;
+    }
+
+    @Override
+    public Optional<String> getDeprecatedBranchName() {
+      return Optional.ofNullable(deprecatedBranchName);
+    }
+
+    @Override
+    public Optional<String> getBranchName() {
+      return Optional.empty();
+    }
+
+    @Override
+    public Optional<String> getPullRequestKey() {
+      return Optional.empty();
+    }
+
+    @Override
+    public boolean isMainBranch() {
+      return key.equals(dbKey);
+    }
+
+    @Override
+    public boolean isDeprecatedBranch() {
+      return deprecatedBranchName != null;
+    }
+
+    @Override
+    public ComponentKey getMainBranchComponentKey() {
+      return this;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      ComponentKeyImpl that = (ComponentKeyImpl) o;
+      return Objects.equals(key, that.key) &&
+        Objects.equals(dbKey, that.dbKey) &&
+        Objects.equals(deprecatedBranchName, that.deprecatedBranchName);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(key, dbKey, deprecatedBranchName);
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/ce/queue/BranchSupportDelegate.java b/server/sonar-server/src/main/java/org/sonar/server/ce/queue/BranchSupportDelegate.java
new file mode 100644 (file)
index 0000000..e2a68de
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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.ce.queue;
+
+import java.util.Map;
+import org.sonar.api.server.ServerSide;
+import org.sonar.db.DbSession;
+import org.sonar.db.ce.CeTaskCharacteristicDto;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.server.ce.queue.BranchSupport.ComponentKey;
+
+@ServerSide
+public interface BranchSupportDelegate {
+  /**
+   * Creates a {@link ComponentKey} for the specified projectKey and the specified {@code characteristics}.
+   *
+   * @throws IllegalArgumentException if {@code characteristics} is empty
+   * @throws IllegalArgumentException if does not contain a supported value for {@link CeTaskCharacteristicDto#BRANCH_TYPE_KEY BRANCH_TYPE_KEY}
+   * @throws IllegalArgumentException if does not contain a value for expected
+   *         {@link CeTaskCharacteristicDto#BRANCH_KEY BRANCH_KEY} or {@link CeTaskCharacteristicDto#PULL_REQUEST PULL_REQUEST}
+   *         given the value of {@link CeTaskCharacteristicDto#BRANCH_TYPE_KEY BRANCH_TYPE_KEY}
+   * @throws IllegalArgumentException if incorrectly contains a value in
+   *         {@link CeTaskCharacteristicDto#BRANCH_KEY BRANCH_KEY} or {@link CeTaskCharacteristicDto#PULL_REQUEST PULL_REQUEST}
+   *         given the value of {@link CeTaskCharacteristicDto#BRANCH_TYPE_KEY BRANCH_TYPE_KEY}
+   */
+  ComponentKey createComponentKey(String projectKey, Map<String, String> characteristics);
+
+  /**
+   * Creates the ComponentDto for the branch described in {@code componentKey} which belongs to the specified
+   * {@code mainComponentDto} in the specified {@code organization}.
+   *
+   * @throws IllegalArgumentException if arguments are inconsistent (such as {@code mainComponentDto} not having the same
+   *         key as {@code componentKey.getKey()}, ...)
+   */
+  ComponentDto createBranchComponent(DbSession dbSession, ComponentKey componentKey,
+    OrganizationDto organization, ComponentDto mainComponentDto);
+}
index 694149444864cbed7dda9872d0a249d5d3c1db76..0c3716e860a5b94726d4b049c7066e1a9fa27fc1 100644 (file)
@@ -31,7 +31,6 @@ import org.sonar.api.server.ServerSide;
 import org.sonar.ce.queue.CeQueue;
 import org.sonar.ce.queue.CeTaskSubmit;
 import org.sonar.ce.task.CeTask;
-import org.sonar.core.component.ComponentKeys;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.ce.CeTaskTypes;
@@ -60,31 +59,33 @@ public class ReportSubmitter {
   private final ComponentUpdater componentUpdater;
   private final PermissionTemplateService permissionTemplateService;
   private final DbClient dbClient;
+  private final BranchSupport branchSupport;
 
   public ReportSubmitter(CeQueue queue, UserSession userSession, ComponentUpdater componentUpdater,
-    PermissionTemplateService permissionTemplateService, DbClient dbClient) {
+    PermissionTemplateService permissionTemplateService, DbClient dbClient, BranchSupport branchSupport) {
     this.queue = queue;
     this.userSession = userSession;
     this.componentUpdater = componentUpdater;
     this.permissionTemplateService = permissionTemplateService;
     this.dbClient = dbClient;
+    this.branchSupport = branchSupport;
   }
 
   /**
    * @throws NotFoundException if the organization with the specified key does not exist
    * @throws IllegalArgumentException if the organization with the specified key is not the organization of the specified project (when it already exists in DB)
    */
-  public CeTask submit(String organizationKey, String projectKey, @Nullable String deprecatedBranch, @Nullable String projectName, Map<String, String> characteristics,
-    InputStream reportInput) {
+  public CeTask submit(String organizationKey, String projectKey, @Nullable String deprecatedBranch, @Nullable String projectName,
+    Map<String, String> characteristics, InputStream reportInput) {
     try (DbSession dbSession = dbClient.openSession(false)) {
       OrganizationDto organizationDto = getOrganizationDtoOrFail(dbSession, organizationKey);
-      String effectiveProjectKey = ComponentKeys.createKey(projectKey, deprecatedBranch);
-      Optional<ComponentDto> component = dbClient.componentDao().selectByKey(dbSession, effectiveProjectKey);
-      validateProject(dbSession, component, projectKey);
-      ensureOrganizationIsConsistent(component, organizationDto);
-      ComponentDto project = component.orElseGet(() -> createProject(dbSession, organizationDto, projectKey, deprecatedBranch, projectName));
-      checkScanPermission(project);
-      return submitReport(dbSession, reportInput, project, characteristics);
+      BranchSupport.ComponentKey componentKey = branchSupport.createComponentKey(projectKey, deprecatedBranch, characteristics);
+      Optional<ComponentDto> existingComponent = dbClient.componentDao().selectByKey(dbSession, componentKey.getDbKey());
+      validateProject(dbSession, existingComponent, projectKey);
+      ensureOrganizationIsConsistent(existingComponent, organizationDto);
+      ComponentDto component = existingComponent.orElseGet(() -> createComponent(dbSession, organizationDto, componentKey, projectName));
+      checkScanPermission(component);
+      return submitReport(dbSession, reportInput, component, characteristics);
     }
   }
 
@@ -135,13 +136,32 @@ public class ReportSubmitter {
     }
   }
 
-  private ComponentDto createProject(DbSession dbSession, OrganizationDto organization, String projectKey, @Nullable String deprecatedBranch, @Nullable String projectName) {
+  private ComponentDto createComponent(DbSession dbSession, OrganizationDto organization, BranchSupport.ComponentKey componentKey, @Nullable String projectName) {
+    if (componentKey.isMainBranch() || componentKey.isDeprecatedBranch()) {
+      ComponentDto project = createProject(dbSession, organization, componentKey, projectName);
+      componentUpdater.commitAndIndex(dbSession, project);
+      return project;
+    }
+
+    Optional<ComponentDto> existingMainComponent = dbClient.componentDao().selectByKey(dbSession, componentKey.getKey());
+    ComponentDto mainComponentDto = existingMainComponent
+      .orElseGet(() -> createProject(dbSession, organization, componentKey.getMainBranchComponentKey(), projectName));
+    ComponentDto branchComponent = branchSupport.createBranchComponent(dbSession, componentKey, organization, mainComponentDto);
+    if (existingMainComponent.isPresent()) {
+      dbSession.commit();
+    } else {
+      componentUpdater.commitAndIndex(dbSession, mainComponentDto);
+    }
+    return branchComponent;
+  }
+
+  private ComponentDto createProject(DbSession dbSession, OrganizationDto organization, BranchSupport.ComponentKey componentKey,
+    @Nullable String projectName) {
     userSession.checkPermission(OrganizationPermission.PROVISION_PROJECTS, organization);
     Integer userId = userSession.getUserId();
 
-    String effectiveProjectKey = ComponentKeys.createEffectiveKey(projectKey, deprecatedBranch);
     boolean wouldCurrentUserHaveScanPermission = permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(
-      dbSession, organization.getUuid(), userId, effectiveProjectKey, Qualifiers.PROJECT);
+      dbSession, organization.getUuid(), userId, componentKey.getDbKey(), Qualifiers.PROJECT);
     if (!wouldCurrentUserHaveScanPermission) {
       throw insufficientPrivilegesException();
     }
@@ -150,13 +170,13 @@ public class ReportSubmitter {
 
     NewComponent newProject = newComponentBuilder()
       .setOrganizationUuid(organization.getUuid())
-      .setKey(projectKey)
-      .setName(defaultIfBlank(projectName, projectKey))
-      .setDeprecatedBranch(deprecatedBranch)
+      .setKey(componentKey.getKey())
+      .setName(defaultIfBlank(projectName, componentKey.getKey()))
+      .setDeprecatedBranch(componentKey.getDeprecatedBranchName().orElse(null))
       .setQualifier(Qualifiers.PROJECT)
       .setPrivate(newProjectPrivate)
       .build();
-    return componentUpdater.create(dbSession, newProject, userId);
+    return componentUpdater.createWithoutCommit(dbSession, newProject, userId);
   }
 
   private CeTask submitReport(DbSession dbSession, InputStream reportInput, ComponentDto project, Map<String, String> characteristics) {
@@ -167,9 +187,10 @@ public class ReportSubmitter {
     dbSession.commit();
 
     submit.setType(CeTaskTypes.REPORT);
-    submit.setComponentUuid(project.uuid());
+    submit.setComponent(CeTaskSubmit.Component.fromDto(project));
     submit.setSubmitterUuid(userSession.getUuid());
     submit.setCharacteristics(characteristics);
     return queue.submit(submit.build());
   }
+
 }
index 35ecc68b82516dd2e5e0ce27828ce46c7277a8e0..c80223efdf70758630c3485424c9e04ccc441e64 100644 (file)
@@ -237,9 +237,9 @@ public class ActivityAction implements CeWsAction {
 
     String componentQuery = request.getQ();
     if (component != null) {
-      query.setComponentUuid(component.uuid());
+      query.setMainComponentUuid(component.uuid());
     } else if (componentQuery != null) {
-      query.setComponentUuids(loadComponents(dbSession, componentQuery).stream()
+      query.setMainComponentUuids(loadComponents(dbSession, componentQuery).stream()
         .map(ComponentDto::uuid)
         .collect(toList()));
     }
index 647ba71f29db11c40cacd4c7cf2c05e5e64957a8..3cf6000d8778ec34240bc1fe4a256c7af6f9f8d0 100644 (file)
@@ -83,9 +83,9 @@ public class ActivityStatusAction implements CeWsAction {
       Optional<ComponentDto> component = searchComponent(dbSession, request);
       String componentUuid = component.isPresent() ? component.get().uuid() : null;
       checkPermissions(component);
-      int pendingCount = dbClient.ceQueueDao().countByStatusAndComponentUuid(dbSession, CeQueueDto.Status.PENDING, componentUuid);
-      int inProgressCount = dbClient.ceQueueDao().countByStatusAndComponentUuid(dbSession, CeQueueDto.Status.IN_PROGRESS, componentUuid);
-      int failingCount = dbClient.ceActivityDao().countLastByStatusAndComponentUuid(dbSession, CeActivityDto.Status.FAILED, componentUuid);
+      int pendingCount = dbClient.ceQueueDao().countByStatusAndMainComponentUuid(dbSession, CeQueueDto.Status.PENDING, componentUuid);
+      int inProgressCount = dbClient.ceQueueDao().countByStatusAndMainComponentUuid(dbSession, CeQueueDto.Status.IN_PROGRESS, componentUuid);
+      int failingCount = dbClient.ceActivityDao().countLastByStatusAndMainComponentUuid(dbSession, CeActivityDto.Status.FAILED, componentUuid);
 
       return ActivityStatusWsResponse.newBuilder()
         .setPending(pendingCount)
index 5d39614fc9faf2bfd8727ce98e87952e1ed4be22..8374b8ff42d0ca93c7493f62b41f12aee13da6a6 100644 (file)
@@ -88,9 +88,9 @@ public class ComponentAction implements CeWsAction {
     try (DbSession dbSession = dbClient.openSession(false)) {
       ComponentDto component = loadComponent(dbSession, wsRequest);
       userSession.checkComponentPermission(UserRole.USER, component);
-      List<CeQueueDto> queueDtos = dbClient.ceQueueDao().selectByComponentUuid(dbSession, component.uuid());
+      List<CeQueueDto> queueDtos = dbClient.ceQueueDao().selectByMainComponentUuid(dbSession, component.uuid());
       CeTaskQuery activityQuery = new CeTaskQuery()
-        .setComponentUuid(component.uuid())
+        .setMainComponentUuid(component.uuid())
         .setOnlyCurrents(true);
       List<CeActivityDto> activityDtos = dbClient.ceActivityDao().selectByQuery(dbSession, activityQuery, forPage(1).andSize(1));
 
index e37e0aec33a4ecb623f7385c42921ffea5b5997c..e606c1c8a4c764e6ba77359a42f2732810ac79bf 100644 (file)
@@ -121,7 +121,7 @@ public class SubmitAction implements CeWsAction {
       CeTask task = reportSubmitter.submit(organizationKey, projectKey, deprecatedBranch, projectName, characteristics, report);
       Ce.SubmitResponse submitResponse = Ce.SubmitResponse.newBuilder()
         .setTaskId(task.getUuid())
-        .setProjectId(task.getComponentUuid())
+        .setProjectId(task.getComponent().get().getUuid())
         .build();
       WsUtils.writeProtobuf(submitResponse, wsRequest, wsResponse);
     }
index 9d6eee10437578c6f96d01d44be2bf377150d923..5b127d927491d5710f007d53b9b5ced566235eab 100644 (file)
@@ -128,6 +128,7 @@ import org.sonar.server.platform.ws.DbMigrationStatusAction;
 import org.sonar.server.platform.ws.HealthActionModule;
 import org.sonar.server.platform.ws.L10nWs;
 import org.sonar.server.platform.ws.LogsAction;
+import org.sonar.server.platform.ws.MigrateDataAction;
 import org.sonar.server.platform.ws.MigrateDbAction;
 import org.sonar.server.platform.ws.PingAction;
 import org.sonar.server.platform.ws.RestartAction;
@@ -512,6 +513,7 @@ public class PlatformLevel4 extends PlatformLevel {
       UpgradesAction.class,
       StatusAction.class,
       MigrateDbAction.class,
+      MigrateDataAction.class,
       LogsAction.class,
       ChangeLogLevelAction.class,
       DbMigrationStatusAction.class,
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/MigrateDataAction.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/MigrateDataAction.java
new file mode 100644 (file)
index 0000000..0c4dde8
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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.ws;
+
+import org.sonar.api.config.Configuration;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.db.DbClient;
+import org.sonar.server.platform.db.migration.version.v74.PopulateTmpColumnsToCeActivity;
+import org.sonar.server.platform.db.migration.version.v74.PopulateTmpColumnsToCeQueue;
+import org.sonar.server.platform.db.migration.version.v74.PopulateTmpLastKeyColumnsToCeActivity;
+import org.sonar.server.user.UserSession;
+
+public class MigrateDataAction implements SystemWsAction {
+  private static final Logger LOG = Loggers.get(MigrateDataAction.class);
+
+  private final UserSession userSession;
+  private final DbClient dbClient;
+
+  public MigrateDataAction(UserSession userSession, DbClient dbClient) {
+    this.userSession = userSession;
+    this.dbClient = dbClient;
+  }
+
+  @Override
+  public void define(WebService.NewController controller) {
+    controller.createAction("migrate_data")
+      .setPost(true)
+      .setHandler(this);
+  }
+
+  @Override
+  public void handle(Request request, Response response) throws Exception {
+    userSession.isSystemAdministrator();
+
+    Configuration emptyConfiguration = new MapSettings().asConfig();
+    new PopulateTmpColumnsToCeQueue(dbClient.getDatabase()).execute();
+    new PopulateTmpColumnsToCeActivity(dbClient.getDatabase(), emptyConfiguration).execute();
+    new PopulateTmpLastKeyColumnsToCeActivity(dbClient.getDatabase(), emptyConfiguration).execute();
+    LOG.info("done");
+
+    response.noContent();
+  }
+}
index bcbca4c5751eb3c42326296d56d1b8ea2ac3af4b..8474aef2e05b3a1602fae2321aa5ebe924a342e9 100644 (file)
@@ -126,7 +126,8 @@ public class UpdateVisibilityAction implements ProjectsWsAction {
   }
 
   private boolean noPendingTask(DbSession dbSession, ComponentDto rootComponent) {
-    return dbClient.ceQueueDao().selectByComponentUuid(dbSession, rootComponent.uuid()).isEmpty();
+    // FIXME this is probably broken in case a branch is passed to the WS
+    return dbClient.ceQueueDao().selectByMainComponentUuid(dbSession, rootComponent.uuid()).isEmpty();
   }
 
   private void updatePermissionsToPrivate(DbSession dbSession, ComponentDto component) {
index 2a92147ee5929b77aac30dff21291b4f72db4ae8..96bd9974b2ff3c031bb0335c2de7f34524bd6890 100644 (file)
@@ -39,7 +39,6 @@ import static org.apache.commons.lang.StringUtils.defaultString;
 
 public abstract class AbstractUserSession implements UserSession {
   private static final String INSUFFICIENT_PRIVILEGES_MESSAGE = "Insufficient privileges";
-  private static final ForbiddenException INSUFFICIENT_PRIVILEGES_EXCEPTION = new ForbiddenException(INSUFFICIENT_PRIVILEGES_MESSAGE);
   private static final String AUTHENTICATION_IS_REQUIRED_MESSAGE = "Authentication is required";
 
   protected static Identity computeIdentity(UserDto userDto) {
@@ -191,7 +190,7 @@ public abstract class AbstractUserSession implements UserSession {
   }
 
   public static ForbiddenException insufficientPrivilegesException() {
-    return INSUFFICIENT_PRIVILEGES_EXCEPTION;
+    return new ForbiddenException(INSUFFICIENT_PRIVILEGES_MESSAGE);
   }
 
   @Override
diff --git a/server/sonar-server/src/test/java/org/sonar/server/ce/queue/BranchReportSubmitterTest.java b/server/sonar-server/src/test/java/org/sonar/server/ce/queue/BranchReportSubmitterTest.java
new file mode 100644 (file)
index 0000000..07f1d61
--- /dev/null
@@ -0,0 +1,301 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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.ce.queue;
+
+import com.google.common.collect.ImmutableMap;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.Random;
+import java.util.function.BiConsumer;
+import java.util.stream.IntStream;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.RandomStringUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.queue.CeQueue;
+import org.sonar.ce.queue.CeQueueImpl;
+import org.sonar.ce.queue.CeTaskSubmit;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.ce.CeTaskTypes;
+import org.sonar.db.component.BranchDto;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.component.ComponentUpdater;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.favorite.FavoriteUpdater;
+import org.sonar.server.permission.PermissionTemplateService;
+import org.sonar.server.tester.UserSessionRule;
+
+import static java.util.Collections.emptyMap;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.core.permission.GlobalPermissions.PROVISIONING;
+import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION;
+import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
+import static org.sonar.db.component.ComponentTesting.newBranchDto;
+import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
+import static org.sonar.db.permission.OrganizationPermission.PROVISION_PROJECTS;
+import static org.sonar.db.permission.OrganizationPermission.SCAN;
+
+/**
+ * Tests of {@link ReportSubmitter} when branch support is installed.
+ */
+@RunWith(DataProviderRunner.class)
+public class BranchReportSubmitterTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+  @Rule
+  public UserSessionRule userSession = UserSessionRule.standalone();
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+
+  private CeQueue queue = mock(CeQueueImpl.class);
+  private ComponentUpdater componentUpdater = mock(ComponentUpdater.class);
+  private PermissionTemplateService permissionTemplateService = mock(PermissionTemplateService.class);
+  private FavoriteUpdater favoriteUpdater = mock(FavoriteUpdater.class);
+  private BranchSupportDelegate branchSupportDelegate = mock(BranchSupportDelegate.class);
+  private BranchSupport branchSupport = spy(new BranchSupport(branchSupportDelegate));
+
+  private ReportSubmitter underTest = new ReportSubmitter(queue, userSession, componentUpdater, permissionTemplateService, db.getDbClient(), branchSupport);
+
+  @Test
+  public void submit_does_not_use_delegate_if_characteristics_are_empty() {
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto project = db.components().insertPrivateProject(organization);
+    UserDto user = db.users().insertUser();
+    userSession.logIn(user).addProjectPermission(SCAN_EXECUTION, project);
+    mockSuccessfulPrepareSubmitCall();
+    InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);
+
+    underTest.submit(organization.getKey(), project.getDbKey(), null, project.name(), emptyMap(), reportInput);
+
+    verifyZeroInteractions(branchSupportDelegate);
+  }
+
+  @Test
+  public void submit_a_report_on_existing_branch() {
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto project = db.components().insertPrivateProject(organization);
+    ComponentDto branch = db.components().insertProjectBranch(project);
+    UserDto user = db.users().insertUser();
+    userSession.logIn(user).addProjectPermission(SCAN_EXECUTION, project);
+    Map<String, String> randomCharacteristics = randomNonEmptyMap();
+    BranchSupport.ComponentKey componentKey = createComponentKeyOfBranch(branch);
+    when(branchSupportDelegate.createComponentKey(project.getDbKey(), randomCharacteristics))
+      .thenReturn(componentKey);
+    InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);
+    String taskUuid = mockSuccessfulPrepareSubmitCall();
+
+    underTest.submit(organization.getKey(), project.getDbKey(), null, project.name(), randomCharacteristics, reportInput);
+
+    verifyZeroInteractions(permissionTemplateService);
+    verifyZeroInteractions(favoriteUpdater);
+    verify(branchSupport, times(0)).createBranchComponent(any(), any(), any(), any());
+    verify(branchSupportDelegate).createComponentKey(project.getDbKey(), randomCharacteristics);
+    verify(branchSupportDelegate, times(0)).createBranchComponent(any(), any(), any(), any());
+    verifyNoMoreInteractions(branchSupportDelegate);
+    verifyQueueSubmit(project, branch, user, randomCharacteristics, taskUuid);
+  }
+
+  @Test
+  public void submit_a_report_on_missing_branch_but_existing_project() {
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto nonExistingProject = db.components().insertPrivateProject(organization);
+    UserDto user = db.users().insertUser();
+    userSession.logIn(user).addProjectPermission(SCAN_EXECUTION, nonExistingProject);
+    Map<String, String> randomCharacteristics = randomNonEmptyMap();
+    ComponentDto createdBranch = createButDoNotInsertBranch(nonExistingProject);
+    BranchSupport.ComponentKey componentKey = createComponentKeyOfBranch(createdBranch);
+    when(branchSupportDelegate.createComponentKey(nonExistingProject.getDbKey(), randomCharacteristics))
+      .thenReturn(componentKey);
+    when(branchSupportDelegate.createBranchComponent(any(DbSession.class), same(componentKey), eq(organization), eq(nonExistingProject)))
+      .thenReturn(createdBranch);
+    InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);
+    String taskUuid = mockSuccessfulPrepareSubmitCall();
+
+    underTest.submit(organization.getKey(), nonExistingProject.getDbKey(), null, nonExistingProject.name(), randomCharacteristics, reportInput);
+
+    verifyZeroInteractions(permissionTemplateService);
+    verifyZeroInteractions(favoriteUpdater);
+    verify(branchSupport).createBranchComponent(any(DbSession.class), same(componentKey), eq(organization), eq(nonExistingProject));
+    verify(branchSupportDelegate).createComponentKey(nonExistingProject.getDbKey(), randomCharacteristics);
+    verify(branchSupportDelegate).createBranchComponent(any(DbSession.class), same(componentKey), eq(organization), eq(nonExistingProject));
+    verifyNoMoreInteractions(branchSupportDelegate);
+    verify(componentUpdater, times(0)).commitAndIndex(any(), any());
+    verifyQueueSubmit(nonExistingProject, createdBranch, user, randomCharacteristics, taskUuid);
+  }
+
+  @Test
+  public void submit_report_on_missing_branch_of_missing_project_provisions_project_when_org_PROVISION_PROJECT_perm() {
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto nonExistingProject = newPrivateProjectDto(organization);
+    UserDto user = db.users().insertUser();
+    userSession.logIn(user)
+      .addPermission(PROVISION_PROJECTS, organization)
+      .addPermission(SCAN, organization);
+
+    Map<String, String> randomCharacteristics = randomNonEmptyMap();
+    ComponentDto createdBranch = createButDoNotInsertBranch(nonExistingProject);
+    BranchSupport.ComponentKey componentKey = createComponentKeyOfBranch(createdBranch);
+    when(branchSupportDelegate.createComponentKey(nonExistingProject.getDbKey(), randomCharacteristics))
+      .thenReturn(componentKey);
+    when(componentUpdater.createWithoutCommit(any(), any(), eq(user.getId()))).thenReturn(nonExistingProject);
+    when(branchSupportDelegate.createBranchComponent(any(DbSession.class), same(componentKey), eq(organization), eq(nonExistingProject)))
+      .thenReturn(createdBranch);
+    when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(), eq(organization.getUuid()), any(),
+      eq(nonExistingProject.getKey()), eq(Qualifiers.PROJECT)))
+      .thenReturn(true);
+    String taskUuid = mockSuccessfulPrepareSubmitCall();
+    InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);
+
+    underTest.submit(organization.getKey(), nonExistingProject.getDbKey(), null, nonExistingProject.name(), randomCharacteristics, reportInput);
+
+    verify(branchSupport).createBranchComponent(any(DbSession.class), same(componentKey), eq(organization), eq(nonExistingProject));
+    verify(branchSupportDelegate).createComponentKey(nonExistingProject.getDbKey(), randomCharacteristics);
+    verify(branchSupportDelegate).createBranchComponent(any(DbSession.class), same(componentKey), eq(organization), eq(nonExistingProject));
+    verifyNoMoreInteractions(branchSupportDelegate);
+    verifyQueueSubmit(nonExistingProject, createdBranch, user, randomCharacteristics, taskUuid);
+    verify(componentUpdater).commitAndIndex(any(DbSession.class), eq(nonExistingProject));
+  }
+
+  @Test
+  public void submit_fails_if_branch_support_delegate_createComponentKey_throws_an_exception() {
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto project = db.components().insertPrivateProject(organization);
+    UserDto user = db.users().insertUser();
+    userSession.logIn(user).addProjectPermission(SCAN_EXECUTION, project);
+    Map<String, String> randomCharacteristics = randomNonEmptyMap();
+    InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);
+    RuntimeException expected = new RuntimeException("Faking an exception thrown by branchSupportDelegate");
+    when(branchSupportDelegate.createComponentKey(any(), any())).thenThrow(expected);
+
+    try {
+      underTest.submit(organization.getKey(), project.getDbKey(), null, project.name(), randomCharacteristics, reportInput);
+      fail("exception should have been thrown");
+    } catch (Exception e) {
+      assertThat(e).isSameAs(expected);
+    }
+  }
+
+  @DataProvider
+  public static Object[][] permissionsAllowingProjectProvisioning() {
+    BiConsumer<ComponentDto, UserSessionRule> noProjectPerm = (cpt, userSession) -> {
+    };
+    BiConsumer<OrganizationDto, UserSessionRule> noOrgPerm = (cpt, userSession) -> {
+    };
+    BiConsumer<ComponentDto, UserSessionRule> provisionOnProject = (cpt, userSession) -> userSession.addProjectPermission(PROVISIONING, cpt);
+    BiConsumer<OrganizationDto, UserSessionRule> provisionOnOrganization = (cpt, userSession) -> userSession.addPermission(PROVISION_PROJECTS, cpt);
+    return new Object[][] {
+      {provisionOnProject, noOrgPerm},
+      {noProjectPerm, provisionOnOrganization},
+      {provisionOnProject, provisionOnOrganization}
+    };
+  }
+
+  @Test
+  public void submit_report_on_missing_branch_of_missing_project_fails_with_ForbiddenException_if_only_scan_permission() {
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto nonExistingProject = newPrivateProjectDto(organization);
+    UserDto user = db.users().insertUser();
+    userSession.logIn(user).addProjectPermission(SCAN_EXECUTION, nonExistingProject);
+    Map<String, String> randomCharacteristics = randomNonEmptyMap();
+    ComponentDto createdBranch = createButDoNotInsertBranch(nonExistingProject);
+    BranchSupport.ComponentKey componentKey = createComponentKeyOfBranch(createdBranch);
+    when(branchSupportDelegate.createComponentKey(nonExistingProject.getDbKey(), randomCharacteristics))
+      .thenReturn(componentKey);
+    when(branchSupportDelegate.createBranchComponent(any(DbSession.class), same(componentKey), eq(organization), eq(nonExistingProject)))
+      .thenReturn(createdBranch);
+    InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8);
+
+    expectedException.expect(ForbiddenException.class);
+    expectedException.expectMessage("Insufficient privileges");
+
+    underTest.submit(organization.getKey(), nonExistingProject.getDbKey(), null, nonExistingProject.name(), randomCharacteristics, reportInput);
+  }
+
+  private static ComponentDto createButDoNotInsertBranch(ComponentDto project) {
+    BranchType randomBranchType = BranchType.values()[new Random().nextInt(BranchType.values().length)];
+    BranchDto branchDto = newBranchDto(project.projectUuid(), randomBranchType);
+    return ComponentTesting.newProjectBranch(project, branchDto);
+  }
+
+  private String mockSuccessfulPrepareSubmitCall() {
+    String taskUuid = RandomStringUtils.randomAlphabetic(12);
+    when(queue.prepareSubmit()).thenReturn(new CeTaskSubmit.Builder(taskUuid));
+    return taskUuid;
+  }
+
+  private void verifyQueueSubmit(ComponentDto project, ComponentDto branch, UserDto user, Map<String, String> characteristics, String taskUuid) {
+    verify(queue).submit(argThat(submit -> submit.getType().equals(CeTaskTypes.REPORT)
+      && submit.getComponent().filter(cpt -> cpt.getUuid().equals(branch.uuid()) && cpt.getMainComponentUuid().equals(project.uuid())).isPresent()
+      && submit.getSubmitterUuid().equals(user.getUuid())
+      && submit.getCharacteristics().equals(characteristics)
+      && submit.getUuid().equals(taskUuid)));
+  }
+
+  private static BranchSupport.ComponentKey createComponentKeyOfBranch(ComponentDto branch) {
+    BranchSupport.ComponentKey mainComponentKey = mockComponentKey(branch.getKey(), branch.getKey());
+    when(mainComponentKey.getMainBranchComponentKey()).thenReturn(mainComponentKey);
+
+    BranchSupport.ComponentKey componentKey = mockComponentKey(branch.getKey(), branch.getDbKey());
+    when(componentKey.getMainBranchComponentKey()).thenReturn(mainComponentKey);
+
+    return componentKey;
+  }
+
+  private static BranchSupport.ComponentKey mockComponentKey(String key, String dbKey) {
+    BranchSupport.ComponentKey componentKey = mock(BranchSupport.ComponentKey.class);
+    when(componentKey.isDeprecatedBranch()).thenReturn(false);
+    when(componentKey.isMainBranch()).thenReturn(key.equals(dbKey));
+    when(componentKey.getKey()).thenReturn(key);
+    when(componentKey.getDbKey()).thenReturn(dbKey);
+    return componentKey;
+  }
+
+  private static ImmutableMap<String, String> randomNonEmptyMap() {
+    return IntStream.range(0, 1 + new Random().nextInt(5))
+      .boxed()
+      .collect(uniqueIndex(i -> "key_" + i, i -> "val_" + i));
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/ce/queue/BranchSupportTest.java b/server/sonar-server/src/test/java/org/sonar/server/ce/queue/BranchSupportTest.java
new file mode 100644 (file)
index 0000000..6c941f6
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info 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.ce.queue;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Random;
+import java.util.stream.IntStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.server.ce.queue.BranchSupport.ComponentKey;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
+
+@RunWith(DataProviderRunner.class)
+public class BranchSupportTest {
+  private static final Map<String, String> NO_CHARACTERISTICS = Collections.emptyMap();
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private BranchSupportDelegate branchSupportDelegate = mock(BranchSupportDelegate.class);
+  private BranchSupport underTestNoBranch = new BranchSupport();
+  private BranchSupport underTestWithBranch = new BranchSupport(branchSupportDelegate);
+
+  @Test
+  public void createComponentKey_of_main_branch() {
+    String projectKey = randomAlphanumeric(12);
+
+    ComponentKey componentKey = underTestNoBranch.createComponentKey(projectKey, null, NO_CHARACTERISTICS);
+
+    assertThat(componentKey)
+      .isEqualTo(underTestWithBranch.createComponentKey(projectKey, null, NO_CHARACTERISTICS));
+    assertThat(componentKey.getKey()).isEqualTo(projectKey);
+    assertThat(componentKey.getDbKey()).isEqualTo(projectKey);
+    assertThat(componentKey.isMainBranch()).isTrue();
+    assertThat(componentKey.isDeprecatedBranch()).isFalse();
+    assertThat(componentKey.getMainBranchComponentKey()).isSameAs(componentKey);
+    assertThat(componentKey.getBranchName()).isEmpty();
+    assertThat(componentKey.getPullRequestKey()).isEmpty();
+  }
+
+  @Test
+  public void createComponentKey_of_deprecated_branch() {
+    String projectKey = randomAlphanumeric(12);
+    String deprecatedBranchName = randomAlphanumeric(12);
+
+    ComponentKey componentKey = underTestNoBranch.createComponentKey(projectKey, deprecatedBranchName, NO_CHARACTERISTICS);
+
+    assertThat(componentKey)
+      .isEqualTo(underTestWithBranch.createComponentKey(projectKey, deprecatedBranchName, NO_CHARACTERISTICS));
+    assertThat(componentKey.getKey()).isEqualTo(projectKey);
+    assertThat(componentKey.getDbKey()).isEqualTo(projectKey + ":" + deprecatedBranchName);
+    assertThat(componentKey.isMainBranch()).isFalse();
+    assertThat(componentKey.isDeprecatedBranch()).isTrue();
+    assertThat(componentKey.getMainBranchComponentKey()).isSameAs(componentKey);
+    assertThat(componentKey.getBranchName()).isEmpty();
+    assertThat(componentKey.getPullRequestKey()).isEmpty();
+  }
+
+  @Test
+  @UseDataProvider("nullOrNonEmpty")
+  public void createComponentKey_with_ISE_if_characteristics_is_not_empty_and_delegate_is_null(String deprecatedBranchName) {
+    String projectKey = randomAlphanumeric(12);
+    Map<String, String> nonEmptyMap = newRandomNonEmptyMap();
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Current edition does not support branch feature");
+    
+    underTestNoBranch.createComponentKey(projectKey, deprecatedBranchName, nonEmptyMap);
+  }
+
+  @Test
+  public void createComponentKey_fails_with_IAE_if_characteristics_is_not_empty_and_deprecatedBranchName_is_non_null() {
+    String projectKey = randomAlphanumeric(12);
+    String deprecatedBranchName = randomAlphanumeric(13);
+    Map<String, String> nonEmptyMap = newRandomNonEmptyMap();
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Deprecated branch feature can't be used at the same time as new branch support");
+    
+    underTestWithBranch.createComponentKey(projectKey, deprecatedBranchName, nonEmptyMap);
+  }
+
+  @Test
+  public void createComponentKey_delegates_to_delegate_if_characteristics_is_not_empty() {
+    String projectKey = randomAlphanumeric(12);
+    Map<String, String> nonEmptyMap = newRandomNonEmptyMap();
+    ComponentKey expected = mock(ComponentKey.class);
+    when(branchSupportDelegate.createComponentKey(projectKey, nonEmptyMap)).thenReturn(expected);
+
+    ComponentKey componentKey = underTestWithBranch.createComponentKey(projectKey, null, nonEmptyMap);
+
+    assertThat(componentKey).isSameAs(expected);
+  }
+
+  @Test
+  public void createBranchComponent_fails_with_ISE_if_delegate_is_null() {
+    DbSession dbSession = mock(DbSession.class);
+    ComponentKey componentKey = mock(ComponentKey.class);
+    OrganizationDto organization = new OrganizationDto();
+    ComponentDto mainComponentDto = new ComponentDto();
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Current edition does not support branch feature");
+    
+    underTestNoBranch.createBranchComponent(dbSession, componentKey, organization, mainComponentDto);
+  }
+
+  @Test
+  public void createBranchComponent_delegates_to_delegate() {
+    DbSession dbSession = mock(DbSession.class);
+    ComponentKey componentKey = mock(ComponentKey.class);
+    OrganizationDto organization = new OrganizationDto();
+    ComponentDto mainComponentDto = new ComponentDto();
+    ComponentDto expected = new ComponentDto();
+    when(branchSupportDelegate.createBranchComponent(dbSession, componentKey, organization, mainComponentDto))
+      .thenReturn(expected);
+
+    ComponentDto dto = underTestWithBranch.createBranchComponent(dbSession, componentKey, organization, mainComponentDto);
+
+    assertThat(dto).isSameAs(expected);
+  }
+
+  @DataProvider
+  public static Object[][] nullOrNonEmpty() {
+    return new Object[][] {
+      {null},
+      {randomAlphabetic(5)},
+    };
+  }
+
+  private static Map<String, String> newRandomNonEmptyMap() {
+    return IntStream.range(0, 1 + new Random().nextInt(10)).boxed().collect(uniqueIndex(i -> "key_" + i, i -> "val_" + i));
+  }
+}
index 6ad3033a68b346526a180833b288f69b2726c295..38196fcf8a34f7410f470b2b2f427438423ed8a1 100644 (file)
  */
 package org.sonar.server.ce.queue;
 
-import com.google.common.collect.ImmutableMap;
+import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.Map;
+import java.util.Random;
+import java.util.stream.IntStream;
 import org.apache.commons.io.IOUtils;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
-import org.mockito.ArgumentCaptor;
 import org.sonar.api.resources.Qualifiers;
 import org.sonar.api.utils.System2;
 import org.sonar.ce.queue.CeQueue;
@@ -36,7 +37,6 @@ import org.sonar.ce.queue.CeTaskSubmit;
 import org.sonar.core.permission.GlobalPermissions;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
-import org.sonar.db.ce.CeTaskCharacteristicDto;
 import org.sonar.db.ce.CeTaskTypes;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.ComponentTesting;
@@ -55,8 +55,8 @@ import org.sonar.server.tester.UserSessionRule;
 import static java.lang.String.format;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Collections.emptyMap;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.entry;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.argThat;
@@ -67,6 +67,7 @@ import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION;
+import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
 import static org.sonar.db.component.ComponentTesting.newModuleDto;
 import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
 import static org.sonar.db.permission.OrganizationPermission.PROVISION_PROJECTS;
@@ -92,8 +93,9 @@ public class ReportSubmitterTest {
   private ComponentUpdater componentUpdater = mock(ComponentUpdater.class);
   private PermissionTemplateService permissionTemplateService = mock(PermissionTemplateService.class);
   private FavoriteUpdater favoriteUpdater = mock(FavoriteUpdater.class);
+  private BranchSupport ossEditionBranchSupport = new BranchSupport();
 
-  private ReportSubmitter underTest = new ReportSubmitter(queue, userSession, componentUpdater, permissionTemplateService, db.getDbClient());
+  private ReportSubmitter underTest = new ReportSubmitter(queue, userSession, componentUpdater, permissionTemplateService, db.getDbClient(), ossEditionBranchSupport);
 
   @Before
   public void setUp() throws Exception {
@@ -102,25 +104,45 @@ public class ReportSubmitterTest {
   }
 
   @Test
-  public void submit_stores_report() {
+  public void submit_with_characteristics_fails_with_ISE_when_no_branch_support_delegate() {
     userSession
       .addPermission(OrganizationPermission.SCAN, db.getDefaultOrganization().getUuid())
       .addPermission(PROVISION_PROJECTS, db.getDefaultOrganization());
 
-    mockSuccessfulPrepareSubmitCall();
     ComponentDto project = newPrivateProjectDto(db.getDefaultOrganization(), PROJECT_UUID).setDbKey(PROJECT_KEY);
+    mockSuccessfulPrepareSubmitCall();
     when(componentUpdater.create(any(), any(), any())).thenReturn(project);
+    when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(), eq(defaultOrganizationUuid), any(), eq(PROJECT_KEY),
+      eq(Qualifiers.PROJECT)))
+      .thenReturn(true);
+    Map<String, String> nonEmptyCharacteristics = IntStream.range(0, 1 + new Random().nextInt(5))
+      .boxed()
+      .collect(uniqueIndex(i -> randomAlphabetic(i + 10), i -> randomAlphabetic(i + 20)));
+    InputStream reportInput = IOUtils.toInputStream("{binary}", UTF_8);
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Current edition does not support branch feature");
+
+    underTest.submit(defaultOrganizationKey, PROJECT_KEY, null, PROJECT_NAME, nonEmptyCharacteristics, reportInput);
+  }
+
+  @Test
+  public void submit_stores_report() {
+    userSession
+      .addPermission(OrganizationPermission.SCAN, db.getDefaultOrganization().getUuid())
+      .addPermission(PROVISION_PROJECTS, db.getDefaultOrganization());
+
+    ComponentDto project = newPrivateProjectDto(db.getDefaultOrganization(), PROJECT_UUID).setDbKey(PROJECT_KEY);
+    mockSuccessfulPrepareSubmitCall();
+    when(componentUpdater.createWithoutCommit(any(), any(), any())).thenReturn(project);
     when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(), eq(defaultOrganizationUuid), any(), eq(PROJECT_KEY),
       eq(Qualifiers.PROJECT)))
         .thenReturn(true);
 
-    Map<String, String> taskCharacteristics = ImmutableMap.of(CeTaskCharacteristicDto.PULL_REQUEST, "123");
-    underTest.submit(defaultOrganizationKey, PROJECT_KEY, null, PROJECT_NAME, taskCharacteristics, IOUtils.toInputStream("{binary}", UTF_8));
+    underTest.submit(defaultOrganizationKey, PROJECT_KEY, null, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}", UTF_8));
 
-    ArgumentCaptor<CeTaskSubmit> submittedTask = ArgumentCaptor.forClass(CeTaskSubmit.class);
-    verify(queue).submit(submittedTask.capture());
-    assertThat(submittedTask.getValue().getCharacteristics())
-      .containsExactly(entry("pullRequest", "123"));
+    verifyReportIsPersisted(TASK_UUID);
+    verify(componentUpdater).commitAndIndex(any(DbSession.class), eq(project));
   }
 
   @Test
@@ -128,7 +150,6 @@ public class ReportSubmitterTest {
     ComponentDto project = db.components().insertPrivateProject(db.getDefaultOrganization());
     UserDto user = db.users().insertUser();
     userSession.logIn(user).addProjectPermission(SCAN_EXECUTION, project);
-
     mockSuccessfulPrepareSubmitCall();
 
     underTest.submit(defaultOrganizationKey, project.getDbKey(), null, project.name(), emptyMap(), IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8));
@@ -137,7 +158,7 @@ public class ReportSubmitterTest {
     verifyZeroInteractions(permissionTemplateService);
     verifyZeroInteractions(favoriteUpdater);
     verify(queue).submit(argThat(submit -> submit.getType().equals(CeTaskTypes.REPORT)
-      && submit.getComponentUuid().equals(project.uuid())
+      && submit.getComponent().filter(cpt -> cpt.getUuid().equals(project.uuid()) && cpt.getMainComponentUuid().equals(project.uuid())).isPresent()
       && submit.getSubmitterUuid().equals(user.getUuid())
       && submit.getUuid().equals(TASK_UUID)));
   }
@@ -151,7 +172,7 @@ public class ReportSubmitterTest {
 
     mockSuccessfulPrepareSubmitCall();
     ComponentDto createdProject = newPrivateProjectDto(organization, PROJECT_UUID).setDbKey(PROJECT_KEY);
-    when(componentUpdater.create(any(), any(), isNull())).thenReturn(createdProject);
+    when(componentUpdater.createWithoutCommit(any(), any(), isNull())).thenReturn(createdProject);
     when(
       permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(), eq(organization.getUuid()), any(), eq(PROJECT_KEY), eq(Qualifiers.PROJECT)))
         .thenReturn(true);
@@ -160,7 +181,10 @@ public class ReportSubmitterTest {
     underTest.submit(organization.getKey(), PROJECT_KEY, null, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));
 
     verifyReportIsPersisted(TASK_UUID);
-    verify(queue).submit(argThat(submit -> submit.getType().equals(CeTaskTypes.REPORT) && submit.getComponentUuid().equals(PROJECT_UUID) && submit.getUuid().equals(TASK_UUID)));
+    verify(queue).submit(argThat(submit -> submit.getType().equals(CeTaskTypes.REPORT)
+      && submit.getComponent().filter(cpt -> cpt.getUuid().equals(PROJECT_UUID) && cpt.getMainComponentUuid().equals(PROJECT_UUID)).isPresent()
+      && submit.getUuid().equals(TASK_UUID)));
+    verify(componentUpdater).commitAndIndex(any(DbSession.class), eq(createdProject));
   }
 
   @Test
@@ -169,17 +193,18 @@ public class ReportSubmitterTest {
       .addPermission(OrganizationPermission.SCAN, db.getDefaultOrganization().getUuid())
       .addPermission(PROVISION_PROJECTS, db.getDefaultOrganization());
 
-    mockSuccessfulPrepareSubmitCall();
     ComponentDto createdProject = newPrivateProjectDto(db.getDefaultOrganization(), PROJECT_UUID).setDbKey(PROJECT_KEY);
-    when(componentUpdater.create(any(), any(), isNull())).thenReturn(createdProject);
+    when(componentUpdater.createWithoutCommit(any(), any(), isNull())).thenReturn(createdProject);
     when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(), eq(defaultOrganizationUuid), any(),
       eq(PROJECT_KEY), eq(Qualifiers.PROJECT)))
         .thenReturn(true);
     when(permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(any(), eq(defaultOrganizationUuid), any())).thenReturn(false);
+    mockSuccessfulPrepareSubmitCall();
 
     underTest.submit(defaultOrganizationKey, PROJECT_KEY, null, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));
 
     verifyZeroInteractions(favoriteUpdater);
+    verify(componentUpdater).commitAndIndex(any(DbSession.class), eq(createdProject));
   }
 
   @Test
@@ -188,9 +213,9 @@ public class ReportSubmitterTest {
       .addPermission(OrganizationPermission.SCAN, db.getDefaultOrganization().getUuid())
       .addPermission(PROVISION_PROJECTS, db.getDefaultOrganization());
 
-    mockSuccessfulPrepareSubmitCall();
     ComponentDto project = newPrivateProjectDto(db.getDefaultOrganization(), PROJECT_UUID).setDbKey(PROJECT_KEY);
-    when(componentUpdater.create(any(), any(), any())).thenReturn(project);
+    mockSuccessfulPrepareSubmitCall();
+    when(componentUpdater.createWithoutCommit(any(), any(), any())).thenReturn(project);
     when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(), eq(defaultOrganizationUuid), any(),
       eq(PROJECT_KEY), eq(Qualifiers.PROJECT)))
         .thenReturn(true);
@@ -198,6 +223,7 @@ public class ReportSubmitterTest {
     underTest.submit(defaultOrganizationKey, PROJECT_KEY, null, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));
 
     verify(queue).submit(any(CeTaskSubmit.class));
+    verify(componentUpdater).commitAndIndex(any(DbSession.class), eq(project));
   }
 
   @Test
@@ -205,7 +231,6 @@ public class ReportSubmitterTest {
     OrganizationDto org = db.organizations().insert();
     ComponentDto project = db.components().insertPrivateProject(org);
     userSession.addPermission(SCAN, org);
-
     mockSuccessfulPrepareSubmitCall();
 
     underTest.submit(org.getKey(), project.getDbKey(), null, project.name(), emptyMap(), IOUtils.toInputStream("{binary}"));
@@ -217,7 +242,6 @@ public class ReportSubmitterTest {
   public void submit_a_report_on_existing_project_with_project_scan_permission() {
     ComponentDto project = db.components().insertPrivateProject(db.getDefaultOrganization());
     userSession.addProjectPermission(SCAN_EXECUTION, project);
-
     mockSuccessfulPrepareSubmitCall();
 
     underTest.submit(defaultOrganizationKey, project.getDbKey(), null, project.name(), emptyMap(), IOUtils.toInputStream("{binary}"));
@@ -230,15 +254,16 @@ public class ReportSubmitterTest {
    */
   @Test
   public void project_branch_must_not_benefit_from_the_scan_permission_on_main_project() {
+    String branchName = "branchFoo";
+
     ComponentDto mainProject = db.components().insertPrivateProject();
     userSession.addProjectPermission(GlobalPermissions.SCAN_EXECUTION, mainProject);
 
     // user does not have the "scan" permission on the branch, so it can't scan it
-    String branchName = "branchFoo";
     ComponentDto branchProject = db.components().insertPrivateProject(p -> p.setDbKey(mainProject.getDbKey() + ":" + branchName));
 
     expectedException.expect(ForbiddenException.class);
-    underTest.submit(defaultOrganizationKey, mainProject.getDbKey(), branchName, PROJECT_NAME,emptyMap(),  IOUtils.toInputStream("{binary}"));
+    underTest.submit(defaultOrganizationKey, mainProject.getDbKey(), branchName, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));
   }
 
   @Test
@@ -298,8 +323,8 @@ public class ReportSubmitterTest {
 
   @Test
   public void fail_with_forbidden_exception_on_new_project_when_only_project_scan_permission() {
-    userSession.addProjectPermission(SCAN_EXECUTION, ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization(), PROJECT_UUID));
-
+    ComponentDto component = ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization(), PROJECT_UUID);
+    userSession.addProjectPermission(SCAN_EXECUTION, component);
     mockSuccessfulPrepareSubmitCall();
     when(componentUpdater.create(any(DbSession.class), any(NewComponent.class), eq(null))).thenReturn(new ComponentDto().setUuid(PROJECT_UUID).setDbKey(PROJECT_KEY));
 
index 6a584456b73a26c6e81bf3adb1b7affe1ab070b0..846a358ca912dbf7b26d28b6069888004c331db9 100644 (file)
@@ -507,7 +507,7 @@ public class ActivityActionTest {
   private CeQueueDto insertQueue(String taskUuid, @Nullable ComponentDto project, CeQueueDto.Status status) {
     CeQueueDto queueDto = new CeQueueDto();
     queueDto.setTaskType(CeTaskTypes.REPORT);
-    queueDto.setComponentUuid(project == null ? null : project.uuid());
+    queueDto.setComponent(project);
     queueDto.setUuid(taskUuid);
     queueDto.setStatus(status);
     db.getDbClient().ceQueueDao().insert(db.getSession(), queueDto);
@@ -522,7 +522,7 @@ public class ActivityActionTest {
   private CeActivityDto insertActivity(String taskUuid, ComponentDto project, Status status, @Nullable SnapshotDto analysis) {
     CeQueueDto queueDto = new CeQueueDto();
     queueDto.setTaskType(CeTaskTypes.REPORT);
-    queueDto.setComponentUuid(project.uuid());
+    queueDto.setComponent(project);
     queueDto.setUuid(taskUuid);
     queueDto.setCreatedAt(EXECUTED_AT);
     CeActivityDto activityDto = new CeActivityDto(queueDto);
index 2fc170c98679902d6651f51c203593f9f1ba8b12..9e7e0a5833feab29c3a912dd78c4e8f316583749 100644 (file)
@@ -45,9 +45,9 @@ import org.sonarqube.ws.Ce;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.sonar.db.ce.CeQueueTesting.newCeQueueDto;
 import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
-import static org.sonar.test.JsonAssert.assertJson;
 import static org.sonar.server.ce.ws.CeWsParameters.DEPRECATED_PARAM_COMPONENT_KEY;
 import static org.sonar.server.ce.ws.CeWsParameters.PARAM_COMPONENT_ID;
+import static org.sonar.test.JsonAssert.assertJson;
 
 public class ActivityStatusActionTest {
 
@@ -84,22 +84,23 @@ public class ActivityStatusActionTest {
     String anotherProjectUuid = "another-project-uuid";
     OrganizationDto organizationDto = db.organizations().insert();
     ComponentDto project = newPrivateProjectDto(organizationDto, projectUuid);
+    ComponentDto anotherProject = newPrivateProjectDto(organizationDto, anotherProjectUuid);
     db.components().insertComponent(project);
     db.components().insertComponent(newPrivateProjectDto(organizationDto, anotherProjectUuid));
     userSession.logIn().addProjectPermission(UserRole.ADMIN, project);
     // pending tasks returned
-    insertInQueue(CeQueueDto.Status.PENDING, projectUuid);
-    insertInQueue(CeQueueDto.Status.PENDING, projectUuid);
+    insertInQueue(CeQueueDto.Status.PENDING, project);
+    insertInQueue(CeQueueDto.Status.PENDING, project);
     // other tasks not returned
-    insertInQueue(CeQueueDto.Status.IN_PROGRESS, projectUuid);
-    insertInQueue(CeQueueDto.Status.PENDING, anotherProjectUuid);
+    insertInQueue(CeQueueDto.Status.IN_PROGRESS, project);
+    insertInQueue(CeQueueDto.Status.PENDING, anotherProject);
     insertInQueue(CeQueueDto.Status.PENDING, null);
     // only one last activity for a given project
-    insertActivity(CeActivityDto.Status.SUCCESS, projectUuid);
-    insertActivity(CeActivityDto.Status.CANCELED, projectUuid);
-    insertActivity(CeActivityDto.Status.FAILED, projectUuid);
-    insertActivity(CeActivityDto.Status.FAILED, projectUuid);
-    insertActivity(CeActivityDto.Status.FAILED, anotherProjectUuid);
+    insertActivity(CeActivityDto.Status.SUCCESS, project);
+    insertActivity(CeActivityDto.Status.CANCELED, project);
+    insertActivity(CeActivityDto.Status.FAILED, project);
+    insertActivity(CeActivityDto.Status.FAILED, project);
+    insertActivity(CeActivityDto.Status.FAILED, anotherProject);
 
     Ce.ActivityStatusWsResponse result = call(projectUuid);
 
@@ -159,18 +160,18 @@ public class ActivityStatusActionTest {
     callByComponentKey(project.getDbKey());
   }
 
-  private void insertInQueue(CeQueueDto.Status status, @Nullable String componentUuid) {
+  private void insertInQueue(CeQueueDto.Status status, @Nullable ComponentDto componentDto) {
     dbClient.ceQueueDao().insert(dbSession, newCeQueueDto(Uuids.createFast())
       .setStatus(status)
-      .setComponentUuid(componentUuid));
+      .setComponent(componentDto));
     db.commit();
   }
 
-  private void insertActivity(CeActivityDto.Status status, @Nullable String componentUuid) {
-    dbClient.ceActivityDao().insert(dbSession, new CeActivityDto(
-      newCeQueueDto(Uuids.createFast())
-        .setComponentUuid(componentUuid))
-          .setStatus(status));
+  private void insertActivity(CeActivityDto.Status status, @Nullable ComponentDto dto) {
+    CeQueueDto ceQueueDto = newCeQueueDto(Uuids.createFast());
+    ceQueueDto.setComponent(dto);
+    dbClient.ceActivityDao().insert(dbSession, new CeActivityDto(ceQueueDto)
+      .setStatus(status));
     db.commit();
   }
 
index 2236a47cc6a6145fb2067fbf083dce154d794fc7..784f0c60fcfedce5e97ce84147a34e4583f51ec3 100644 (file)
@@ -62,7 +62,7 @@ public class CancelActionTest {
   public void cancel_pending_task_on_project() {
     logInAsSystemAdministrator();
     ComponentDto project = db.components().insertPrivateProject();
-    CeQueueDto queue = createTaskSubmit(project.uuid());
+    CeQueueDto queue = createTaskSubmit(project);
 
     tester.newRequest()
       .setParam("id", queue.getUuid())
@@ -87,7 +87,7 @@ public class CancelActionTest {
   public void cancel_pending_task_when_system_administer() {
     logInAsSystemAdministrator();
     ComponentDto project = db.components().insertPrivateProject();
-    CeQueueDto queue = createTaskSubmit(project.uuid());
+    CeQueueDto queue = createTaskSubmit(project);
 
     tester.newRequest()
       .setParam("id", queue.getUuid())
@@ -100,7 +100,7 @@ public class CancelActionTest {
   public void cancel_pending_task_when_project_administer() {
     ComponentDto project = db.components().insertPrivateProject();
     userSession.addProjectPermission(UserRole.ADMIN, project);
-    CeQueueDto queue = createTaskSubmit(project.uuid());
+    CeQueueDto queue = createTaskSubmit(project);
 
     tester.newRequest()
       .setParam("id", queue.getUuid())
@@ -132,7 +132,7 @@ public class CancelActionTest {
   public void throw_ForbiddenException_if_not_enough_permission_when_canceling_task_on_project() {
     userSession.logIn().setNonSystemAdministrator();
     ComponentDto project = db.components().insertPrivateProject();
-    CeQueueDto queue = createTaskSubmit(project.uuid());
+    CeQueueDto queue = createTaskSubmit(project);
 
     expectedException.expect(ForbiddenException.class);
     expectedException.expectMessage("Insufficient privileges");
@@ -158,7 +158,7 @@ public class CancelActionTest {
   @Test
   public void throw_ForbiddenException_if_not_enough_permission_when_canceling_task_when_project_does_not_exist() {
     userSession.logIn().setNonSystemAdministrator();
-    CeQueueDto queue = createTaskSubmit("UNKNOWN");
+    CeQueueDto queue = createTaskSubmit(nonExistentComponentDot());
 
     expectedException.expect(ForbiddenException.class);
     expectedException.expectMessage("Insufficient privileges");
@@ -168,16 +168,22 @@ public class CancelActionTest {
       .execute();
   }
 
+  private static ComponentDto nonExistentComponentDot() {
+    return new ComponentDto().setUuid("does_not_exist").setProjectUuid("unknown");
+  }
+
   private void logInAsSystemAdministrator() {
     userSession.logIn().setSystemAdministrator();
   }
 
-  private CeQueueDto createTaskSubmit(@Nullable String componentUuid) {
+  private CeQueueDto createTaskSubmit(@Nullable ComponentDto componentDto) {
     CeTaskSubmit.Builder submission = queue.prepareSubmit()
       .setType(CeTaskTypes.REPORT)
-      .setComponentUuid(componentUuid)
       .setSubmitterUuid(null)
       .setCharacteristics(emptyMap());
+    if (componentDto != null) {
+      submission.setComponent(CeTaskSubmit.Component.fromDto(componentDto));
+    }
     CeTask task = queue.submit(submission.build());
     return db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), task.getUuid()).get();
   }
index d1b8d61af243c542c13e270c6bb0812ec0076e8f..ff927dc762da8a355b8dd31911d975921c36d73a 100644 (file)
@@ -281,7 +281,7 @@ public class ComponentActionTest {
   private CeQueueDto insertQueue(String taskUuid, ComponentDto component, CeQueueDto.Status status) {
     CeQueueDto queueDto = new CeQueueDto();
     queueDto.setTaskType(CeTaskTypes.REPORT);
-    queueDto.setComponentUuid(component.uuid());
+    queueDto.setComponent(component);
     queueDto.setUuid(taskUuid);
     queueDto.setStatus(status);
     db.getDbClient().ceQueueDao().insert(db.getSession(), queueDto);
@@ -296,7 +296,7 @@ public class ComponentActionTest {
   private CeActivityDto insertActivity(String taskUuid, ComponentDto component, CeActivityDto.Status status, @Nullable SnapshotDto analysis) {
     CeQueueDto queueDto = new CeQueueDto();
     queueDto.setTaskType(CeTaskTypes.REPORT);
-    queueDto.setComponentUuid(component.uuid());
+    queueDto.setComponent(component);
     queueDto.setUuid(taskUuid);
     CeActivityDto activityDto = new CeActivityDto(queueDto);
     activityDto.setStatus(status);
index 59813b50e21f43ed1d401023f501985325586c06..5755411dee632fbeda4c393775fe8f301bddd400 100644 (file)
@@ -51,11 +51,14 @@ import static org.mockito.Mockito.when;
 
 public class SubmitActionTest {
 
+  private static final String PROJECT_UUID = "PROJECT_1";
+  private static final CeTask.Component COMPONENT = new CeTask.Component(PROJECT_UUID, "KEY_1", "NAME_1");
   private static final CeTask A_CE_TASK = new CeTask.Builder()
     .setOrganizationUuid("org1")
     .setUuid("TASK_1")
     .setType(CeTaskTypes.REPORT)
-    .setComponentUuid("PROJECT_1")
+    .setComponent(COMPONENT)
+    .setMainComponent(COMPONENT)
     .setSubmitterUuid("robert")
     .build();
 
@@ -89,7 +92,7 @@ public class SubmitActionTest {
       anyMap(), any());
 
     assertThat(submitResponse.getTaskId()).isEqualTo("TASK_1");
-    assertThat(submitResponse.getProjectId()).isEqualTo("PROJECT_1");
+    assertThat(submitResponse.getProjectId()).isEqualTo(PROJECT_UUID);
   }
 
   @Test
@@ -132,7 +135,7 @@ public class SubmitActionTest {
       anyMap(), any());
 
     assertThat(submitResponse.getTaskId()).isEqualTo("TASK_1");
-    assertThat(submitResponse.getProjectId()).isEqualTo("PROJECT_1");
+    assertThat(submitResponse.getProjectId()).isEqualTo(PROJECT_UUID);
   }
 
   @Test
index 8ad0902bf8f35fc252bf37cca03b575d46227d38..3f6296abe4c09de0b55d13d648112ac6ce5569d2 100644 (file)
@@ -266,7 +266,7 @@ public class UpdateVisibilityActionTest {
   }
 
   @Test
-  public void execute_throws_BadRequestException_if_specified_component_has_in_progress_tasks() {
+  public void execute_throws_BadRequestException_if_main_component_of_specified_component_has_in_progress_tasks() {
     ComponentDto project = randomPublicOrPrivateProject();
     IntStream.range(0, 1 + Math.abs(random.nextInt(5)))
       .forEach(i -> insertInProgressTask(project));
@@ -734,7 +734,7 @@ public class UpdateVisibilityActionTest {
   private void insertCeQueueDto(ComponentDto project, CeQueueDto.Status status) {
     dbClient.ceQueueDao().insert(dbTester.getSession(), new CeQueueDto()
       .setUuid("pending" + counter++)
-      .setComponentUuid(project.uuid())
+      .setComponent(project)
       .setTaskType("foo")
       .setStatus(status));
     dbTester.commit();