]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7903 persist analysis reports in db
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Thu, 4 Aug 2016 15:14:51 +0000 (17:14 +0200)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Fri, 5 Aug 2016 13:45:01 +0000 (15:45 +0200)
instead of file system (data/ce/reports). That allows
support of clustering.

40 files changed:
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
server/sonar-server/src/main/java/org/sonar/ce/CeModule.java
server/sonar-server/src/main/java/org/sonar/ce/queue/CeQueueImpl.java
server/sonar-server/src/main/java/org/sonar/ce/queue/CeQueueListener.java [deleted file]
server/sonar-server/src/main/java/org/sonar/ce/queue/report/ReportFiles.java [deleted file]
server/sonar-server/src/main/java/org/sonar/ce/queue/report/ReportSubmitter.java [deleted file]
server/sonar-server/src/main/java/org/sonar/ce/queue/report/package-info.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/ce/ws/SubmitAction.java
server/sonar-server/src/main/java/org/sonar/server/computation/CeQueueModule.java
server/sonar-server/src/main/java/org/sonar/server/computation/queue/CeQueueCleaner.java
server/sonar-server/src/main/java/org/sonar/server/computation/queue/InternalCeQueueImpl.java
server/sonar-server/src/main/java/org/sonar/server/computation/queue/ReportSubmitter.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/ProjectAnalysisTaskModule.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/queue/CleanReportQueueListener.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/queue/package-info.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/ExtractReportStep.java
server/sonar-server/src/test/java/org/sonar/ce/queue/CeQueueImplTest.java
server/sonar-server/src/test/java/org/sonar/ce/queue/report/ReportSubmitterTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/ce/ws/CeWsTest.java
server/sonar-server/src/test/java/org/sonar/server/ce/ws/SubmitActionTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/ReportFilesTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/computation/queue/CeQueueCleanerTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/queue/InternalCeQueueImplTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/queue/ReportSubmitterTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/queue/CleanReportQueueListenerTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/ExtractReportStepTest.java
server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1302_create_table_ce_task_data.rb [new file with mode: 0644]
sonar-db/src/main/java/org/sonar/db/DaoModule.java
sonar-db/src/main/java/org/sonar/db/DbClient.java
sonar-db/src/main/java/org/sonar/db/MyBatis.java
sonar-db/src/main/java/org/sonar/db/ce/CeTaskDataDao.java [new file with mode: 0644]
sonar-db/src/main/java/org/sonar/db/ce/CeTaskDataMapper.java [new file with mode: 0644]
sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java
sonar-db/src/main/resources/org/sonar/db/ce/CeTaskDataMapper.xml [new file with mode: 0644]
sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql
sonar-db/src/main/resources/org/sonar/db/version/schema-h2.ddl
sonar-db/src/test/java/org/sonar/db/DaoModuleTest.java
sonar-db/src/test/java/org/sonar/db/DbTester.java
sonar-db/src/test/java/org/sonar/db/ce/CeTaskDataDaoTest.java [new file with mode: 0644]
sonar-db/src/test/java/org/sonar/db/version/v56/UpdateUsersExternalIdentityWhenEmptyTest.java

index c9a83fa91779e1f5a671e9033b1487750ca0a051..9fc85e9f8a64db1b92cb25efcd121be574fd359a 100644 (file)
@@ -89,9 +89,9 @@ public class ComputeEngineContainerImplTest {
       .hasSize(
         CONTAINER_ITSELF
           + 75 // level 4
-          + 7 // content of CeModule
+          + 6 // content of CeModule
           + 7 // content of CeQueueModule
-          + 4 // content of ProjectAnalysisTaskModule
+          + 3 // content of ProjectAnalysisTaskModule
           + 4 // content of CeTaskProcessorModule
     );
     assertThat(picoContainer.getParent().getComponentAdapters()).hasSize(
@@ -105,7 +105,7 @@ public class ComputeEngineContainerImplTest {
     assertThat(picoContainer.getParent().getParent().getParent().getComponentAdapters()).hasSize(
       COMPONENTS_IN_LEVEL_1_AT_CONSTRUCTION
         + 26 // level 1
-        + 46 // content of DaoModule
+        + 47 // content of DaoModule
         + 2 // content of EsSearchModule
         + 55 // content of CorePropertyDefinitions
         + 1 // content of CePropertyDefinitions
index 1f4bb4b7f3542334c9ae9e9defaa5f107b5ca069..09b13d7434c1c026dc76b254cef62f063e6cd65d 100644 (file)
@@ -21,10 +21,9 @@ package org.sonar.ce;
 
 import org.sonar.ce.log.CeLogging;
 import org.sonar.ce.queue.CeQueueImpl;
-import org.sonar.ce.queue.report.ReportFiles;
-import org.sonar.ce.queue.report.ReportSubmitter;
 import org.sonar.ce.taskprocessor.ReportTaskProcessorDeclaration;
 import org.sonar.core.platform.Module;
+import org.sonar.server.computation.queue.ReportSubmitter;
 
 public class CeModule extends Module {
   @Override
@@ -34,8 +33,7 @@ public class CeModule extends Module {
       // Queue
       CeQueueImpl.class,
       ReportSubmitter.class,
-      ReportFiles.class,
-
+      
       // Core tasks processors
       ReportTaskProcessorDeclaration.class);
   }
index 63adafa270c88a8d6ee988cb3b2723d59b525a1f..316f7a65042ea2c5d702c7391b7d9155a4681306 100644 (file)
@@ -41,6 +41,7 @@ import org.sonar.db.component.ComponentDto;
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.base.Predicates.notNull;
 import static com.google.common.collect.FluentIterable.from;
+import static java.util.Collections.singletonList;
 import static org.sonar.db.component.ComponentDtoFunctions.toUuid;
 
 @ComputeEngineSide
@@ -48,22 +49,13 @@ public class CeQueueImpl implements CeQueue {
 
   private final DbClient dbClient;
   private final UuidFactory uuidFactory;
-  private final CeQueueListener[] listeners;
 
   // state
   private AtomicBoolean submitPaused = new AtomicBoolean(false);
 
-  /**
-   * Constructor in case there is no CeQueueListener
-   */
   public CeQueueImpl(DbClient dbClient, UuidFactory uuidFactory) {
-    this(dbClient, uuidFactory, new CeQueueListener[] {});
-  }
-
-  public CeQueueImpl(DbClient dbClient, UuidFactory uuidFactory, CeQueueListener[] listeners) {
     this.dbClient = dbClient;
     this.uuidFactory = uuidFactory;
-    this.listeners = listeners;
   }
 
   @Override
@@ -126,7 +118,7 @@ public class CeQueueImpl implements CeQueue {
       .toSet();
     Map<String, ComponentDto> componentDtoByUuid = from(dbClient.componentDao()
       .selectByUuids(dbSession, componentUuids))
-      .uniqueIndex(toUuid());
+        .uniqueIndex(toUuid());
 
     return from(dtos)
       .transform(new CeQueueDtoToCeTask(componentDtoByUuid))
@@ -150,10 +142,9 @@ public class CeQueueImpl implements CeQueue {
   }
 
   protected void cancelImpl(DbSession dbSession, CeQueueDto q) {
-    CeTask task = loadTask(dbSession, q);
     CeActivityDto activityDto = new CeActivityDto(q);
     activityDto.setStatus(CeActivityDto.Status.CANCELED);
-    remove(dbSession, task, q, activityDto);
+    remove(dbSession, q, activityDto);
   }
 
   @Override
@@ -177,13 +168,11 @@ public class CeQueueImpl implements CeQueue {
     }
   }
 
-  protected void remove(DbSession dbSession, CeTask task, CeQueueDto queueDto, CeActivityDto activityDto) {
+  protected void remove(DbSession dbSession, CeQueueDto queueDto, CeActivityDto activityDto) {
     dbClient.ceActivityDao().insert(dbSession, activityDto);
     dbClient.ceQueueDao().deleteByUuid(dbSession, queueDto.getUuid());
+    dbClient.ceTaskDataDao().deleteByUuids(dbSession, singletonList(queueDto.getUuid()));
     dbSession.commit();
-    for (CeQueueListener listener : listeners) {
-      listener.onRemoved(task, activityDto.getStatus());
-    }
   }
 
   @Override
diff --git a/server/sonar-server/src/main/java/org/sonar/ce/queue/CeQueueListener.java b/server/sonar-server/src/main/java/org/sonar/ce/queue/CeQueueListener.java
deleted file mode 100644 (file)
index 90e3a0a..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.ce.queue;
-
-import org.sonar.db.ce.CeActivityDto;
-
-public interface CeQueueListener {
-
-  void onRemoved(CeTask task, CeActivityDto.Status status);
-
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/ce/queue/report/ReportFiles.java b/server/sonar-server/src/main/java/org/sonar/ce/queue/report/ReportFiles.java
deleted file mode 100644 (file)
index 223a88c..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.ce.queue.report;
-
-import java.io.File;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.FilenameUtils;
-import org.apache.commons.io.IOUtils;
-import org.sonar.api.config.Settings;
-import org.sonar.api.ce.ComputeEngineSide;
-import org.sonar.process.ProcessProperties;
-
-import static java.lang.String.format;
-
-@ComputeEngineSide
-public class ReportFiles {
-
-  private static final String ZIP_EXTENSION = "zip";
-
-  private final Settings settings;
-
-  public ReportFiles(Settings settings) {
-    this.settings = settings;
-  }
-
-  public void save(String taskUuid, InputStream reportInput) {
-    File file = fileForUuid(taskUuid);
-    try {
-      FileUtils.copyInputStreamToFile(reportInput, file);
-    } catch (Exception e) {
-      org.sonar.core.util.FileUtils.deleteQuietly(file);
-      IOUtils.closeQuietly(reportInput);
-      throw new IllegalStateException(format("Fail to copy report to file: %s", file.getAbsolutePath()), e);
-    }
-  }
-
-  public void deleteIfExists(String taskUuid) {
-    org.sonar.core.util.FileUtils.deleteQuietly(fileForUuid(taskUuid));
-  }
-
-  public void deleteAll() {
-    File dir = reportDir();
-    if (dir.exists()) {
-      try {
-        org.sonar.core.util.FileUtils.cleanDirectory(dir);
-      } catch (Exception e) {
-        throw new IllegalStateException(format("Fail to clean directory: %s", dir.getAbsolutePath()), e);
-      }
-    }
-  }
-
-  private File reportDir() {
-    return new File(settings.getString(ProcessProperties.PATH_DATA), "ce/reports");
-  }
-
-  /**
-   * The analysis report to be processed. Can't be null
-   * but may no exist on file system.
-   */
-  public File fileForUuid(String taskUuid) {
-    return new File(reportDir(), format("%s.%s", taskUuid, ZIP_EXTENSION));
-  }
-
-  public List<String> listUuids() {
-    List<String> uuids = new ArrayList<>();
-    File dir = reportDir();
-    if (dir.exists()) {
-      Collection<File> files = FileUtils.listFiles(dir, new String[]{ZIP_EXTENSION}, false);
-      for (File file : files) {
-        uuids.add(FilenameUtils.getBaseName(file.getName()));
-      }
-    }
-    return uuids;
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/ce/queue/report/ReportSubmitter.java b/server/sonar-server/src/main/java/org/sonar/ce/queue/report/ReportSubmitter.java
deleted file mode 100644 (file)
index de515bd..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.ce.queue.report;
-
-import java.io.InputStream;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.ce.ComputeEngineSide;
-import org.sonar.api.resources.Qualifiers;
-import org.sonar.ce.queue.CeQueue;
-import org.sonar.ce.queue.CeTask;
-import org.sonar.ce.queue.CeTaskSubmit;
-import org.sonar.core.component.ComponentKeys;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.ce.CeTaskTypes;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.server.component.ComponentService;
-import org.sonar.server.component.NewComponent;
-import org.sonar.server.permission.PermissionService;
-import org.sonar.server.user.UserSession;
-
-import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION;
-import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException;
-
-@ComputeEngineSide
-public class ReportSubmitter {
-
-  private final CeQueue queue;
-  private final UserSession userSession;
-  private final ReportFiles reportFiles;
-  private final ComponentService componentService;
-  private final PermissionService permissionService;
-  private final DbClient dbClient;
-
-  public ReportSubmitter(CeQueue queue, UserSession userSession, ReportFiles reportFiles,
-    ComponentService componentService, PermissionService permissionService, DbClient dbClient) {
-    this.queue = queue;
-    this.userSession = userSession;
-    this.reportFiles = reportFiles;
-    this.componentService = componentService;
-    this.permissionService = permissionService;
-    this.dbClient = dbClient;
-  }
-
-  public CeTask submit(String projectKey, @Nullable String projectBranch, @Nullable String projectName, InputStream reportInput) {
-    String effectiveProjectKey = ComponentKeys.createKey(projectKey, projectBranch);
-    ComponentDto project = componentService.getNullableByKey(effectiveProjectKey);
-    if (project == null) {
-      project = createProject(projectKey, projectBranch, projectName);
-    }
-
-    userSession.checkComponentPermission(SCAN_EXECUTION, projectKey);
-
-    return submitReport(reportInput, project);
-  }
-
-  @CheckForNull
-  private ComponentDto createProject(String projectKey, @Nullable String projectBranch, @Nullable String projectName) {
-    DbSession dbSession = dbClient.openSession(false);
-    try {
-      boolean wouldCurrentUserHaveScanPermission = permissionService.wouldCurrentUserHavePermissionWithDefaultTemplate(dbSession, SCAN_EXECUTION, projectBranch, projectKey,
-        Qualifiers.PROJECT);
-      if (!wouldCurrentUserHaveScanPermission) {
-        throw insufficientPrivilegesException();
-      }
-
-      NewComponent newProject = new NewComponent(projectKey, StringUtils.defaultIfBlank(projectName, projectKey));
-      newProject.setBranch(projectBranch);
-      newProject.setQualifier(Qualifiers.PROJECT);
-      // "provisioning" permission is check in ComponentService
-      ComponentDto project = componentService.create(dbSession, newProject);
-      permissionService.applyDefaultPermissionTemplate(dbSession, project.getKey());
-      return project;
-    } finally {
-      dbClient.closeSession(dbSession);
-    }
-  }
-
-  private CeTask submitReport(InputStream reportInput, ComponentDto project) {
-    // the report file must be saved before submitting the task
-    CeTaskSubmit.Builder submit = queue.prepareSubmit();
-    reportFiles.save(submit.getUuid(), reportInput);
-
-    submit.setType(CeTaskTypes.REPORT);
-    submit.setComponentUuid(project.uuid());
-    submit.setSubmitterLogin(userSession.getLogin());
-    return queue.submit(submit.build());
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/ce/queue/report/package-info.java b/server/sonar-server/src/main/java/org/sonar/ce/queue/report/package-info.java
deleted file mode 100644 (file)
index 3c197b8..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-@ParametersAreNonnullByDefault
-package org.sonar.ce.queue.report;
-
-import javax.annotation.ParametersAreNonnullByDefault;
index 33ca21f62e234d42d045d06dec95c09c23d18cb7..f2e264c447c5ee807b43c74dde9a7aaa8143c2a0 100644 (file)
@@ -25,7 +25,7 @@ import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.ce.queue.CeTask;
-import org.sonar.ce.queue.report.ReportSubmitter;
+import org.sonar.server.computation.queue.ReportSubmitter;
 import org.sonar.server.ws.WsUtils;
 import org.sonarqube.ws.WsCe;
 
index c2a4506ec8bea01dcdea5b2a2471551578b7e946..c34c9be326f0a9ed29a650098dec7904d6b84ce3 100644 (file)
@@ -19,7 +19,6 @@
  */
 package org.sonar.server.computation;
 
-import org.sonar.ce.queue.report.ReportFiles;
 import org.sonar.core.platform.Module;
 import org.sonar.server.computation.monitoring.CEQueueStatusImpl;
 import org.sonar.server.computation.monitoring.CeTasksMBeanImpl;
@@ -40,8 +39,7 @@ public class CeQueueModule extends Module {
 
       // queue cleaning
       CeQueueCleaner.class,
-      ReportFiles.class,
-
+      
       // init queue state and queue processing
       CeQueueInitializer.class);
   }
index b7e644a85585a87e4219f08a3615e2efc0f94ecb..21673dddb30c8fc2fb52ebf2aa95cf2acc31c779 100644 (file)
  */
 package org.sonar.server.computation.queue;
 
-import java.util.HashSet;
-import java.util.Set;
-import org.sonar.api.platform.ServerUpgradeStatus;
+import java.util.List;
 import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.api.platform.ServerUpgradeStatus;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
-import org.sonar.ce.queue.report.ReportFiles;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
-import org.sonar.db.ce.CeQueueDto;
-import org.sonar.db.ce.CeTaskTypes;
 
 /**
  * Cleans-up the Compute Engine queue and resets the JMX counters.
@@ -42,13 +38,11 @@ public class CeQueueCleaner {
 
   private final DbClient dbClient;
   private final ServerUpgradeStatus serverUpgradeStatus;
-  private final ReportFiles reportFiles;
   private final InternalCeQueue queue;
 
-  public CeQueueCleaner(DbClient dbClient, ServerUpgradeStatus serverUpgradeStatus, ReportFiles reportFiles, InternalCeQueue queue) {
+  public CeQueueCleaner(DbClient dbClient, ServerUpgradeStatus serverUpgradeStatus, InternalCeQueue queue) {
     this.dbClient = dbClient;
     this.serverUpgradeStatus = serverUpgradeStatus;
-    this.reportFiles = reportFiles;
     this.queue = queue;
   }
 
@@ -72,21 +66,11 @@ public class CeQueueCleaner {
     dbClient.ceQueueDao().resetAllToPendingStatus(dbSession);
     dbSession.commit();
 
-    // verify that the report files are available for the tasks in queue
-    Set<String> uuidsInQueue = new HashSet<>();
-    for (CeQueueDto queueDto : dbClient.ceQueueDao().selectAllInAscOrder(dbSession)) {
-      uuidsInQueue.add(queueDto.getUuid());
-      if (CeTaskTypes.REPORT.equals(queueDto.getTaskType()) && !reportFiles.fileForUuid(queueDto.getUuid()).exists()) {
-        // the report is not available on file system
-        queue.cancel(dbSession, queueDto);
-      }
-    }
-
-    // clean-up filesystem
-    for (String uuid : reportFiles.listUuids()) {
-      if (!uuidsInQueue.contains(uuid)) {
-        reportFiles.deleteIfExists(uuid);
-      }
-    }
+    // Reports that have been processed are not kept in database yet.
+    // They are supposed to be systematically dropped.
+    // Let's clean-up orphans if any.
+    List<String> uuids = dbClient.ceTaskDataDao().selectUuidsNotInQueue(dbSession);
+    dbClient.ceTaskDataDao().deleteByUuids(dbSession, uuids);
+    dbSession.commit();
   }
 }
index 6eb9497d81c8d4a640063047cdaa8a66789eee39..34cd6d4f337a9711398a5ba1f68a32d3d9bec590 100644 (file)
@@ -26,7 +26,6 @@ import org.sonar.api.ce.ComputeEngineSide;
 import org.sonar.api.utils.System2;
 import org.sonar.ce.monitoring.CEQueueStatus;
 import org.sonar.ce.queue.CeQueueImpl;
-import org.sonar.ce.queue.CeQueueListener;
 import org.sonar.ce.queue.CeTask;
 import org.sonar.ce.queue.CeTaskResult;
 import org.sonar.core.util.UuidFactory;
@@ -48,8 +47,8 @@ public class InternalCeQueueImpl extends CeQueueImpl implements InternalCeQueue
   private AtomicBoolean peekPaused = new AtomicBoolean(false);
 
   public InternalCeQueueImpl(System2 system2, DbClient dbClient, UuidFactory uuidFactory,
-    CEQueueStatus queueStatus, CeQueueListener[] listeners) {
-    super(dbClient, uuidFactory, listeners);
+    CEQueueStatus queueStatus) {
+    super(dbClient, uuidFactory);
     this.system2 = system2;
     this.dbClient = dbClient;
     this.queueStatus = queueStatus;
@@ -92,7 +91,7 @@ public class InternalCeQueueImpl extends CeQueueImpl implements InternalCeQueue
       activityDto.setStatus(status);
       updateQueueStatus(status, activityDto);
       updateTaskResult(activityDto, taskResult);
-      remove(dbSession, task, queueDto.get(), activityDto);
+      remove(dbSession, queueDto.get(), activityDto);
 
     } finally {
       dbClient.closeSession(dbSession);
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/queue/ReportSubmitter.java b/server/sonar-server/src/main/java/org/sonar/server/computation/queue/ReportSubmitter.java
new file mode 100644 (file)
index 0000000..f60f666
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.queue;
+
+import java.io.InputStream;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.server.ServerSide;
+import org.sonar.ce.queue.CeQueue;
+import org.sonar.ce.queue.CeTask;
+import org.sonar.ce.queue.CeTaskSubmit;
+import org.sonar.core.component.ComponentKeys;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.ce.CeTaskTypes;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.server.component.ComponentService;
+import org.sonar.server.component.NewComponent;
+import org.sonar.server.permission.PermissionService;
+import org.sonar.server.user.UserSession;
+
+import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION;
+import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException;
+
+@ServerSide
+public class ReportSubmitter {
+
+  private final CeQueue queue;
+  private final UserSession userSession;
+  private final ComponentService componentService;
+  private final PermissionService permissionService;
+  private final DbClient dbClient;
+
+  public ReportSubmitter(CeQueue queue, UserSession userSession,
+    ComponentService componentService, PermissionService permissionService, DbClient dbClient) {
+    this.queue = queue;
+    this.userSession = userSession;
+    this.componentService = componentService;
+    this.permissionService = permissionService;
+    this.dbClient = dbClient;
+  }
+
+  public CeTask submit(String projectKey, @Nullable String projectBranch, @Nullable String projectName, InputStream reportInput) {
+    String effectiveProjectKey = ComponentKeys.createKey(projectKey, projectBranch);
+    ComponentDto project = componentService.getNullableByKey(effectiveProjectKey);
+    if (project == null) {
+      project = createProject(projectKey, projectBranch, projectName);
+    }
+
+    userSession.checkComponentPermission(SCAN_EXECUTION, projectKey);
+
+    return submitReport(reportInput, project);
+  }
+
+  @CheckForNull
+  private ComponentDto createProject(String projectKey, @Nullable String projectBranch, @Nullable String projectName) {
+    DbSession dbSession = dbClient.openSession(false);
+    try {
+      boolean wouldCurrentUserHaveScanPermission = permissionService.wouldCurrentUserHavePermissionWithDefaultTemplate(dbSession, SCAN_EXECUTION, projectBranch, projectKey,
+        Qualifiers.PROJECT);
+      if (!wouldCurrentUserHaveScanPermission) {
+        throw insufficientPrivilegesException();
+      }
+
+      NewComponent newProject = new NewComponent(projectKey, StringUtils.defaultIfBlank(projectName, projectKey));
+      newProject.setBranch(projectBranch);
+      newProject.setQualifier(Qualifiers.PROJECT);
+      // "provisioning" permission is check in ComponentService
+      ComponentDto project = componentService.create(dbSession, newProject);
+      permissionService.applyDefaultPermissionTemplate(dbSession, project.getKey());
+      return project;
+    } finally {
+      dbClient.closeSession(dbSession);
+    }
+  }
+
+  private CeTask submitReport(InputStream reportInput, ComponentDto project) {
+    // the report file must be saved before submitting the task
+    CeTaskSubmit.Builder submit = queue.prepareSubmit();
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      dbClient.ceTaskDataDao().insert(dbSession, submit.getUuid(), reportInput);
+      dbSession.commit();
+    }
+
+    submit.setType(CeTaskTypes.REPORT);
+    submit.setComponentUuid(project.uuid());
+    submit.setSubmitterLogin(userSession.getLogin());
+    return queue.submit(submit.build());
+  }
+}
index 7c4bb3b4980eb73382ea54c99cc5a1f696916156..846bb413a2f5149b8dd35289400f5577ed9a38b4 100644 (file)
  */
 package org.sonar.server.computation.task.projectanalysis;
 
-import org.sonar.ce.queue.report.ReportSubmitter;
+import org.sonar.server.computation.queue.ReportSubmitter;
 import org.sonar.core.platform.Module;
 import org.sonar.server.computation.task.projectanalysis.container.ContainerFactoryImpl;
-import org.sonar.server.computation.task.projectanalysis.queue.CleanReportQueueListener;
 import org.sonar.server.computation.task.projectanalysis.taskprocessor.ReportTaskProcessor;
 import org.sonar.server.computation.task.step.ComputationStepExecutor;
 
@@ -30,9 +29,6 @@ public class ProjectAnalysisTaskModule extends Module {
   @Override
   protected void configureModule() {
     add(
-      // queue
-      CleanReportQueueListener.class,
-
       // task
       ContainerFactoryImpl.class,
       ComputationStepExecutor.class,
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/queue/CleanReportQueueListener.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/queue/CleanReportQueueListener.java
deleted file mode 100644 (file)
index 8c90ead..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.computation.task.projectanalysis.queue;
-
-import org.sonar.ce.queue.report.ReportFiles;
-import org.sonar.db.ce.CeActivityDto;
-import org.sonar.ce.queue.CeQueueListener;
-import org.sonar.ce.queue.CeTask;
-
-public class CleanReportQueueListener implements CeQueueListener {
-
-  private final ReportFiles reportFiles;
-
-  public CleanReportQueueListener(ReportFiles reportFiles) {
-    this.reportFiles = reportFiles;
-  }
-
-  @Override
-  public void onRemoved(CeTask task, CeActivityDto.Status status) {
-    reportFiles.deleteIfExists(task.getUuid());
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/queue/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/queue/package-info.java
deleted file mode 100644 (file)
index 76cc120..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-@ParametersAreNonnullByDefault
-package org.sonar.server.computation.task.projectanalysis.queue;
-
-import javax.annotation.ParametersAreNonnullByDefault;
index 566e3afeca4fd61e8bec9475b53401412b0efaca..6d94a12a777d4f6c14f1c5e655e31a9934fccfe7 100644 (file)
@@ -21,13 +21,16 @@ package org.sonar.server.computation.task.projectanalysis.step;
 
 import java.io.File;
 import java.io.IOException;
-import org.apache.commons.io.FileUtils;
+import java.util.Optional;
+import org.sonar.api.utils.MessageException;
 import org.sonar.api.utils.TempFolder;
 import org.sonar.api.utils.ZipUtils;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.ce.queue.CeTask;
-import org.sonar.ce.queue.report.ReportFiles;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.ce.CeTaskDataDao;
 import org.sonar.server.computation.task.projectanalysis.batch.MutableBatchReportDirectoryHolder;
 import org.sonar.server.computation.task.step.ComputationStep;
 
@@ -38,14 +41,14 @@ import org.sonar.server.computation.task.step.ComputationStep;
 public class ExtractReportStep implements ComputationStep {
   private static final Logger LOG = Loggers.get(ExtractReportStep.class);
 
-  private final ReportFiles reportFiles;
+  private final DbClient dbClient;
   private final CeTask task;
   private final TempFolder tempFolder;
   private final MutableBatchReportDirectoryHolder reportDirectoryHolder;
 
-  public ExtractReportStep(ReportFiles reportFiles, CeTask task, TempFolder tempFolder,
+  public ExtractReportStep(DbClient dbClient, CeTask task, TempFolder tempFolder,
     MutableBatchReportDirectoryHolder reportDirectoryHolder) {
-    this.reportFiles = reportFiles;
+    this.dbClient = dbClient;
     this.task = task;
     this.tempFolder = tempFolder;
     this.reportDirectoryHolder = reportDirectoryHolder;
@@ -53,14 +56,20 @@ public class ExtractReportStep implements ComputationStep {
 
   @Override
   public void execute() {
-    File dir = tempFolder.newDir();
-    File zip = reportFiles.fileForUuid(task.getUuid());
-    try {
-      ZipUtils.unzip(zip, dir);
-      reportDirectoryHolder.setDirectory(dir);
-      LOG.info("Analysis report extracted | compressedSize={}", FileUtils.byteCountToDisplaySize(FileUtils.sizeOf(zip)));
-    } catch (IOException e) {
-      throw new IllegalStateException(String.format("Fail to unzip %s into %s", zip, dir), e);
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      Optional<CeTaskDataDao.DataStream> opt = dbClient.ceTaskDataDao().selectData(dbSession, task.getUuid());
+      if (opt.isPresent()) {
+        File unzippedDir = tempFolder.newDir();
+        try (CeTaskDataDao.DataStream reportStream = opt.get()) {
+          ZipUtils.unzip(reportStream.getInputStream(), unzippedDir);
+        } catch (IOException e) {
+          throw new IllegalStateException("Fail to extract report " + task.getUuid() + " from database", e);
+        }
+        reportDirectoryHolder.setDirectory(unzippedDir);
+        LOG.info("Analysis report extracted");
+      } else {
+        throw MessageException.of("Analysis report " + task.getUuid() + " is missing in database");
+      }
     }
   }
 
index cc75f377a9d7cb29c70e8bc8c73493b69e06541b..4debf37fc32238136b37901b9e33d63f0aaa1fa9 100644 (file)
@@ -40,9 +40,6 @@ import org.sonar.db.component.ComponentTesting;
 import static java.util.Arrays.asList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.hamcrest.Matchers.startsWith;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
 
 public class CeQueueImplTest {
 
@@ -56,8 +53,7 @@ public class CeQueueImplTest {
   DbSession session = dbTester.getSession();
 
   UuidFactory uuidFactory = UuidFactoryImpl.INSTANCE;
-  CeQueueListener listener = mock(CeQueueListener.class);
-  CeQueue underTest = new CeQueueImpl(dbTester.getDbClient(), uuidFactory, new CeQueueListener[] {listener});
+  CeQueue underTest = new CeQueueImpl(dbTester.getDbClient(), uuidFactory);
 
   @Test
   public void submit_returns_task_populated_from_CeTaskSubmit_and_creates_CeQueue_row() {
@@ -131,14 +127,12 @@ public class CeQueueImplTest {
     // ignore
     boolean canceled = underTest.cancel("UNKNOWN");
     assertThat(canceled).isFalse();
-    verifyZeroInteractions(listener);
 
     canceled = underTest.cancel(task.getUuid());
     assertThat(canceled).isTrue();
     Optional<CeActivityDto> activity = dbTester.getDbClient().ceActivityDao().selectByUuid(dbTester.getSession(), task.getUuid());
     assertThat(activity.isPresent()).isTrue();
     assertThat(activity.get().getStatus()).isEqualTo(CeActivityDto.Status.CANCELED);
-    verify(listener).onRemoved(task, CeActivityDto.Status.CANCELED);
   }
 
   @Test
@@ -170,9 +164,6 @@ public class CeQueueImplTest {
     assertThat(history.get().getStatus()).isEqualTo(CeActivityDto.Status.CANCELED);
     history = dbTester.getDbClient().ceActivityDao().selectByUuid(dbTester.getSession(), inProgressTask.getUuid());
     assertThat(history.isPresent()).isFalse();
-
-    verify(listener).onRemoved(pendingTask1, CeActivityDto.Status.CANCELED);
-    verify(listener).onRemoved(pendingTask2, CeActivityDto.Status.CANCELED);
   }
 
   @Test
diff --git a/server/sonar-server/src/test/java/org/sonar/ce/queue/report/ReportSubmitterTest.java b/server/sonar-server/src/test/java/org/sonar/ce/queue/report/ReportSubmitterTest.java
deleted file mode 100644 (file)
index c98b751..0000000
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.ce.queue.report;
-
-import org.apache.commons.io.IOUtils;
-import org.hamcrest.Description;
-import org.hamcrest.TypeSafeMatcher;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.api.resources.Qualifiers;
-import org.sonar.ce.queue.CeQueue;
-import org.sonar.ce.queue.CeQueueImpl;
-import org.sonar.ce.queue.CeTaskSubmit;
-import org.sonar.core.permission.GlobalPermissions;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.ce.CeTaskTypes;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.server.component.ComponentService;
-import org.sonar.server.component.NewComponent;
-import org.sonar.server.exceptions.ForbiddenException;
-import org.sonar.server.permission.PermissionService;
-import org.sonar.server.tester.UserSessionRule;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-import static org.sonar.core.permission.GlobalPermissions.PROVISIONING;
-import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION;
-
-public class ReportSubmitterTest {
-
-  static final String PROJECT_KEY = "MY_PROJECT";
-  static final String PROJECT_UUID = "P1";
-  static final String PROJECT_NAME = "My Project";
-  static final String TASK_UUID = "TASK_1";
-
-  @Rule
-  public ExpectedException thrown = ExpectedException.none();
-
-  @Rule
-  public UserSessionRule userSession = UserSessionRule.standalone();
-
-  CeQueue queue = mock(CeQueueImpl.class);
-  ReportFiles reportFiles = mock(ReportFiles.class);
-  ComponentService componentService = mock(ComponentService.class);
-  PermissionService permissionService = mock(PermissionService.class);
-  DbClient dbClient = mock(DbClient.class);
-  ReportSubmitter underTest = new ReportSubmitter(queue, userSession, reportFiles, componentService, permissionService, dbClient);
-
-  @Test
-  public void submit_a_report_on_existing_project() {
-    userSession.setGlobalPermissions(SCAN_EXECUTION);
-
-    when(queue.prepareSubmit()).thenReturn(new CeTaskSubmit.Builder(TASK_UUID));
-    when(componentService.getNullableByKey(PROJECT_KEY)).thenReturn(new ComponentDto().setUuid(PROJECT_UUID));
-
-    underTest.submit(PROJECT_KEY, null, PROJECT_NAME, IOUtils.toInputStream("{binary}"));
-
-    verifyZeroInteractions(permissionService);
-    verify(queue).submit(argThat(new TypeSafeMatcher<CeTaskSubmit>() {
-      @Override
-      protected boolean matchesSafely(CeTaskSubmit submit) {
-        return submit.getType().equals(CeTaskTypes.REPORT) && submit.getComponentUuid().equals(PROJECT_UUID) &&
-          submit.getUuid().equals(TASK_UUID);
-      }
-
-      @Override
-      public void describeTo(Description description) {
-
-      }
-    }));
-  }
-
-  @Test
-  public void provision_project_if_does_not_exist() throws Exception {
-    userSession.setGlobalPermissions(SCAN_EXECUTION, PROVISIONING);
-
-    when(queue.prepareSubmit()).thenReturn(new CeTaskSubmit.Builder(TASK_UUID));
-    when(componentService.getNullableByKey(PROJECT_KEY)).thenReturn(null);
-    when(componentService.create(any(DbSession.class), any(NewComponent.class))).thenReturn(new ComponentDto().setUuid(PROJECT_UUID).setKey(PROJECT_KEY));
-    when(permissionService.wouldCurrentUserHavePermissionWithDefaultTemplate(any(DbSession.class), eq(SCAN_EXECUTION), anyString(), eq(PROJECT_KEY), eq(Qualifiers.PROJECT)))
-      .thenReturn(true);
-
-    underTest.submit(PROJECT_KEY, null, PROJECT_NAME, IOUtils.toInputStream("{binary}"));
-
-    verify(permissionService).applyDefaultPermissionTemplate(any(DbSession.class), eq(PROJECT_KEY));
-    verify(queue).submit(argThat(new TypeSafeMatcher<CeTaskSubmit>() {
-      @Override
-      protected boolean matchesSafely(CeTaskSubmit submit) {
-        return submit.getType().equals(CeTaskTypes.REPORT) && submit.getComponentUuid().equals(PROJECT_UUID) &&
-          submit.getUuid().equals(TASK_UUID);
-      }
-
-      @Override
-      public void describeTo(Description description) {
-
-      }
-    }));
-  }
-
-  @Test
-  public void submit_a_report_on_new_project_with_global_scan_permission() {
-    userSession.setGlobalPermissions(SCAN_EXECUTION, PROVISIONING);
-
-    when(queue.prepareSubmit()).thenReturn(new CeTaskSubmit.Builder(TASK_UUID));
-    when(componentService.getNullableByKey(PROJECT_KEY)).thenReturn(null);
-    when(componentService.create(any(DbSession.class), any(NewComponent.class))).thenReturn(new ComponentDto().setUuid(PROJECT_UUID).setKey(PROJECT_KEY));
-    when(permissionService.wouldCurrentUserHavePermissionWithDefaultTemplate(any(DbSession.class), eq(SCAN_EXECUTION), anyString(), eq(PROJECT_KEY), eq(Qualifiers.PROJECT)))
-      .thenReturn(true);
-
-    underTest.submit(PROJECT_KEY, null, PROJECT_NAME, IOUtils.toInputStream("{binary}"));
-
-    verify(queue).submit(any(CeTaskSubmit.class));
-  }
-
-  @Test
-  public void submit_a_report_on_existing_project_with_global_scan_permission() {
-    userSession.setGlobalPermissions(SCAN_EXECUTION);
-
-    when(queue.prepareSubmit()).thenReturn(new CeTaskSubmit.Builder(TASK_UUID));
-    when(componentService.getNullableByKey(PROJECT_KEY)).thenReturn(new ComponentDto().setUuid(PROJECT_UUID));
-
-    underTest.submit(PROJECT_KEY, null, PROJECT_NAME, IOUtils.toInputStream("{binary}"));
-
-    verify(queue).submit(any(CeTaskSubmit.class));
-  }
-
-  @Test
-  public void submit_a_report_on_existing_project_with_project_scan_permission() {
-    userSession.addProjectPermissions(SCAN_EXECUTION, PROJECT_KEY);
-
-    when(queue.prepareSubmit()).thenReturn(new CeTaskSubmit.Builder(TASK_UUID));
-    when(componentService.getNullableByKey(PROJECT_KEY)).thenReturn(new ComponentDto().setUuid(PROJECT_UUID));
-
-    underTest.submit(PROJECT_KEY, null, PROJECT_NAME, IOUtils.toInputStream("{binary}"));
-
-    verify(queue).submit(any(CeTaskSubmit.class));
-  }
-
-  @Test
-  public void fail_with_forbidden_exception_when_no_scan_permission() {
-    userSession.setGlobalPermissions(GlobalPermissions.DASHBOARD_SHARING);
-
-    thrown.expect(ForbiddenException.class);
-    underTest.submit(PROJECT_KEY, null, PROJECT_NAME, IOUtils.toInputStream("{binary}"));
-  }
-
-  @Test
-  public void fail_with_forbidden_exception_on_new_project_when_only_project_scan_permission() {
-    userSession.addProjectPermissions(SCAN_EXECUTION, PROJECT_KEY);
-
-    when(queue.prepareSubmit()).thenReturn(new CeTaskSubmit.Builder(TASK_UUID));
-    when(componentService.getNullableByKey(PROJECT_KEY)).thenReturn(null);
-    when(componentService.create(any(DbSession.class), any(NewComponent.class))).thenReturn(new ComponentDto().setUuid(PROJECT_UUID).setKey(PROJECT_KEY));
-
-    thrown.expect(ForbiddenException.class);
-    underTest.submit(PROJECT_KEY, null, PROJECT_NAME, IOUtils.toInputStream("{binary}"));
-  }
-
-}
index 1ec0618dbb5621be6a323d16d1d4f4d2f71efe3b..16f7dd9b2f5ab418bf0171b4adcb52bbcb37012a 100644 (file)
@@ -22,7 +22,7 @@ package org.sonar.server.ce.ws;
 import org.junit.Test;
 import org.mockito.Mockito;
 import org.sonar.api.server.ws.WebService;
-import org.sonar.ce.queue.report.ReportSubmitter;
+import org.sonar.server.computation.queue.ReportSubmitter;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
index b34cb071f2327f359deede161452c38314d62001..b7899753a283d8501d5ad11dae739f3cf3608b80 100644 (file)
@@ -25,7 +25,7 @@ import org.mockito.Matchers;
 import org.sonar.core.util.Protobuf;
 import org.sonar.db.ce.CeTaskTypes;
 import org.sonar.ce.queue.CeTask;
-import org.sonar.ce.queue.report.ReportSubmitter;
+import org.sonar.server.computation.queue.ReportSubmitter;
 import org.sonarqube.ws.MediaTypes;
 import org.sonar.server.ws.TestResponse;
 import org.sonar.server.ws.WsActionTester;
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/ReportFilesTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/ReportFilesTest.java
deleted file mode 100644 (file)
index f4aee71..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.computation;
-
-import java.io.File;
-import java.io.IOException;
-import org.apache.commons.io.FileUtils;
-import org.h2.util.IOUtils;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.api.config.Settings;
-import org.sonar.process.ProcessProperties;
-import org.sonar.ce.queue.report.ReportFiles;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class ReportFilesTest {
-
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-
-  File reportDir;
-  Settings settings = new Settings();
-  ReportFiles underTest = new ReportFiles(settings);
-
-  @Before
-  public void setUp() throws IOException {
-    File dataDir = temp.newFolder();
-    reportDir = new File(dataDir, "ce/reports");
-    settings.setProperty(ProcessProperties.PATH_DATA, dataDir.getCanonicalPath());
-  }
-
-  @Test
-  public void save_report() throws IOException {
-    underTest.save("TASK_1", IOUtils.getInputStreamFromString("{binary}"));
-
-    assertThat(FileUtils.readFileToString(new File(reportDir, "TASK_1.zip"))).isEqualTo("{binary}");
-
-  }
-
-  @Test
-  public void deleteIfExists_uuid_does_not_exist() {
-    // do not fail, does nothing
-    underTest.deleteIfExists("TASK_1");
-  }
-
-  @Test
-  public void deleteIfExists() throws IOException {
-    File report = new File(reportDir, "TASK_1.zip");
-    FileUtils.touch(report);
-    assertThat(report).exists();
-
-    underTest.deleteIfExists("TASK_1");
-    assertThat(report).doesNotExist();
-  }
-
-  /**
-   * List the zip files contained in the report directory
-   */
-  @Test
-  public void listUuids() throws IOException {
-    FileUtils.touch(new File(reportDir, "TASK_1.zip"));
-    FileUtils.touch(new File(reportDir, "TASK_2.zip"));
-    FileUtils.touch(new File(reportDir, "something.else"));
-
-    assertThat(underTest.listUuids()).containsOnly("TASK_1", "TASK_2");
-  }
-
-  @Test
-  public void listUuids_dir_does_not_exist_yet() throws IOException {
-    FileUtils.deleteQuietly(reportDir);
-
-    assertThat(underTest.listUuids()).isEmpty();
-  }
-
-  @Test
-  public void deleteAll() throws IOException {
-    FileUtils.touch(new File(reportDir, "TASK_1.zip"));
-    FileUtils.touch(new File(reportDir, "TASK_2.zip"));
-    FileUtils.touch(new File(reportDir, "something.else"));
-
-    underTest.deleteAll();
-
-    // directory still exists but is empty
-    assertThat(reportDir).exists().isDirectory();
-    assertThat(reportDir.listFiles()).isEmpty();
-  }
-
-  @Test
-  public void deleteAll_dir_does_not_exist_yet() throws IOException {
-    FileUtils.deleteQuietly(reportDir);
-
-    underTest.deleteAll();
-
-    assertThat(reportDir).doesNotExist();
-  }
-}
index 7ea418c57488ce0d7f1f4ce315106ce34bca7e57..923a684213cbd5e3216d4e77a3a3213ce0d3c557 100644 (file)
  */
 package org.sonar.server.computation.queue;
 
-import java.io.File;
 import java.io.IOException;
+import java.util.Optional;
+import org.apache.commons.io.IOUtils;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
-import org.mockito.Mockito;
 import org.sonar.api.platform.ServerUpgradeStatus;
 import org.sonar.api.utils.System2;
-import org.sonar.ce.queue.report.ReportFiles;
-import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
 import org.sonar.db.ce.CeQueueDto;
+import org.sonar.db.ce.CeTaskDataDao;
 import org.sonar.db.ce.CeTaskTypes;
 
-import static java.util.Arrays.asList;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -49,10 +45,9 @@ public class CeQueueCleanerTest {
   @Rule
   public TemporaryFolder tempFolder = new TemporaryFolder();
 
-  ServerUpgradeStatus serverUpgradeStatus = mock(ServerUpgradeStatus.class);
-  ReportFiles reportFiles = mock(ReportFiles.class, Mockito.RETURNS_DEEP_STUBS);
-  InternalCeQueue queue = mock(InternalCeQueue.class);
-  CeQueueCleaner underTest = new CeQueueCleaner(dbTester.getDbClient(), serverUpgradeStatus, reportFiles, queue);
+  private ServerUpgradeStatus serverUpgradeStatus = mock(ServerUpgradeStatus.class);
+  private InternalCeQueue queue = mock(InternalCeQueue.class);
+  private CeQueueCleaner underTest = new CeQueueCleaner(dbTester.getDbClient(), serverUpgradeStatus, queue);
 
   @Test
   public void reset_in_progress_tasks_to_pending() throws IOException {
@@ -74,44 +69,37 @@ public class CeQueueCleanerTest {
     verify(queue).clear();
   }
 
-  @Test
-  public void cancel_task_if_report_file_is_missing() throws IOException {
-    CeQueueDto task = insertInQueue("TASK_1", CeQueueDto.Status.PENDING, false);
-
-    underTest.clean(dbTester.getSession());
-
-    verify(queue).cancel(any(DbSession.class), eq(task));
-  }
-
   @Test
   public void delete_orphan_report_files() throws Exception {
-    // two files on disk but on task in queue
-    insertInQueue("TASK_1", CeQueueDto.Status.PENDING, true);
-    when(reportFiles.listUuids()).thenReturn(asList("TASK_1", "TASK_2"));
+    // analysis reports are persisted but the associated
+    // task is not in the queue
+    insertInQueue("TASK_1", CeQueueDto.Status.PENDING);
+    insertTaskData("TASK_1");
+    insertTaskData("TASK_2");
 
     underTest.clean(dbTester.getSession());
 
-    verify(reportFiles).deleteIfExists("TASK_2");
-  }
+    CeTaskDataDao dataDao = dbTester.getDbClient().ceTaskDataDao();
+    Optional<CeTaskDataDao.DataStream> task1Data = dataDao.selectData(dbTester.getSession(), "TASK_1");
+    assertThat(task1Data).isPresent();
+    task1Data.get().close();
 
-  private void insertInQueue(String taskUuid, CeQueueDto.Status status) throws IOException {
-    insertInQueue(taskUuid, status, true);
+    assertThat(dataDao.selectData(dbTester.getSession(), "TASK_2")).isNotPresent();
   }
 
-  private CeQueueDto insertInQueue(String taskUuid, CeQueueDto.Status status, boolean createFile) throws IOException {
-    CeQueueDto queueDto = new CeQueueDto();
-    queueDto.setTaskType(CeTaskTypes.REPORT);
-    queueDto.setComponentUuid("PROJECT_1");
-    queueDto.setUuid(taskUuid);
-    queueDto.setStatus(status);
-    dbTester.getDbClient().ceQueueDao().insert(dbTester.getSession(), queueDto);
+  private CeQueueDto insertInQueue(String taskUuid, CeQueueDto.Status status) throws IOException {
+    CeQueueDto dto = new CeQueueDto();
+    dto.setTaskType(CeTaskTypes.REPORT);
+    dto.setComponentUuid("PROJECT_1");
+    dto.setUuid(taskUuid);
+    dto.setStatus(status);
+    dbTester.getDbClient().ceQueueDao().insert(dbTester.getSession(), dto);
     dbTester.getSession().commit();
+    return dto;
+  }
 
-    File file = tempFolder.newFile();
-    when(reportFiles.fileForUuid(taskUuid)).thenReturn(file);
-    if (!createFile) {
-      file.delete();
-    }
-    return queueDto;
+  private void insertTaskData(String taskUuid) throws IOException {
+    dbTester.getDbClient().ceTaskDataDao().insert(dbTester.getSession(), taskUuid, IOUtils.toInputStream("{binary}"));
+    dbTester.getSession().commit();
   }
 }
index 0512c1097e13f0dcac19133e1a930ee83fcd032a..2ec21d16240810af126e544ff69b8e46216a3240 100644 (file)
@@ -28,7 +28,6 @@ import org.junit.rules.ExpectedException;
 import org.sonar.api.utils.System2;
 import org.sonar.api.utils.internal.TestSystem2;
 import org.sonar.ce.monitoring.CEQueueStatus;
-import org.sonar.ce.queue.CeQueueListener;
 import org.sonar.ce.queue.CeTask;
 import org.sonar.ce.queue.CeTaskResult;
 import org.sonar.ce.queue.CeTaskSubmit;
@@ -46,12 +45,7 @@ import org.sonar.server.computation.monitoring.CEQueueStatusImpl;
 import static java.util.Arrays.asList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.hamcrest.Matchers.startsWith;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 public class InternalCeQueueImplTest {
@@ -69,8 +63,7 @@ public class InternalCeQueueImplTest {
 
   UuidFactory uuidFactory = UuidFactoryImpl.INSTANCE;
   CEQueueStatus queueStatus = new CEQueueStatusImpl(dbTester.getDbClient());
-  CeQueueListener listener = mock(CeQueueListener.class);
-  InternalCeQueue underTest = new InternalCeQueueImpl(system2, dbTester.getDbClient(), uuidFactory, queueStatus, new CeQueueListener[] {listener});
+  InternalCeQueue underTest = new InternalCeQueueImpl(system2, dbTester.getDbClient(), uuidFactory, queueStatus);
 
   @Test
   public void submit_returns_task_populated_from_CeTaskSubmit_and_creates_CeQueue_row() {
@@ -153,8 +146,6 @@ public class InternalCeQueueImplTest {
     assertThat(history.get().getStatus()).isEqualTo(CeActivityDto.Status.SUCCESS);
     assertThat(history.get().getIsLast()).isTrue();
     assertThat(history.get().getAnalysisUuid()).isNull();
-
-    verify(listener).onRemoved(task, CeActivityDto.Status.SUCCESS);
   }
 
   @Test
@@ -205,8 +196,6 @@ public class InternalCeQueueImplTest {
     // no more pending tasks
     peek = underTest.peek();
     assertThat(peek.isPresent()).isFalse();
-
-    verify(listener, never()).onRemoved(eq(task), any(CeActivityDto.Status.class));
   }
 
   @Test
@@ -225,14 +214,12 @@ public class InternalCeQueueImplTest {
     // ignore
     boolean canceled = underTest.cancel("UNKNOWN");
     assertThat(canceled).isFalse();
-    verifyZeroInteractions(listener);
 
     canceled = underTest.cancel(task.getUuid());
     assertThat(canceled).isTrue();
     Optional<CeActivityDto> activity = dbTester.getDbClient().ceActivityDao().selectByUuid(dbTester.getSession(), task.getUuid());
     assertThat(activity.isPresent()).isTrue();
     assertThat(activity.get().getStatus()).isEqualTo(CeActivityDto.Status.CANCELED);
-    verify(listener).onRemoved(task, CeActivityDto.Status.CANCELED);
   }
 
   @Test
@@ -262,9 +249,6 @@ public class InternalCeQueueImplTest {
     assertThat(history.get().getStatus()).isEqualTo(CeActivityDto.Status.CANCELED);
     history = dbTester.getDbClient().ceActivityDao().selectByUuid(dbTester.getSession(), inProgressTask.getUuid());
     assertThat(history.isPresent()).isFalse();
-
-    verify(listener).onRemoved(pendingTask1, CeActivityDto.Status.CANCELED);
-    verify(listener).onRemoved(pendingTask2, CeActivityDto.Status.CANCELED);
   }
 
   @Test
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/queue/ReportSubmitterTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/queue/ReportSubmitterTest.java
new file mode 100644 (file)
index 0000000..0e6443b
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.queue;
+
+import org.apache.commons.io.IOUtils;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+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.core.permission.GlobalPermissions;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.ce.CeTaskTypes;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.server.component.ComponentService;
+import org.sonar.server.component.NewComponent;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.permission.PermissionService;
+import org.sonar.server.tester.UserSessionRule;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.core.permission.GlobalPermissions.PROVISIONING;
+import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION;
+
+public class ReportSubmitterTest {
+
+  static final String PROJECT_KEY = "MY_PROJECT";
+  static final String PROJECT_UUID = "P1";
+  static final String PROJECT_NAME = "My Project";
+  static final String TASK_UUID = "TASK_1";
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  @Rule
+  public UserSessionRule userSession = UserSessionRule.standalone();
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private CeQueue queue = mock(CeQueueImpl.class);
+  private ComponentService componentService = mock(ComponentService.class);
+  private PermissionService permissionService = mock(PermissionService.class);
+  private ReportSubmitter underTest = new ReportSubmitter(queue, userSession, componentService, permissionService, dbTester.getDbClient());
+
+  @Test
+  public void submit_a_report_on_existing_project() {
+    userSession.setGlobalPermissions(SCAN_EXECUTION);
+
+    when(queue.prepareSubmit()).thenReturn(new CeTaskSubmit.Builder(TASK_UUID));
+    when(componentService.getNullableByKey(PROJECT_KEY)).thenReturn(new ComponentDto().setUuid(PROJECT_UUID));
+
+    underTest.submit(PROJECT_KEY, null, PROJECT_NAME, IOUtils.toInputStream("{binary}"));
+
+    verifyReportIsPersisted(TASK_UUID);
+    verifyZeroInteractions(permissionService);
+    verify(queue).submit(argThat(new TypeSafeMatcher<CeTaskSubmit>() {
+      @Override
+      protected boolean matchesSafely(CeTaskSubmit submit) {
+        return submit.getType().equals(CeTaskTypes.REPORT) && submit.getComponentUuid().equals(PROJECT_UUID) &&
+          submit.getUuid().equals(TASK_UUID);
+      }
+
+      @Override
+      public void describeTo(Description description) {
+
+      }
+    }));
+  }
+
+  @Test
+  public void provision_project_if_does_not_exist() throws Exception {
+    userSession.setGlobalPermissions(SCAN_EXECUTION, PROVISIONING);
+
+    when(queue.prepareSubmit()).thenReturn(new CeTaskSubmit.Builder(TASK_UUID));
+    when(componentService.getNullableByKey(PROJECT_KEY)).thenReturn(null);
+    when(componentService.create(any(DbSession.class), any(NewComponent.class))).thenReturn(new ComponentDto().setUuid(PROJECT_UUID).setKey(PROJECT_KEY));
+    when(permissionService.wouldCurrentUserHavePermissionWithDefaultTemplate(any(DbSession.class), eq(SCAN_EXECUTION), anyString(), eq(PROJECT_KEY), eq(Qualifiers.PROJECT)))
+      .thenReturn(true);
+
+    underTest.submit(PROJECT_KEY, null, PROJECT_NAME, IOUtils.toInputStream("{binary}"));
+
+    verifyReportIsPersisted(TASK_UUID);
+    verify(permissionService).applyDefaultPermissionTemplate(any(DbSession.class), eq(PROJECT_KEY));
+    verify(queue).submit(argThat(new TypeSafeMatcher<CeTaskSubmit>() {
+      @Override
+      protected boolean matchesSafely(CeTaskSubmit submit) {
+        return submit.getType().equals(CeTaskTypes.REPORT) && submit.getComponentUuid().equals(PROJECT_UUID) &&
+          submit.getUuid().equals(TASK_UUID);
+      }
+
+      @Override
+      public void describeTo(Description description) {
+
+      }
+    }));
+  }
+
+  @Test
+  public void submit_a_report_on_new_project_with_global_scan_permission() {
+    userSession.setGlobalPermissions(SCAN_EXECUTION, PROVISIONING);
+
+    when(queue.prepareSubmit()).thenReturn(new CeTaskSubmit.Builder(TASK_UUID));
+    when(componentService.getNullableByKey(PROJECT_KEY)).thenReturn(null);
+    when(componentService.create(any(DbSession.class), any(NewComponent.class))).thenReturn(new ComponentDto().setUuid(PROJECT_UUID).setKey(PROJECT_KEY));
+    when(permissionService.wouldCurrentUserHavePermissionWithDefaultTemplate(any(DbSession.class), eq(SCAN_EXECUTION), anyString(), eq(PROJECT_KEY), eq(Qualifiers.PROJECT)))
+      .thenReturn(true);
+
+    underTest.submit(PROJECT_KEY, null, PROJECT_NAME, IOUtils.toInputStream("{binary}"));
+
+    verify(queue).submit(any(CeTaskSubmit.class));
+  }
+
+  @Test
+  public void submit_a_report_on_existing_project_with_global_scan_permission() {
+    userSession.setGlobalPermissions(SCAN_EXECUTION);
+
+    when(queue.prepareSubmit()).thenReturn(new CeTaskSubmit.Builder(TASK_UUID));
+    when(componentService.getNullableByKey(PROJECT_KEY)).thenReturn(new ComponentDto().setUuid(PROJECT_UUID));
+
+    underTest.submit(PROJECT_KEY, null, PROJECT_NAME, IOUtils.toInputStream("{binary}"));
+
+    verify(queue).submit(any(CeTaskSubmit.class));
+  }
+
+  @Test
+  public void submit_a_report_on_existing_project_with_project_scan_permission() {
+    userSession.addProjectPermissions(SCAN_EXECUTION, PROJECT_KEY);
+
+    when(queue.prepareSubmit()).thenReturn(new CeTaskSubmit.Builder(TASK_UUID));
+    when(componentService.getNullableByKey(PROJECT_KEY)).thenReturn(new ComponentDto().setUuid(PROJECT_UUID));
+
+    underTest.submit(PROJECT_KEY, null, PROJECT_NAME, IOUtils.toInputStream("{binary}"));
+
+    verify(queue).submit(any(CeTaskSubmit.class));
+  }
+
+  @Test
+  public void fail_with_forbidden_exception_when_no_scan_permission() {
+    userSession.setGlobalPermissions(GlobalPermissions.DASHBOARD_SHARING);
+
+    thrown.expect(ForbiddenException.class);
+    underTest.submit(PROJECT_KEY, null, PROJECT_NAME, IOUtils.toInputStream("{binary}"));
+  }
+
+  @Test
+  public void fail_with_forbidden_exception_on_new_project_when_only_project_scan_permission() {
+    userSession.addProjectPermissions(SCAN_EXECUTION, PROJECT_KEY);
+
+    when(queue.prepareSubmit()).thenReturn(new CeTaskSubmit.Builder(TASK_UUID));
+    when(componentService.getNullableByKey(PROJECT_KEY)).thenReturn(null);
+    when(componentService.create(any(DbSession.class), any(NewComponent.class))).thenReturn(new ComponentDto().setUuid(PROJECT_UUID).setKey(PROJECT_KEY));
+
+    thrown.expect(ForbiddenException.class);
+    underTest.submit(PROJECT_KEY, null, PROJECT_NAME, IOUtils.toInputStream("{binary}"));
+  }
+
+  private void verifyReportIsPersisted(String taskUuid) {
+    assertThat(dbTester.selectFirst("select task_uuid from ce_task_data where task_uuid='" + taskUuid + "'")).isNotNull();
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/queue/CleanReportQueueListenerTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/queue/CleanReportQueueListenerTest.java
deleted file mode 100644 (file)
index 3d3c2bf..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.computation.task.projectanalysis.queue;
-
-import org.junit.Test;
-import org.sonar.ce.queue.CeTask;
-import org.sonar.ce.queue.report.ReportFiles;
-import org.sonar.db.ce.CeActivityDto;
-import org.sonar.db.ce.CeTaskTypes;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-public class CleanReportQueueListenerTest {
-
-  ReportFiles reportFiles = mock(ReportFiles.class);
-  CleanReportQueueListener underTest = new CleanReportQueueListener(reportFiles);
-
-  @Test
-  public void remove_report_file_if_success() {
-    CeTask task = new CeTask.Builder().setUuid("TASK_1").setType(CeTaskTypes.REPORT).setComponentUuid("PROJECT_1").setSubmitterLogin(null).build();
-
-    underTest.onRemoved(task, CeActivityDto.Status.SUCCESS);
-    verify(reportFiles).deleteIfExists("TASK_1");
-  }
-
-  @Test
-  public void remove_report_file_if_failure() {
-    CeTask task = new CeTask.Builder().setUuid("TASK_1").setType(CeTaskTypes.REPORT).setComponentUuid("PROJECT_1").setSubmitterLogin(null).build();
-
-    underTest.onRemoved(task, CeActivityDto.Status.FAILED);
-    verify(reportFiles).deleteIfExists("TASK_1");
-  }
-}
index eb6c586d1e9830e538304db572768fed3341b177..f6077caf95c90742d36187b934919cd66a0cfec7 100644 (file)
@@ -21,29 +21,29 @@ package org.sonar.server.computation.task.projectanalysis.step;
 
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
 import org.apache.commons.io.FileUtils;
-import org.hamcrest.Description;
-import org.hamcrest.TypeSafeMatcher;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.MessageException;
+import org.sonar.api.utils.System2;
 import org.sonar.api.utils.ZipUtils;
 import org.sonar.api.utils.internal.JUnitTempFolder;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.ce.queue.CeTask;
+import org.sonar.db.DbTester;
 import org.sonar.db.ce.CeTaskTypes;
+import org.sonar.server.computation.task.projectanalysis.batch.BatchReportDirectoryHolderImpl;
 import org.sonar.server.computation.task.projectanalysis.batch.MutableBatchReportDirectoryHolder;
-import org.sonar.ce.queue.CeTask;
-import org.sonar.ce.queue.report.ReportFiles;
 
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.assertj.core.api.Assertions.assertThat;
 
 public class ExtractReportStepTest {
 
-  public static final String TASK_UUID = "1";
+  private static final String TASK_UUID = "1";
+
   @Rule
   public JUnitTempFolder tempFolder = new JUnitTempFolder();
 
@@ -53,52 +53,46 @@ public class ExtractReportStepTest {
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
 
-  MutableBatchReportDirectoryHolder reportDirectoryHolder = mock(MutableBatchReportDirectoryHolder.class);
-  ReportFiles reportFiles = mock(ReportFiles.class);
-  CeTask ceTask = new CeTask.Builder().setType(CeTaskTypes.REPORT).setUuid(TASK_UUID).build();
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private MutableBatchReportDirectoryHolder reportDirectoryHolder = new BatchReportDirectoryHolderImpl();
+  private CeTask ceTask = new CeTask.Builder().setType(CeTaskTypes.REPORT).setUuid(TASK_UUID).build();
 
-  ExtractReportStep underTest = new ExtractReportStep(reportFiles, ceTask, tempFolder, reportDirectoryHolder);
+  private ExtractReportStep underTest = new ExtractReportStep(dbTester.getDbClient(), ceTask, tempFolder, reportDirectoryHolder);
 
   @Test
   public void fail_if_report_zip_does_not_exist() throws Exception {
-    File zip = tempFolder.newFile();
-    FileUtils.forceDelete(zip);
-    expectedException.expect(IllegalStateException.class);
-    expectedException.expectMessage("Fail to unzip " + zip.getPath());
-
-    when(reportFiles.fileForUuid(TASK_UUID)).thenReturn(zip);
+    expectedException.expect(MessageException.class);
+    expectedException.expectMessage("Analysis report 1 is missing in database");
 
     underTest.execute();
   }
 
   @Test
   public void unzip_report() throws Exception {
-    File zipDir = tempFolder.newDir();
-    final File metadataFile = new File(zipDir, "metadata.pb");
-    FileUtils.write(metadataFile, "{report}");
-    File zip = tempFolder.newFile();
-    ZipUtils.zipDir(zipDir, zip);
-    when(reportFiles.fileForUuid(TASK_UUID)).thenReturn(zip);
+    File reportFile = generateReport();
+    try (InputStream input = FileUtils.openInputStream(reportFile)) {
+      dbTester.getDbClient().ceTaskDataDao().insert(dbTester.getSession(), TASK_UUID, input);
+    }
+    dbTester.getSession().commit();
+    dbTester.getSession().close();
 
     underTest.execute();
 
-    verify(reportDirectoryHolder).setDirectory(argThat(new TypeSafeMatcher<File>() {
-      @Override
-      protected boolean matchesSafely(File dir) {
-        try {
-          return dir.isDirectory() && dir.exists() &&
-            // directory contains the uncompressed report (which contains only metadata.pb in this test)
-            dir.listFiles().length == 1 &&
-            FileUtils.contentEquals(dir.listFiles()[0], metadataFile);
-        } catch (IOException e) {
-          throw new IllegalStateException(e);
-        }
-      }
-
-      @Override
-      public void describeTo(Description description) {
-
-      }
-    }));
+    // directory contains the uncompressed report (which contains only metadata.pb in this test)
+    File unzippedDir = reportDirectoryHolder.getDirectory();
+    assertThat(unzippedDir).isDirectory().exists();
+    assertThat(unzippedDir.listFiles()).hasSize(1);
+    assertThat(new File(unzippedDir, "metadata.pb")).hasContent("{metadata}");
+  }
+
+  private File generateReport() throws IOException {
+    File zipDir = tempFolder.newDir();
+    File metadataFile = new File(zipDir, "metadata.pb");
+    FileUtils.write(metadataFile, "{metadata}");
+    File zip = tempFolder.newFile();
+    ZipUtils.zipDir(zipDir, zip);
+    return zip;
   }
 }
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1302_create_table_ce_task_data.rb b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1302_create_table_ce_task_data.rb
new file mode 100644 (file)
index 0000000..c463717
--- /dev/null
@@ -0,0 +1,36 @@
+#
+# SonarQube, open source software quality management tool.
+# Copyright (C) 2008-2014 SonarSource
+# mailto:contact AT sonarsource DOT com
+#
+# SonarQube is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 3 of the License, or (at your option) any later version.
+#
+# SonarQube is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+#
+
+#
+# SonarQube 6.1
+#
+class CreateTableCeTaskData < ActiveRecord::Migration
+
+  def self.up
+    # FIXME define primary key
+    create_table 'ce_task_data', :id => false do |t|
+      t.column 'task_uuid', :string, :limit => 40, :null => false
+      t.column 'data', :binary, :null => true
+      t.column 'created_at', :big_integer, :null => false
+      t.column 'updated_at', :big_integer, :null => false
+    end
+    add_index 'ce_task_data', 'task_uuid', :name => 'ce_task_data_uuid', :unique => true
+  end
+end
index 4649f70ecc2e17f0f051064281123c34364040d0..1f73c55a4c98de6638dccde8a7c878837d0e6531 100644 (file)
@@ -25,6 +25,7 @@ import org.sonar.core.platform.Module;
 import org.sonar.db.activity.ActivityDao;
 import org.sonar.db.ce.CeActivityDao;
 import org.sonar.db.ce.CeQueueDao;
+import org.sonar.db.ce.CeTaskDataDao;
 import org.sonar.db.component.ComponentDao;
 import org.sonar.db.component.ComponentLinkDao;
 import org.sonar.db.component.ResourceDao;
@@ -77,6 +78,7 @@ public class DaoModule extends Module {
     AuthorizationDao.class,
     CeActivityDao.class,
     CeQueueDao.class,
+    CeTaskDataDao.class,
     ComponentDao.class,
     ComponentLinkDao.class,
     CustomMeasureDao.class,
index e883d1cb62b68232bfcca024316246bdbaad8391..5564c6c145be9eeeb46da0ac06c71e735b22d7c6 100644 (file)
@@ -25,6 +25,7 @@ import javax.annotation.Nullable;
 import org.sonar.db.activity.ActivityDao;
 import org.sonar.db.ce.CeActivityDao;
 import org.sonar.db.ce.CeQueueDao;
+import org.sonar.db.ce.CeTaskDataDao;
 import org.sonar.db.component.ComponentDao;
 import org.sonar.db.component.ComponentLinkDao;
 import org.sonar.db.component.ResourceDao;
@@ -97,8 +98,9 @@ public class DbClient {
   private final IssueFilterDao issueFilterDao;
   private final IssueFilterFavouriteDao issueFilterFavouriteDao;
   private final IssueChangeDao issueChangeDao;
-  private final CeQueueDao ceQueueDao;
   private final CeActivityDao ceActivityDao;
+  private final CeQueueDao ceQueueDao;
+  private final CeTaskDataDao ceTaskDataDao;
   private final DashboardDao dashboardDao;
   private final ActiveDashboardDao activeDashboardDao;
   private final WidgetDao widgetDao;
@@ -152,8 +154,9 @@ public class DbClient {
     issueFilterDao = getDao(map, IssueFilterDao.class);
     issueFilterFavouriteDao = getDao(map, IssueFilterFavouriteDao.class);
     issueChangeDao = getDao(map, IssueChangeDao.class);
-    ceQueueDao = getDao(map, CeQueueDao.class);
     ceActivityDao = getDao(map, CeActivityDao.class);
+    ceQueueDao = getDao(map, CeQueueDao.class);
+    ceTaskDataDao = getDao(map, CeTaskDataDao.class);
     dashboardDao = getDao(map, DashboardDao.class);
     activeDashboardDao = getDao(map, ActiveDashboardDao.class);
     widgetDao = getDao(map, WidgetDao.class);
@@ -290,12 +293,16 @@ public class DbClient {
     return permissionTemplateCharacteristicDao;
   }
 
+  public CeActivityDao ceActivityDao() {
+    return ceActivityDao;
+  }
+
   public CeQueueDao ceQueueDao() {
     return ceQueueDao;
   }
 
-  public CeActivityDao ceActivityDao() {
-    return ceActivityDao;
+  public CeTaskDataDao ceTaskDataDao() {
+    return ceTaskDataDao;
   }
 
   public DashboardDao dashboardDao() {
index 45a675457b3b2e33a83f9d4cdf20b1b9db510b35..4e974a06572fc7c255d7f14a3aa7fd36dad468b0 100644 (file)
@@ -33,6 +33,7 @@ import org.sonar.db.activity.ActivityDto;
 import org.sonar.db.activity.ActivityMapper;
 import org.sonar.db.ce.CeActivityMapper;
 import org.sonar.db.ce.CeQueueMapper;
+import org.sonar.db.ce.CeTaskDataMapper;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.ComponentDtoWithSnapshotId;
 import org.sonar.db.component.ComponentLinkDto;
@@ -233,7 +234,7 @@ public class MyBatis {
       GroupMembershipMapper.class, QualityProfileMapper.class, ActiveRuleMapper.class,
       MeasureMapper.class, MetricMapper.class, CustomMeasureMapper.class, QualityGateMapper.class, QualityGateConditionMapper.class, ComponentMapper.class, SnapshotMapper.class,
       ProjectQgateAssociationMapper.class, EventMapper.class,
-      CeQueueMapper.class, CeActivityMapper.class, ComponentLinkMapper.class,
+      CeQueueMapper.class, CeActivityMapper.class, CeTaskDataMapper.class, ComponentLinkMapper.class,
       Migration45Mapper.class, Migration50Mapper.class, Migration53Mapper.class
     };
     confBuilder.loadMappers(mappers);
diff --git a/sonar-db/src/main/java/org/sonar/db/ce/CeTaskDataDao.java b/sonar-db/src/main/java/org/sonar/db/ce/CeTaskDataDao.java
new file mode 100644 (file)
index 0000000..36d99c5
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.db.ce;
+
+import java.io.InputStream;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Optional;
+import org.apache.commons.io.IOUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Dao;
+import org.sonar.db.DatabaseUtils;
+import org.sonar.db.DbSession;
+
+public class CeTaskDataDao implements Dao {
+
+  private final System2 system;
+
+  public CeTaskDataDao(System2 system) {
+    this.system = system;
+  }
+
+  public void insert(DbSession dbSession, String taskUuid, InputStream data) {
+    long now = system.now();
+    try (PreparedStatement stmt = dbSession.getConnection().prepareStatement(
+      "INSERT INTO ce_task_data (task_uuid, created_at, updated_at, data) VALUES (?, ?, ?, ?)")) {
+      stmt.setString(1, taskUuid);
+      stmt.setLong(2, now);
+      stmt.setLong(3, now);
+      stmt.setBinaryStream(4, data);
+      stmt.executeUpdate();
+    } catch (SQLException e) {
+      throw new IllegalStateException("Fail to insert data of CE task " + taskUuid, e);
+    }
+  }
+
+  public Optional<DataStream> selectData(DbSession dbSession, String taskUuid) {
+    PreparedStatement stmt = null;
+    ResultSet rs = null;
+    DataStream result = null;
+    try {
+      stmt = dbSession.getConnection().prepareStatement("SELECT data FROM ce_task_data WHERE task_uuid=? AND data IS NOT NULL");
+      stmt.setString(1, taskUuid);
+      rs = stmt.executeQuery();
+      if (rs.next()) {
+        result = new DataStream(stmt, rs, rs.getBinaryStream(1));
+        return Optional.of(result);
+      }
+      return Optional.empty();
+    } catch (SQLException e) {
+      throw new IllegalStateException("Fail to select data of CE task " + taskUuid, e);
+    } finally {
+      if (result == null) {
+        DatabaseUtils.closeQuietly(rs);
+        DatabaseUtils.closeQuietly(stmt);
+      }
+    }
+  }
+
+  public List<String> selectUuidsNotInQueue(DbSession dbSession) {
+    return dbSession.getMapper(CeTaskDataMapper.class).selectUuidsNotInQueue();
+  }
+
+  public void deleteByUuids(DbSession dbSession, List<String> uuids) {
+    CeTaskDataMapper mapper = dbSession.getMapper(CeTaskDataMapper.class);
+    DatabaseUtils.executeLargeUpdates(uuids, mapper::deleteByUuids);
+  }
+
+  public static class DataStream implements AutoCloseable {
+    private final PreparedStatement stmt;
+    private final ResultSet rs;
+    private final InputStream stream;
+
+    private DataStream(PreparedStatement stmt, ResultSet rs, InputStream stream) {
+      this.stmt = stmt;
+      this.rs = rs;
+      this.stream = stream;
+    }
+
+    public InputStream getInputStream() {
+      return stream;
+    }
+
+    @Override
+    public void close() {
+      IOUtils.closeQuietly(stream);
+      DatabaseUtils.closeQuietly(rs);
+      DatabaseUtils.closeQuietly(stmt);
+    }
+  }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/ce/CeTaskDataMapper.java b/sonar-db/src/main/java/org/sonar/db/ce/CeTaskDataMapper.java
new file mode 100644 (file)
index 0000000..728f6d3
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.db.ce;
+
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+public interface CeTaskDataMapper {
+
+  void deleteByUuids(@Param("uuids") List<String> uuids);
+
+  List<String> selectUuidsNotInQueue();
+
+}
index 3ddf44e75e6816fb339a189e6e689e4ad66dbe3b..8be4800dac9802f5601a1d66224919b7896cab23 100644 (file)
@@ -30,7 +30,7 @@ import org.sonar.db.MyBatis;
 
 public class DatabaseVersion {
 
-  public static final int LAST_VERSION = 1_301;
+  public static final int LAST_VERSION = 1_302;
 
   /**
    * The minimum supported version which can be upgraded. Lower
@@ -52,6 +52,7 @@ public class DatabaseVersion {
     "authors",
     "ce_activity",
     "ce_queue",
+    "ce_task_data",
     "dashboards",
     "duplications_index",
     "events",
diff --git a/sonar-db/src/main/resources/org/sonar/db/ce/CeTaskDataMapper.xml b/sonar-db/src/main/resources/org/sonar/db/ce/CeTaskDataMapper.xml
new file mode 100644 (file)
index 0000000..ea36ab6
--- /dev/null
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.ce.CeTaskDataMapper">
+
+  <select id="selectUuidsNotInQueue" resultType="String">
+    select d.task_uuid
+    from ce_task_data d
+    left join ce_queue q on d.task_uuid = q.uuid
+    where q.uuid is null
+  </select>
+
+  <delete id="deleteByUuids" parameterType="String">
+    delete from ce_task_data
+    where task_uuid in <foreach collection="uuids" open="(" close=")" item="uuid" separator=",">#{uuid}</foreach>
+  </delete>
+
+</mapper>
index af97f0ae4999288ca41c01e3e0155b316b770d6f..02da496f80588f35b4378565efb78e97d9c0aac3 100644 (file)
@@ -488,6 +488,7 @@ INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1277');
 
 INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1300');
 INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1301');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1302');
 
 INSERT INTO USERS(ID, LOGIN, NAME, EMAIL, EXTERNAL_IDENTITY, EXTERNAL_IDENTITY_PROVIDER, USER_LOCAL, CRYPTED_PASSWORD, SALT, CREATED_AT, UPDATED_AT) VALUES (1, 'admin', 'Administrator', '', 'admin', 'sonarqube', true, 'a373a0e667abb2604c1fd571eb4ad47fe8cc0878', '48bc4b0d93179b5103fd3885ea9119498e9d161b', '1418215735482', '1418215735482');
 ALTER TABLE USERS ALTER COLUMN ID RESTART WITH 2;
index f9704f1a0c6b0bf1924d6c656c819018c4e74d76..15fca8d0a796195b72f20cbce173386ee189da20 100644 (file)
@@ -533,6 +533,14 @@ CREATE TABLE "CE_ACTIVITY" (
   "EXECUTION_TIME_MS" BIGINT NULL
 );
 
+CREATE TABLE "CE_TASK_DATA" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "TASK_UUID" VARCHAR(40) NOT NULL,
+  "DATA" BLOB(167772150),
+  "CREATED_AT" BIGINT NOT NULL,
+  "UPDATED_AT" BIGINT NOT NULL
+);
+
 CREATE TABLE "USER_TOKENS" (
   "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
   "LOGIN" VARCHAR(255) NOT NULL,
index c76d29e2dc1201981cb57e7004524031041f7976..5e8d8910d50510d7aa5d23378b9d2951b40f37bc 100644 (file)
@@ -29,6 +29,6 @@ public class DaoModuleTest {
   public void verify_count_of_added_components() {
     ComponentContainer container = new ComponentContainer();
     new DaoModule().configure(container);
-    assertThat(container.size()).isEqualTo(2 + 46);
+    assertThat(container.size()).isEqualTo(2 + 47);
   }
 }
index 170a863862f3e5ece572434d9877ef4ef5da8aec..b729146fbf11968a4bb8bdc8c8d885ed5387d4f3 100644 (file)
@@ -21,7 +21,9 @@ package org.sonar.db;
 
 import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
+import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
 import java.io.InputStream;
 import java.math.BigDecimal;
 import java.sql.Clob;
@@ -130,7 +132,7 @@ public class DbTester extends ExternalResource {
     return client;
   }
 
-  public void executeUpdateSql(String sql, String... params) {
+  public void executeUpdateSql(String sql, Object... params) {
     try (Connection connection = db.getDatabase().getDataSource().getConnection()) {
       new QueryRunner().update(connection, sql, params);
     } catch (Exception e) {
@@ -144,15 +146,16 @@ public class DbTester extends ExternalResource {
    *
    * @param valuesByColumn column name and value pairs, if any value is null, the associated column won't be inserted
    */
-  public void executeInsert(String table, String... valuesByColumn) {
-    executeInsert(table, mapOf(valuesByColumn));
+  public void executeInsert(String table, String firstColumn, Object... others) {
+    executeInsert(table, mapOf(firstColumn, others));
   }
 
-  private static Map<String, String> mapOf(String... values) {
-    ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
-    for (int i = 0; i < values.length; i++) {
-      String key = values[i];
-      String value = values[i + 1];
+  private static Map<String, Object> mapOf(String firstColumn, Object... values) {
+    ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
+    List<Object> args = Lists.asList(firstColumn, values);
+    for (int i = 0; i < args.size(); i++) {
+      String key = args.get(i).toString();
+      Object value = args.get(i + 1);
       if (value != null) {
         builder.put(key, value);
       }
@@ -165,7 +168,7 @@ public class DbTester extends ExternalResource {
    * Very simple helper method to insert some data into a table.
    * It's the responsibility of the caller to convert column values to string.
    */
-  public void executeInsert(String table, Map<String, String> valuesByColumn) {
+  public void executeInsert(String table, Map<String, Object> valuesByColumn) {
     if (valuesByColumn.isEmpty()) {
       throw new IllegalArgumentException("Values cannot be empty");
     }
@@ -175,7 +178,7 @@ public class DbTester extends ExternalResource {
       ") values (" +
       COMMA_JOINER.join(Collections.nCopies(valuesByColumn.size(), '?')) +
       ")";
-    executeUpdateSql(sql, valuesByColumn.values().toArray(new String[valuesByColumn.size()]));
+    executeUpdateSql(sql, valuesByColumn.values().toArray(new Object[valuesByColumn.size()]));
   }
 
   /**
diff --git a/sonar-db/src/test/java/org/sonar/db/ce/CeTaskDataDaoTest.java b/sonar-db/src/test/java/org/sonar/db/ce/CeTaskDataDaoTest.java
new file mode 100644 (file)
index 0000000..bacd710
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.db.ce;
+
+import java.io.InputStream;
+import java.util.Optional;
+import org.apache.commons.io.IOUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class CeTaskDataDaoTest {
+
+  private static final String A_UUID = "U1";
+  private static final String SOME_DATA = "this_is_a_report";
+  private static final long NOW = 1_500_000_000_000L;
+  private static final String TABLE_NAME = "CE_TASK_DATA";
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private System2 system = mock(System2.class);
+  private CeTaskDataDao underTest = new CeTaskDataDao(system);
+
+  @Test
+  public void insert_and_select_data_stream() throws Exception {
+    when(system.now()).thenReturn(NOW);
+
+    InputStream report = IOUtils.toInputStream(SOME_DATA);
+    underTest.insert(dbTester.getSession(), A_UUID, report);
+
+    Optional<CeTaskDataDao.DataStream> result = underTest.selectData(dbTester.getSession(), A_UUID);
+    assertThat(result).isPresent();
+    try {
+      assertThat(IOUtils.toString(result.get().getInputStream())).isEqualTo(SOME_DATA);
+    } finally {
+      result.get().close();
+    }
+  }
+
+  @Test
+  public void fail_to_insert_invalid_row() throws Exception {
+    expectedException.expectMessage("Fail to insert data of CE task null");
+    underTest.insert(dbTester.getSession(), null, IOUtils.toInputStream(SOME_DATA));
+  }
+
+  @Test
+  public void selectData_returns_absent_if_uuid_not_found() {
+    Optional<CeTaskDataDao.DataStream> result = underTest.selectData(dbTester.getSession(), A_UUID);
+    assertThat(result).isNotPresent();
+  }
+
+  @Test
+  public void selectData_returns_absent_if_uuid_exists_but_data_is_null() {
+    insertData(A_UUID);
+    dbTester.commit();
+
+    Optional<CeTaskDataDao.DataStream> result = underTest.selectData(dbTester.getSession(), A_UUID);
+    assertThat(result).isNotPresent();
+  }
+
+  @Test
+  public void selectUuidsNotInQueue() {
+    assertThat(underTest.selectUuidsNotInQueue(dbTester.getSession())).isEmpty();
+
+    insertData("U1");
+    insertData("U2");
+    assertThat(underTest.selectUuidsNotInQueue(dbTester.getSession())).containsOnly("U1", "U2");
+
+    CeQueueDto inQueue = new CeQueueDto().setUuid("U2").setTaskType(CeTaskTypes.REPORT).setStatus(CeQueueDto.Status.IN_PROGRESS);
+    new CeQueueDao(system).insert(dbTester.getSession(), inQueue);
+    assertThat(underTest.selectUuidsNotInQueue(dbTester.getSession())).containsOnly("U1");
+  }
+
+  @Test
+  public void deleteByUuids() {
+    insertData(A_UUID);
+    assertThat(dbTester.countRowsOfTable(TABLE_NAME)).isEqualTo(1);
+
+    underTest.deleteByUuids(dbTester.getSession(), asList(A_UUID));
+    dbTester.commit();
+    assertThat(dbTester.countRowsOfTable(TABLE_NAME)).isEqualTo(0);
+  }
+
+  private void insertData(String uuid) {
+
+    dbTester.executeInsert(TABLE_NAME, "TASK_UUID", uuid, "CREATED_AT", NOW, "UPDATED_AT", NOW);
+    dbTester.commit();
+  }
+}
index 7f0abdcc7dbcd209fd213ff066ba1faf034308ab..60471734237ec8f4fe6aa3b16203645b6b1035f0 100644 (file)
@@ -19,7 +19,7 @@
  */
 package org.sonar.db.version.v56;
 
-import com.google.common.collect.ImmutableMap;
+import java.util.HashMap;
 import java.util.Map;
 import javax.annotation.Nullable;
 import org.junit.Before;
@@ -29,7 +29,6 @@ import org.sonar.api.utils.System2;
 import org.sonar.db.DbTester;
 import org.sonar.db.version.MigrationStep;
 
-import static com.google.common.collect.Maps.newHashMap;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -73,10 +72,10 @@ public class UpdateUsersExternalIdentityWhenEmptyTest {
   }
 
   private void insertUser(String login, @Nullable String externalIdentity, @Nullable String externalIdentityProvider, long updatedAt) {
-    Map<String, String> params = newHashMap(ImmutableMap.of(
-      "LOGIN", login,
-      "CREATED_AT", Long.toString(PAST),
-      "UPDATED_AT", Long.toString(updatedAt)));
+    Map<String, Object> params = new HashMap<>();
+    params.put("LOGIN", login);
+    params.put("CREATED_AT", Long.toString(PAST));
+    params.put("UPDATED_AT", Long.toString(updatedAt));
     if (externalIdentity != null) {
       params.put("EXTERNAL_IDENTITY", externalIdentity);
     }