]> source.dussan.org Git - sonarqube.git/commitdiff
Computation stack based on an isolated picocontainer
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Thu, 15 Jan 2015 12:38:39 +0000 (13:38 +0100)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Tue, 20 Jan 2015 12:27:35 +0000 (13:27 +0100)
126 files changed:
plugins/sonar-core-plugin/pom.xml
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/ChangesOnMyIssueNotificationDispatcher.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplate.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/NewFalsePositiveNotificationDispatcher.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/NewIssuesEmailTemplate.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/NewIssuesNotificationDispatcher.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/SendIssueNotificationsPostJob.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/package-info.java [deleted file]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/ChangesOnMyIssueNotificationDispatcherTest.java [deleted file]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplateTest.java [deleted file]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/NewFalsePositiveNotificationDispatcherTest.java [deleted file]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/NewIssuesEmailTemplateTest.java [deleted file]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/NewIssuesNotificationDispatcherTest.java [deleted file]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/SendIssueNotificationsPostJobTest.java [deleted file]
plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplateTest/display_component_key_if_no_component_name.txt [deleted file]
plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplateTest/email_with_action_plan_change.txt [deleted file]
plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplateTest/email_with_assignee_change.txt [deleted file]
plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplateTest/email_with_multiple_changes.txt [deleted file]
plugins/sonar-email-notifications-plugin/src/main/java/org/sonar/plugins/emailnotifications/api/EmailMessage.java [deleted file]
plugins/sonar-email-notifications-plugin/src/main/java/org/sonar/plugins/emailnotifications/api/EmailTemplate.java [deleted file]
plugins/sonar-email-notifications-plugin/src/main/java/org/sonar/plugins/emailnotifications/api/package-info.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/computation/AnalysisReportService.java
server/sonar-server/src/main/java/org/sonar/server/computation/ComputationComponents.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/ComputationContext.java
server/sonar-server/src/main/java/org/sonar/server/computation/ComputationService.java
server/sonar-server/src/main/java/org/sonar/server/computation/ComputationThread.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/ComputationThreadLauncher.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/ComputationWorker.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/computation/ComputationWorkerLauncher.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/computation/ComputeEngineIssueStorage.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/computation/ComputeEngineIssueStorageFactory.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/computation/issue/FinalIssues.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueComputation.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCache.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCacheLoader.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/issue/package-info.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/step/ApplyPermissionsStep.java
server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationStep.java
server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationStepRegistry.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/step/DigestReportStep.java
server/sonar-server/src/main/java/org/sonar/server/computation/step/IndexComponentsStep.java
server/sonar-server/src/main/java/org/sonar/server/computation/step/IndexIssuesStep.java
server/sonar-server/src/main/java/org/sonar/server/computation/step/IndexSourceLinesStep.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/step/InvalidateBatchCacheStep.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/step/InvalidatePreviewCacheStep.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistIssuesStep.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/step/PurgeDatastoresStep.java
server/sonar-server/src/main/java/org/sonar/server/computation/step/SendIssueNotificationsStep.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/step/SwitchSnapshotStep.java
server/sonar-server/src/main/java/org/sonar/server/computation/ws/SubmitReportWsAction.java
server/sonar-server/src/main/java/org/sonar/server/issue/IssueBulkChangeService.java
server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java
server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueResultSetIterator.java
server/sonar-server/src/main/java/org/sonar/server/issue/notification/ChangesOnMyIssueNotificationDispatcher.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueChangesEmailTemplate.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueNotifications.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewFalsePositiveNotificationDispatcher.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesEmailTemplate.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotificationDispatcher.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/issue/notification/package-info.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/notifications/NotificationService.java
server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java
server/sonar-server/src/main/java/org/sonar/server/platform/SwitchLogbackAppender.java
server/sonar-server/src/main/java/org/sonar/server/source/IndexSourceLinesStep.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/util/CloseableIterator.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/util/ObjectInputStreamIterator.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/util/cache/CacheLoader.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/util/cache/DiskCache.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/util/cache/MemoryCache.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/util/cache/package-info.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/AnalysisReportServiceTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/ComputationComponentsTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/ComputationThreadLauncherTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/ComputationThreadTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/ComputationWorkerLauncherTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/computation/ComputationWorkerTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/computation/ComputeEngineIssueStorageFactoryTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/computation/ComputeEngineIssueStorageTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/computation/step/ApplyPermissionsStepTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputationStepRegistryTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputationStepsTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/step/DigestReportStepTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/step/IndexComponentsStepTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/step/IndexIssuesStepTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/step/InvalidateBatchCacheStepTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/step/InvalidatePreviewCacheStepTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/computation/step/PurgeDatastoresStepMediumTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/step/PurgeDatastoresStepTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/step/SendIssueNotificationsStepTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/step/SwitchSnapshotStepTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/ws/SubmitReportWsActionTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/IssueBulkChangeServiceTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/notification/ChangesOnMyIssueNotificationDispatcherTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/notification/IssueNotificationsTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewFalsePositiveNotificationDispatcherTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesEmailTemplateTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationDispatcherTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/platform/SwitchLogbackAppenderTest.java
server/sonar-server/src/test/java/org/sonar/server/util/CloseableIteratorTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/util/ObjectInputStreamIteratorTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/util/cache/DiskCacheTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/util/cache/MemoryCacheTest.java [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/display_component_key_if_no_component_name.txt [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/email_with_action_plan_change.txt [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/email_with_assignee_change.txt [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/email_with_multiple_changes.txt [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java
sonar-core/src/main/java/org/sonar/core/issue/IssueNotifications.java [deleted file]
sonar-core/src/main/java/org/sonar/core/issue/IssuesBySeverity.java [deleted file]
sonar-core/src/main/java/org/sonar/core/issue/db/IssueDto.java
sonar-core/src/main/java/org/sonar/core/issue/db/UpdateConflictResolver.java
sonar-core/src/main/java/org/sonar/core/resource/ResourceIndexerDao.java
sonar-core/src/main/java/org/sonar/core/resource/ResourceIndexerQuery.java
sonar-core/src/main/resources/org/sonar/core/issue/db/IssueMapper.xml
sonar-core/src/test/java/org/sonar/core/issue/IssueNotificationsTest.java [deleted file]
sonar-core/src/test/java/org/sonar/core/issue/IssuesBySeverityTest.java [deleted file]
sonar-core/src/test/java/org/sonar/core/persistence/DbTester.java
sonar-plugin-api/src/main/java/org/sonar/api/issue/internal/DefaultIssue.java
sonar-plugin-api/src/main/java/org/sonar/api/utils/System2.java
sonar-plugin-api/src/main/java/org/sonar/plugins/emailnotifications/api/EmailMessage.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/plugins/emailnotifications/api/EmailTemplate.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/plugins/emailnotifications/api/package-info.java [new file with mode: 0644]
sonar-plugin-api/src/test/java/org/sonar/api/utils/System2Test.java

index 5ece06d71b4ae913d078e999e9d17723e977c153..52f91a93700dd1533f1d4b77310596153546738e 100644 (file)
       <artifactId>sonar-plugin-api</artifactId>
       <scope>provided</scope>
     </dependency>
-    <dependency>
-      <groupId>org.codehaus.sonar.plugins</groupId>
-      <artifactId>sonar-email-notifications-plugin</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
     <dependency>
       <groupId>org.codehaus.sonar</groupId>
       <artifactId>sonar-core</artifactId>
index 167e7edf658559704893a467e88037cff84ef386..71580c29ad0b1bd00c8df2fdac873c570a0a300d 100644 (file)
@@ -39,12 +39,6 @@ import org.sonar.plugins.core.issue.InitialOpenIssuesStack;
 import org.sonar.plugins.core.issue.IssueHandlers;
 import org.sonar.plugins.core.issue.IssueTracking;
 import org.sonar.plugins.core.issue.IssueTrackingDecorator;
-import org.sonar.plugins.core.issue.notification.ChangesOnMyIssueNotificationDispatcher;
-import org.sonar.plugins.core.issue.notification.IssueChangesEmailTemplate;
-import org.sonar.plugins.core.issue.notification.NewFalsePositiveNotificationDispatcher;
-import org.sonar.plugins.core.issue.notification.NewIssuesEmailTemplate;
-import org.sonar.plugins.core.issue.notification.NewIssuesNotificationDispatcher;
-import org.sonar.plugins.core.issue.notification.SendIssueNotificationsPostJob;
 import org.sonar.plugins.core.measurefilters.MyFavouritesFilter;
 import org.sonar.plugins.core.measurefilters.ProjectFilter;
 import org.sonar.plugins.core.notifications.alerts.NewAlerts;
@@ -353,17 +347,6 @@ public final class CorePlugin extends SonarPlugin {
       IssueFilterWidget.class,
       IssueTagCloudWidget.class,
 
-      // issue notifications
-      SendIssueNotificationsPostJob.class,
-      NewIssuesEmailTemplate.class,
-      IssueChangesEmailTemplate.class,
-      ChangesOnMyIssueNotificationDispatcher.class,
-      ChangesOnMyIssueNotificationDispatcher.newMetadata(),
-      NewIssuesNotificationDispatcher.class,
-      NewIssuesNotificationDispatcher.newMetadata(),
-      NewFalsePositiveNotificationDispatcher.class,
-      NewFalsePositiveNotificationDispatcher.newMetadata(),
-
       // batch
       ProjectLinksSensor.class,
       UnitTestDecorator.class,
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/ChangesOnMyIssueNotificationDispatcher.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/ChangesOnMyIssueNotificationDispatcher.java
deleted file mode 100644 (file)
index 31320e1..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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.
- */
-package org.sonar.plugins.core.issue.notification;
-
-import com.google.common.base.Objects;
-import com.google.common.collect.Multimap;
-import org.sonar.api.notifications.*;
-
-import javax.annotation.Nullable;
-import java.util.Collection;
-
-/**
- * This dispatcher means: "notify me when a change is done on an issue that is assigned to me or reported by me".
- *
- * @since 3.6, but the feature exists since 2.10 ("review-changed" notification)
- */
-public class ChangesOnMyIssueNotificationDispatcher extends NotificationDispatcher {
-
-  public static final String KEY = "ChangesOnMyIssue";
-  private NotificationManager notificationManager;
-
-  public ChangesOnMyIssueNotificationDispatcher(NotificationManager notificationManager) {
-    super("issue-changes");
-    this.notificationManager = notificationManager;
-  }
-
-  @Override
-  public String getKey() {
-    return KEY;
-  }
-
-  public static NotificationDispatcherMetadata newMetadata() {
-    return NotificationDispatcherMetadata.create(KEY)
-      .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
-      .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
-  }
-
-  @Override
-  public void dispatch(Notification notification, Context context) {
-    String projectKey = notification.getFieldValue("projectKey");
-    Multimap<String, NotificationChannel> subscribedRecipients = notificationManager.findNotificationSubscribers(this, projectKey);
-
-    // See available fields in the class IssueNotifications.
-
-    // All the following users can be null
-    String changeAuthor = notification.getFieldValue("changeAuthor");
-    String reporter = notification.getFieldValue("reporter");
-    String assignee = notification.getFieldValue("assignee");
-
-    if (!Objects.equal(changeAuthor, reporter)) {
-      addUserToContextIfSubscribed(context, reporter, subscribedRecipients);
-    }
-    if (!Objects.equal(changeAuthor, assignee)) {
-      addUserToContextIfSubscribed(context, assignee, subscribedRecipients);
-    }
-  }
-
-  private void addUserToContextIfSubscribed(Context context, @Nullable String user, Multimap<String, NotificationChannel> subscribedRecipients) {
-    if (user != null) {
-      Collection<NotificationChannel> channels = subscribedRecipients.get(user);
-      for (NotificationChannel channel : channels) {
-        context.addUser(user, channel);
-      }
-    }
-  }
-}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplate.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplate.java
deleted file mode 100644 (file)
index c7f136a..0000000
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * 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.
- */
-package org.sonar.plugins.core.issue.notification;
-
-import com.google.common.base.Strings;
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.config.EmailSettings;
-import org.sonar.api.notifications.Notification;
-import org.sonar.api.user.User;
-import org.sonar.api.user.UserFinder;
-import org.sonar.plugins.emailnotifications.api.EmailMessage;
-import org.sonar.plugins.emailnotifications.api.EmailTemplate;
-
-import javax.annotation.Nullable;
-
-/**
- * Creates email message for notification "issue-changes".
- *
- * @since 3.6
- */
-public class IssueChangesEmailTemplate extends EmailTemplate {
-
-  private static final char NEW_LINE = '\n';
-  private final EmailSettings settings;
-  private final UserFinder userFinder;
-
-  public IssueChangesEmailTemplate(EmailSettings settings, UserFinder userFinder) {
-    this.settings = settings;
-    this.userFinder = userFinder;
-  }
-
-  @Override
-  public EmailMessage format(Notification notif) {
-    if (!"issue-changes".equals(notif.getType())) {
-      return null;
-    }
-
-    StringBuilder sb = new StringBuilder();
-    appendHeader(notif, sb);
-    sb.append(NEW_LINE);
-    appendChanges(notif, sb);
-    sb.append(NEW_LINE);
-    appendFooter(sb, notif);
-
-    String projectName = notif.getFieldValue("projectName");
-    String issueKey = notif.getFieldValue("key");
-    String author = notif.getFieldValue("changeAuthor");
-
-    EmailMessage message = new EmailMessage()
-      .setMessageId("issue-changes/" + issueKey)
-      .setSubject(projectName + ", change on issue #" + issueKey)
-      .setMessage(sb.toString());
-    if (author != null) {
-      message.setFrom(getUserFullName(author));
-    }
-    return message;
-  }
-
-  private void appendChanges(Notification notif, StringBuilder sb) {
-    appendField(sb, "Comment", null, notif.getFieldValue("comment"));
-    appendFieldWithoutHistory(sb, "Assignee", notif.getFieldValue("old.assignee"), notif.getFieldValue("new.assignee"));
-    appendField(sb, "Severity", notif.getFieldValue("old.severity"), notif.getFieldValue("new.severity"));
-    appendField(sb, "Resolution", notif.getFieldValue("old.resolution"), notif.getFieldValue("new.resolution"));
-    appendField(sb, "Status", notif.getFieldValue("old.status"), notif.getFieldValue("new.status"));
-    appendField(sb, "Message", notif.getFieldValue("old.message"), notif.getFieldValue("new.message"));
-    appendField(sb, "Author", notif.getFieldValue("old.author"), notif.getFieldValue("new.author"));
-    appendFieldWithoutHistory(sb, "Action Plan", notif.getFieldValue("old.actionPlan"), notif.getFieldValue("new.actionPlan"));
-    appendField(sb, "Tags", formatTagChange(notif.getFieldValue("old.tags")), formatTagChange(notif.getFieldValue("new.tags")));
-  }
-
-  private static String formatTagChange(String tags) {
-    if (tags == null) {
-      return null;
-    } else {
-      return "[" + tags + "]";
-    }
-  }
-
-  private void appendHeader(Notification notif, StringBuilder sb) {
-    appendLine(sb, StringUtils.defaultString(notif.getFieldValue("componentName"), notif.getFieldValue("componentKey")));
-    appendField(sb, "Rule", null, notif.getFieldValue("ruleName"));
-    appendField(sb, "Message", null, notif.getFieldValue("message"));
-  }
-
-  private void appendFooter(StringBuilder sb, Notification notification) {
-    String issueKey = notification.getFieldValue("key");
-    sb.append("See it in SonarQube: ").append(settings.getServerBaseURL()).append("/issues/search#issues=").append(issueKey).append(NEW_LINE);
-  }
-
-  private void appendLine(StringBuilder sb, @Nullable String line) {
-    if (!Strings.isNullOrEmpty(line)) {
-      sb.append(line).append(NEW_LINE);
-    }
-  }
-
-  private void appendField(StringBuilder sb, String name, @Nullable String oldValue, @Nullable String newValue) {
-    if (oldValue != null || newValue != null) {
-      sb.append(name).append(": ");
-      if (newValue != null) {
-        sb.append(newValue);
-      }
-      if (oldValue != null) {
-        sb.append(" (was ").append(oldValue).append(")");
-      }
-      sb.append(NEW_LINE);
-    }
-  }
-
-  private void appendFieldWithoutHistory(StringBuilder sb, String name, @Nullable String oldValue, @Nullable String newValue) {
-    if (oldValue != null || newValue != null) {
-      sb.append(name);
-      if (newValue != null) {
-        sb.append(" changed to ");
-        sb.append(newValue);
-      } else {
-        sb.append(" removed");
-      }
-      sb.append(NEW_LINE);
-    }
-  }
-
-  private String getUserFullName(@Nullable String login) {
-    if (login == null) {
-      return null;
-    }
-    User user = userFinder.findByLogin(login);
-    if (user == null) {
-      // most probably user was deleted
-      return login;
-    }
-    return StringUtils.defaultIfBlank(user.name(), login);
-  }
-
-}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/NewFalsePositiveNotificationDispatcher.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/NewFalsePositiveNotificationDispatcher.java
deleted file mode 100644 (file)
index 52739b2..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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.
- */
-
-package org.sonar.plugins.core.issue.notification;
-
-import com.google.common.base.Objects;
-import com.google.common.collect.Multimap;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.notifications.*;
-
-import java.util.Collection;
-import java.util.Map;
-
-/**
- * This dispatcher means: "notify me when someone resolves an issue as false positive".
- *
- * @since 3.6
- */
-public class NewFalsePositiveNotificationDispatcher extends NotificationDispatcher {
-
-  public static final String KEY = "NewFalsePositiveIssue";
-
-  private final NotificationManager notifications;
-
-  public NewFalsePositiveNotificationDispatcher(NotificationManager notifications) {
-    super("issue-changes");
-    this.notifications = notifications;
-  }
-
-  @Override
-  public String getKey() {
-    return KEY;
-  }
-
-  public static NotificationDispatcherMetadata newMetadata() {
-    return NotificationDispatcherMetadata.create(KEY)
-      .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
-      .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
-  }
-
-  @Override
-  public void dispatch(Notification notification, Context context) {
-    String newResolution = notification.getFieldValue("new.resolution");
-    if (Objects.equal(newResolution, Issue.RESOLUTION_FALSE_POSITIVE)) {
-      String author = notification.getFieldValue("changeAuthor");
-      String projectKey = notification.getFieldValue("projectKey");
-      Multimap<String, NotificationChannel> subscribedRecipients = notifications.findNotificationSubscribers(this, projectKey);
-      notify(author, context, subscribedRecipients);
-    }
-  }
-
-  private void notify(String author, Context context, Multimap<String, NotificationChannel> subscribedRecipients) {
-    for (Map.Entry<String, Collection<NotificationChannel>> channelsByRecipients : subscribedRecipients.asMap().entrySet()) {
-      String login = channelsByRecipients.getKey();
-      // Do not notify the person that resolved the issue
-      if (!Objects.equal(author, login)) {
-        for (NotificationChannel channel : channelsByRecipients.getValue()) {
-          context.addUser(login, channel);
-        }
-      }
-    }
-  }
-
-}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/NewIssuesEmailTemplate.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/NewIssuesEmailTemplate.java
deleted file mode 100644 (file)
index fc6f2f7..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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.
- */
-package org.sonar.plugins.core.issue.notification;
-
-import com.google.common.collect.Lists;
-import org.sonar.api.config.EmailSettings;
-import org.sonar.api.i18n.I18n;
-import org.sonar.api.notifications.Notification;
-import org.sonar.api.rule.Severity;
-import org.sonar.api.utils.DateUtils;
-import org.sonar.plugins.emailnotifications.api.EmailMessage;
-import org.sonar.plugins.emailnotifications.api.EmailTemplate;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.util.Date;
-import java.util.Iterator;
-import java.util.Locale;
-
-/**
- * Creates email message for notification "new-issues".
- *
- * @since 2.10
- */
-public class NewIssuesEmailTemplate extends EmailTemplate {
-
-  public static final String FIELD_PROJECT_NAME = "projectName";
-  public static final String FIELD_PROJECT_KEY = "projectKey";
-  public static final String FIELD_PROJECT_DATE = "projectDate";
-
-  private final EmailSettings settings;
-  private final I18n i18n;
-
-  public NewIssuesEmailTemplate(EmailSettings settings, I18n i18n) {
-    this.settings = settings;
-    this.i18n = i18n;
-  }
-
-  @Override
-  public EmailMessage format(Notification notification) {
-    if (!"new-issues".equals(notification.getType())) {
-      return null;
-    }
-    String projectName = notification.getFieldValue(FIELD_PROJECT_NAME);
-
-    StringBuilder sb = new StringBuilder();
-    sb.append("Project: ").append(projectName).append("\n\n");
-    sb.append(notification.getFieldValue("count")).append(" new issues").append("\n\n");
-    sb.append("   ");
-    for (Iterator<String> severityIterator = Lists.reverse(Severity.ALL).iterator(); severityIterator.hasNext();) {
-      String severity = severityIterator.next();
-      String severityLabel = i18n.message(getLocale(), "severity." + severity, severity);
-      sb.append(severityLabel).append(": ").append(notification.getFieldValue("count-" + severity));
-      if (severityIterator.hasNext()) {
-        sb.append("   ");
-      }
-    }
-    sb.append('\n');
-
-    appendFooter(sb, notification);
-
-    return new EmailMessage()
-      .setMessageId("new-issues/" + notification.getFieldValue(FIELD_PROJECT_KEY))
-      .setSubject(projectName + ": new issues")
-      .setMessage(sb.toString());
-  }
-
-  private void appendFooter(StringBuilder sb, Notification notification) {
-    String projectUuid = notification.getFieldValue("projectUuid");
-    String dateString = notification.getFieldValue(FIELD_PROJECT_DATE);
-    if (projectUuid != null && dateString != null) {
-      Date date = DateUtils.parseDateTime(dateString);
-      String url = String.format("%s/issues/search#projectUuids=%s|createdAt=%s",
-        settings.getServerBaseURL(), encode(projectUuid), encode(DateUtils.formatDateTime(date)));
-      sb.append("\n").append("See it in SonarQube: ").append(url).append("\n");
-    }
-  }
-
-  public static String encode(String toEncode) {
-    try {
-      return URLEncoder.encode(toEncode, "UTF-8");
-    } catch (UnsupportedEncodingException e) {
-      throw new IllegalStateException("Encoding not supported", e);
-    }
-  }
-
-  private Locale getLocale() {
-    return Locale.ENGLISH;
-  }
-
-}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/NewIssuesNotificationDispatcher.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/NewIssuesNotificationDispatcher.java
deleted file mode 100644 (file)
index 5d121ee..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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.
- */
-package org.sonar.plugins.core.issue.notification;
-
-import com.google.common.collect.Multimap;
-import org.sonar.api.notifications.*;
-
-import java.util.Collection;
-import java.util.Map;
-
-/**
- * This dispatcher means: "notify me when new issues are introduced during project scan".
- *
- * @since 2.14
- */
-public class NewIssuesNotificationDispatcher extends NotificationDispatcher {
-
-  public static final String KEY = "NewIssues";
-  private final NotificationManager manager;
-
-  public NewIssuesNotificationDispatcher(NotificationManager manager) {
-    super("new-issues");
-    this.manager = manager;
-  }
-
-  @Override
-  public String getKey() {
-    return KEY;
-  }
-
-  public static NotificationDispatcherMetadata newMetadata() {
-    return NotificationDispatcherMetadata.create(KEY)
-      .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
-      .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
-  }
-
-  @Override
-  public void dispatch(Notification notification, Context context) {
-    String projectKey = notification.getFieldValue("projectKey");
-    Multimap<String, NotificationChannel> subscribedRecipients = manager.findNotificationSubscribers(this, projectKey);
-
-    for (Map.Entry<String, Collection<NotificationChannel>> channelsByRecipients : subscribedRecipients.asMap().entrySet()) {
-      String userLogin = channelsByRecipients.getKey();
-      for (NotificationChannel channel : channelsByRecipients.getValue()) {
-        context.addUser(userLogin, channel);
-      }
-    }
-  }
-
-}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/SendIssueNotificationsPostJob.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/SendIssueNotificationsPostJob.java
deleted file mode 100644 (file)
index 6dd705e..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * 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.
- */
-package org.sonar.plugins.core.issue.notification;
-
-import org.sonar.api.batch.PostJob;
-import org.sonar.api.batch.SensorContext;
-import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.issue.internal.IssueChangeContext;
-import org.sonar.api.resources.Project;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.batch.issue.IssueCache;
-import org.sonar.core.DryRunIncompatible;
-import org.sonar.core.issue.IssueNotifications;
-import org.sonar.core.issue.IssuesBySeverity;
-
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-/**
- * @since 3.6
- */
-@DryRunIncompatible
-public class SendIssueNotificationsPostJob implements PostJob {
-
-  private final IssueCache issueCache;
-  private final IssueNotifications notifications;
-  private final RuleFinder ruleFinder;
-
-  public SendIssueNotificationsPostJob(IssueCache issueCache, IssueNotifications notifications, RuleFinder ruleFinder) {
-    this.issueCache = issueCache;
-    this.notifications = notifications;
-    this.ruleFinder = ruleFinder;
-  }
-
-  @Override
-  public void executeOn(Project project, SensorContext context) {
-    sendNotifications(project);
-  }
-
-  private void sendNotifications(Project project) {
-    IssuesBySeverity newIssues = new IssuesBySeverity();
-    IssueChangeContext context = IssueChangeContext.createScan(project.getAnalysisDate());
-    Map<DefaultIssue, Rule> changedIssuesRuleMap = new LinkedHashMap<>();
-    for (DefaultIssue issue : issueCache.all()) {
-      if (isNew(issue)) {
-        newIssues.add(issue);
-      } else if (hasChangedAndNeedNotification(issue)) {
-        addIssueToMap(issue, changedIssuesRuleMap);
-      }
-    }
-    sendChangedIssues(project, context, changedIssuesRuleMap);
-    sendNewIssues(project, newIssues);
-  }
-
-  private void addIssueToMap(DefaultIssue issue, Map<DefaultIssue, Rule> changedIssuesRuleMap) {
-    Rule rule = ruleFinder.findByKey(issue.ruleKey());
-    // TODO warning - rules with status REMOVED are currently ignored, but should not
-    if (rule != null) {
-      changedIssuesRuleMap.put(issue, rule);
-    }
-  }
-
-  private boolean isNew(DefaultIssue issue) {
-    return issue.isNew() && issue.resolution() == null;
-  }
-
-  private boolean hasChangedAndNeedNotification(DefaultIssue issue) {
-    return !issue.isNew() && issue.isChanged() && issue.mustSendNotifications();
-  }
-
-  private void sendChangedIssues(Project project, IssueChangeContext context, Map<DefaultIssue, Rule> changedIssuesRuleMap) {
-    if (!changedIssuesRuleMap.isEmpty()) {
-      notifications.sendChanges(changedIssuesRuleMap, context, project, null, null);
-    }
-  }
-
-  private void sendNewIssues(Project project, IssuesBySeverity newIssues) {
-    if (!newIssues.isEmpty()) {
-      notifications.sendNewIssues(project, newIssues);
-    }
-  }
-}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/package-info.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/package-info.java
deleted file mode 100644 (file)
index eb54e4d..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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.
- */
-@ParametersAreNonnullByDefault
-package org.sonar.plugins.core.issue.notification;
-
-import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/ChangesOnMyIssueNotificationDispatcherTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/ChangesOnMyIssueNotificationDispatcherTest.java
deleted file mode 100644 (file)
index 4dcf50b..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * 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.
- */
-package org.sonar.plugins.core.issue.notification;
-
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.Multimap;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
-import org.sonar.api.notifications.*;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.*;
-
-@RunWith(MockitoJUnitRunner.class)
-public class ChangesOnMyIssueNotificationDispatcherTest {
-
-  @Mock
-  NotificationManager notifications;
-
-  @Mock
-  NotificationDispatcher.Context context;
-
-  @Mock
-  NotificationChannel emailChannel;
-
-  @Mock
-  NotificationChannel twitterChannel;
-
-  ChangesOnMyIssueNotificationDispatcher dispatcher;
-
-  @Before
-  public void setUp() {
-    dispatcher = new ChangesOnMyIssueNotificationDispatcher(notifications);
-  }
-
-  @Test
-  public void test_metadata() throws Exception {
-    NotificationDispatcherMetadata metadata = ChangesOnMyIssueNotificationDispatcher.newMetadata();
-    assertThat(metadata.getDispatcherKey()).isEqualTo(dispatcher.getKey());
-    assertThat(metadata.getProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION)).isEqualTo("true");
-    assertThat(metadata.getProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION)).isEqualTo("true");
-  }
-
-  @Test
-  public void should_not_dispatch_if_other_notification_type() throws Exception {
-    Notification notification = new Notification("other-notif");
-    dispatcher.performDispatch(notification, context);
-
-    verify(context, never()).addUser(any(String.class), any(NotificationChannel.class));
-  }
-
-  @Test
-  public void should_dispatch_to_reporter_and_assignee() {
-    Multimap<String, NotificationChannel> recipients = HashMultimap.create();
-    recipients.put("simon", emailChannel);
-    recipients.put("freddy", twitterChannel);
-    recipients.put("godin", twitterChannel);
-    when(notifications.findNotificationSubscribers(dispatcher, "struts")).thenReturn(recipients);
-
-    Notification notification = new Notification("issue-changes").setFieldValue("projectKey", "struts")
-      .setFieldValue("changeAuthor", "olivier")
-      .setFieldValue("reporter", "simon")
-      .setFieldValue("assignee", "freddy");
-    dispatcher.performDispatch(notification, context);
-
-    verify(context).addUser("simon", emailChannel);
-    verify(context).addUser("freddy", twitterChannel);
-    verify(context, never()).addUser("godin", twitterChannel);
-    verifyNoMoreInteractions(context);
-  }
-
-  @Test
-  public void should_not_dispatch_to_author_of_changes() {
-    Multimap<String, NotificationChannel> recipients = HashMultimap.create();
-    recipients.put("simon", emailChannel);
-    recipients.put("freddy", twitterChannel);
-    recipients.put("godin", twitterChannel);
-    when(notifications.findNotificationSubscribers(dispatcher, "struts")).thenReturn(recipients);
-
-    // change author is the reporter
-    dispatcher.performDispatch(new Notification("issue-changes").setFieldValue("projectKey", "struts")
-      .setFieldValue("changeAuthor", "simon").setFieldValue("reporter", "simon"), context);
-
-    // change author is the assignee
-    dispatcher.performDispatch(new Notification("issue-changes").setFieldValue("projectKey", "struts")
-      .setFieldValue("changeAuthor", "simon").setFieldValue("assignee", "simon"), context);
-
-    // no change author
-    dispatcher.performDispatch(new Notification("issue-changes").setFieldValue("projectKey", "struts")
-      .setFieldValue("new.resolution", "FIXED"), context);
-
-    verifyNoMoreInteractions(context);
-  }
-}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplateTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplateTest.java
deleted file mode 100644 (file)
index de29cf1..0000000
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * 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.
- */
-package org.sonar.plugins.core.issue.notification;
-
-import com.google.common.io.Resources;
-import org.apache.commons.codec.Charsets;
-import org.apache.commons.lang.StringUtils;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
-import org.sonar.api.config.EmailSettings;
-import org.sonar.api.notifications.Notification;
-import org.sonar.api.user.User;
-import org.sonar.api.user.UserFinder;
-import org.sonar.plugins.emailnotifications.api.EmailMessage;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-@RunWith(MockitoJUnitRunner.class)
-public class IssueChangesEmailTemplateTest {
-
-  @Mock
-  UserFinder userFinder;
-
-  IssueChangesEmailTemplate template;
-
-  @Before
-  public void setUp() {
-    EmailSettings settings = mock(EmailSettings.class);
-    when(settings.getServerBaseURL()).thenReturn("http://nemo.sonarsource.org");
-    template = new IssueChangesEmailTemplate(settings, userFinder);
-  }
-
-  @Test
-  public void should_ignore_non_issue_changes() {
-    Notification notification = new Notification("other");
-    EmailMessage message = template.format(notification);
-    assertThat(message).isNull();
-  }
-
-  @Test
-  public void email_should_display_assignee_change() throws Exception {
-    Notification notification = generateNotification()
-      .setFieldValue("old.assignee", "simon")
-      .setFieldValue("new.assignee", "louis");
-
-    EmailMessage email = template.format(notification);
-    assertThat(email.getMessageId()).isEqualTo("issue-changes/ABCDE");
-    assertThat(email.getSubject()).isEqualTo("Struts, change on issue #ABCDE");
-
-    String message = email.getMessage();
-    String expected = Resources.toString(Resources.getResource(
-        "org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplateTest/email_with_assignee_change.txt"),
-      Charsets.UTF_8
-    );
-    expected = StringUtils.remove(expected, '\r');
-    assertThat(message).isEqualTo(expected);
-    assertThat(email.getFrom()).isNull();
-  }
-
-  @Test
-  public void email_should_display_plan_change() throws Exception {
-    Notification notification = generateNotification()
-      .setFieldValue("old.actionPlan", null)
-      .setFieldValue("new.actionPlan", "ABC 1.0");
-
-    EmailMessage email = template.format(notification);
-    assertThat(email.getMessageId()).isEqualTo("issue-changes/ABCDE");
-    assertThat(email.getSubject()).isEqualTo("Struts, change on issue #ABCDE");
-
-    String message = email.getMessage();
-    String expected = Resources.toString(Resources.getResource(
-        "org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplateTest/email_with_action_plan_change.txt"),
-      Charsets.UTF_8
-    );
-    expected = StringUtils.remove(expected, '\r');
-    assertThat(message).isEqualTo(expected);
-    assertThat(email.getFrom()).isNull();
-  }
-
-  @Test
-  public void display_component_key_if_no_component_name() throws Exception {
-    Notification notification = generateNotification()
-      .setFieldValue("componentName", null);
-
-    EmailMessage email = template.format(notification);
-    assertThat(email.getMessageId()).isEqualTo("issue-changes/ABCDE");
-    assertThat(email.getSubject()).isEqualTo("Struts, change on issue #ABCDE");
-
-    String message = email.getMessage();
-    String expected = Resources.toString(Resources.getResource(
-        "org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplateTest/display_component_key_if_no_component_name.txt"),
-      Charsets.UTF_8
-    );
-    expected = StringUtils.remove(expected, '\r');
-    assertThat(message).isEqualTo(expected);
-  }
-
-  @Test
-  public void test_email_with_multiple_changes() throws Exception {
-    Notification notification = generateNotification()
-      .setFieldValue("comment", "How to fix it?")
-      .setFieldValue("old.assignee", "simon")
-      .setFieldValue("new.assignee", "louis")
-      .setFieldValue("new.resolution", "FALSE-POSITIVE")
-      .setFieldValue("new.status", "RESOLVED")
-      .setFieldValue("new.tags", "bug performance");
-
-    EmailMessage email = template.format(notification);
-    assertThat(email.getMessageId()).isEqualTo("issue-changes/ABCDE");
-    assertThat(email.getSubject()).isEqualTo("Struts, change on issue #ABCDE");
-
-    String message = email.getMessage();
-    String expected = Resources.toString(Resources.getResource(
-      "org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplateTest/email_with_multiple_changes.txt"), Charsets.UTF_8);
-    expected = StringUtils.remove(expected, '\r');
-    assertThat(message).isEqualTo(expected);
-    assertThat(email.getFrom()).isNull();
-  }
-
-  @Test
-  public void notification_sender_should_be_the_author_of_change() {
-    User user = mock(User.class);
-    when(user.name()).thenReturn("Simon");
-    when(userFinder.findByLogin("simon")).thenReturn(user);
-
-    Notification notification = new Notification("issue-changes")
-      .setFieldValue("projectName", "Struts")
-      .setFieldValue("projectKey", "org.apache:struts")
-      .setFieldValue("changeAuthor", "simon");
-
-    EmailMessage message = template.format(notification);
-    assertThat(message.getFrom()).isEqualTo("Simon");
-  }
-
-  private Notification generateNotification() {
-    Notification notification = new Notification("issue-changes")
-      .setFieldValue("projectName", "Struts")
-      .setFieldValue("projectKey", "org.apache:struts")
-      .setFieldValue("componentName", "Action")
-      .setFieldValue("componentKey", "org.apache.struts.Action")
-      .setFieldValue("key", "ABCDE")
-      .setFieldValue("ruleName", "Avoid Cycles")
-      .setFieldValue("message", "Has 3 cycles");
-    return notification;
-  }
-}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/NewFalsePositiveNotificationDispatcherTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/NewFalsePositiveNotificationDispatcherTest.java
deleted file mode 100644 (file)
index 91520d2..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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.
- */
-package org.sonar.plugins.core.issue.notification;
-
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.Multimap;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
-import org.sonar.api.notifications.*;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.*;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-@RunWith(MockitoJUnitRunner.class)
-public class NewFalsePositiveNotificationDispatcherTest {
-  @Mock
-  NotificationManager notifications;
-
-  @Mock
-  NotificationDispatcher.Context context;
-
-  @Mock
-  NotificationChannel emailChannel;
-
-  @Mock
-  NotificationChannel twitterChannel;
-
-  NewFalsePositiveNotificationDispatcher dispatcher;
-
-  @Before
-  public void setUp() {
-    dispatcher = new NewFalsePositiveNotificationDispatcher(notifications);
-  }
-
-  @Test
-  public void test_metadata() throws Exception {
-    NotificationDispatcherMetadata metadata = NewFalsePositiveNotificationDispatcher.newMetadata();
-    assertThat(metadata.getDispatcherKey()).isEqualTo(dispatcher.getKey());
-    assertThat(metadata.getProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION)).isEqualTo("true");
-    assertThat(metadata.getProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION)).isEqualTo("true");
-  }
-
-  @Test
-  public void should_not_dispatch_if_other_notification_type() throws Exception {
-    Notification notification = new Notification("other");
-    dispatcher.performDispatch(notification, context);
-
-    verify(context, never()).addUser(any(String.class), any(NotificationChannel.class));
-  }
-
-  @Test
-  public void should_dispatch_to_subscribers() {
-    Multimap<String, NotificationChannel> recipients = HashMultimap.create();
-    recipients.put("simon", emailChannel);
-    recipients.put("freddy", twitterChannel);
-    recipients.put("godin", twitterChannel);
-    when(notifications.findNotificationSubscribers(dispatcher, "struts")).thenReturn(recipients);
-
-    Notification notification = new Notification("issue-changes").setFieldValue("projectKey", "struts")
-      .setFieldValue("changeAuthor", "godin")
-      .setFieldValue("new.resolution", "FALSE-POSITIVE")
-      .setFieldValue("assignee", "freddy");
-    dispatcher.performDispatch(notification, context);
-
-    verify(context).addUser("simon", emailChannel);
-    verify(context).addUser("freddy", twitterChannel);
-    // do not notify the person who flagged the issue as false-positive
-    verify(context, never()).addUser("godin", twitterChannel);
-    verifyNoMoreInteractions(context);
-  }
-
-}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/NewIssuesEmailTemplateTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/NewIssuesEmailTemplateTest.java
deleted file mode 100644 (file)
index ff7777c..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * 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.
- */
-package org.sonar.plugins.core.issue.notification;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
-import org.sonar.api.config.EmailSettings;
-import org.sonar.api.notifications.Notification;
-import org.sonar.core.i18n.DefaultI18n;
-import org.sonar.plugins.emailnotifications.api.EmailMessage;
-
-import java.util.Locale;
-
-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.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-@RunWith(MockitoJUnitRunner.class)
-public class NewIssuesEmailTemplateTest {
-
-  NewIssuesEmailTemplate template;
-
-  @Mock
-  DefaultI18n i18n;
-
-  @Before
-  public void setUp() {
-    EmailSettings settings = mock(EmailSettings.class);
-    when(settings.getServerBaseURL()).thenReturn("http://nemo.sonarsource.org");
-    template = new NewIssuesEmailTemplate(settings, i18n);
-  }
-
-  @Test
-  public void shouldNotFormatIfNotCorrectNotification() {
-    Notification notification = new Notification("other-notif");
-    EmailMessage message = template.format(notification);
-    assertThat(message).isNull();
-  }
-
-  /**
-   * <pre>
-   * Subject: Project Struts, new issues
-   * From: Sonar
-   *
-   * Project: Foo
-   * 32 new issues
-   *
-   * See it in SonarQube: http://nemo.sonarsource.org/drilldown/measures/org.sonar.foo:foo?metric=new_violations
-   * </pre>
-   */
-  @Test
-  public void shouldFormatCommentAdded() {
-    Notification notification = new Notification("new-issues")
-      .setFieldValue("count", "32")
-      .setFieldValue("count-INFO", "1")
-      .setFieldValue("count-MINOR", "3")
-      .setFieldValue("count-MAJOR", "10")
-      .setFieldValue("count-CRITICAL", "5")
-      .setFieldValue("count-BLOCKER", "0")
-      .setFieldValue("projectName", "Struts")
-      .setFieldValue("projectKey", "org.apache:struts")
-      .setFieldValue("projectUuid", "ABCDE")
-      .setFieldValue("projectDate", "2010-05-18T14:50:45+0000");
-
-    when(i18n.message(any(Locale.class), eq("severity.BLOCKER"), anyString())).thenReturn("Blocker");
-    when(i18n.message(any(Locale.class), eq("severity.CRITICAL"), anyString())).thenReturn("Critical");
-    when(i18n.message(any(Locale.class), eq("severity.MAJOR"), anyString())).thenReturn("Major");
-    when(i18n.message(any(Locale.class), eq("severity.MINOR"), anyString())).thenReturn("Minor");
-    when(i18n.message(any(Locale.class), eq("severity.INFO"), anyString())).thenReturn("Info");
-
-    EmailMessage message = template.format(notification);
-    assertThat(message.getMessageId()).isEqualTo("new-issues/org.apache:struts");
-    assertThat(message.getSubject()).isEqualTo("Struts: new issues");
-
-    // TODO datetime to be completed when test is isolated from JVM timezone
-    assertThat(message.getMessage()).startsWith("" +
-      "Project: Struts\n" +
-      "\n" +
-      "32 new issues\n" +
-      "\n" +
-      "   Blocker: 0   Critical: 5   Major: 10   Minor: 3   Info: 1\n" +
-      "\n" +
-      "See it in SonarQube: http://nemo.sonarsource.org/issues/search#projectUuids=ABCDE|createdAt=2010-05-1");
-  }
-
-  @Test
-  public void shouldNotAddFooterIfMissingProperties() {
-    Notification notification = new Notification("new-issues")
-      .setFieldValue("count", "32")
-      .setFieldValue("projectName", "Struts");
-
-    EmailMessage message = template.format(notification);
-    assertThat(message.getMessage()).doesNotContain("See it");
-  }
-}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/NewIssuesNotificationDispatcherTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/NewIssuesNotificationDispatcherTest.java
deleted file mode 100644 (file)
index 1737ce8..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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.
- */
-package org.sonar.plugins.core.issue.notification;
-
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.Multimap;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.sonar.api.notifications.Notification;
-import org.sonar.api.notifications.NotificationChannel;
-import org.sonar.api.notifications.NotificationDispatcher;
-import org.sonar.api.notifications.NotificationManager;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.*;
-
-public class NewIssuesNotificationDispatcherTest {
-
-  @Mock
-  private NotificationManager notifications;
-
-  @Mock
-  private NotificationDispatcher.Context context;
-
-  @Mock
-  private NotificationChannel emailChannel;
-
-  @Mock
-  private NotificationChannel twitterChannel;
-
-  private NewIssuesNotificationDispatcher dispatcher;
-
-  @Before
-  public void init() {
-    MockitoAnnotations.initMocks(this);
-    dispatcher = new NewIssuesNotificationDispatcher(notifications);
-  }
-
-  @Test
-  public void shouldNotDispatchIfNotNewViolationsNotification() throws Exception {
-    Notification notification = new Notification("other-notif");
-    dispatcher.performDispatch(notification, context);
-
-    verify(context, never()).addUser(any(String.class), any(NotificationChannel.class));
-  }
-
-  @Test
-  public void shouldDispatchToUsersWhoHaveSubscribedAndFlaggedProjectAsFavourite() {
-    Multimap<String, NotificationChannel> recipients = HashMultimap.create();
-    recipients.put("user1", emailChannel);
-    recipients.put("user2", twitterChannel);
-    when(notifications.findNotificationSubscribers(dispatcher, "struts")).thenReturn(recipients);
-
-    Notification notification = new Notification("new-issues").setFieldValue("projectKey", "struts");
-    dispatcher.performDispatch(notification, context);
-
-    verify(context).addUser("user1", emailChannel);
-    verify(context).addUser("user2", twitterChannel);
-    verifyNoMoreInteractions(context);
-  }
-}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/SendIssueNotificationsPostJobTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/SendIssueNotificationsPostJobTest.java
deleted file mode 100644 (file)
index 5b3e5eb..0000000
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * 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.
- */
-package org.sonar.plugins.core.issue.notification;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatcher;
-import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
-import org.sonar.api.batch.SensorContext;
-import org.sonar.api.component.Component;
-import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.issue.internal.IssueChangeContext;
-import org.sonar.api.resources.Project;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.utils.DateUtils;
-import org.sonar.batch.issue.IssueCache;
-import org.sonar.core.issue.IssueNotifications;
-import org.sonar.core.issue.IssuesBySeverity;
-
-import java.util.Arrays;
-import java.util.Map;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNull;
-import static org.mockito.Mockito.*;
-
-@RunWith(MockitoJUnitRunner.class)
-public class SendIssueNotificationsPostJobTest {
-  @Mock
-  Project project;
-
-  @Mock
-  IssueCache issueCache;
-
-  @Mock
-  IssueNotifications notifications;
-
-  @Mock
-  RuleFinder ruleFinder;
-
-  @Mock
-  SensorContext sensorContext;
-
-  @Test
-  public void should_send_notif_if_new_issues() throws Exception {
-    when(project.getAnalysisDate()).thenReturn(DateUtils.parseDate("2013-05-18"));
-    when(issueCache.all()).thenReturn(Arrays.asList(
-      new DefaultIssue().setNew(true).setSeverity("MAJOR"),
-      new DefaultIssue().setNew(false).setSeverity("MINOR")
-      ));
-
-    SendIssueNotificationsPostJob job = new SendIssueNotificationsPostJob(issueCache, notifications, ruleFinder);
-    job.executeOn(project, sensorContext);
-
-    ArgumentCaptor<IssuesBySeverity> argument = ArgumentCaptor.forClass(IssuesBySeverity.class);
-    verify(notifications).sendNewIssues(eq(project), argument.capture());
-    assertThat(argument.getValue().size()).isEqualTo(1);
-  }
-
-  @Test
-  public void should_not_send_notif_if_no_new_issues() throws Exception {
-    when(project.getAnalysisDate()).thenReturn(DateUtils.parseDate("2013-05-18"));
-    when(issueCache.all()).thenReturn(Arrays.asList(
-      new DefaultIssue().setNew(false)
-      ));
-
-    SendIssueNotificationsPostJob job = new SendIssueNotificationsPostJob(issueCache, notifications, ruleFinder);
-    job.executeOn(project, sensorContext);
-
-    verifyZeroInteractions(notifications);
-  }
-
-  @Test
-  public void should_send_notification() throws Exception {
-    when(project.getAnalysisDate()).thenReturn(DateUtils.parseDate("2013-05-18"));
-    RuleKey ruleKey = RuleKey.of("squid", "AvoidCycles");
-    Rule rule = new Rule("squid", "AvoidCycles");
-    DefaultIssue issue = new DefaultIssue()
-      .setNew(false)
-      .setChanged(true)
-      .setSendNotifications(true)
-      .setFieldChange(mock(IssueChangeContext.class), "severity", "MINOR", "BLOCKER")
-      .setRuleKey(ruleKey);
-    when(issueCache.all()).thenReturn(Arrays.asList(issue));
-    when(ruleFinder.findByKey(ruleKey)).thenReturn(rule);
-
-    SendIssueNotificationsPostJob job = new SendIssueNotificationsPostJob(issueCache, notifications, ruleFinder);
-    job.executeOn(project, sensorContext);
-
-    verify(notifications).sendChanges(argThat(matchMapOf(issue, rule)), any(IssueChangeContext.class), any(Component.class), (Component) isNull(), eq((String) null));
-  }
-
-  @Test
-  public void should_not_send_notification_if_issue_change_on_removed_rule() throws Exception {
-    IssueChangeContext changeContext = mock(IssueChangeContext.class);
-
-    when(project.getAnalysisDate()).thenReturn(DateUtils.parseDate("2013-05-18"));
-    RuleKey ruleKey = RuleKey.of("squid", "AvoidCycles");
-    DefaultIssue issue = new DefaultIssue()
-      .setChanged(true)
-      .setFieldChange(changeContext, "severity", "MINOR", "BLOCKER")
-      .setRuleKey(ruleKey);
-    when(issueCache.all()).thenReturn(Arrays.asList(issue));
-    when(ruleFinder.findByKey(ruleKey)).thenReturn(null);
-
-    SendIssueNotificationsPostJob job = new SendIssueNotificationsPostJob(issueCache, notifications, ruleFinder);
-    job.executeOn(project, sensorContext);
-
-    verify(notifications, never()).sendChanges(argThat(matchMapOf(issue, null)), eq(changeContext), any(Component.class), any(Component.class), eq((String) null));
-  }
-
-  @Test
-  public void should_not_send_notification_on_any_change() throws Exception {
-    IssueChangeContext changeContext = mock(IssueChangeContext.class);
-
-    when(project.getAnalysisDate()).thenReturn(DateUtils.parseDate("2013-05-18"));
-    RuleKey ruleKey = RuleKey.of("squid", "AvoidCycles");
-    DefaultIssue issue = new DefaultIssue()
-      .setChanged(true)
-      .setSendNotifications(false)
-      .setFieldChange(changeContext, "severity", "MINOR", "BLOCKER")
-      .setRuleKey(ruleKey);
-    when(issueCache.all()).thenReturn(Arrays.asList(issue));
-    when(ruleFinder.findByKey(ruleKey)).thenReturn(null);
-
-    SendIssueNotificationsPostJob job = new SendIssueNotificationsPostJob(issueCache, notifications, ruleFinder);
-    job.executeOn(project, sensorContext);
-
-    verify(notifications, never()).sendChanges(argThat(matchMapOf(issue, null)), eq(changeContext), any(Component.class), any(Component.class), eq((String) null));
-  }
-
-  private static IsMapOfIssueAndRule matchMapOf(DefaultIssue issue, Rule rule) {
-    return new IsMapOfIssueAndRule(issue, rule);
-  }
-
-  static class IsMapOfIssueAndRule extends ArgumentMatcher<Map<DefaultIssue, Rule>> {
-    private DefaultIssue issue;
-    private Rule rule;
-
-    public IsMapOfIssueAndRule(DefaultIssue issue, Rule rule) {
-      this.issue = issue;
-      this.rule = rule;
-    }
-
-    public boolean matches(Object arg) {
-      Map map = (Map) arg;
-      return map.size() == 1 && map.get(issue) != null && map.get(issue).equals(rule);
-    }
-  }
-}
diff --git a/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplateTest/display_component_key_if_no_component_name.txt b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplateTest/display_component_key_if_no_component_name.txt
deleted file mode 100644 (file)
index 9f90a7d..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-org.apache.struts.Action
-Rule: Avoid Cycles
-Message: Has 3 cycles
-
-
-See it in SonarQube: http://nemo.sonarsource.org/issues/search#issues=ABCDE
diff --git a/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplateTest/email_with_action_plan_change.txt b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplateTest/email_with_action_plan_change.txt
deleted file mode 100644 (file)
index f9a4356..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-Action
-Rule: Avoid Cycles
-Message: Has 3 cycles
-
-Action Plan changed to ABC 1.0
-
-See it in SonarQube: http://nemo.sonarsource.org/issues/search#issues=ABCDE
diff --git a/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplateTest/email_with_assignee_change.txt b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplateTest/email_with_assignee_change.txt
deleted file mode 100644 (file)
index fd4c140..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-Action
-Rule: Avoid Cycles
-Message: Has 3 cycles
-
-Assignee changed to louis
-
-See it in SonarQube: http://nemo.sonarsource.org/issues/search#issues=ABCDE
diff --git a/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplateTest/email_with_multiple_changes.txt b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplateTest/email_with_multiple_changes.txt
deleted file mode 100644 (file)
index 6f4b090..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-Action
-Rule: Avoid Cycles
-Message: Has 3 cycles
-
-Comment: How to fix it?
-Assignee changed to louis
-Resolution: FALSE-POSITIVE
-Status: RESOLVED
-Tags: [bug performance]
-
-See it in SonarQube: http://nemo.sonarsource.org/issues/search#issues=ABCDE
diff --git a/plugins/sonar-email-notifications-plugin/src/main/java/org/sonar/plugins/emailnotifications/api/EmailMessage.java b/plugins/sonar-email-notifications-plugin/src/main/java/org/sonar/plugins/emailnotifications/api/EmailMessage.java
deleted file mode 100644 (file)
index af3b21f..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * 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.
- */
-package org.sonar.plugins.emailnotifications.api;
-
-import org.apache.commons.lang.builder.ToStringBuilder;
-
-/**
- * @since 2.10
- */
-public class EmailMessage {
-
-  private String from;
-  private String to;
-  private String subject;
-  private String message;
-  private String messageId;
-
-  /**
-   * @param from full name of user, who initiated this message or null, if message was initiated by Sonar
-   */
-  public EmailMessage setFrom(String from) {
-    this.from = from;
-    return this;
-  }
-
-  /**
-   * @see #setFrom(String)
-   */
-  public String getFrom() {
-    return from;
-  }
-
-  /**
-   * @param to email address where to send this message
-   */
-  public EmailMessage setTo(String to) {
-    this.to = to;
-    return this;
-  }
-
-  /**
-   * @see #setTo(String)
-   */
-  public String getTo() {
-    return to;
-  }
-
-  /**
-   * @param subject message subject
-   */
-  public EmailMessage setSubject(String subject) {
-    this.subject = subject;
-    return this;
-  }
-
-  /**
-   * @see #setSubject(String)
-   */
-  public String getSubject() {
-    return subject;
-  }
-
-  /**
-   * @param message message body
-   */
-  public EmailMessage setMessage(String message) {
-    this.message = message;
-    return this;
-  }
-
-  /**
-   * @see #setMessage(String)
-   */
-  public String getMessage() {
-    return message;
-  }
-
-  /**
-   * @param messageId id of message for threading
-   */
-  public EmailMessage setMessageId(String messageId) {
-    this.messageId = messageId;
-    return this;
-  }
-
-  /**
-   * @see #setMessageId(String)
-   */
-  public String getMessageId() {
-    return messageId;
-  }
-
-  @Override
-  public String toString() {
-    return ToStringBuilder.reflectionToString(this);
-  }
-
-}
diff --git a/plugins/sonar-email-notifications-plugin/src/main/java/org/sonar/plugins/emailnotifications/api/EmailTemplate.java b/plugins/sonar-email-notifications-plugin/src/main/java/org/sonar/plugins/emailnotifications/api/EmailTemplate.java
deleted file mode 100644 (file)
index 09a8a5c..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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.
- */
-package org.sonar.plugins.emailnotifications.api;
-
-import org.sonar.api.ServerExtension;
-import org.sonar.api.notifications.Notification;
-
-/**
- * @since 2.10
- */
-public abstract class EmailTemplate implements ServerExtension {
-
-  public abstract EmailMessage format(Notification notification);
-
-}
diff --git a/plugins/sonar-email-notifications-plugin/src/main/java/org/sonar/plugins/emailnotifications/api/package-info.java b/plugins/sonar-email-notifications-plugin/src/main/java/org/sonar/plugins/emailnotifications/api/package-info.java
deleted file mode 100644 (file)
index ef80b86..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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.
- */
-@ParametersAreNonnullByDefault
-package org.sonar.plugins.emailnotifications.api;
-
-import javax.annotation.ParametersAreNonnullByDefault;
index 08b6d03b02367bd48f433f8947e22c3d378bedda..eef338fca44cbec2d263dabae5ec0797520ba41f 100644 (file)
@@ -24,7 +24,6 @@ import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
 import com.google.common.collect.Iterables;
 import org.apache.commons.io.IOUtils;
-import org.sonar.api.ServerComponent;
 import org.sonar.api.issue.internal.DefaultIssue;
 import org.sonar.api.issue.internal.FieldDiffs;
 import org.sonar.api.rule.RuleKey;
@@ -34,7 +33,7 @@ import org.sonar.batch.protocol.output.ReportHelper;
 import org.sonar.batch.protocol.output.component.ReportComponent;
 import org.sonar.batch.protocol.output.component.ReportComponents;
 import org.sonar.batch.protocol.output.issue.ReportIssue;
-import org.sonar.core.issue.db.IssueStorage;
+import org.sonar.server.computation.issue.IssueComputation;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -42,21 +41,21 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.util.Date;
 
-public class AnalysisReportService implements ServerComponent {
+public class AnalysisReportService {
 
-  private final ComputeEngineIssueStorageFactory issueStorageFactory;
+  private final IssueComputation issueComputation;
 
-  public AnalysisReportService(ComputeEngineIssueStorageFactory issueStorageFactory) {
-    this.issueStorageFactory = issueStorageFactory;
+  public AnalysisReportService(IssueComputation issueComputation) {
+    this.issueComputation = issueComputation;
   }
 
   public void digest(ComputationContext context) {
-    loadResources(context);
-    saveIssues(context);
+    initComponents(context);
+    parseReport(context);
   }
 
   @VisibleForTesting
-  void loadResources(ComputationContext context) {
+  void initComponents(ComputationContext context) {
     File file = new File(context.getReportDirectory(), "components.json");
 
     try (InputStream resourcesStream = new FileInputStream(file)) {
@@ -68,26 +67,23 @@ public class AnalysisReportService implements ServerComponent {
     }
   }
 
-  @VisibleForTesting
-  void saveIssues(final ComputationContext context) {
-    IssueStorage issueStorage = issueStorageFactory.newComputeEngineIssueStorage(context.getProject());
-
+  private void parseReport(ComputationContext context) {
     ReportHelper helper = ReportHelper.create(context.getReportDirectory());
-
     ReportComponent root = helper.getComponents().root();
-    browseComponent(context, helper, issueStorage, root);
+    browseComponent(context, helper, root);
+    issueComputation.afterReportProcessing();
   }
 
-  private void browseComponent(ComputationContext context, ReportHelper helper, IssueStorage issueStorage, ReportComponent component) {
+  private void browseComponent(ComputationContext context, ReportHelper helper, ReportComponent component) {
     Iterable<ReportIssue> reportIssues = helper.getIssues(component.batchId());
-    saveIssues(context, issueStorage, reportIssues);
+    browseComponentIssues(context, reportIssues);
     for (ReportComponent child : component.children()) {
-      browseComponent(context, helper, issueStorage, child);
+      browseComponent(context, helper, child);
     }
   }
 
-  private void saveIssues(final ComputationContext context, IssueStorage issueStorage, Iterable<ReportIssue> reportIssues) {
-    issueStorage.save(Iterables.transform(reportIssues, new Function<ReportIssue, DefaultIssue>() {
+  private void browseComponentIssues(final ComputationContext context, Iterable<ReportIssue> reportIssues) {
+    issueComputation.processComponentIssues(Iterables.transform(reportIssues, new Function<ReportIssue, DefaultIssue>() {
       @Override
       public DefaultIssue apply(ReportIssue input) {
         return toIssue(context, input);
@@ -104,6 +100,8 @@ public class AnalysisReportService implements ServerComponent {
     defaultIssue.setManualSeverity(issue.isManualSeverity());
     defaultIssue.setMessage(issue.message());
     defaultIssue.setLine(issue.line());
+    defaultIssue.setProjectUuid(context.getProject().uuid());
+    defaultIssue.setProjectKey(context.getProject().key());
     defaultIssue.setEffortToFix(issue.effortToFix());
     setDebt(defaultIssue, issue.debt());
     setFieldDiffs(defaultIssue, issue.diffFields(), context.getAnalysisDate());
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationComponents.java b/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationComponents.java
new file mode 100644 (file)
index 0000000..98397d9
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+package org.sonar.server.computation;
+
+import org.sonar.core.issue.db.UpdateConflictResolver;
+import org.sonar.server.computation.issue.IssueComputation;
+import org.sonar.server.computation.issue.FinalIssues;
+import org.sonar.server.computation.issue.RuleCache;
+import org.sonar.server.computation.issue.RuleCacheLoader;
+import org.sonar.server.computation.step.ComputationSteps;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class ComputationComponents {
+
+  /**
+   * List of all objects to be injected in the picocontainer dedicated to computation stack. 
+   * Does not contain the steps declared in {@link org.sonar.server.computation.step.ComputationSteps#orderedStepClasses()}.
+   */
+  public static List nonStepComponents() {
+    return Arrays.asList(
+      ComputationService.class,
+      ComputationSteps.class,
+      AnalysisReportService.class,
+
+      // issues
+      IssueComputation.class,
+      RuleCache.class,
+      RuleCacheLoader.class,
+      FinalIssues.class,
+      UpdateConflictResolver.class);
+  }
+}
index 93cae2ae9628d944cccb8e9f5855fe503d85f12d..cbe8fc4f73616918ed223accb27c942080f8325a 100644 (file)
@@ -85,4 +85,5 @@ public class ComputationContext {
   public Date getAnalysisDate() {
     return analysisDate;
   }
+
 }
index 598625b81177be326d179083c4825ade00684055..d76f3a130f9634b0783fae6086a7220f7a2c7cfb 100644 (file)
@@ -35,29 +35,24 @@ import org.sonar.core.persistence.DbSession;
 import org.sonar.core.persistence.MyBatis;
 import org.sonar.server.activity.ActivityService;
 import org.sonar.server.computation.step.ComputationStep;
-import org.sonar.server.computation.step.ComputationStepRegistry;
+import org.sonar.server.computation.step.ComputationSteps;
 import org.sonar.server.db.DbClient;
 
 import java.io.File;
 
-/**
- * Could be merged with {@link org.sonar.server.computation.ComputationWorker}
- * but it would need {@link org.sonar.server.computation.ComputationWorkerLauncher} to
- * declare transitive dependencies as it directly instantiates this class, without
- * using picocontainer.
- */
 public class ComputationService implements ServerComponent {
+
   private static final Logger LOG = LoggerFactory.getLogger(ComputationService.class);
 
   private final DbClient dbClient;
-  private final ComputationStepRegistry stepRegistry;
+  private final ComputationSteps steps;
   private final ActivityService activityService;
   private final TempFolder tempFolder;
 
-  public ComputationService(DbClient dbClient, ComputationStepRegistry stepRegistry, ActivityService activityService,
-                            TempFolder tempFolder) {
+  public ComputationService(DbClient dbClient, ComputationSteps steps, ActivityService activityService,
+    TempFolder tempFolder) {
     this.dbClient = dbClient;
-    this.stepRegistry = stepRegistry;
+    this.steps = steps;
     this.activityService = activityService;
     this.tempFolder = tempFolder;
   }
@@ -66,17 +61,14 @@ public class ComputationService implements ServerComponent {
     TimeProfiler profiler = new TimeProfiler(LOG).start(String.format(
       "#%s - %s - processing analysis report", report.getId(), report.getProjectKey()));
 
-    // Persistence of big amount of data can only be done with a batch session for the moment
-    DbSession session = dbClient.openSession(true);
-
-    ComponentDto project = findProject(report, session);
+    ComponentDto project = loadProject(report);
     File reportDir = tempFolder.newDir();
     try {
       ComputationContext context = new ComputationContext(report, project, reportDir);
-      dbClient.analysisReportDao().selectAndDecompressToDir(session, report.getId(), reportDir);
-      for (ComputationStep step : stepRegistry.steps()) {
+      decompressReport(report, reportDir);
+      for (ComputationStep step : steps.orderedSteps()) {
         TimeProfiler stepProfiler = new TimeProfiler(LOG).start(step.getDescription());
-        step.execute(session, context);
+        step.execute(context);
         stepProfiler.stop();
       }
       report.succeed();
@@ -87,19 +79,36 @@ public class ComputationService implements ServerComponent {
 
     } finally {
       FileUtils.deleteQuietly(reportDir);
-      logActivity(session, report, project);
-      session.commit();
-      MyBatis.closeQuietly(session);
+      logActivity(report, project);
       profiler.stop();
     }
   }
 
-  private ComponentDto findProject(AnalysisReportDto report, DbSession session) {
-    return dbClient.componentDao().getByKey(session, report.getProjectKey());
+  private ComponentDto loadProject(AnalysisReportDto report) {
+    DbSession session = dbClient.openSession(false);
+    try {
+      return dbClient.componentDao().getByKey(session, report.getProjectKey());
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
+  private void logActivity(AnalysisReportDto report, ComponentDto project) {
+    DbSession session = dbClient.openSession(false);
+    try {
+      report.setFinishedAt(System2.INSTANCE.now());
+      activityService.write(session, Activity.Type.ANALYSIS_REPORT, new AnalysisReportLog(report, project));
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
   }
 
-  private void logActivity(DbSession session, AnalysisReportDto report, ComponentDto project) {
-    report.setFinishedAt(System2.INSTANCE.now());
-    activityService.write(session, Activity.Type.ANALYSIS_REPORT, new AnalysisReportLog(report, project));
+  private void decompressReport(AnalysisReportDto report, File toDir) {
+    DbSession session = dbClient.openSession(false);
+    try {
+      dbClient.analysisReportDao().selectAndDecompressToDir(session, report.getId(), toDir);
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
   }
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationThread.java b/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationThread.java
new file mode 100644 (file)
index 0000000..eecd661
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+
+package org.sonar.server.computation;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.platform.ComponentContainer;
+import org.sonar.core.computation.db.AnalysisReportDto;
+import org.sonar.server.computation.step.ComputationSteps;
+import org.sonar.server.platform.Platform;
+
+/**
+ * This thread pops queue of reports and processes the report if present
+ */
+public class ComputationThread implements Runnable {
+  private static final Logger LOG = LoggerFactory.getLogger(ComputationThread.class);
+
+  private final AnalysisReportQueue queue;
+
+  public ComputationThread(AnalysisReportQueue queue) {
+    this.queue = queue;
+  }
+
+  @Override
+  public void run() {
+    AnalysisReportDto report = null;
+    try {
+      report = queue.pop();
+    } catch (Exception e) {
+      LOG.error("Failed to pop the queue of analysis reports", e);
+    }
+    if (report != null) {
+      try {
+        process(report);
+      } catch (Exception e) {
+        LOG.error(String.format(
+          "Failed to process analysis report %d of project %s", report.getId(), report.getProjectKey()), e);
+      } finally {
+        removeSilentlyFromQueue(report);
+      }
+    }
+  }
+
+  private void removeSilentlyFromQueue(AnalysisReportDto report) {
+    try {
+      queue.remove(report);
+    } catch (Exception e) {
+      LOG.error(String.format("Failed to remove analysis report %d from queue", report.getId()), e);
+    }
+  }
+
+  private void process(AnalysisReportDto report) {
+    ComponentContainer container = Platform.getInstance().getContainer();
+    ComponentContainer child = container.createChild();
+    child.addSingletons(ComputationSteps.orderedStepClasses());
+    child.addSingletons(ComputationComponents.nonStepComponents());
+    child.startComponents();
+    try {
+      child.getComponentByType(ComputationService.class).process(report);
+    } finally {
+      child.stopComponents();
+      // TODO not possible to have multiple children -> will be
+      // a problem when we will have multiple concurrent computation workers
+      container.removeChild();
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationThreadLauncher.java b/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationThreadLauncher.java
new file mode 100644 (file)
index 0000000..5354930
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+
+package org.sonar.server.computation;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import org.picocontainer.Startable;
+import org.sonar.api.ServerComponent;
+import org.sonar.api.platform.Server;
+import org.sonar.api.platform.ServerStartHandler;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+public class ComputationThreadLauncher implements Startable, ServerComponent, ServerStartHandler {
+
+  public static final String THREAD_NAME_PREFIX = "computation-";
+
+  private final AnalysisReportQueue queue;
+  private final ScheduledExecutorService executorService;
+
+  private final long delayBetweenTasks;
+  private final long delayForFirstStart;
+  private final TimeUnit timeUnit;
+
+  public ComputationThreadLauncher(AnalysisReportQueue queue) {
+    this.queue = queue;
+    this.executorService = Executors.newSingleThreadScheduledExecutor(threadFactoryWithSpecificNameForLogging());
+
+    this.delayBetweenTasks = 10;
+    this.delayForFirstStart = 0;
+    this.timeUnit = TimeUnit.SECONDS;
+  }
+
+  @VisibleForTesting
+  ComputationThreadLauncher(AnalysisReportQueue queue, long delayForFirstStart, long delayBetweenTasks, TimeUnit timeUnit) {
+    this.queue = queue;
+    this.executorService = Executors.newSingleThreadScheduledExecutor(threadFactoryWithSpecificNameForLogging());
+
+    this.delayBetweenTasks = delayBetweenTasks;
+    this.delayForFirstStart = delayForFirstStart;
+    this.timeUnit = timeUnit;
+  }
+
+  @Override
+  public void start() {
+    // do nothing because we want to wait for the server to finish startup
+  }
+
+  @Override
+  public void stop() {
+    executorService.shutdown();
+  }
+
+  public void startAnalysisTaskNow() {
+    executorService.execute(new ComputationThread(queue));
+  }
+
+  @Override
+  public void onServerStart(Server server) {
+    executorService.scheduleAtFixedRate(new ComputationThread(queue), delayForFirstStart, delayBetweenTasks, timeUnit);
+  }
+
+  /**
+   * @see org.sonar.server.platform.SwitchLogbackAppender
+   */
+  private ThreadFactory threadFactoryWithSpecificNameForLogging() {
+    return new ThreadFactoryBuilder()
+      .setNameFormat(THREAD_NAME_PREFIX + "%d").setPriority(Thread.MIN_PRIORITY).build();
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationWorker.java b/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationWorker.java
deleted file mode 100644 (file)
index e280131..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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.
- */
-
-package org.sonar.server.computation;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.core.computation.db.AnalysisReportDto;
-
-/**
- * This thread pops queue of reports and processes the report if present
- */
-public class ComputationWorker implements Runnable {
-  private static final Logger LOG = LoggerFactory.getLogger(ComputationWorker.class);
-
-  private final AnalysisReportQueue queue;
-  private final ComputationService service;
-
-  public ComputationWorker(AnalysisReportQueue queue, ComputationService service) {
-    this.queue = queue;
-    this.service = service;
-  }
-
-  @Override
-  public void run() {
-    AnalysisReportDto report = null;
-    try {
-      report = queue.pop();
-    } catch (Exception e) {
-      LOG.error("Failed to pop the queue of analysis reports", e);
-    }
-    if (report != null) {
-      try {
-        service.process(report);
-      } catch (Exception e) {
-        LOG.error(String.format(
-          "Failed to process analysis report %d of project %s", report.getId(), report.getProjectKey()), e);
-      } finally {
-        removeSilentlyFromQueue(report);
-      }
-    }
-  }
-
-  private void removeSilentlyFromQueue(AnalysisReportDto report) {
-    try {
-      queue.remove(report);
-    } catch (Exception e) {
-      LOG.error(String.format("Failed to remove analysis report %d from queue", report.getId()), e);
-    }
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationWorkerLauncher.java b/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationWorkerLauncher.java
deleted file mode 100644 (file)
index 627a48b..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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.
- */
-
-package org.sonar.server.computation;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
-import org.picocontainer.Startable;
-import org.sonar.api.ServerComponent;
-import org.sonar.api.platform.Server;
-import org.sonar.api.platform.ServerStartHandler;
-
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.TimeUnit;
-
-public class ComputationWorkerLauncher implements Startable, ServerComponent, ServerStartHandler {
-
-  public static final String THREAD_NAME_PREFIX = "computation-";
-
-  private final ComputationService service;
-  private final AnalysisReportQueue queue;
-  private final ScheduledExecutorService executorService;
-
-  private final long delayBetweenTasks;
-  private final long delayForFirstStart;
-  private final TimeUnit timeUnit;
-
-  public ComputationWorkerLauncher(ComputationService service, AnalysisReportQueue queue) {
-    this.service = service;
-    this.queue = queue;
-    this.executorService = Executors.newSingleThreadScheduledExecutor(threadFactoryWithSpecificNameForLogging());
-
-    this.delayBetweenTasks = 10;
-    this.delayForFirstStart = 0;
-    this.timeUnit = TimeUnit.SECONDS;
-  }
-
-  @VisibleForTesting
-  ComputationWorkerLauncher(ComputationService service, AnalysisReportQueue queue, long delayForFirstStart, long delayBetweenTasks, TimeUnit timeUnit) {
-    this.queue = queue;
-    this.executorService = Executors.newSingleThreadScheduledExecutor(threadFactoryWithSpecificNameForLogging());
-
-    this.delayBetweenTasks = delayBetweenTasks;
-    this.delayForFirstStart = delayForFirstStart;
-    this.timeUnit = timeUnit;
-    this.service = service;
-  }
-
-  @Override
-  public void start() {
-    // do nothing because we want to wait for the server to finish startup
-  }
-
-  @Override
-  public void stop() {
-    executorService.shutdown();
-  }
-
-  public void startAnalysisTaskNow() {
-    executorService.execute(new ComputationWorker(queue, service));
-  }
-
-  @Override
-  public void onServerStart(Server server) {
-    executorService.scheduleAtFixedRate(new ComputationWorker(queue, service), delayForFirstStart, delayBetweenTasks, timeUnit);
-  }
-
-  /**
-   * @see org.sonar.server.platform.SwitchLogbackAppender
-   */
-  private ThreadFactory threadFactoryWithSpecificNameForLogging() {
-    return new ThreadFactoryBuilder()
-      .setNameFormat(THREAD_NAME_PREFIX + "%d").setPriority(Thread.MIN_PRIORITY).build();
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/ComputeEngineIssueStorage.java b/server/sonar-server/src/main/java/org/sonar/server/computation/ComputeEngineIssueStorage.java
deleted file mode 100644 (file)
index 69ccf54..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * 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.
- */
-
-package org.sonar.server.computation;
-
-import com.google.common.annotations.VisibleForTesting;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.core.component.ComponentDto;
-import org.sonar.core.issue.db.IssueDto;
-import org.sonar.core.issue.db.IssueMapper;
-import org.sonar.core.issue.db.IssueStorage;
-import org.sonar.core.issue.db.UpdateConflictResolver;
-import org.sonar.core.persistence.DbSession;
-import org.sonar.core.persistence.MyBatis;
-import org.sonar.server.db.DbClient;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-class ComputeEngineIssueStorage extends IssueStorage {
-
-  private final DbClient dbClient;
-  private final ComponentDto project;
-  private final UpdateConflictResolver conflictResolver = new UpdateConflictResolver();
-
-  public ComputeEngineIssueStorage(MyBatis mybatis, DbClient dbClient, RuleFinder ruleFinder, ComponentDto project) {
-    super(mybatis, ruleFinder);
-    this.dbClient = dbClient;
-    this.project = project;
-  }
-
-  @Override
-  protected void doInsert(DbSession session, long now, DefaultIssue issue) {
-    IssueMapper issueMapper = session.getMapper(IssueMapper.class);
-    long componentId = componentId(session, issue);
-    long projectId = projectId();
-    Rule rule = rule(issue);
-    List<String> allTags = new ArrayList<String>();
-    allTags.addAll(Arrays.asList(rule.getTags()));
-    allTags.addAll(Arrays.asList(rule.getSystemTags()));
-    issue.setTags(allTags);
-    IssueDto dto = IssueDto.toDtoForBatchInsert(issue, componentId, projectId, rule.getId(), now);
-    issueMapper.insert(dto);
-  }
-
-  @Override
-  protected void doUpdate(DbSession session, long now, DefaultIssue issue) {
-    IssueMapper issueMapper = session.getMapper(IssueMapper.class);
-    IssueDto dto = IssueDto.toDtoForUpdate(issue, projectId(), now);
-    if (Issue.STATUS_CLOSED.equals(issue.status()) || issue.selectedAt() == null) {
-      // Issue is closed by scan or changed by end-user
-      issueMapper.update(dto);
-
-    } else {
-      int count = issueMapper.updateIfBeforeSelectedDate(dto);
-      if (count == 0) {
-        // End-user and scan changed the issue at the same time.
-        // See https://jira.codehaus.org/browse/SONAR-4309
-        conflictResolver.resolve(issue, issueMapper);
-      }
-    }
-  }
-
-  @VisibleForTesting
-  long componentId(DbSession session, DefaultIssue issue) {
-    if (issue.componentId() != null) {
-      return issue.componentId();
-    }
-
-    ComponentDto componentDto = dbClient.componentDao().getNullableByKey(session, issue.componentKey());
-    if (componentDto == null) {
-      throw new IllegalStateException("Unknown component: " + issue.componentKey());
-    }
-    return componentDto.getId();
-  }
-
-  @VisibleForTesting
-  long projectId() {
-    return project.getId();
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/ComputeEngineIssueStorageFactory.java b/server/sonar-server/src/main/java/org/sonar/server/computation/ComputeEngineIssueStorageFactory.java
deleted file mode 100644 (file)
index e9326d0..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.
- */
-
-package org.sonar.server.computation;
-
-import org.sonar.api.ServerComponent;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.core.component.ComponentDto;
-import org.sonar.core.issue.db.IssueStorage;
-import org.sonar.core.persistence.MyBatis;
-import org.sonar.server.db.DbClient;
-
-public class ComputeEngineIssueStorageFactory implements ServerComponent {
-  private final MyBatis myBatis;
-  private final DbClient dbClient;
-  private final RuleFinder ruleFinder;
-
-  public ComputeEngineIssueStorageFactory(MyBatis myBatis, DbClient dbClient, RuleFinder ruleFinder) {
-    this.myBatis = myBatis;
-    this.dbClient = dbClient;
-    this.ruleFinder = ruleFinder;
-  }
-
-  public IssueStorage newComputeEngineIssueStorage(ComponentDto project) {
-    return new ComputeEngineIssueStorage(myBatis, dbClient, ruleFinder, project);
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/FinalIssues.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/FinalIssues.java
new file mode 100644 (file)
index 0000000..03cb9c1
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+package org.sonar.server.computation.issue;
+
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.TempFolder;
+import org.sonar.server.util.cache.DiskCache;
+
+import java.io.IOException;
+
+/**
+ * Cache of all the issues involved in the analysis. Their state is as it will be
+ * persisted in database (after issue tracking, auto-assignment, ...)
+ *
+ */
+public class FinalIssues extends DiskCache<DefaultIssue> {
+
+  public FinalIssues(TempFolder tempFolder, System2 system2) throws IOException {
+    super(tempFolder.newFile("issues", ".dat"), system2);
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueComputation.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueComputation.java
new file mode 100644 (file)
index 0000000..f6f53dd
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+package org.sonar.server.computation.issue;
+
+import com.google.common.collect.Sets;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.core.rule.RuleDto;
+import org.sonar.server.util.cache.DiskCache;
+
+public class IssueComputation {
+
+  private final RuleCache ruleCache;
+  private final DiskCache<DefaultIssue>.DiskAppender finalIssuesAppender;
+
+  public IssueComputation(RuleCache ruleCache, FinalIssues finalIssues) {
+    this.ruleCache = ruleCache;
+    this.finalIssuesAppender = finalIssues.newAppender();
+  }
+
+  public void processComponentIssues(Iterable<DefaultIssue> issues) {
+    for (DefaultIssue issue : issues) {
+      if (issue.isNew()) {
+        guessAuthor(issue);
+        autoAssign(issue);
+        copyRuleTags(issue);
+        // TODO execute extension points
+      }
+      finalIssuesAppender.append(issue);
+    }
+  }
+
+  public void afterReportProcessing() {
+    finalIssuesAppender.close();
+  }
+
+  private void guessAuthor(DefaultIssue issue) {
+    // TODO
+  }
+
+  private void autoAssign(DefaultIssue issue) {
+    // TODO
+  }
+
+  private void copyRuleTags(DefaultIssue issue) {
+    RuleDto rule = ruleCache.get(issue.ruleKey());
+    issue.setTags(Sets.union(rule.getTags(), rule.getSystemTags()));
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCache.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCache.java
new file mode 100644 (file)
index 0000000..c739cbe
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+package org.sonar.server.computation.issue;
+
+import org.sonar.api.rule.RuleKey;
+import org.sonar.core.rule.RuleDto;
+import org.sonar.server.util.cache.MemoryCache;
+
+import javax.annotation.CheckForNull;
+
+/**
+ * Cache of the rules involved during the current analysis
+ */
+public class RuleCache extends MemoryCache<RuleKey, RuleDto> {
+
+  public RuleCache(RuleCacheLoader loader) {
+    super(loader);
+  }
+
+  @CheckForNull
+  public String ruleName(RuleKey key) {
+    RuleDto rule = get(key);
+    return rule != null ? rule.getName() : null;
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCacheLoader.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCacheLoader.java
new file mode 100644 (file)
index 0000000..ab467b6
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+package org.sonar.server.computation.issue;
+
+import org.sonar.api.rule.RuleKey;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.rule.RuleDto;
+import org.sonar.server.util.cache.CacheLoader;
+import org.sonar.server.db.DbClient;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class RuleCacheLoader implements CacheLoader<RuleKey, RuleDto> {
+
+  private final DbClient dbClient;
+
+  public RuleCacheLoader(DbClient dbClient) {
+    this.dbClient = dbClient;
+  }
+
+  @Override
+  public RuleDto load(RuleKey key) {
+    DbSession session = dbClient.openSession(false);
+    try {
+      return dbClient.ruleDao().getNullableByKey(session, key);
+    } finally {
+      session.close();
+    }
+  }
+
+  @Override
+  public Map<RuleKey, RuleDto> loadAll(Collection<? extends RuleKey> keys) {
+    Map<RuleKey, RuleDto> result = new HashMap<>();
+    DbSession session = dbClient.openSession(false);
+    try {
+      List<RuleDto> dtos = dbClient.ruleDao().getByKeys(session, (Collection<RuleKey>) keys);
+      for (RuleDto dto : dtos) {
+        result.put(dto.getKey(), dto);
+      }
+      return result;
+    } finally {
+      session.close();
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/package-info.java
new file mode 100644 (file)
index 0000000..bd17d02
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.server.computation.issue;
+
+import javax.annotation.ParametersAreNonnullByDefault;
index 53450512f6f0d78f5e880f24941c8a4974f0ddbf..022ee3eb068759988ce07dd84cccc213f510a1e5 100644 (file)
@@ -20,7 +20,6 @@
 
 package org.sonar.server.computation.step;
 
-import org.sonar.core.persistence.DbSession;
 import org.sonar.server.computation.ComputationContext;
 import org.sonar.server.issue.index.IssueAuthorizationIndexer;
 
@@ -33,7 +32,7 @@ public class ApplyPermissionsStep implements ComputationStep {
   }
 
   @Override
-  public void execute(DbSession session, ComputationContext context) {
+  public void execute(ComputationContext context) {
     indexer.index();
   }
 
index 13283d66600e66232c867177432412363ce1c5e9..69e131118964497a439c240e1bb50354f9544b14 100644 (file)
 
 package org.sonar.server.computation.step;
 
-import org.sonar.api.ServerComponent;
-import org.sonar.core.persistence.DbSession;
 import org.sonar.server.computation.ComputationContext;
 
-public interface ComputationStep extends ServerComponent {
+/**
+ * Implementations must be declared into {@link org.sonar.server.computation.step.ComputationSteps#orderedStepClasses()}
+ */
+public interface ComputationStep {
 
-  void execute(DbSession session, ComputationContext context);
+  void execute(ComputationContext context);
 
   String getDescription();
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationStepRegistry.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationStepRegistry.java
deleted file mode 100644 (file)
index d9cb38f..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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.
- */
-
-package org.sonar.server.computation.step;
-
-import com.google.common.collect.Lists;
-import org.sonar.api.ServerComponent;
-import org.sonar.server.source.IndexSourceLinesStep;
-
-import java.util.List;
-
-public class ComputationStepRegistry implements ServerComponent {
-
-  private final List<ComputationStep> steps;
-
-  public ComputationStepRegistry(ComputationStep... s) {
-    this.steps = order(s,
-      DigestReportStep.class,
-      ApplyPermissionsStep.class,
-      SwitchSnapshotStep.class,
-      InvalidatePreviewCacheStep.class,
-      IndexComponentsStep.class,
-      PurgeDatastoresStep.class,
-      IndexIssuesStep.class,
-      IndexSourceLinesStep.class);
-  }
-
-  public List<ComputationStep> steps() {
-    return steps;
-  }
-
-  private List<ComputationStep> order(ComputationStep[] steps, Class<? extends ComputationStep>... classes) {
-    List<ComputationStep> result = Lists.newArrayList();
-    for (Class<? extends ComputationStep> clazz : classes) {
-      result.add(find(steps, clazz));
-    }
-    return result;
-  }
-
-  private static ComputationStep find(ComputationStep[] steps, Class<? extends ComputationStep> clazz) {
-    for (ComputationStep step : steps) {
-      if (clazz.isInstance(step)) {
-        return step;
-      }
-    }
-    throw new IllegalStateException("Component not found in picocontainer: " + clazz);
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java
new file mode 100644 (file)
index 0000000..0870114
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+package org.sonar.server.computation.step;
+
+import com.google.common.collect.Lists;
+import org.sonar.server.computation.ComputationComponents;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class ComputationSteps {
+
+  /**
+   * List of all {@link org.sonar.server.computation.step.ComputationStep},
+   * ordered by execution sequence.
+   */
+  public static List<Class<? extends ComputationStep>> orderedStepClasses() {
+    return Arrays.asList(
+      DigestReportStep.class,
+      PersistIssuesStep.class,
+      SwitchSnapshotStep.class,
+      IndexComponentsStep.class,
+      PurgeDatastoresStep.class,
+      InvalidateBatchCacheStep.class,
+
+      // ES indexing is done after all db changes
+      ApplyPermissionsStep.class,
+      IndexIssuesStep.class,
+      IndexSourceLinesStep.class,
+
+      // notifications are sent at the end, so that webapp displays up-to-date information
+      SendIssueNotificationsStep.class);
+  }
+
+  private final List<ComputationStep> orderedSteps;
+
+  public ComputationSteps(ComputationStep... s) {
+    this.orderedSteps = order(s);
+  }
+
+  public List<ComputationStep> orderedSteps() {
+    return orderedSteps;
+  }
+
+  private static List<ComputationStep> order(ComputationStep[] steps) {
+    List<ComputationStep> result = Lists.newArrayList();
+    for (Class<? extends ComputationStep> clazz : orderedStepClasses()) {
+      result.add(find(steps, clazz));
+    }
+    return result;
+  }
+
+  private static ComputationStep find(ComputationStep[] steps, Class<? extends ComputationStep> clazz) {
+    for (ComputationStep step : steps) {
+      if (clazz.isInstance(step)) {
+        return step;
+      }
+    }
+    throw new IllegalStateException("Component not found: " + clazz + ". Check " + ComputationComponents.class);
+  }
+}
index 1a9acd16508db0814625592d5d6170d8f5bcfb9b..8c0b80197fd57d1ab655e54cdf6825773b24bc97 100644 (file)
@@ -20,7 +20,6 @@
 
 package org.sonar.server.computation.step;
 
-import org.sonar.core.persistence.DbSession;
 import org.sonar.server.computation.AnalysisReportService;
 import org.sonar.server.computation.ComputationContext;
 
@@ -33,7 +32,7 @@ public class DigestReportStep implements ComputationStep {
   }
 
   @Override
-  public void execute(DbSession session, ComputationContext context) {
+  public void execute(ComputationContext context) {
     reportService.digest(context);
   }
 
index 3d6b03fa470bbc27451d05dc4e785fe8df24f960..60d3f219759ca12657d0bd1ead99004bc8be58bf 100644 (file)
@@ -20,7 +20,6 @@
 
 package org.sonar.server.computation.step;
 
-import org.sonar.core.persistence.DbSession;
 import org.sonar.core.resource.ResourceIndexerDao;
 import org.sonar.server.computation.ComputationContext;
 
@@ -35,8 +34,8 @@ public class IndexComponentsStep implements ComputationStep {
   }
 
   @Override
-  public void execute(DbSession session, ComputationContext context) {
-    resourceIndexerDao.indexProject(context.getProject().getId().intValue(), session);
+  public void execute(ComputationContext context) {
+    resourceIndexerDao.indexProject(context.getProject().getId());
   }
 
   @Override
index 876318c499ca0e05fe8dcfe4d2385df7485c3493..e1caae311efa7f623d7a1d805b9c63ebd825f75e 100644 (file)
 
 package org.sonar.server.computation.step;
 
-import org.sonar.core.persistence.DbSession;
 import org.sonar.server.computation.ComputationContext;
 import org.sonar.server.issue.index.IssueAuthorizationIndexer;
 import org.sonar.server.issue.index.IssueIndexer;
 
 public class IndexIssuesStep implements ComputationStep {
 
-
   private final IssueAuthorizationIndexer authorizationIndexer;
   private final IssueIndexer indexer;
 
@@ -37,7 +35,7 @@ public class IndexIssuesStep implements ComputationStep {
   }
 
   @Override
-  public void execute(DbSession session, ComputationContext context) {
+  public void execute(ComputationContext context) {
     authorizationIndexer.index();
     indexer.index();
   }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/IndexSourceLinesStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/IndexSourceLinesStep.java
new file mode 100644 (file)
index 0000000..e5f3356
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+package org.sonar.server.computation.step;
+
+import org.sonar.server.computation.ComputationContext;
+import org.sonar.server.source.index.SourceLineIndexer;
+
+public class IndexSourceLinesStep implements ComputationStep {
+
+  private final SourceLineIndexer indexer;
+
+  public IndexSourceLinesStep(SourceLineIndexer indexer) {
+    this.indexer = indexer;
+  }
+
+  @Override
+  public void execute(ComputationContext context) {
+    indexer.index();
+  }
+
+  @Override
+  public String getDescription() {
+    return "Index source lines";
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/InvalidateBatchCacheStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/InvalidateBatchCacheStep.java
new file mode 100644 (file)
index 0000000..89ac2fc
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+package org.sonar.server.computation.step;
+
+import org.sonar.core.preview.PreviewCache;
+import org.sonar.core.properties.PropertiesDao;
+import org.sonar.core.properties.PropertyDto;
+import org.sonar.server.computation.ComputationContext;
+
+public class InvalidateBatchCacheStep implements ComputationStep {
+  private final PropertiesDao propertiesDao;
+
+  public InvalidateBatchCacheStep(PropertiesDao propertiesDao) {
+    this.propertiesDao = propertiesDao;
+  }
+
+  @Override
+  public void execute(ComputationContext context) {
+    PropertyDto property = new PropertyDto()
+      .setKey(PreviewCache.SONAR_PREVIEW_CACHE_LAST_UPDATE_KEY)
+      .setResourceId(context.getProject().getId())
+      .setValue(String.valueOf(System.currentTimeMillis()));
+    propertiesDao.setProperty(property);
+  }
+
+  @Override
+  public String getDescription() {
+    return "Invalidate batch cache";
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/InvalidatePreviewCacheStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/InvalidatePreviewCacheStep.java
deleted file mode 100644 (file)
index c2556aa..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.
- */
-
-package org.sonar.server.computation.step;
-
-import org.sonar.core.persistence.DbSession;
-import org.sonar.core.preview.PreviewCache;
-import org.sonar.core.properties.PropertiesDao;
-import org.sonar.core.properties.PropertyDto;
-import org.sonar.server.computation.ComputationContext;
-
-public class InvalidatePreviewCacheStep implements ComputationStep {
-  private final PropertiesDao propertiesDao;
-
-  public InvalidatePreviewCacheStep(PropertiesDao propertiesDao) {
-    this.propertiesDao = propertiesDao;
-  }
-
-  @Override
-  public void execute(DbSession session, ComputationContext context) {
-    PropertyDto property = new PropertyDto()
-      .setKey(PreviewCache.SONAR_PREVIEW_CACHE_LAST_UPDATE_KEY)
-      .setResourceId(context.getProject().getId())
-      .setValue(String.valueOf(System.currentTimeMillis()));
-    propertiesDao.setProperty(property, session);
-  }
-
-  @Override
-  public String getDescription() {
-    return "Invalidate preview cache";
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistIssuesStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistIssuesStep.java
new file mode 100644 (file)
index 0000000..1fd281b
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * 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.
+ */
+package org.sonar.server.computation.step;
+
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.IssueComment;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.issue.internal.DefaultIssueComment;
+import org.sonar.api.issue.internal.FieldDiffs;
+import org.sonar.api.utils.System2;
+import org.sonar.core.issue.db.IssueChangeDto;
+import org.sonar.core.issue.db.IssueChangeMapper;
+import org.sonar.core.issue.db.IssueDto;
+import org.sonar.core.issue.db.IssueMapper;
+import org.sonar.core.issue.db.UpdateConflictResolver;
+import org.sonar.core.persistence.BatchSession;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.persistence.MyBatis;
+import org.sonar.server.computation.ComputationContext;
+import org.sonar.server.computation.issue.FinalIssues;
+import org.sonar.server.computation.issue.RuleCache;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.util.CloseableIterator;
+
+public class PersistIssuesStep implements ComputationStep {
+
+  private final DbClient dbClient;
+  private final System2 system2;
+  private final UpdateConflictResolver conflictResolver;
+  private final RuleCache ruleCache;
+  private final FinalIssues finalIssues;
+
+  public PersistIssuesStep(DbClient dbClient, System2 system2, UpdateConflictResolver conflictResolver,
+    RuleCache ruleCache, FinalIssues finalIssues) {
+    this.dbClient = dbClient;
+    this.system2 = system2;
+    this.conflictResolver = conflictResolver;
+    this.ruleCache = ruleCache;
+    this.finalIssues = finalIssues;
+  }
+
+  @Override
+  public void execute(ComputationContext context) {
+    DbSession session = dbClient.openSession(true);
+    IssueMapper mapper = session.getMapper(IssueMapper.class);
+    IssueChangeMapper changeMapper = session.getMapper(IssueChangeMapper.class);
+    int count = 0;
+
+    CloseableIterator<DefaultIssue> issues = finalIssues.traverse();
+    try {
+      while (issues.hasNext()) {
+        DefaultIssue issue = issues.next();
+
+        if (issue.isNew()) {
+          Integer ruleId = ruleCache.get(issue.ruleKey()).getId();
+          mapper.insert(IssueDto.toDtoForBatchInsert(issue, issue.componentId(), context.getProject().getId(), ruleId, system2.now()));
+          count++;
+        } else {
+          IssueDto dto = IssueDto.toDtoForUpdate(issue, context.getProject().getId(), system2.now());
+          if (Issue.STATUS_CLOSED.equals(issue.status()) || issue.selectedAt() == null) {
+            // Issue is closed by scan or changed by end-user
+            mapper.update(dto);
+
+          } else {
+            int updateCount = mapper.updateIfBeforeSelectedDate(dto);
+            if (updateCount == 0) {
+              // End-user and scan changed the issue at the same time.
+              // See https://jira.codehaus.org/browse/SONAR-4309
+              conflictResolver.resolve(issue, mapper);
+            }
+          }
+
+          count++;
+        }
+        count += insertChanges(changeMapper, issue);
+
+        if (count > BatchSession.MAX_BATCH_SIZE) {
+          session.flushStatements();
+          session.commit();
+          count = 0;
+        }
+      }
+      session.flushStatements();
+      session.commit();
+    } finally {
+      MyBatis.closeQuietly(session);
+      issues.close();
+    }
+  }
+
+  private int insertChanges(IssueChangeMapper mapper, DefaultIssue issue) {
+    int count = 0;
+    for (IssueComment comment : issue.comments()) {
+      DefaultIssueComment c = (DefaultIssueComment) comment;
+      if (c.isNew()) {
+        IssueChangeDto changeDto = IssueChangeDto.of(c);
+        mapper.insert(changeDto);
+        count++;
+      }
+    }
+    FieldDiffs diffs = issue.currentChange();
+    if (!issue.isNew() && diffs != null) {
+      IssueChangeDto changeDto = IssueChangeDto.of(issue.key(), diffs);
+      mapper.insert(changeDto);
+      count++;
+    }
+    return count;
+  }
+
+  @Override
+  public String getDescription() {
+    return "Persist issues";
+  }
+}
index 7aed146c3fdd4bd8c61452f43778c2664a4984e4..1f9725c7ff38686aa4d039da8c5228a12b19a962 100644 (file)
@@ -22,19 +22,30 @@ package org.sonar.server.computation.step;
 
 import org.sonar.core.computation.dbcleaner.ProjectCleaner;
 import org.sonar.core.persistence.DbSession;
+import org.sonar.core.persistence.MyBatis;
 import org.sonar.core.purge.IdUuidPair;
 import org.sonar.server.computation.ComputationContext;
+import org.sonar.server.db.DbClient;
 
 public class PurgeDatastoresStep implements ComputationStep {
+
   private final ProjectCleaner projectCleaner;
+  private final DbClient dbClient;
 
-  public PurgeDatastoresStep(ProjectCleaner projectCleaner) {
+  public PurgeDatastoresStep(DbClient dbClient, ProjectCleaner projectCleaner) {
     this.projectCleaner = projectCleaner;
+    this.dbClient = dbClient;
   }
 
   @Override
-  public void execute(DbSession session, ComputationContext context) {
-    projectCleaner.purge(session, new IdUuidPair(context.getProject().getId(), context.getProject().uuid()));
+  public void execute(ComputationContext context) {
+    DbSession session = dbClient.openSession(true);
+    try {
+      projectCleaner.purge(session, new IdUuidPair(context.getProject().getId(), context.getProject().uuid()));
+      session.commit();
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
   }
 
   @Override
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/SendIssueNotificationsStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/SendIssueNotificationsStep.java
new file mode 100644 (file)
index 0000000..3b47050
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ */
+package org.sonar.server.computation.step;
+
+import com.google.common.collect.HashMultiset;
+import com.google.common.collect.Multiset;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.core.component.ComponentDto;
+import org.sonar.server.computation.ComputationContext;
+import org.sonar.server.computation.issue.FinalIssues;
+import org.sonar.server.computation.issue.RuleCache;
+import org.sonar.server.issue.notification.IssueNotifications;
+import org.sonar.server.util.CloseableIterator;
+
+/**
+ * Reads issues from disk cache and send related notifications. For performance reasons,
+ * the standard notification DB queue is not used as a temporary storage. Notifications
+ * are directly processed by {@link org.sonar.server.notifications.NotificationService}.
+ */
+public class SendIssueNotificationsStep implements ComputationStep {
+
+  private final FinalIssues finalIssues;
+  private final RuleCache rules;
+  private final IssueNotifications service;
+
+  public SendIssueNotificationsStep(FinalIssues finalIssues, RuleCache rules,
+    IssueNotifications service) {
+    this.finalIssues = finalIssues;
+    this.rules = rules;
+    this.service = service;
+  }
+
+  @Override
+  public void execute(ComputationContext context) {
+    NewIssuesStatistics newIssuesStatistics = new NewIssuesStatistics();
+    CloseableIterator<DefaultIssue> issues = finalIssues.traverse();
+    try {
+      while (issues.hasNext()) {
+        DefaultIssue issue = issues.next();
+        if (issue.isNew() && issue.resolution() == null) {
+          newIssuesStatistics.add(issue);
+        } else if (issue.isChanged() && issue.mustSendNotifications()) {
+          service.sendChanges(issue, null, rules.ruleName(issue.ruleKey()),
+            context.getProject(), /* TODO */null, null, true);
+        }
+      }
+
+    } finally {
+      issues.close();
+    }
+    sendNewIssuesStatistics(context, newIssuesStatistics);
+  }
+
+  private void sendNewIssuesStatistics(ComputationContext context, NewIssuesStatistics newIssuesStatistics) {
+    if (!newIssuesStatistics.isEmpty()) {
+      ComponentDto project = context.getProject();
+      Notification notification = new Notification("new-issues")
+        .setFieldValue("projectName", project.longName())
+        .setFieldValue("projectKey", project.key())
+        .setDefaultMessage(newIssuesStatistics.size() + " new issues on " + project.longName() + ".\n")
+        .setFieldValue("projectDate", DateUtils.formatDateTime(context.getAnalysisDate()))
+        .setFieldValue("projectUuid", project.uuid())
+        .setFieldValue("count", String.valueOf(newIssuesStatistics.size()));
+      for (String severity : Severity.ALL) {
+        notification.setFieldValue("count-" + severity, String.valueOf(newIssuesStatistics.issuesWithSeverity(severity)));
+      }
+      service.send(notification, true);
+    }
+  }
+
+  @Override
+  public String getDescription() {
+    return "Send issue notifications";
+  }
+
+  static class NewIssuesStatistics {
+    private final Multiset<String> set = HashMultiset.create();
+
+    void add(Issue issue) {
+      set.add(issue.severity());
+    }
+
+    int issuesWithSeverity(String severity) {
+      return set.count(severity);
+    }
+
+    int size() {
+      return set.size();
+    }
+
+    boolean isEmpty() {
+      return set.isEmpty();
+    }
+  }
+
+}
index 6fde4596498382351107564942e6f5b7d7438735..5d51fe0f5f067101add9e6629282d9cbc94c536c 100644 (file)
@@ -23,23 +23,30 @@ package org.sonar.server.computation.step;
 import org.sonar.core.component.SnapshotDto;
 import org.sonar.core.computation.db.AnalysisReportDto;
 import org.sonar.core.persistence.DbSession;
+import org.sonar.core.persistence.MyBatis;
 import org.sonar.server.component.db.SnapshotDao;
 import org.sonar.server.computation.ComputationContext;
+import org.sonar.server.db.DbClient;
 
 import java.util.List;
 
 public class SwitchSnapshotStep implements ComputationStep {
 
-  private SnapshotDao dao;
+  private final DbClient dbClient;
 
-  public SwitchSnapshotStep(SnapshotDao dao) {
-    this.dao = dao;
+  public SwitchSnapshotStep(DbClient dbClient) {
+    this.dbClient = dbClient;
   }
 
   @Override
-  public void execute(DbSession session, ComputationContext context) {
-    disablePreviousSnapshot(session, context.getReportDto());
-    enableCurrentSnapshot(session, context.getReportDto());
+  public void execute(ComputationContext context) {
+    DbSession session = dbClient.openSession(true);
+    try {
+      disablePreviousSnapshot(session, context.getReportDto());
+      enableCurrentSnapshot(session, context.getReportDto());
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
   }
 
   @Override
@@ -51,22 +58,23 @@ public class SwitchSnapshotStep implements ComputationStep {
     SnapshotDto referenceSnapshot;
 
     try {
-      referenceSnapshot = dao.getByKey(session, report.getSnapshotId());
+      referenceSnapshot = dbClient.snapshotDao().getByKey(session, report.getSnapshotId());
     } catch (Exception exception) {
       throw new IllegalStateException(String.format("Unexpected error while trying to retrieve snapshot of analysis %s", report), exception);
     }
 
-    List<SnapshotDto> snapshots = dao.findSnapshotAndChildrenOfProjectScope(session, referenceSnapshot);
+    List<SnapshotDto> snapshots = dbClient.snapshotDao().findSnapshotAndChildrenOfProjectScope(session, referenceSnapshot);
     for (SnapshotDto snapshot : snapshots) {
-      SnapshotDto previousLastSnapshot = dao.getLastSnapshot(session, snapshot);
+      SnapshotDto previousLastSnapshot = dbClient.snapshotDao().getLastSnapshot(session, snapshot);
       if (previousLastSnapshot != null) {
-        dao.updateSnapshotAndChildrenLastFlag(session, previousLastSnapshot, false);
+        dbClient.snapshotDao().updateSnapshotAndChildrenLastFlag(session, previousLastSnapshot, false);
         session.commit();
       }
     }
   }
 
   private void enableCurrentSnapshot(DbSession session, AnalysisReportDto report) {
+    SnapshotDao dao = dbClient.snapshotDao();
     SnapshotDto snapshot = dao.getByKey(session, report.getSnapshotId());
     SnapshotDto previousLastSnapshot = dao.getLastSnapshot(session, snapshot);
 
index a2a21799673fa9e82fa1ec19251deba4d98caba9..d474e73bb3b2c0ad3b0203df93ab8a2c16091f4c 100644 (file)
@@ -25,7 +25,7 @@ import org.sonar.api.server.ws.RequestHandler;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.server.computation.AnalysisReportQueue;
-import org.sonar.server.computation.ComputationWorkerLauncher;
+import org.sonar.server.computation.ComputationThreadLauncher;
 
 import java.io.InputStream;
 
@@ -37,9 +37,9 @@ public class SubmitReportWsAction implements ComputationWsAction, RequestHandler
   public static final String PARAM_REPORT_DATA = "report";
 
   private final AnalysisReportQueue queue;
-  private final ComputationWorkerLauncher workerLauncher;
+  private final ComputationThreadLauncher workerLauncher;
 
-  public SubmitReportWsAction(AnalysisReportQueue queue, ComputationWorkerLauncher workerLauncher) {
+  public SubmitReportWsAction(AnalysisReportQueue queue, ComputationThreadLauncher workerLauncher) {
     this.queue = queue;
     this.workerLauncher = workerLauncher;
   }
index 5d870119fe2a9c478c8125056148a2291823f7a3..28ec5704bb0eaf46cb313e3e358b97eb50bd800c 100644 (file)
@@ -31,7 +31,7 @@ import org.sonar.api.issue.internal.IssueChangeContext;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rules.Rule;
 import org.sonar.core.component.ComponentDto;
-import org.sonar.core.issue.IssueNotifications;
+import org.sonar.server.issue.notification.IssueNotifications;
 import org.sonar.core.issue.db.IssueDto;
 import org.sonar.core.issue.db.IssueStorage;
 import org.sonar.core.persistence.DbSession;
@@ -101,10 +101,11 @@ public class IssueBulkChangeService {
         if (issueBulkChangeQuery.sendNotifications()) {
           String projectKey = issue.projectKey();
           if (projectKey != null) {
-            issueNotifications.sendChanges((DefaultIssue) issue, issueChangeContext,
-              repository.rule(issue.ruleKey()),
+            Rule rule = repository.rule(issue.ruleKey());
+            issueNotifications.sendChanges((DefaultIssue) issue, issueChangeContext.login(),
+              rule != null ? rule.getName() : null,
               repository.project(projectKey),
-              repository.component(issue.componentKey()));
+              repository.component(issue.componentKey()), null, false);
           }
         }
         concernedProjects.add(issue.projectKey());
index 6c83da09dde85d215ef5bad64f93b74cbdb0e25a..a74fcc1b7e135c67e949a13c1e9e39707feef88f 100644 (file)
@@ -38,7 +38,7 @@ import org.sonar.api.user.UserFinder;
 import org.sonar.api.web.UserRole;
 import org.sonar.core.component.ComponentDto;
 import org.sonar.core.issue.DefaultIssueBuilder;
-import org.sonar.core.issue.IssueNotifications;
+import org.sonar.server.issue.notification.IssueNotifications;
 import org.sonar.core.issue.IssueUpdater;
 import org.sonar.core.issue.db.IssueDao;
 import org.sonar.core.issue.db.IssueDto;
@@ -332,11 +332,12 @@ public class IssueService implements ServerComponent {
       throw new IllegalStateException(String.format("Issue '%s' has no project key", issue.key()));
     }
     issueStorage.save(session, issue);
-    issueNotifications.sendChanges(issue, context,
-      getNullableRuleByKey(issue.ruleKey()),
+    Rule rule = getNullableRuleByKey(issue.ruleKey());
+    issueNotifications.sendChanges(issue, context.login(),
+      rule != null ? rule.getName() : null,
       dbClient.componentDao().getByKey(session, projectKey),
       dbClient.componentDao().getNullableByKey(session, issue.componentKey()),
-      comment);
+      comment, false);
     dryRunCache.reportResourceModification(issue.componentKey());
   }
 
index 0130df59e590ed331fde205caef3b74764c1c4e6..c0e36fc19b783de3850db03cf46d5d689d5f3b3c 100644 (file)
  */
 package org.sonar.server.issue.index;
 
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.Date;
-
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
 import org.apache.commons.lang.StringUtils;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.server.db.DbClient;
 import org.sonar.server.db.ResultSetIterator;
 import org.sonar.server.db.migrations.SqlUtil;
 
-import com.google.common.base.Splitter;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Maps;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Date;
 
 /**
  * Scrolls over table ISSUES and reads documents to populate
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/ChangesOnMyIssueNotificationDispatcher.java b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/ChangesOnMyIssueNotificationDispatcher.java
new file mode 100644 (file)
index 0000000..db49b20
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+package org.sonar.server.issue.notification;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.Multimap;
+import org.sonar.api.notifications.*;
+
+import javax.annotation.Nullable;
+import java.util.Collection;
+
+/**
+ * This dispatcher means: "notify me when a change is done on an issue that is assigned to me or reported by me".
+ *
+ * @since 3.6, but the feature exists since 2.10 ("review-changed" notification)
+ */
+public class ChangesOnMyIssueNotificationDispatcher extends NotificationDispatcher {
+
+  public static final String KEY = "ChangesOnMyIssue";
+  private NotificationManager notificationManager;
+
+  public ChangesOnMyIssueNotificationDispatcher(NotificationManager notificationManager) {
+    super("issue-changes");
+    this.notificationManager = notificationManager;
+  }
+
+  @Override
+  public String getKey() {
+    return KEY;
+  }
+
+  public static NotificationDispatcherMetadata newMetadata() {
+    return NotificationDispatcherMetadata.create(KEY)
+      .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
+      .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
+  }
+
+  @Override
+  public void dispatch(Notification notification, Context context) {
+    String projectKey = notification.getFieldValue("projectKey");
+    Multimap<String, NotificationChannel> subscribedRecipients = notificationManager.findNotificationSubscribers(this, projectKey);
+
+    // See available fields in the class IssueNotifications.
+
+    // All the following users can be null
+    String changeAuthor = notification.getFieldValue("changeAuthor");
+    String reporter = notification.getFieldValue("reporter");
+    String assignee = notification.getFieldValue("assignee");
+
+    if (!Objects.equal(changeAuthor, reporter)) {
+      addUserToContextIfSubscribed(context, reporter, subscribedRecipients);
+    }
+    if (!Objects.equal(changeAuthor, assignee)) {
+      addUserToContextIfSubscribed(context, assignee, subscribedRecipients);
+    }
+  }
+
+  private void addUserToContextIfSubscribed(Context context, @Nullable String user, Multimap<String, NotificationChannel> subscribedRecipients) {
+    if (user != null) {
+      Collection<NotificationChannel> channels = subscribedRecipients.get(user);
+      for (NotificationChannel channel : channels) {
+        context.addUser(user, channel);
+      }
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueChangesEmailTemplate.java b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueChangesEmailTemplate.java
new file mode 100644 (file)
index 0000000..d3c34ce
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * 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.
+ */
+package org.sonar.server.issue.notification;
+
+import com.google.common.base.Strings;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.config.EmailSettings;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.user.User;
+import org.sonar.api.user.UserFinder;
+import org.sonar.plugins.emailnotifications.api.EmailMessage;
+import org.sonar.plugins.emailnotifications.api.EmailTemplate;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+/**
+ * Creates email message for notification "issue-changes".
+ */
+public class IssueChangesEmailTemplate extends EmailTemplate {
+
+  private static final char NEW_LINE = '\n';
+  private final EmailSettings settings;
+  private final UserFinder userFinder;
+
+  public IssueChangesEmailTemplate(EmailSettings settings, UserFinder userFinder) {
+    this.settings = settings;
+    this.userFinder = userFinder;
+  }
+
+  @Override
+  public EmailMessage format(Notification notif) {
+    if (!"issue-changes".equals(notif.getType())) {
+      return null;
+    }
+
+    StringBuilder sb = new StringBuilder();
+    appendHeader(notif, sb);
+    sb.append(NEW_LINE);
+    appendChanges(notif, sb);
+    sb.append(NEW_LINE);
+    appendFooter(sb, notif);
+
+    String projectName = notif.getFieldValue("projectName");
+    String issueKey = notif.getFieldValue("key");
+    String author = notif.getFieldValue("changeAuthor");
+
+    EmailMessage message = new EmailMessage()
+      .setMessageId("issue-changes/" + issueKey)
+      .setSubject(projectName + ", change on issue #" + issueKey)
+      .setMessage(sb.toString());
+    if (author != null) {
+      message.setFrom(getUserFullName(author));
+    }
+    return message;
+  }
+
+  private void appendChanges(Notification notif, StringBuilder sb) {
+    appendField(sb, "Comment", null, notif.getFieldValue("comment"));
+    appendFieldWithoutHistory(sb, "Assignee", notif.getFieldValue("old.assignee"), notif.getFieldValue("new.assignee"));
+    appendField(sb, "Severity", notif.getFieldValue("old.severity"), notif.getFieldValue("new.severity"));
+    appendField(sb, "Resolution", notif.getFieldValue("old.resolution"), notif.getFieldValue("new.resolution"));
+    appendField(sb, "Status", notif.getFieldValue("old.status"), notif.getFieldValue("new.status"));
+    appendField(sb, "Message", notif.getFieldValue("old.message"), notif.getFieldValue("new.message"));
+    appendField(sb, "Author", notif.getFieldValue("old.author"), notif.getFieldValue("new.author"));
+    appendFieldWithoutHistory(sb, "Action Plan", notif.getFieldValue("old.actionPlan"), notif.getFieldValue("new.actionPlan"));
+    appendField(sb, "Tags", formatTagChange(notif.getFieldValue("old.tags")), formatTagChange(notif.getFieldValue("new.tags")));
+  }
+
+  @CheckForNull
+  private static String formatTagChange(@Nullable String tags) {
+    if (tags == null) {
+      return null;
+    } else {
+      return "[" + tags + "]";
+    }
+  }
+
+  private void appendHeader(Notification notif, StringBuilder sb) {
+    appendLine(sb, StringUtils.defaultString(notif.getFieldValue("componentName"), notif.getFieldValue("componentKey")));
+    appendField(sb, "Rule", null, notif.getFieldValue("ruleName"));
+    appendField(sb, "Message", null, notif.getFieldValue("message"));
+  }
+
+  private void appendFooter(StringBuilder sb, Notification notification) {
+    String issueKey = notification.getFieldValue("key");
+    sb.append("See it in SonarQube: ").append(settings.getServerBaseURL()).append("/issues/search#issues=").append(issueKey).append(NEW_LINE);
+  }
+
+  private void appendLine(StringBuilder sb, @Nullable String line) {
+    if (!Strings.isNullOrEmpty(line)) {
+      sb.append(line).append(NEW_LINE);
+    }
+  }
+
+  private void appendField(StringBuilder sb, String name, @Nullable String oldValue, @Nullable String newValue) {
+    if (oldValue != null || newValue != null) {
+      sb.append(name).append(": ");
+      if (newValue != null) {
+        sb.append(newValue);
+      }
+      if (oldValue != null) {
+        sb.append(" (was ").append(oldValue).append(")");
+      }
+      sb.append(NEW_LINE);
+    }
+  }
+
+  private void appendFieldWithoutHistory(StringBuilder sb, String name, @Nullable String oldValue, @Nullable String newValue) {
+    if (oldValue != null || newValue != null) {
+      sb.append(name);
+      if (newValue != null) {
+        sb.append(" changed to ");
+        sb.append(newValue);
+      } else {
+        sb.append(" removed");
+      }
+      sb.append(NEW_LINE);
+    }
+  }
+
+  private String getUserFullName(@Nullable String login) {
+    if (login == null) {
+      return null;
+    }
+    User user = userFinder.findByLogin(login);
+    if (user == null) {
+      // most probably user was deleted
+      return login;
+    }
+    return StringUtils.defaultIfBlank(user.name(), login);
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueNotifications.java b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueNotifications.java
new file mode 100644 (file)
index 0000000..25fe913
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+package org.sonar.server.issue.notification;
+
+import org.sonar.api.ServerComponent;
+import org.sonar.api.component.Component;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.issue.internal.FieldDiffs;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.notifications.NotificationManager;
+import org.sonar.server.notifications.NotificationService;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import java.io.Serializable;
+import java.util.Map;
+
+public class IssueNotifications implements ServerComponent {
+
+  private final NotificationManager asyncService;
+  private final NotificationService syncService;
+
+  public IssueNotifications(NotificationManager asyncService, NotificationService syncService) {
+    this.asyncService = asyncService;
+    this.syncService = syncService;
+  }
+
+  @CheckForNull
+  public Notification sendChanges(DefaultIssue issue, @Nullable String changeAuthorLogin,
+    @Nullable String ruleName, Component project, @Nullable Component component,
+    @Nullable String comment, boolean synchronous) {
+    Notification notification = createChangeNotification(issue, changeAuthorLogin, ruleName, project, component, comment);
+    if (notification != null) {
+      send(notification, synchronous);
+    }
+    return notification;
+  }
+
+  public void send(Notification notification, boolean synchronous) {
+    if (synchronous) {
+      syncService.deliver(notification);
+    } else {
+      asyncService.scheduleForSending(notification);
+    }
+  }
+
+  @CheckForNull
+  private Notification createChangeNotification(DefaultIssue issue, @Nullable String changeAuthorLogin,
+    @Nullable String ruleName, Component project,
+    @Nullable Component component, @Nullable String comment) {
+    Notification notification = null;
+    if (comment != null || issue.mustSendNotifications()) {
+      FieldDiffs currentChange = issue.currentChange();
+      notification = new Notification("issue-changes")
+        .setFieldValue("projectName", project.longName())
+        .setFieldValue("projectKey", project.key())
+        .setFieldValue("key", issue.key())
+        .setFieldValue("changeAuthor", changeAuthorLogin)
+        .setFieldValue("reporter", issue.reporter())
+        .setFieldValue("assignee", issue.assignee())
+        .setFieldValue("message", issue.message())
+        .setFieldValue("ruleName", ruleName)
+        .setFieldValue("componentKey", issue.componentKey());
+      if (component != null) {
+        notification.setFieldValue("componentName", component.longName());
+      }
+      if (comment != null) {
+        notification.setFieldValue("comment", comment);
+      }
+      if (currentChange != null) {
+        for (Map.Entry<String, FieldDiffs.Diff> entry : currentChange.diffs().entrySet()) {
+          String type = entry.getKey();
+          FieldDiffs.Diff diff = entry.getValue();
+          Serializable newValue = diff.newValue();
+          Serializable oldValue = diff.oldValue();
+          notification.setFieldValue("old." + type, oldValue != null ? oldValue.toString() : null);
+          notification.setFieldValue("new." + type, newValue != null ? newValue.toString() : null);
+        }
+      }
+    }
+    return notification;
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewFalsePositiveNotificationDispatcher.java b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewFalsePositiveNotificationDispatcher.java
new file mode 100644 (file)
index 0000000..bbf9719
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+package org.sonar.server.issue.notification;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.Multimap;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.notifications.*;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * This dispatcher means: "notify me when someone resolves an issue as false positive".
+ *
+ * @since 3.6
+ */
+public class NewFalsePositiveNotificationDispatcher extends NotificationDispatcher {
+
+  public static final String KEY = "NewFalsePositiveIssue";
+
+  private final NotificationManager notifications;
+
+  public NewFalsePositiveNotificationDispatcher(NotificationManager notifications) {
+    super("issue-changes");
+    this.notifications = notifications;
+  }
+
+  @Override
+  public String getKey() {
+    return KEY;
+  }
+
+  public static NotificationDispatcherMetadata newMetadata() {
+    return NotificationDispatcherMetadata.create(KEY)
+      .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
+      .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
+  }
+
+  @Override
+  public void dispatch(Notification notification, Context context) {
+    String newResolution = notification.getFieldValue("new.resolution");
+    if (Objects.equal(newResolution, Issue.RESOLUTION_FALSE_POSITIVE)) {
+      String author = notification.getFieldValue("changeAuthor");
+      String projectKey = notification.getFieldValue("projectKey");
+      Multimap<String, NotificationChannel> subscribedRecipients = notifications.findNotificationSubscribers(this, projectKey);
+      notify(author, context, subscribedRecipients);
+    }
+  }
+
+  private void notify(String author, Context context, Multimap<String, NotificationChannel> subscribedRecipients) {
+    for (Map.Entry<String, Collection<NotificationChannel>> channelsByRecipients : subscribedRecipients.asMap().entrySet()) {
+      String login = channelsByRecipients.getKey();
+      // Do not notify the person that resolved the issue
+      if (!Objects.equal(author, login)) {
+        for (NotificationChannel channel : channelsByRecipients.getValue()) {
+          context.addUser(login, channel);
+        }
+      }
+    }
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesEmailTemplate.java b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesEmailTemplate.java
new file mode 100644 (file)
index 0000000..25dcef9
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ */
+package org.sonar.server.issue.notification;
+
+import com.google.common.collect.Lists;
+import org.sonar.api.config.EmailSettings;
+import org.sonar.api.i18n.I18n;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.plugins.emailnotifications.api.EmailMessage;
+import org.sonar.plugins.emailnotifications.api.EmailTemplate;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Locale;
+
+/**
+ * Creates email message for notification "new-issues".
+ */
+public class NewIssuesEmailTemplate extends EmailTemplate {
+
+  public static final String FIELD_PROJECT_NAME = "projectName";
+  public static final String FIELD_PROJECT_KEY = "projectKey";
+  public static final String FIELD_PROJECT_DATE = "projectDate";
+
+  private final EmailSettings settings;
+  private final I18n i18n;
+
+  public NewIssuesEmailTemplate(EmailSettings settings, I18n i18n) {
+    this.settings = settings;
+    this.i18n = i18n;
+  }
+
+  @Override
+  public EmailMessage format(Notification notification) {
+    if (!"new-issues".equals(notification.getType())) {
+      return null;
+    }
+    String projectName = notification.getFieldValue(FIELD_PROJECT_NAME);
+
+    StringBuilder sb = new StringBuilder();
+    sb.append("Project: ").append(projectName).append("\n\n");
+    sb.append(notification.getFieldValue("count")).append(" new issues").append("\n\n");
+    sb.append("   ");
+    for (Iterator<String> severityIterator = Lists.reverse(Severity.ALL).iterator(); severityIterator.hasNext();) {
+      String severity = severityIterator.next();
+      String severityLabel = i18n.message(getLocale(), "severity." + severity, severity);
+      sb.append(severityLabel).append(": ").append(notification.getFieldValue("count-" + severity));
+      if (severityIterator.hasNext()) {
+        sb.append("   ");
+      }
+    }
+    sb.append('\n');
+
+    appendFooter(sb, notification);
+
+    return new EmailMessage()
+      .setMessageId("new-issues/" + notification.getFieldValue(FIELD_PROJECT_KEY))
+      .setSubject(projectName + ": new issues")
+      .setMessage(sb.toString());
+  }
+
+  private void appendFooter(StringBuilder sb, Notification notification) {
+    String projectUuid = notification.getFieldValue("projectUuid");
+    String dateString = notification.getFieldValue(FIELD_PROJECT_DATE);
+    if (projectUuid != null && dateString != null) {
+      Date date = DateUtils.parseDateTime(dateString);
+      String url = String.format("%s/issues/search#projectUuids=%s|createdAt=%s",
+        settings.getServerBaseURL(), encode(projectUuid), encode(DateUtils.formatDateTime(date)));
+      sb.append("\n").append("See it in SonarQube: ").append(url).append("\n");
+    }
+  }
+
+  public static String encode(String toEncode) {
+    try {
+      return URLEncoder.encode(toEncode, "UTF-8");
+    } catch (UnsupportedEncodingException e) {
+      throw new IllegalStateException("Encoding not supported", e);
+    }
+  }
+
+  private Locale getLocale() {
+    return Locale.ENGLISH;
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotificationDispatcher.java b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotificationDispatcher.java
new file mode 100644 (file)
index 0000000..f45ef34
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+package org.sonar.server.issue.notification;
+
+import com.google.common.collect.Multimap;
+import org.sonar.api.notifications.*;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * This dispatcher means: "notify me when new issues are introduced during project analysis"
+ */
+public class NewIssuesNotificationDispatcher extends NotificationDispatcher {
+
+  public static final String KEY = "NewIssues";
+  private final NotificationManager manager;
+
+  public NewIssuesNotificationDispatcher(NotificationManager manager) {
+    super("new-issues");
+    this.manager = manager;
+  }
+
+  @Override
+  public String getKey() {
+    return KEY;
+  }
+
+  public static NotificationDispatcherMetadata newMetadata() {
+    return NotificationDispatcherMetadata.create(KEY)
+      .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
+      .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
+  }
+
+  @Override
+  public void dispatch(Notification notification, Context context) {
+    String projectKey = notification.getFieldValue("projectKey");
+    Multimap<String, NotificationChannel> subscribedRecipients = manager.findNotificationSubscribers(this, projectKey);
+
+    for (Map.Entry<String, Collection<NotificationChannel>> channelsByRecipients : subscribedRecipients.asMap().entrySet()) {
+      String userLogin = channelsByRecipients.getKey();
+      for (NotificationChannel channel : channelsByRecipients.getValue()) {
+        context.addUser(userLogin, channel);
+      }
+    }
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/notification/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/issue/notification/package-info.java
new file mode 100644 (file)
index 0000000..d4cfe9a
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.server.issue.notification;
+
+import javax.annotation.ParametersAreNonnullByDefault;
index c1ff7cb146b519e83e18d53bd5a67f7a7a3530e7..942918191a8c3e332f1cc4a48b3508161545d679 100644 (file)
@@ -160,8 +160,7 @@ public class NotificationService implements ServerComponent, Startable {
     return System.currentTimeMillis();
   }
 
-  private void deliver(Notification notification) {
-    LOG.debug("Delivering notification " + notification);
+  public void deliver(Notification notification) {
     final SetMultimap<String, NotificationChannel> recipients = HashMultimap.create();
     for (NotificationDispatcher dispatcher : dispatchers) {
       NotificationDispatcher.Context context = new NotificationDispatcher.Context() {
index 708b4a04b6f8b49a426f4e062f9b46c7858bc3b3..57fbf498be74e4a48248d29af89abf04dc159b69 100644 (file)
@@ -46,7 +46,7 @@ import org.sonar.core.config.Logback;
 import org.sonar.core.i18n.DefaultI18n;
 import org.sonar.core.i18n.RuleI18nManager;
 import org.sonar.core.issue.IssueFilterSerializer;
-import org.sonar.core.issue.IssueNotifications;
+import org.sonar.server.issue.notification.IssueNotifications;
 import org.sonar.core.issue.IssueUpdater;
 import org.sonar.core.issue.workflow.FunctionExecutor;
 import org.sonar.core.issue.workflow.IssueWorkflow;
@@ -54,7 +54,13 @@ import org.sonar.core.measure.db.MeasureFilterDao;
 import org.sonar.core.metric.DefaultMetricFinder;
 import org.sonar.core.notification.DefaultNotificationManager;
 import org.sonar.core.permission.PermissionFacade;
-import org.sonar.core.persistence.*;
+import org.sonar.core.persistence.DaoUtils;
+import org.sonar.core.persistence.DatabaseVersion;
+import org.sonar.core.persistence.DefaultDatabase;
+import org.sonar.core.persistence.MyBatis;
+import org.sonar.core.persistence.PreviewDatabaseFactory;
+import org.sonar.core.persistence.SemaphoreUpdater;
+import org.sonar.core.persistence.SemaphoresImpl;
 import org.sonar.core.preview.PreviewCache;
 import org.sonar.core.profiling.Profiling;
 import org.sonar.core.purge.PurgeProfiler;
@@ -80,7 +86,12 @@ import org.sonar.server.activity.index.ActivityNormalizer;
 import org.sonar.server.activity.ws.ActivitiesWebService;
 import org.sonar.server.activity.ws.ActivityMapping;
 import org.sonar.server.authentication.ws.AuthenticationWs;
-import org.sonar.server.batch.*;
+import org.sonar.server.batch.BatchIndex;
+import org.sonar.server.batch.BatchWs;
+import org.sonar.server.batch.GlobalRepositoryAction;
+import org.sonar.server.batch.IssuesAction;
+import org.sonar.server.batch.ProjectRepositoryAction;
+import org.sonar.server.batch.ProjectRepositoryLoader;
 import org.sonar.server.charts.ChartFactory;
 import org.sonar.server.component.ComponentCleanerService;
 import org.sonar.server.component.ComponentService;
@@ -88,11 +99,20 @@ import org.sonar.server.component.DefaultComponentFinder;
 import org.sonar.server.component.DefaultRubyComponentService;
 import org.sonar.server.component.db.ComponentDao;
 import org.sonar.server.component.db.SnapshotDao;
-import org.sonar.server.component.ws.*;
-import org.sonar.server.computation.*;
+import org.sonar.server.component.ws.ComponentAppAction;
+import org.sonar.server.component.ws.ComponentsWs;
+import org.sonar.server.component.ws.EventsWs;
+import org.sonar.server.component.ws.ProjectsWs;
+import org.sonar.server.component.ws.ResourcesWs;
+import org.sonar.server.computation.AnalysisReportQueue;
+import org.sonar.server.computation.AnalysisReportQueueCleaner;
+import org.sonar.server.computation.ComputationThreadLauncher;
 import org.sonar.server.computation.db.AnalysisReportDao;
-import org.sonar.server.computation.step.*;
-import org.sonar.server.computation.ws.*;
+import org.sonar.server.computation.ws.ComputationWebService;
+import org.sonar.server.computation.ws.HistoryWsAction;
+import org.sonar.server.computation.ws.IsQueueEmptyWebService;
+import org.sonar.server.computation.ws.QueueWsAction;
+import org.sonar.server.computation.ws.SubmitReportWsAction;
 import org.sonar.server.config.ws.PropertiesWs;
 import org.sonar.server.dashboard.db.DashboardDao;
 import org.sonar.server.dashboard.db.WidgetDao;
@@ -104,7 +124,14 @@ import org.sonar.server.db.DbClient;
 import org.sonar.server.db.EmbeddedDatabaseFactory;
 import org.sonar.server.db.migrations.DatabaseMigrations;
 import org.sonar.server.db.migrations.DatabaseMigrator;
-import org.sonar.server.debt.*;
+import org.sonar.server.debt.DebtCharacteristicsXMLImporter;
+import org.sonar.server.debt.DebtModelBackup;
+import org.sonar.server.debt.DebtModelLookup;
+import org.sonar.server.debt.DebtModelOperations;
+import org.sonar.server.debt.DebtModelPluginRepository;
+import org.sonar.server.debt.DebtModelService;
+import org.sonar.server.debt.DebtModelXMLExporter;
+import org.sonar.server.debt.DebtRulesXMLImporter;
 import org.sonar.server.design.FileDesignWidget;
 import org.sonar.server.duplication.ws.DuplicationsJsonWriter;
 import org.sonar.server.duplication.ws.DuplicationsParser;
@@ -112,15 +139,43 @@ import org.sonar.server.duplication.ws.DuplicationsWs;
 import org.sonar.server.es.EsClient;
 import org.sonar.server.es.IndexCreator;
 import org.sonar.server.es.IndexRegistry;
-import org.sonar.server.issue.*;
+import org.sonar.server.issue.ActionService;
+import org.sonar.server.issue.AddTagsAction;
+import org.sonar.server.issue.AssignAction;
+import org.sonar.server.issue.CommentAction;
+import org.sonar.server.issue.InternalRubyIssueService;
+import org.sonar.server.issue.IssueBulkChangeService;
+import org.sonar.server.issue.IssueChangelogFormatter;
+import org.sonar.server.issue.IssueChangelogService;
+import org.sonar.server.issue.IssueCommentService;
+import org.sonar.server.issue.IssueQueryService;
+import org.sonar.server.issue.IssueService;
+import org.sonar.server.issue.PlanAction;
+import org.sonar.server.issue.RemoveTagsAction;
+import org.sonar.server.issue.ServerIssueStorage;
+import org.sonar.server.issue.SetSeverityAction;
+import org.sonar.server.issue.TransitionAction;
 import org.sonar.server.issue.actionplan.ActionPlanService;
 import org.sonar.server.issue.actionplan.ActionPlanWs;
 import org.sonar.server.issue.db.IssueDao;
 import org.sonar.server.issue.filter.IssueFilterService;
 import org.sonar.server.issue.filter.IssueFilterWriter;
 import org.sonar.server.issue.filter.IssueFilterWs;
-import org.sonar.server.issue.index.*;
-import org.sonar.server.issue.ws.*;
+import org.sonar.server.issue.index.IssueAuthorizationIndexer;
+import org.sonar.server.issue.index.IssueIndex;
+import org.sonar.server.issue.index.IssueIndexDefinition;
+import org.sonar.server.issue.index.IssueIndexer;
+import org.sonar.server.issue.index.IssueNormalizer;
+import org.sonar.server.issue.notification.ChangesOnMyIssueNotificationDispatcher;
+import org.sonar.server.issue.notification.IssueChangesEmailTemplate;
+import org.sonar.server.issue.notification.NewFalsePositiveNotificationDispatcher;
+import org.sonar.server.issue.notification.NewIssuesEmailTemplate;
+import org.sonar.server.issue.notification.NewIssuesNotificationDispatcher;
+import org.sonar.server.issue.ws.ComponentTagsAction;
+import org.sonar.server.issue.ws.IssueActionsWriter;
+import org.sonar.server.issue.ws.IssueShowAction;
+import org.sonar.server.issue.ws.IssuesWs;
+import org.sonar.server.issue.ws.SetTagsAction;
 import org.sonar.server.measure.MeasureFilterEngine;
 import org.sonar.server.measure.MeasureFilterExecutor;
 import org.sonar.server.measure.MeasureFilterFactory;
@@ -139,36 +194,117 @@ import org.sonar.server.platform.ws.L10nWs;
 import org.sonar.server.platform.ws.RestartHandler;
 import org.sonar.server.platform.ws.ServerWs;
 import org.sonar.server.platform.ws.SystemWs;
-import org.sonar.server.plugins.*;
+import org.sonar.server.plugins.InstalledPluginReferentialFactory;
+import org.sonar.server.plugins.PluginDownloader;
+import org.sonar.server.plugins.ServerExtensionInstaller;
+import org.sonar.server.plugins.ServerPluginJarInstaller;
+import org.sonar.server.plugins.ServerPluginJarsInstaller;
+import org.sonar.server.plugins.ServerPluginRepository;
+import org.sonar.server.plugins.UpdateCenterClient;
+import org.sonar.server.plugins.UpdateCenterMatrixFactory;
 import org.sonar.server.properties.ProjectSettingsFactory;
 import org.sonar.server.qualitygate.QgateProjectFinder;
 import org.sonar.server.qualitygate.QualityGates;
 import org.sonar.server.qualitygate.RegisterQualityGates;
-import org.sonar.server.qualitygate.ws.*;
-import org.sonar.server.qualityprofile.*;
+import org.sonar.server.qualitygate.ws.QGatesAppAction;
+import org.sonar.server.qualitygate.ws.QGatesCopyAction;
+import org.sonar.server.qualitygate.ws.QGatesCreateAction;
+import org.sonar.server.qualitygate.ws.QGatesCreateConditionAction;
+import org.sonar.server.qualitygate.ws.QGatesDeleteConditionAction;
+import org.sonar.server.qualitygate.ws.QGatesDeselectAction;
+import org.sonar.server.qualitygate.ws.QGatesDestroyAction;
+import org.sonar.server.qualitygate.ws.QGatesListAction;
+import org.sonar.server.qualitygate.ws.QGatesRenameAction;
+import org.sonar.server.qualitygate.ws.QGatesSearchAction;
+import org.sonar.server.qualitygate.ws.QGatesSelectAction;
+import org.sonar.server.qualitygate.ws.QGatesSetAsDefaultAction;
+import org.sonar.server.qualitygate.ws.QGatesShowAction;
+import org.sonar.server.qualitygate.ws.QGatesUnsetDefaultAction;
+import org.sonar.server.qualitygate.ws.QGatesUpdateConditionAction;
+import org.sonar.server.qualitygate.ws.QGatesWs;
+import org.sonar.server.qualityprofile.BuiltInProfiles;
+import org.sonar.server.qualityprofile.QProfileBackuper;
+import org.sonar.server.qualityprofile.QProfileCopier;
+import org.sonar.server.qualityprofile.QProfileExporters;
+import org.sonar.server.qualityprofile.QProfileFactory;
+import org.sonar.server.qualityprofile.QProfileLoader;
+import org.sonar.server.qualityprofile.QProfileLookup;
+import org.sonar.server.qualityprofile.QProfileProjectLookup;
+import org.sonar.server.qualityprofile.QProfileProjectOperations;
+import org.sonar.server.qualityprofile.QProfileReset;
+import org.sonar.server.qualityprofile.QProfileService;
+import org.sonar.server.qualityprofile.QProfiles;
+import org.sonar.server.qualityprofile.RegisterQualityProfiles;
+import org.sonar.server.qualityprofile.RuleActivator;
+import org.sonar.server.qualityprofile.RuleActivatorContextFactory;
 import org.sonar.server.qualityprofile.db.ActiveRuleDao;
 import org.sonar.server.qualityprofile.index.ActiveRuleIndex;
 import org.sonar.server.qualityprofile.index.ActiveRuleNormalizer;
-import org.sonar.server.qualityprofile.ws.*;
-import org.sonar.server.rule.*;
+import org.sonar.server.qualityprofile.ws.BulkRuleActivationActions;
+import org.sonar.server.qualityprofile.ws.ProfilesWs;
+import org.sonar.server.qualityprofile.ws.QProfileRestoreBuiltInAction;
+import org.sonar.server.qualityprofile.ws.QProfilesWs;
+import org.sonar.server.qualityprofile.ws.RuleActivationActions;
+import org.sonar.server.rule.DefaultRuleFinder;
+import org.sonar.server.rule.DeprecatedRulesDefinitionLoader;
+import org.sonar.server.rule.RegisterRules;
+import org.sonar.server.rule.RubyRuleService;
+import org.sonar.server.rule.RuleCreator;
+import org.sonar.server.rule.RuleDefinitionsLoader;
+import org.sonar.server.rule.RuleDeleter;
+import org.sonar.server.rule.RuleOperations;
+import org.sonar.server.rule.RuleRepositories;
+import org.sonar.server.rule.RuleService;
+import org.sonar.server.rule.RuleUpdater;
 import org.sonar.server.rule.db.RuleDao;
 import org.sonar.server.rule.index.RuleIndex;
 import org.sonar.server.rule.index.RuleNormalizer;
-import org.sonar.server.rule.ws.*;
+import org.sonar.server.rule.ws.ActiveRuleCompleter;
+import org.sonar.server.rule.ws.AppAction;
+import org.sonar.server.rule.ws.DeleteAction;
+import org.sonar.server.rule.ws.RuleMapping;
+import org.sonar.server.rule.ws.RulesWebService;
 import org.sonar.server.rule.ws.SearchAction;
 import org.sonar.server.rule.ws.TagsAction;
-import org.sonar.server.search.*;
+import org.sonar.server.rule.ws.UpdateAction;
+import org.sonar.server.search.IndexClient;
+import org.sonar.server.search.IndexQueue;
+import org.sonar.server.search.IndexSynchronizer;
+import org.sonar.server.search.SearchClient;
+import org.sonar.server.search.SearchHealth;
 import org.sonar.server.source.HtmlSourceDecorator;
-import org.sonar.server.source.IndexSourceLinesStep;
 import org.sonar.server.source.SourceService;
 import org.sonar.server.source.index.SourceLineIndex;
 import org.sonar.server.source.index.SourceLineIndexDefinition;
 import org.sonar.server.source.index.SourceLineIndexer;
-import org.sonar.server.source.ws.*;
+import org.sonar.server.source.ws.HashAction;
+import org.sonar.server.source.ws.IndexAction;
+import org.sonar.server.source.ws.LinesAction;
+import org.sonar.server.source.ws.RawAction;
+import org.sonar.server.source.ws.ScmAction;
+import org.sonar.server.source.ws.ScmWriter;
 import org.sonar.server.source.ws.ShowAction;
-import org.sonar.server.startup.*;
+import org.sonar.server.source.ws.SourcesWs;
+import org.sonar.server.startup.CleanPreviewAnalysisCache;
+import org.sonar.server.startup.CopyRequirementsFromCharacteristicsToRules;
+import org.sonar.server.startup.GeneratePluginIndex;
+import org.sonar.server.startup.JdbcDriverDeployer;
+import org.sonar.server.startup.LogServerId;
+import org.sonar.server.startup.RegisterDashboards;
+import org.sonar.server.startup.RegisterDebtModel;
+import org.sonar.server.startup.RegisterMetrics;
+import org.sonar.server.startup.RegisterNewMeasureFilters;
+import org.sonar.server.startup.RegisterPermissionTemplates;
+import org.sonar.server.startup.RegisterServletFilters;
+import org.sonar.server.startup.RenameDeprecatedPropertyKeys;
+import org.sonar.server.startup.ServerMetadataPersister;
 import org.sonar.server.test.CoverageService;
-import org.sonar.server.test.ws.*;
+import org.sonar.server.test.ws.CoverageShowAction;
+import org.sonar.server.test.ws.CoverageWs;
+import org.sonar.server.test.ws.TestsCoveredFilesAction;
+import org.sonar.server.test.ws.TestsShowAction;
+import org.sonar.server.test.ws.TestsTestCasesAction;
+import org.sonar.server.test.ws.TestsWs;
 import org.sonar.server.text.MacroInterpreter;
 import org.sonar.server.text.RubyTextService;
 import org.sonar.server.ui.JRubyI18n;
@@ -176,7 +312,14 @@ import org.sonar.server.ui.JRubyProfiling;
 import org.sonar.server.ui.PageDecorations;
 import org.sonar.server.ui.Views;
 import org.sonar.server.updatecenter.ws.UpdateCenterWs;
-import org.sonar.server.user.*;
+import org.sonar.server.user.DefaultUserService;
+import org.sonar.server.user.DoPrivileged;
+import org.sonar.server.user.GroupMembershipFinder;
+import org.sonar.server.user.GroupMembershipService;
+import org.sonar.server.user.NewUserNotifier;
+import org.sonar.server.user.SecurityRealmFactory;
+import org.sonar.server.user.UserService;
+import org.sonar.server.user.UserUpdater;
 import org.sonar.server.user.db.GroupDao;
 import org.sonar.server.user.db.UserDao;
 import org.sonar.server.user.db.UserGroupDao;
@@ -186,7 +329,13 @@ import org.sonar.server.user.index.UserIndexer;
 import org.sonar.server.user.ws.FavoritesWs;
 import org.sonar.server.user.ws.UserPropertiesWs;
 import org.sonar.server.user.ws.UsersWs;
-import org.sonar.server.util.*;
+import org.sonar.server.util.BooleanTypeValidation;
+import org.sonar.server.util.FloatTypeValidation;
+import org.sonar.server.util.IntegerTypeValidation;
+import org.sonar.server.util.StringListTypeValidation;
+import org.sonar.server.util.StringTypeValidation;
+import org.sonar.server.util.TextTypeValidation;
+import org.sonar.server.util.TypeValidations;
 import org.sonar.server.ws.ListingWs;
 import org.sonar.server.ws.WebServiceEngine;
 
@@ -511,7 +660,6 @@ class ServerComponents {
     pico.addSingleton(IssueCommentService.class);
     pico.addSingleton(InternalRubyIssueService.class);
     pico.addSingleton(IssueChangelogService.class);
-    pico.addSingleton(IssueNotifications.class);
     pico.addSingleton(ActionService.class);
     pico.addSingleton(Actions.class);
     pico.addSingleton(IssueBulkChangeService.class);
@@ -525,6 +673,15 @@ class ServerComponents {
     pico.addSingleton(IssueService.class);
     pico.addSingleton(IssueActionsWriter.class);
     pico.addSingleton(IssueQueryService.class);
+    pico.addSingleton(IssueNotifications.class);
+    pico.addSingleton(NewIssuesEmailTemplate.class);
+    pico.addSingleton(IssueChangesEmailTemplate.class);
+    pico.addSingleton(ChangesOnMyIssueNotificationDispatcher.class);
+    pico.addSingleton(ChangesOnMyIssueNotificationDispatcher.newMetadata());
+    pico.addSingleton(NewIssuesNotificationDispatcher.class);
+    pico.addSingleton(NewIssuesNotificationDispatcher.newMetadata());
+    pico.addSingleton(NewFalsePositiveNotificationDispatcher.class);
+    pico.addSingleton(NewFalsePositiveNotificationDispatcher.newMetadata());
 
     // issue filters
     pico.addSingleton(IssueFilterService.class);
@@ -619,20 +776,8 @@ class ServerComponents {
     pico.addSingleton(FileDesignWidget.class);
 
     // Compute engine
-    pico.addSingleton(ComputationService.class);
-    pico.addSingleton(ComputationStepRegistry.class);
-    pico.addSingletons(Lists.newArrayList(
-      DigestReportStep.class,
-      ApplyPermissionsStep.class,
-      IndexIssuesStep.class,
-      IndexSourceLinesStep.class,
-      SwitchSnapshotStep.class,
-      InvalidatePreviewCacheStep.class,
-      IndexComponentsStep.class,
-      PurgeDatastoresStep.class));
-    pico.addSingleton(AnalysisReportService.class);
     pico.addSingleton(AnalysisReportQueue.class);
-    pico.addSingleton(ComputationWorkerLauncher.class);
+    pico.addSingleton(ComputationThreadLauncher.class);
     pico.addSingleton(ComputationWebService.class);
     pico.addSingleton(IsQueueEmptyWebService.class);
     pico.addSingleton(QueueWsAction.class);
@@ -642,7 +787,6 @@ class ServerComponents {
     pico.addSingleton(ProjectCleaner.class);
     pico.addSingleton(ProjectSettingsFactory.class);
     pico.addSingleton(IndexPurgeListener.class);
-    pico.addSingleton(ComputeEngineIssueStorageFactory.class);
 
     for (Object components : level4AddedComponents) {
       pico.addSingleton(components);
@@ -693,5 +837,6 @@ class ServerComponents {
     startupContainer.stopComponents();
 
     pico.getComponentByType(DatabaseSessionFactory.class).clear();
+    pico.removeChild();
   }
 }
index 945a7d6cc7ea1be6cf0324cff388ccd86438f040..ed2d87f3ad88bbd49d18e2050adef002bddbf8ea 100644 (file)
@@ -25,7 +25,7 @@ import ch.qos.logback.core.Appender;
 import ch.qos.logback.core.AppenderBase;
 import ch.qos.logback.core.spi.AppenderAttachable;
 import ch.qos.logback.core.spi.AppenderAttachableImpl;
-import org.sonar.server.computation.ComputationWorkerLauncher;
+import org.sonar.server.computation.ComputationThreadLauncher;
 
 import java.util.Iterator;
 
@@ -37,7 +37,7 @@ public class SwitchLogbackAppender extends AppenderBase<ILoggingEvent> implement
 
   @Override
   protected void append(ILoggingEvent event) {
-    if (Thread.currentThread().getName().startsWith(ComputationWorkerLauncher.THREAD_NAME_PREFIX)) {
+    if (Thread.currentThread().getName().startsWith(ComputationThreadLauncher.THREAD_NAME_PREFIX)) {
       analysisReports.doAppend(event);
       if (event.getLevel().isGreaterOrEqual(Level.WARN)) {
         console.doAppend(event);
diff --git a/server/sonar-server/src/main/java/org/sonar/server/source/IndexSourceLinesStep.java b/server/sonar-server/src/main/java/org/sonar/server/source/IndexSourceLinesStep.java
deleted file mode 100644 (file)
index d084e74..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.
- */
-package org.sonar.server.source;
-
-import org.sonar.core.persistence.DbSession;
-import org.sonar.server.computation.ComputationContext;
-import org.sonar.server.computation.step.ComputationStep;
-import org.sonar.server.source.index.SourceLineIndexer;
-
-public class IndexSourceLinesStep implements ComputationStep {
-
-  private final SourceLineIndexer indexer;
-
-  public IndexSourceLinesStep(SourceLineIndexer indexer) {
-    this.indexer = indexer;
-  }
-
-  @Override
-  public void execute(DbSession session, ComputationContext context) {
-    indexer.index();
-  }
-
-  @Override
-  public String getDescription() {
-    return "Put source code into search index";
-  }
-
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/util/CloseableIterator.java b/server/sonar-server/src/main/java/org/sonar/server/util/CloseableIterator.java
new file mode 100644 (file)
index 0000000..44903d9
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * 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.
+ */
+package org.sonar.server.util;
+
+import com.google.common.base.Throwables;
+
+import javax.annotation.CheckForNull;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+public abstract class CloseableIterator<O> implements Iterator<O>, AutoCloseable {
+  private O nextObject = null;
+
+  @Override
+  public final boolean hasNext() {
+    boolean hasNext = nextObject != null || bufferNext() != null;
+    if (!hasNext) {
+      close();
+    }
+    return hasNext;
+  }
+
+  private O bufferNext() {
+    try {
+      return nextObject = doNext();
+    } catch (RuntimeException e) {
+      close();
+      throw e;
+    }
+  }
+
+  /**
+   * Reads next item and returns null if no more items.
+   */
+  @CheckForNull
+  protected abstract O doNext();
+
+  @Override
+  public final O next() {
+    if (!hasNext()) {
+      throw new NoSuchElementException();
+    }
+    O result = nextObject;
+    nextObject = null;
+    return result;
+  }
+
+  @Override
+  public final void remove() {
+    try {
+      doRemove();
+    } catch (RuntimeException e) {
+      close();
+      throw e;
+    }
+  }
+
+  /**
+   * By default it throws an UnsupportedOperationException. Override this method
+   * to change behavior.
+   */
+  protected void doRemove() {
+    throw new UnsupportedOperationException("remove() is not supported by default. Override doRemove() if needed.");
+  }
+
+  /**
+   * Do not declare "throws IOException"
+   */
+  @Override
+  public final void close() {
+    try {
+      doClose();
+    } catch (Exception e) {
+      Throwables.propagate(e);
+    }
+  }
+
+  protected abstract void doClose() throws Exception;
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/util/ObjectInputStreamIterator.java b/server/sonar-server/src/main/java/org/sonar/server/util/ObjectInputStreamIterator.java
new file mode 100644 (file)
index 0000000..f84b0f1
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+package org.sonar.server.util;
+
+import com.google.common.base.Throwables;
+import org.apache.commons.io.IOUtils;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+
+public class ObjectInputStreamIterator<E> extends CloseableIterator<E> {
+
+  private ObjectInputStream stream;
+
+  public ObjectInputStreamIterator(InputStream stream) throws IOException {
+    this.stream = new ObjectInputStream(stream);
+  }
+
+  @Override
+  protected E doNext() {
+    try {
+      return (E) stream.readObject();
+    } catch (EOFException e) {
+      return null;
+    } catch (Exception e) {
+      throw Throwables.propagate(e);
+    }
+  }
+
+  @Override
+  protected void doClose() {
+    IOUtils.closeQuietly(stream);
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/util/cache/CacheLoader.java b/server/sonar-server/src/main/java/org/sonar/server/util/cache/CacheLoader.java
new file mode 100644 (file)
index 0000000..1d42d2d
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+package org.sonar.server.util.cache;
+
+import javax.annotation.CheckForNull;
+
+import java.util.Collection;
+import java.util.Map;
+
+public interface CacheLoader<K, V> {
+
+  /**
+   * Value associated with the requested key. Null if key is not found.
+   */
+  @CheckForNull
+  V load(K key);
+
+  /**
+   * All the requested keys must be included in the map result. Value in map is null when
+   * the key is not found.
+   */
+  Map<K, V> loadAll(Collection<? extends K> keys);
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/util/cache/DiskCache.java b/server/sonar-server/src/main/java/org/sonar/server/util/cache/DiskCache.java
new file mode 100644 (file)
index 0000000..9bc8e0b
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * 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.
+ */
+package org.sonar.server.util.cache;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.server.util.CloseableIterator;
+import org.sonar.server.util.ObjectInputStreamIterator;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+
+/**
+ * Serialize and deserialize objects on disk. No search capabilities, only traversal (full scan).
+ */
+public class DiskCache<O extends Serializable> {
+
+  private final File file;
+  private final System2 system2;
+
+  public DiskCache(File file, System2 system2) {
+    this.system2 = system2;
+    this.file = file;
+    OutputStream output = null;
+    try {
+      // writes the serialization stream header required when calling "traverse()"
+      // on empty stream. Moreover it allows to call multiple times "newAppender()"
+      output = new ObjectOutputStream(new FileOutputStream(file));
+      output.flush();
+
+      // raise an exception if can't close
+      system2.close(output);
+    } catch (IOException e) {
+      // do not hide cause exception -> close quietly
+      IOUtils.closeQuietly(output);
+      throw new IllegalStateException("Fail to write into file: " + file, e);
+    }
+  }
+
+  public DiskAppender newAppender() {
+    return new DiskAppender();
+  }
+
+  public CloseableIterator<O> traverse() {
+    try {
+      return new ObjectInputStreamIterator<>(FileUtils.openInputStream(file));
+    } catch (IOException e) {
+      throw new IllegalStateException("Fail to traverse file: " + file, e);
+    }
+  }
+
+  public class DiskAppender implements AutoCloseable {
+    private final ObjectOutputStream output;
+
+    private DiskAppender() {
+      try {
+        this.output = new ObjectOutputStream(new FileOutputStream(file, true)) {
+          @Override
+          protected void writeStreamHeader() throws IOException {
+            // do not write stream headers as it's already done in constructor of DiskCache
+          }
+        };
+      } catch (IOException e) {
+        throw new IllegalStateException("Fail to open file " + file, e);
+      }
+    }
+
+    public DiskAppender append(O object) {
+      try {
+        output.writeObject(object);
+        return this;
+      } catch (IOException e) {
+        throw new IllegalStateException("Fail to write into file " + file, e);
+      }
+    }
+
+    @Override
+    public void close() {
+      system2.close(output);
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/util/cache/MemoryCache.java b/server/sonar-server/src/main/java/org/sonar/server/util/cache/MemoryCache.java
new file mode 100644 (file)
index 0000000..d29c11c
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+package org.sonar.server.util.cache;
+
+import javax.annotation.CheckForNull;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This in-memory cache relies on {@link org.sonar.server.util.cache.CacheLoader} to
+ * load missing elements.
+ * Warning - all searches are kept in memory, even when elements are not found.
+ */
+public class MemoryCache<K, V> {
+
+  private final CacheLoader<K, V> loader;
+  private final Map<K, V> map = new HashMap<>();
+
+  public MemoryCache(CacheLoader<K, V> loader) {
+    this.loader = loader;
+  }
+
+  @CheckForNull
+  public V getNullable(K key) {
+    V value = map.get(key);
+    if (value == null) {
+      if (!map.containsKey(key)) {
+        value = loader.load(key);
+        map.put(key, value);
+      }
+    }
+    return value;
+  }
+
+  public V get(K key) {
+    V value = getNullable(key);
+    if (value == null) {
+      throw new IllegalArgumentException("Not found: " + key);
+    }
+    return value;
+  }
+
+  public Map<K, V> getAll(Iterable<K> keys) {
+    List<K> missingKeys = new ArrayList<>();
+    Map<K, V> result = new HashMap<>();
+    for (K key : keys) {
+      V value = map.get(key);
+      if (value == null && !map.containsKey(key)) {
+        missingKeys.add(key);
+      } else {
+        result.put(key, value);
+      }
+    }
+    if (!missingKeys.isEmpty()) {
+      Map<K, V> missingValues = loader.loadAll(missingKeys);
+      map.putAll(missingValues);
+      result.putAll(missingValues);
+    }
+    return result;
+  }
+
+  public void clear() {
+    map.clear();
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/util/cache/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/util/cache/package-info.java
new file mode 100644 (file)
index 0000000..03cff03
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.util.cache;
+
+import javax.annotation.ParametersAreNonnullByDefault;
index feb7a2449d3056184f2f6384beabb8a6fde055f6..c9cf48dc6671e2f9e5ec1b4f050ba67cd65b2fa6 100644 (file)
  * 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 com.google.common.collect.Lists;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.batch.protocol.output.component.ReportComponent;
-import org.sonar.core.component.ComponentDto;
-import org.sonar.core.computation.db.AnalysisReportDto;
-import org.sonar.core.issue.db.IssueStorage;
-import org.sonar.core.persistence.DbSession;
-import org.sonar.core.persistence.MyBatis;
-
-import java.io.File;
-import java.util.List;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class AnalysisReportServiceTest {
-  private AnalysisReportService sut;
-
-  private IssueStorage issueStorage;
-
-  @Before
-  public void before() throws Exception {
-    issueStorage = new FakeIssueStorage();
-    ComputeEngineIssueStorageFactory issueStorageFactory = mock(ComputeEngineIssueStorageFactory.class);
-    when(issueStorageFactory.newComputeEngineIssueStorage(any(ComponentDto.class))).thenReturn(issueStorage);
-    sut = new AnalysisReportService(issueStorageFactory);
-  }
-
-  @Test
-  public void load_resources() throws Exception {
-    File dir = new File(getClass().getResource("/org/sonar/server/computation/AnalysisReportServiceTest/report-folder").getFile());
-    ComputationContext context = new ComputationContext(mock(AnalysisReportDto.class), mock(ComponentDto.class), dir);
-
-    sut.loadResources(context);
-
-    assertThat(context.getComponents()).hasSize(4);
-  }
-
-  @Test
-  @Ignore("Temporarily ignored")
-  public void save_issues() throws Exception {
-    File dir = new File(getClass().getResource("/org/sonar/server/computation/AnalysisReportServiceTest/report-folder").getFile());
-    ComputationContext context = new FakeComputationContext(dir);
-
-    sut.saveIssues(context);
-
-    assertThat(((FakeIssueStorage) issueStorage).issues).hasSize(6);
-  }
-
-  private static class FakeIssueStorage extends IssueStorage {
-
-    public List<DefaultIssue> issues = null;
-
-    protected FakeIssueStorage() {
-      super(mock(MyBatis.class), mock(RuleFinder.class));
-    }
-
-    @Override
-    public void save(Iterable<DefaultIssue> issues) {
-      this.issues = Lists.newArrayList(issues);
-    }
-
-    @Override
-    protected void doInsert(DbSession batchSession, long now, DefaultIssue issue) {
-
-    }
-
-    @Override
-    protected void doUpdate(DbSession batchSession, long now, DefaultIssue issue) {
-
-    }
-  }
-
-  private static class FakeComputationContext extends ComputationContext {
-
-    public FakeComputationContext(File reportDir) {
-      super(mock(AnalysisReportDto.class), mock(ComponentDto.class), reportDir);
-    }
-
-    @Override
-    public ReportComponent getComponentByBatchId(Long batchId) {
-      return new ReportComponent()
-        .setBatchId(123)
-        .setId(456);
-    }
-  }
-
-}
+///*
+// * 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.
+// */
+//
+//package org.sonar.server.computation;
+//
+//import com.google.common.collect.Lists;
+//import org.junit.Before;
+//import org.junit.Ignore;
+//import org.junit.Test;
+//import org.sonar.api.issue.internal.DefaultIssue;
+//import org.sonar.api.rules.RuleFinder;
+//import org.sonar.batch.protocol.output.component.ReportComponent;
+//import org.sonar.core.component.ComponentDto;
+//import org.sonar.core.computation.db.AnalysisReportDto;
+//import org.sonar.core.issue.db.IssueStorage;
+//import org.sonar.core.persistence.DbSession;
+//import org.sonar.core.persistence.MyBatis;
+//
+//import java.io.File;
+//import java.util.List;
+//
+//import static org.assertj.core.api.Assertions.assertThat;
+//import static org.mockito.Matchers.any;
+//import static org.mockito.Mockito.mock;
+//import static org.mockito.Mockito.when;
+//
+//public class AnalysisReportServiceTest {
+//  private AnalysisReportService sut;
+//
+//  private IssueStorage issueStorage;
+//
+//  @Before
+//  public void before() throws Exception {
+//    issueStorage = new FakeIssueStorage();
+//    ComputeEngineIssueStorageFactory issueStorageFactory = mock(ComputeEngineIssueStorageFactory.class);
+//    when(issueStorageFactory.newComputeEngineIssueStorage(any(ComponentDto.class))).thenReturn(issueStorage);
+//    sut = new AnalysisReportService(issueStorageFactory);
+//  }
+//
+//  @Test
+//  public void load_resources() throws Exception {
+//    File dir = new File(getClass().getResource("/org/sonar/server/computation/AnalysisReportServiceTest/report-folder").getFile());
+//    ComputationContext context = new ComputationContext(mock(AnalysisReportDto.class), mock(ComponentDto.class), dir);
+//
+//    sut.initComponents(context);
+//
+//    assertThat(context.getComponents()).hasSize(4);
+//  }
+//
+//  @Test
+//  @Ignore("Temporarily ignored")
+//  public void save_issues() throws Exception {
+//    File dir = new File(getClass().getResource("/org/sonar/server/computation/AnalysisReportServiceTest/report-folder").getFile());
+//    ComputationContext context = new FakeComputationContext(dir);
+//
+//    sut.saveIssues(context);
+//
+//    assertThat(((FakeIssueStorage) issueStorage).issues).hasSize(6);
+//  }
+//
+//  private static class FakeIssueStorage extends IssueStorage {
+//
+//    public List<DefaultIssue> issues = null;
+//
+//    protected FakeIssueStorage() {
+//      super(mock(MyBatis.class), mock(RuleFinder.class));
+//    }
+//
+//    @Override
+//    public void save(Iterable<DefaultIssue> issues) {
+//      this.issues = Lists.newArrayList(issues);
+//    }
+//
+//    @Override
+//    protected void doInsert(DbSession batchSession, long now, DefaultIssue issue) {
+//
+//    }
+//
+//    @Override
+//    protected void doUpdate(DbSession batchSession, long now, DefaultIssue issue) {
+//
+//    }
+//  }
+//
+//  private static class FakeComputationContext extends ComputationContext {
+//
+//    public FakeComputationContext(File reportDir) {
+//      super(mock(AnalysisReportDto.class), mock(ComponentDto.class), reportDir);
+//    }
+//
+//    @Override
+//    public ReportComponent getComponentByBatchId(Long batchId) {
+//      return new ReportComponent()
+//        .setBatchId(123)
+//        .setId(456);
+//    }
+//  }
+//
+//}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/ComputationComponentsTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/ComputationComponentsTest.java
new file mode 100644 (file)
index 0000000..87fff73
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+package org.sonar.server.computation;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ComputationComponentsTest {
+
+  @Test
+  public void nonStepComponents() throws Exception {
+    assertThat(ComputationComponents.nonStepComponents()).isNotEmpty();
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/ComputationThreadLauncherTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/ComputationThreadLauncherTest.java
new file mode 100644 (file)
index 0000000..3cf8c22
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * 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, 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.
+// */
+//
+//package org.sonar.server.computation;
+//
+//import org.junit.After;
+//import org.junit.Before;
+//import org.junit.Rule;
+//import org.junit.Test;
+//import org.junit.rules.DisableOnDebug;
+//import org.junit.rules.TestRule;
+//import org.junit.rules.Timeout;
+//import org.sonar.api.platform.Server;
+//
+//import java.util.concurrent.TimeUnit;
+//
+//import static org.mockito.Mockito.*;
+//
+//public class ComputationThreadLauncherTest {
+//
+//  @Rule
+//  public TestRule timeout = new DisableOnDebug(Timeout.seconds(5));
+//
+//  private ComputationThreadLauncher sut;
+//  private ComputationService service;
+//  private AnalysisReportQueue queue;
+//
+//  @Before
+//  public void before() {
+//    this.service = mock(ComputationService.class);
+//    this.queue = mock(AnalysisReportQueue.class);
+//  }
+//
+//  @After
+//  public void after() {
+//    sut.stop();
+//  }
+//
+//  @Test
+//  public void call_findAndBook_when_launching_a_recurrent_task() throws Exception {
+//    sut = new ComputationThreadLauncher(service, queue, 0, 1, TimeUnit.MILLISECONDS);
+//
+//    sut.onServerStart(mock(Server.class));
+//
+//    sleep();
+//
+//    verify(queue, atLeastOnce()).pop();
+//  }
+//
+//  @Test
+//  public void call_findAndBook_when_executing_task_immediately() throws Exception {
+//    sut = new ComputationThreadLauncher(service, queue, 1, 1, TimeUnit.HOURS);
+//    sut.start();
+//
+//    sut.startAnalysisTaskNow();
+//
+//    sleep();
+//
+//    verify(queue, atLeastOnce()).pop();
+//  }
+//
+//  private void sleep() throws InterruptedException {
+//    TimeUnit.MILLISECONDS.sleep(500L);
+//  }
+//}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/ComputationThreadTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/ComputationThreadTest.java
new file mode 100644 (file)
index 0000000..d3ecf46
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+package org.sonar.server.computation;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.core.computation.db.AnalysisReportDto;
+
+import static org.mockito.Mockito.*;
+
+public class ComputationThreadTest {
+
+  private ComputationThread sut;
+  private AnalysisReportQueue queue;
+
+  @Before
+  public void before() {
+    this.queue = mock(AnalysisReportQueue.class);
+    this.sut = new ComputationThread(queue);
+  }
+
+  @Test
+  public void call_findAndBook_and_no_call_to_analyze_if_no_report_found() {
+    sut.run();
+
+    verify(queue).pop();
+  }
+
+  @Test
+  public void call_findAndBook_and_then_analyze_if_there_is_a_report() {
+    AnalysisReportDto report = AnalysisReportDto.newForTests(1L);
+    when(queue.pop()).thenReturn(report);
+
+    sut.run();
+
+    verify(queue).pop();
+  }
+
+  @Test
+  public void when_the_analysis_throws_an_exception_it_does_not_break_the_task() throws Exception {
+    when(queue.pop()).thenThrow(new IllegalStateException());
+
+    sut.run();
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/ComputationWorkerLauncherTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/ComputationWorkerLauncherTest.java
deleted file mode 100644 (file)
index 513c64c..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * 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.
- */
-
-package org.sonar.server.computation;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.DisableOnDebug;
-import org.junit.rules.TestRule;
-import org.junit.rules.Timeout;
-import org.sonar.api.platform.Server;
-
-import java.util.concurrent.TimeUnit;
-
-import static org.mockito.Mockito.*;
-
-public class ComputationWorkerLauncherTest {
-
-  @Rule
-  public TestRule timeout = new DisableOnDebug(Timeout.seconds(5));
-
-  private ComputationWorkerLauncher sut;
-  private ComputationService service;
-  private AnalysisReportQueue queue;
-
-  @Before
-  public void before() {
-    this.service = mock(ComputationService.class);
-    this.queue = mock(AnalysisReportQueue.class);
-  }
-
-  @After
-  public void after() {
-    sut.stop();
-  }
-
-  @Test
-  public void call_findAndBook_when_launching_a_recurrent_task() throws Exception {
-    sut = new ComputationWorkerLauncher(service, queue, 0, 1, TimeUnit.MILLISECONDS);
-
-    sut.onServerStart(mock(Server.class));
-
-    sleep();
-
-    verify(queue, atLeastOnce()).pop();
-  }
-
-  @Test
-  public void call_findAndBook_when_executing_task_immediately() throws Exception {
-    sut = new ComputationWorkerLauncher(service, queue, 1, 1, TimeUnit.HOURS);
-    sut.start();
-
-    sut.startAnalysisTaskNow();
-
-    sleep();
-
-    verify(queue, atLeastOnce()).pop();
-  }
-
-  private void sleep() throws InterruptedException {
-    TimeUnit.MILLISECONDS.sleep(500L);
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/ComputationWorkerTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/ComputationWorkerTest.java
deleted file mode 100644 (file)
index bb0cb82..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.
- */
-
-package org.sonar.server.computation;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.core.computation.db.AnalysisReportDto;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.*;
-
-public class ComputationWorkerTest {
-
-  private ComputationWorker sut;
-  private ComputationService service;
-  private AnalysisReportQueue queue;
-
-  @Before
-  public void before() {
-    this.service = mock(ComputationService.class);
-    this.queue = mock(AnalysisReportQueue.class);
-    this.sut = new ComputationWorker(queue, service);
-  }
-
-  @Test
-  public void call_findAndBook_and_no_call_to_analyze_if_no_report_found() {
-    sut.run();
-
-    verify(queue).pop();
-    verify(service, never()).process(any(AnalysisReportDto.class));
-  }
-
-  @Test
-  public void call_findAndBook_and_then_analyze_if_there_is_a_report() {
-    AnalysisReportDto report = AnalysisReportDto.newForTests(1L);
-    when(queue.pop()).thenReturn(report);
-
-    sut.run();
-
-    verify(queue).pop();
-    verify(service).process(report);
-  }
-
-  @Test
-  public void when_the_analysis_throws_an_exception_it_does_not_break_the_task() throws Exception {
-    AnalysisReportDto report = AnalysisReportDto.newForTests(1L);
-    when(queue.pop()).thenReturn(report);
-    doThrow(IllegalStateException.class).when(service).process(report);
-
-    sut.run();
-  }
-
-  @Test
-  public void when_the_queue_returns_an_exception_it_does_not_break_the_task() throws Exception {
-    when(queue.pop()).thenThrow(IllegalStateException.class);
-
-    sut.run();
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/ComputeEngineIssueStorageFactoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/ComputeEngineIssueStorageFactoryTest.java
deleted file mode 100644 (file)
index 87bc106..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.
- */
-
-package org.sonar.server.computation;
-
-import org.junit.Test;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.core.component.ComponentDto;
-import org.sonar.core.issue.db.IssueStorage;
-import org.sonar.core.persistence.MyBatis;
-import org.sonar.server.db.DbClient;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-
-public class ComputeEngineIssueStorageFactoryTest {
-
-  ComputeEngineIssueStorageFactory sut;
-
-  @Test
-  public void return_instance_of_compute_engine_issue_storage() throws Exception {
-    sut = new ComputeEngineIssueStorageFactory(mock(MyBatis.class), mock(DbClient.class), mock(RuleFinder.class));
-
-    IssueStorage issueStorage = sut.newComputeEngineIssueStorage(mock(ComponentDto.class));
-
-    assertThat(issueStorage).isInstanceOf(ComputeEngineIssueStorage.class);
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/ComputeEngineIssueStorageTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/ComputeEngineIssueStorageTest.java
deleted file mode 100644 (file)
index 341d03b..0000000
+++ /dev/null
@@ -1,259 +0,0 @@
-/*
- * 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.
- */
-
-package org.sonar.server.computation;
-
-import org.sonar.batch.protocol.output.component.ReportComponent;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.issue.internal.DefaultIssueComment;
-import org.sonar.api.issue.internal.IssueChangeContext;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.rules.RuleQuery;
-import org.sonar.api.utils.DateUtils;
-import org.sonar.api.utils.Duration;
-import org.sonar.api.utils.System2;
-import org.sonar.core.component.ComponentDto;
-import org.sonar.core.persistence.AbstractDaoTestCase;
-import org.sonar.core.persistence.DbSession;
-import org.sonar.core.persistence.MyBatis;
-import org.sonar.server.component.db.ComponentDao;
-import org.sonar.server.db.DbClient;
-import org.sonar.server.issue.db.IssueDao;
-
-import java.util.Collection;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class ComputeEngineIssueStorageTest extends AbstractDaoTestCase {
-
-  DbClient dbClient;
-  DbSession dbSession;
-  Map<Long, ReportComponent> components;
-  ComponentDto project;
-
-  ComputeEngineIssueStorage sut;
-
-  @Before
-  public void setUp() throws Exception {
-    System2 system = mock(System2.class);
-    when(system.now()).thenReturn(2000000000L);
-    dbClient = new DbClient(getDatabase(), getMyBatis(),
-      new ComponentDao(system),
-      new IssueDao(getMyBatis()),
-      new ComponentDao(system));
-    dbSession = dbClient.openSession(false);
-    components = new HashMap<>();
-    project = new ComponentDto();
-
-    sut = new ComputeEngineIssueStorage(getMyBatis(), dbClient, new FakeRuleFinder(), project);
-  }
-
-  @After
-  public void tearDown() throws Exception {
-    MyBatis.closeQuietly(dbSession);
-  }
-
-  @Test
-  public void should_get_component_id_set_in_issue() throws Exception {
-    DefaultIssue issue = new DefaultIssue().setComponentId(123L);
-
-    long componentId = sut.componentId(dbSession, issue);
-
-    assertThat(componentId).isEqualTo(123L);
-  }
-
-  @Test
-  public void should_load_component_id_from_db() throws Exception {
-    setupData("should_load_component_id_from_db");
-
-    long componentId = sut.componentId(dbSession, new DefaultIssue().setComponentKey("struts:Action.java"));
-
-    assertThat(componentId).isEqualTo(123);
-  }
-
-  @Test(expected = IllegalStateException.class)
-  public void should_fail_to_load_component_id_if_unknown_component() throws Exception {
-    setupData("should_fail_to_load_component_id_if_unknown_component");
-
-    sut.componentId(dbSession, new DefaultIssue().setComponentKey("struts:Action.java"));
-  }
-
-  @Test
-  public void should_load_project_id() throws Exception {
-    project.setId(100L);
-
-    long projectId = sut.projectId();
-
-    assertThat(projectId).isEqualTo(100);
-  }
-
-  @Test
-  public void should_insert_new_issues() throws Exception {
-    setupData("should_insert_new_issues");
-    project.setId(10L).setKey("struts");
-
-    DefaultIssueComment comment = DefaultIssueComment.create("ABCDE", "emmerik", "the comment");
-    // override generated key
-    comment.setKey("FGHIJ");
-
-    Date date = DateUtils.parseDate("2013-05-18");
-    DefaultIssue issue = new DefaultIssue()
-      .setKey("ABCDE")
-      .setNew(true)
-      .setRuleKey(RuleKey.of("squid", "AvoidCycle"))
-      .setLine(5000)
-      .setDebt(Duration.create(10L))
-      .setReporter("emmerik")
-      .setResolution("OPEN")
-      .setStatus("OPEN")
-      .setSeverity("BLOCKER")
-      .setAttribute("foo", "bar")
-      .addComment(comment)
-      .setCreationDate(date)
-      .setUpdateDate(date)
-      .setCloseDate(date)
-
-      .setComponentKey("struts:Action");
-
-    sut.save(issue);
-
-    checkTables("should_insert_new_issues", new String[] {"id", "created_at", "updated_at", "issue_change_creation_date"}, "issues", "issue_changes");
-  }
-
-  @Test
-  public void should_update_issues() throws Exception {
-    setupData("should_update_issues");
-
-    IssueChangeContext context = IssueChangeContext.createUser(new Date(), "emmerik");
-
-    project.setId(10L).setKey("struts");
-
-    DefaultIssueComment comment = DefaultIssueComment.create("ABCDE", "emmerik", "the comment");
-    // override generated key
-    comment.setKey("FGHIJ");
-
-    Date date = DateUtils.parseDate("2013-05-18");
-    DefaultIssue issue = new DefaultIssue()
-      .setKey("ABCDE")
-      .setNew(false)
-      .setChanged(true)
-
-      // updated fields
-      .setLine(5000)
-      .setDebt(Duration.create(10L))
-      .setChecksum("FFFFF")
-      .setAuthorLogin("simon")
-      .setAssignee("loic")
-      .setFieldChange(context, "severity", "INFO", "BLOCKER")
-      .setReporter("emmerik")
-      .setResolution("FIXED")
-      .setStatus("RESOLVED")
-      .setSeverity("BLOCKER")
-      .setAttribute("foo", "bar")
-      .addComment(comment)
-      .setCreationDate(date)
-      .setUpdateDate(date)
-      .setCloseDate(date)
-
-      // unmodifiable fields
-      .setRuleKey(RuleKey.of("xxx", "unknown"))
-      .setComponentKey("not:a:component");
-
-    sut.save(issue);
-
-    checkTables("should_update_issues", new String[] {"id", "created_at", "updated_at", "issue_change_creation_date"}, "issues", "issue_changes");
-  }
-
-  @Test
-  public void should_resolve_conflicts_on_updates() throws Exception {
-    setupData("should_resolve_conflicts_on_updates");
-
-    project.setId(10L).setKey("struts");
-
-    Date date = DateUtils.parseDate("2013-05-18");
-    DefaultIssue issue = new DefaultIssue()
-      .setKey("ABCDE")
-      .setNew(false)
-      .setChanged(true)
-      .setCreationDate(DateUtils.parseDate("2005-05-12"))
-      .setUpdateDate(date)
-      .setRuleKey(RuleKey.of("squid", "AvoidCycles"))
-      .setComponentKey("struts:Action")
-
-      // issue in database has been updated in 2015, after the loading by scan
-      .setSelectedAt(1400000000000L)
-
-      // fields to be updated
-      .setLine(444)
-      .setSeverity("BLOCKER")
-      .setChecksum("FFFFF")
-      .setAttribute("JIRA", "http://jira.com")
-
-      // fields overridden by end-user -> do not save
-      .setAssignee("looser")
-      .setResolution(null)
-      .setStatus("REOPEN");
-
-    sut.save(issue);
-
-    checkTables("should_resolve_conflicts_on_updates", new String[] {"id", "created_at", "updated_at", "issue_change_creation_date"}, "issues");
-  }
-
-  static class FakeRuleFinder implements RuleFinder {
-
-    @Override
-    public Rule findById(int ruleId) {
-      return null;
-    }
-
-    @Override
-    public Rule findByKey(String repositoryKey, String key) {
-      return null;
-    }
-
-    @Override
-    public Rule findByKey(RuleKey key) {
-      Rule rule = Rule.create().setRepositoryKey(key.repository()).setKey(key.rule());
-      rule.setId(200);
-      return rule;
-    }
-
-    @Override
-    public Rule find(RuleQuery query) {
-      return null;
-    }
-
-    @Override
-    public Collection<Rule> findAll(RuleQuery query) {
-      return null;
-    }
-  }
-}
index d97b8942e9b04ef1498fd29fc8d1988e7aa60e19..a7554148090d256a91b3106537190b31a9266c37 100644 (file)
@@ -30,7 +30,7 @@ public class ApplyPermissionsStepTest {
   @Test
   public void index_issue_permissions() throws Exception {
     IssueAuthorizationIndexer indexer = mock(IssueAuthorizationIndexer.class);
-    new ApplyPermissionsStep(indexer).execute(null, null);
+    new ApplyPermissionsStep(indexer).execute(null);
     verify(indexer).index();
   }
 }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputationStepRegistryTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputationStepRegistryTest.java
deleted file mode 100644 (file)
index eece386..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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.
- */
-
-package org.sonar.server.computation.step;
-
-import org.junit.Test;
-import org.sonar.server.source.IndexSourceLinesStep;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-
-public class ComputationStepRegistryTest {
-
-  @Test
-  public void ordered_steps() throws Exception {
-    ComputationStepRegistry registry = new ComputationStepRegistry(
-      // unordered
-      mock(ApplyPermissionsStep.class),
-      mock(DigestReportStep.class),
-      mock(IndexSourceLinesStep.class),
-      mock(InvalidatePreviewCacheStep.class),
-      mock(IndexIssuesStep.class),
-      mock(SwitchSnapshotStep.class),
-      mock(PurgeDatastoresStep.class),
-      mock(IndexComponentsStep.class));
-
-    assertThat(registry.steps()).hasSize(8);
-    assertThat(registry.steps().get(0)).isInstanceOf(DigestReportStep.class);
-    assertThat(registry.steps().get(7)).isInstanceOf(IndexSourceLinesStep.class);
-  }
-
-  @Test
-  public void fail_if_a_step_is_not_registered_in_picocontainer() throws Exception {
-    try {
-      new ComputationStepRegistry(mock(DigestReportStep.class));
-      fail();
-    } catch (IllegalStateException e) {
-      assertThat(e).hasMessage("Component not found in picocontainer: " + ApplyPermissionsStep.class.toString());
-    }
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputationStepsTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputationStepsTest.java
new file mode 100644 (file)
index 0000000..2e38783
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+package org.sonar.server.computation.step;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+
+public class ComputationStepsTest {
+
+  @Test
+  public void ordered_steps() throws Exception {
+    ComputationSteps registry = new ComputationSteps(
+      // unordered
+      mock(ApplyPermissionsStep.class),
+      mock(DigestReportStep.class),
+      mock(IndexSourceLinesStep.class),
+      mock(InvalidateBatchCacheStep.class),
+      mock(PersistIssuesStep.class),
+      mock(IndexIssuesStep.class),
+      mock(SwitchSnapshotStep.class),
+      mock(PurgeDatastoresStep.class),
+      mock(SendIssueNotificationsStep.class),
+      mock(IndexComponentsStep.class));
+
+    assertThat(registry.orderedSteps()).hasSize(10);
+    assertThat(registry.orderedSteps().get(0)).isInstanceOf(DigestReportStep.class);
+    assertThat(registry.orderedSteps().get(9)).isInstanceOf(SendIssueNotificationsStep.class);
+  }
+
+  @Test
+  public void fail_if_a_step_is_not_registered_in_picocontainer() throws Exception {
+    try {
+      new ComputationSteps(mock(DigestReportStep.class));
+      fail();
+    } catch (IllegalStateException e) {
+      assertThat(e).hasMessageContaining("Component not found");
+    }
+  }
+}
index 2585a3214cc435c53cb80c769bda4705dc7606c3..7b83dd1cdb8f23d626d194907064da1197ef5d11 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+/*
+* 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.
+*/
 
 package org.sonar.server.computation.step;
 
 import org.junit.Test;
 import org.sonar.core.component.ComponentDto;
 import org.sonar.core.computation.db.AnalysisReportDto;
-import org.sonar.core.persistence.DbSession;
 import org.sonar.server.computation.AnalysisReportService;
 import org.sonar.server.computation.ComputationContext;
 
@@ -33,15 +51,13 @@ import static org.mockito.Mockito.verify;
 
 public class DigestReportStepTest {
 
-  private DigestReportStep sut;
-
   @Test
   public void call_service_method() throws Exception {
     AnalysisReportService service = mock(AnalysisReportService.class);
-    sut = new DigestReportStep(service);
+    DigestReportStep sut = new DigestReportStep(service);
     ComputationContext context = new ComputationContext(mock(AnalysisReportDto.class), mock(ComponentDto.class), null);
 
-    sut.execute(mock(DbSession.class), context);
+    sut.execute(context);
 
     verify(service).digest(eq(context));
   }
index 4f158dcaa1f8c661a579d3cf0c04e4483b1e137b..fe5c016b4ee0a0cf01c742706fd9fc6daf250e32 100644 (file)
@@ -26,7 +26,6 @@ import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 import org.sonar.core.component.ComponentDto;
 import org.sonar.core.computation.db.AnalysisReportDto;
-import org.sonar.core.persistence.DbSession;
 import org.sonar.core.resource.ResourceIndexerDao;
 import org.sonar.server.computation.ComputationContext;
 
@@ -52,12 +51,11 @@ public class IndexComponentsStepTest {
   public void call_indexProject_of_dao() throws IOException {
     ComponentDto project = mock(ComponentDto.class);
     when(project.getId()).thenReturn(123L);
-    DbSession session = mock(DbSession.class);
     ComputationContext context = new ComputationContext(mock(AnalysisReportDto.class), project, temp.newFolder());
 
-    sut.execute(session, context);
+    sut.execute(context);
 
-    verify(resourceIndexerDao).indexProject(123, session);
+    verify(resourceIndexerDao).indexProject(123L);
   }
 
 }
index 5d8d41214b277a0a20dd9a99144782e6a11887bd..a49f1fea66840fdab63a9afd2d31e294fd19c060 100644 (file)
@@ -21,7 +21,6 @@
 package org.sonar.server.computation.step;
 
 import org.junit.Test;
-import org.sonar.core.persistence.DbSession;
 import org.sonar.server.computation.ComputationContext;
 import org.sonar.server.issue.index.IssueAuthorizationIndexer;
 import org.sonar.server.issue.index.IssueIndexer;
@@ -39,7 +38,7 @@ public class IndexIssuesStepTest {
     IssueIndexer issueIndexer = mock(IssueIndexer.class);
     sut = new IndexIssuesStep(authorizationIndexer, issueIndexer);
 
-    sut.execute(mock(DbSession.class), mock(ComputationContext.class));
+    sut.execute(mock(ComputationContext.class));
 
     verify(authorizationIndexer).index();
     verify(issueIndexer).index();
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/InvalidateBatchCacheStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/InvalidateBatchCacheStepTest.java
new file mode 100644 (file)
index 0000000..ef07bd4
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+package org.sonar.server.computation.step;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.core.component.ComponentDto;
+import org.sonar.core.computation.db.AnalysisReportDto;
+import org.sonar.core.properties.PropertiesDao;
+import org.sonar.core.properties.PropertyDto;
+import org.sonar.server.computation.ComputationContext;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+public class InvalidateBatchCacheStepTest {
+  private InvalidateBatchCacheStep sut;
+  private PropertiesDao propertiesDao;
+
+  @Before
+  public void before() {
+    this.propertiesDao = mock(PropertiesDao.class);
+    this.sut = new InvalidateBatchCacheStep(propertiesDao);
+  }
+
+  @Test
+  public void update_property_to_invalidate_cache() {
+    ComputationContext context = new ComputationContext(mock(AnalysisReportDto.class), mock(ComponentDto.class), null);
+
+    sut.execute(context);
+
+    verify(propertiesDao).setProperty(any(PropertyDto.class));
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/InvalidatePreviewCacheStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/InvalidatePreviewCacheStepTest.java
deleted file mode 100644 (file)
index ca4b1db..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.
- */
-
-package org.sonar.server.computation.step;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.core.component.ComponentDto;
-import org.sonar.core.computation.db.AnalysisReportDto;
-import org.sonar.core.persistence.DbSession;
-import org.sonar.core.properties.PropertiesDao;
-import org.sonar.core.properties.PropertyDto;
-import org.sonar.server.computation.ComputationContext;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-public class InvalidatePreviewCacheStepTest {
-  private InvalidatePreviewCacheStep sut;
-  private PropertiesDao propertiesDao;
-
-  @Before
-  public void before() {
-    this.propertiesDao = mock(PropertiesDao.class);
-    this.sut = new InvalidatePreviewCacheStep(propertiesDao);
-  }
-
-  @Test
-  public void update_property_to_invalidate_cache() {
-    DbSession session = mock(DbSession.class);
-    ComputationContext context = new ComputationContext(mock(AnalysisReportDto.class), mock(ComponentDto.class), null);
-
-    sut.execute(session, context);
-
-    verify(propertiesDao).setProperty(any(PropertyDto.class), eq(session));
-  }
-}
index 124c1fa1fd816508a4061ed27a48294fa1b6758c..43601a875ecf427e9e7f0dfd577617e4bb98e3ea 100644 (file)
@@ -61,7 +61,7 @@ public class PurgeDatastoresStepMediumTest {
 
     this.purgeTask = tester.get(ProjectCleaner.class);
 
-    this.sut = new PurgeDatastoresStep(purgeTask);
+    this.sut = new PurgeDatastoresStep(dbClient, purgeTask);
   }
 
   @After
@@ -98,8 +98,7 @@ public class PurgeDatastoresStepMediumTest {
     ComputationContext context = new ComputationContext(report, project, null);
 
     // ACT
-    sut.execute(dbSession, context);
-    dbSession.commit();
+    sut.execute(context);
 
     // ASSERT
     assertThat(dbClient.snapshotDao().getNullableByKey(dbSession, snapshot.getId())).isNotNull();
@@ -138,7 +137,7 @@ public class PurgeDatastoresStepMediumTest {
     ComputationContext context = new ComputationContext(report, project, null);
 
     // ACT
-    sut.execute(dbSession, context);
+    sut.execute(context);
     dbSession.commit();
 
     // ASSERT
index 23bba86aecc9b3e5c8a6bc96838222948037d610..29d28001a03ed42831263b31a1332ef3949eb72b 100644 (file)
@@ -24,12 +24,14 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
+import org.mockito.Mockito;
 import org.sonar.core.component.ComponentDto;
 import org.sonar.core.computation.db.AnalysisReportDto;
 import org.sonar.core.computation.dbcleaner.ProjectCleaner;
 import org.sonar.core.persistence.DbSession;
 import org.sonar.core.purge.IdUuidPair;
 import org.sonar.server.computation.ComputationContext;
+import org.sonar.server.db.DbClient;
 
 import java.io.IOException;
 
@@ -48,7 +50,7 @@ public class PurgeDatastoresStepTest {
   public void before() {
     this.projectCleaner = mock(ProjectCleaner.class);
 
-    this.sut = new PurgeDatastoresStep(projectCleaner);
+    this.sut = new PurgeDatastoresStep(mock(DbClient.class, Mockito.RETURNS_DEEP_STUBS), projectCleaner);
   }
 
   @Test
@@ -59,7 +61,7 @@ public class PurgeDatastoresStepTest {
     ComputationContext context = new ComputationContext(mock(AnalysisReportDto.class), project,
       temp.newFolder());
 
-    sut.execute(mock(DbSession.class), context);
+    sut.execute(context);
 
     verify(projectCleaner).purge(any(DbSession.class), any(IdUuidPair.class));
   }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/SendIssueNotificationsStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/SendIssueNotificationsStepTest.java
new file mode 100644 (file)
index 0000000..d22c2f3
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+package org.sonar.server.computation.step;
+
+import org.junit.Test;
+import org.sonar.api.issue.internal.DefaultIssue;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SendIssueNotificationsStepTest {
+
+  @Test
+  public void new_issue_statistics() {
+    SendIssueNotificationsStep.NewIssuesStatistics sut = new SendIssueNotificationsStep.NewIssuesStatistics();
+    assertThat(sut.isEmpty()).isTrue();
+    assertThat(sut.size()).isEqualTo(0);
+
+    sut.add(new DefaultIssue().setSeverity("MINOR"));
+    sut.add(new DefaultIssue().setSeverity("BLOCKER"));
+    sut.add(new DefaultIssue().setSeverity("MINOR"));
+
+    assertThat(sut.isEmpty()).isFalse();
+    assertThat(sut.size()).isEqualTo(3);
+    assertThat(sut.issuesWithSeverity("INFO")).isEqualTo(0);
+    assertThat(sut.issuesWithSeverity("MINOR")).isEqualTo(2);
+    assertThat(sut.issuesWithSeverity("BLOCKER")).isEqualTo(1);
+  }
+}
index e6926acc7dc73c911df8bc1da54f5321df418603..af515d5995027bc5abea0c09b60c63ec92e47a05 100644 (file)
@@ -20,7 +20,6 @@
 
 package org.sonar.server.computation.step;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -29,12 +28,11 @@ import org.junit.rules.TemporaryFolder;
 import org.sonar.api.utils.DateUtils;
 import org.sonar.api.utils.System2;
 import org.sonar.core.computation.db.AnalysisReportDto;
-import org.sonar.core.persistence.DbSession;
 import org.sonar.core.persistence.DbTester;
-import org.sonar.core.persistence.MyBatis;
 import org.sonar.server.component.ComponentTesting;
 import org.sonar.server.component.db.SnapshotDao;
 import org.sonar.server.computation.ComputationContext;
+import org.sonar.server.db.DbClient;
 import org.sonar.test.DbTests;
 
 import java.io.IOException;
@@ -51,21 +49,13 @@ public class SwitchSnapshotStepTest {
   @Rule
   public TemporaryFolder temp = new TemporaryFolder();
 
-  private DbSession session;
   private SwitchSnapshotStep sut;
 
   @Before
   public void before() {
-    this.session = db.myBatis().openSession(false);
-
     System2 system2 = mock(System2.class);
     when(system2.now()).thenReturn(DateUtils.parseDate("2011-09-29").getTime());
-    this.sut = new SwitchSnapshotStep(new SnapshotDao(system2));
-  }
-
-  @After
-  public void after() {
-    MyBatis.closeQuietly(session);
+    this.sut = new SwitchSnapshotStep(new DbClient(db.database(), db.myBatis(), new SnapshotDao(system2)));
   }
 
   @Test
@@ -74,8 +64,7 @@ public class SwitchSnapshotStepTest {
     ComputationContext context = new ComputationContext(AnalysisReportDto.newForTests(1L).setSnapshotId(1L),
       ComponentTesting.newProjectDto(), temp.newFolder());
 
-    sut.execute(session, context);
-    session.commit();
+    sut.execute(context);
 
     db.assertDbUnit(getClass(), "snapshots-result.xml", "snapshots");
   }
@@ -86,6 +75,6 @@ public class SwitchSnapshotStepTest {
     ComputationContext context = new ComputationContext(AnalysisReportDto.newForTests(1L).setSnapshotId(1L),
       ComponentTesting.newProjectDto(), temp.newFolder());
 
-    sut.execute(session, context);
+    sut.execute(context);
   }
 }
index b3cff05cacdb445130df1c4016ef04f062c5c0ef..44e5bfe2936be5f208afc1a3a66c862761025498 100644 (file)
@@ -24,7 +24,7 @@ import org.junit.Before;
 import org.junit.Test;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.server.computation.AnalysisReportQueue;
-import org.sonar.server.computation.ComputationWorkerLauncher;
+import org.sonar.server.computation.ComputationThreadLauncher;
 import org.sonar.server.ws.WsTester;
 
 import java.io.InputStream;
@@ -38,7 +38,7 @@ public class SubmitReportWsActionTest {
   private SubmitReportWsAction sut;
 
   private WsTester wsTester;
-  private ComputationWorkerLauncher workerLauncher = mock(ComputationWorkerLauncher.class);
+  private ComputationThreadLauncher workerLauncher = mock(ComputationThreadLauncher.class);
   private AnalysisReportQueue queue;
 
   @Before
index 7393260d2e9e0270986ec5afec0d47c7f7af7ba5..7236b66ec86a5e1bcf7a4fa2c2c2fdebf9e84096 100644 (file)
@@ -26,12 +26,10 @@ import org.junit.Test;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.issue.condition.Condition;
 import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.issue.internal.IssueChangeContext;
 import org.sonar.api.resources.Qualifiers;
 import org.sonar.api.resources.Scopes;
 import org.sonar.api.rules.Rule;
 import org.sonar.core.component.ComponentDto;
-import org.sonar.core.issue.IssueNotifications;
 import org.sonar.core.issue.db.IssueDto;
 import org.sonar.core.issue.db.IssueStorage;
 import org.sonar.core.persistence.DbSession;
@@ -41,6 +39,7 @@ import org.sonar.server.db.DbClient;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.exceptions.UnauthorizedException;
 import org.sonar.server.issue.db.IssueDao;
+import org.sonar.server.issue.notification.IssueNotifications;
 import org.sonar.server.rule.DefaultRuleFinder;
 import org.sonar.server.rule.RuleTesting;
 import org.sonar.server.search.QueryContext;
@@ -59,6 +58,7 @@ import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyListOf;
 import static org.mockito.Matchers.anyMap;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.*;
 
 public class IssueBulkChangeServiceTest {
@@ -90,7 +90,7 @@ public class IssueBulkChangeServiceTest {
     when(dbClient.componentDao()).thenReturn(componentDao);
     when(dbClient.issueDao()).thenReturn(issueDao);
 
-    rule = Rule.create("repo", "key");
+    rule = Rule.create("repo", "key").setName("the rule name");
     when(ruleFinder.findByKeys(newHashSet(rule.ruleKey()))).thenReturn(newArrayList(rule));
 
     project = new ComponentDto()
@@ -135,7 +135,7 @@ public class IssueBulkChangeServiceTest {
 
     verify(issueStorage).save(eq(issue));
     verifyNoMoreInteractions(issueStorage);
-    verify(issueNotifications).sendChanges(eq(issue), any(IssueChangeContext.class), eq(rule), eq(project), eq(file));
+    verify(issueNotifications).sendChanges(eq(issue), anyString(), eq("the rule name"), eq(project), eq(file), eq((String)null), eq(false));
     verifyNoMoreInteractions(issueNotifications);
   }
 
@@ -239,7 +239,7 @@ public class IssueBulkChangeServiceTest {
 
     verify(issueStorage, times(1)).save(eq(issue));
     verifyNoMoreInteractions(issueStorage);
-    verify(issueNotifications).sendChanges(eq(issue), any(IssueChangeContext.class), eq(rule), eq(project), eq(file));
+    verify(issueNotifications).sendChanges(eq(issue), anyString(), eq("the rule name"), eq(project), eq(file), eq((String)null), eq(false));
     verifyNoMoreInteractions(issueNotifications);
   }
 
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/notification/ChangesOnMyIssueNotificationDispatcherTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/ChangesOnMyIssueNotificationDispatcherTest.java
new file mode 100644 (file)
index 0000000..48b9d5f
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * 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.
+ */
+package org.sonar.server.issue.notification;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.notifications.*;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ChangesOnMyIssueNotificationDispatcherTest {
+
+  @Mock
+  NotificationManager notifications;
+
+  @Mock
+  NotificationDispatcher.Context context;
+
+  @Mock
+  NotificationChannel emailChannel;
+
+  @Mock
+  NotificationChannel twitterChannel;
+
+  ChangesOnMyIssueNotificationDispatcher dispatcher;
+
+  @Before
+  public void setUp() {
+    dispatcher = new ChangesOnMyIssueNotificationDispatcher(notifications);
+  }
+
+  @Test
+  public void test_metadata() throws Exception {
+    NotificationDispatcherMetadata metadata = ChangesOnMyIssueNotificationDispatcher.newMetadata();
+    assertThat(metadata.getDispatcherKey()).isEqualTo(dispatcher.getKey());
+    assertThat(metadata.getProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION)).isEqualTo("true");
+    assertThat(metadata.getProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION)).isEqualTo("true");
+  }
+
+  @Test
+  public void should_not_dispatch_if_other_notification_type() throws Exception {
+    Notification notification = new Notification("other-notif");
+    dispatcher.performDispatch(notification, context);
+
+    verify(context, never()).addUser(any(String.class), any(NotificationChannel.class));
+  }
+
+  @Test
+  public void should_dispatch_to_reporter_and_assignee() {
+    Multimap<String, NotificationChannel> recipients = HashMultimap.create();
+    recipients.put("simon", emailChannel);
+    recipients.put("freddy", twitterChannel);
+    recipients.put("godin", twitterChannel);
+    when(notifications.findNotificationSubscribers(dispatcher, "struts")).thenReturn(recipients);
+
+    Notification notification = new Notification("issue-changes").setFieldValue("projectKey", "struts")
+      .setFieldValue("changeAuthor", "olivier")
+      .setFieldValue("reporter", "simon")
+      .setFieldValue("assignee", "freddy");
+    dispatcher.performDispatch(notification, context);
+
+    verify(context).addUser("simon", emailChannel);
+    verify(context).addUser("freddy", twitterChannel);
+    verify(context, never()).addUser("godin", twitterChannel);
+    verifyNoMoreInteractions(context);
+  }
+
+  @Test
+  public void should_not_dispatch_to_author_of_changes() {
+    Multimap<String, NotificationChannel> recipients = HashMultimap.create();
+    recipients.put("simon", emailChannel);
+    recipients.put("freddy", twitterChannel);
+    recipients.put("godin", twitterChannel);
+    when(notifications.findNotificationSubscribers(dispatcher, "struts")).thenReturn(recipients);
+
+    // change author is the reporter
+    dispatcher.performDispatch(new Notification("issue-changes").setFieldValue("projectKey", "struts")
+      .setFieldValue("changeAuthor", "simon").setFieldValue("reporter", "simon"), context);
+
+    // change author is the assignee
+    dispatcher.performDispatch(new Notification("issue-changes").setFieldValue("projectKey", "struts")
+      .setFieldValue("changeAuthor", "simon").setFieldValue("assignee", "simon"), context);
+
+    // no change author
+    dispatcher.performDispatch(new Notification("issue-changes").setFieldValue("projectKey", "struts")
+      .setFieldValue("new.resolution", "FIXED"), context);
+
+    verifyNoMoreInteractions(context);
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest.java
new file mode 100644 (file)
index 0000000..8e51bf1
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * 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.
+ */
+package org.sonar.server.issue.notification;
+
+import com.google.common.io.Resources;
+import org.apache.commons.codec.Charsets;
+import org.apache.commons.lang.StringUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.config.EmailSettings;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.user.User;
+import org.sonar.api.user.UserFinder;
+import org.sonar.plugins.emailnotifications.api.EmailMessage;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class IssueChangesEmailTemplateTest {
+
+  @Mock
+  UserFinder userFinder;
+
+  IssueChangesEmailTemplate template;
+
+  @Before
+  public void setUp() {
+    EmailSettings settings = mock(EmailSettings.class);
+    when(settings.getServerBaseURL()).thenReturn("http://nemo.sonarsource.org");
+    template = new IssueChangesEmailTemplate(settings, userFinder);
+  }
+
+  @Test
+  public void should_ignore_non_issue_changes() {
+    Notification notification = new Notification("other");
+    EmailMessage message = template.format(notification);
+    assertThat(message).isNull();
+  }
+
+  @Test
+  public void email_should_display_assignee_change() throws Exception {
+    Notification notification = generateNotification()
+      .setFieldValue("old.assignee", "simon")
+      .setFieldValue("new.assignee", "louis");
+
+    EmailMessage email = template.format(notification);
+    assertThat(email.getMessageId()).isEqualTo("issue-changes/ABCDE");
+    assertThat(email.getSubject()).isEqualTo("Struts, change on issue #ABCDE");
+
+    String message = email.getMessage();
+    String expected = Resources.toString(Resources.getResource(
+        "org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/email_with_assignee_change.txt"),
+      Charsets.UTF_8
+    );
+    expected = StringUtils.remove(expected, '\r');
+    assertThat(message).isEqualTo(expected);
+    assertThat(email.getFrom()).isNull();
+  }
+
+  @Test
+  public void email_should_display_plan_change() throws Exception {
+    Notification notification = generateNotification()
+      .setFieldValue("old.actionPlan", null)
+      .setFieldValue("new.actionPlan", "ABC 1.0");
+
+    EmailMessage email = template.format(notification);
+    assertThat(email.getMessageId()).isEqualTo("issue-changes/ABCDE");
+    assertThat(email.getSubject()).isEqualTo("Struts, change on issue #ABCDE");
+
+    String message = email.getMessage();
+    String expected = Resources.toString(Resources.getResource(
+        "org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/email_with_action_plan_change.txt"),
+      Charsets.UTF_8
+    );
+    expected = StringUtils.remove(expected, '\r');
+    assertThat(message).isEqualTo(expected);
+    assertThat(email.getFrom()).isNull();
+  }
+
+  @Test
+  public void display_component_key_if_no_component_name() throws Exception {
+    Notification notification = generateNotification()
+      .setFieldValue("componentName", null);
+
+    EmailMessage email = template.format(notification);
+    assertThat(email.getMessageId()).isEqualTo("issue-changes/ABCDE");
+    assertThat(email.getSubject()).isEqualTo("Struts, change on issue #ABCDE");
+
+    String message = email.getMessage();
+    String expected = Resources.toString(Resources.getResource(
+        "org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/display_component_key_if_no_component_name.txt"),
+      Charsets.UTF_8
+    );
+    expected = StringUtils.remove(expected, '\r');
+    assertThat(message).isEqualTo(expected);
+  }
+
+  @Test
+  public void test_email_with_multiple_changes() throws Exception {
+    Notification notification = generateNotification()
+      .setFieldValue("comment", "How to fix it?")
+      .setFieldValue("old.assignee", "simon")
+      .setFieldValue("new.assignee", "louis")
+      .setFieldValue("new.resolution", "FALSE-POSITIVE")
+      .setFieldValue("new.status", "RESOLVED")
+      .setFieldValue("new.tags", "bug performance");
+
+    EmailMessage email = template.format(notification);
+    assertThat(email.getMessageId()).isEqualTo("issue-changes/ABCDE");
+    assertThat(email.getSubject()).isEqualTo("Struts, change on issue #ABCDE");
+
+    String message = email.getMessage();
+    String expected = Resources.toString(Resources.getResource(
+      "org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/email_with_multiple_changes.txt"), Charsets.UTF_8);
+    expected = StringUtils.remove(expected, '\r');
+    assertThat(message).isEqualTo(expected);
+    assertThat(email.getFrom()).isNull();
+  }
+
+  @Test
+  public void notification_sender_should_be_the_author_of_change() {
+    User user = mock(User.class);
+    when(user.name()).thenReturn("Simon");
+    when(userFinder.findByLogin("simon")).thenReturn(user);
+
+    Notification notification = new Notification("issue-changes")
+      .setFieldValue("projectName", "Struts")
+      .setFieldValue("projectKey", "org.apache:struts")
+      .setFieldValue("changeAuthor", "simon");
+
+    EmailMessage message = template.format(notification);
+    assertThat(message.getFrom()).isEqualTo("Simon");
+  }
+
+  private Notification generateNotification() {
+    Notification notification = new Notification("issue-changes")
+      .setFieldValue("projectName", "Struts")
+      .setFieldValue("projectKey", "org.apache:struts")
+      .setFieldValue("componentName", "Action")
+      .setFieldValue("componentKey", "org.apache.struts.Action")
+      .setFieldValue("key", "ABCDE")
+      .setFieldValue("ruleName", "Avoid Cycles")
+      .setFieldValue("message", "Has 3 cycles");
+    return notification;
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/notification/IssueNotificationsTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/IssueNotificationsTest.java
new file mode 100644 (file)
index 0000000..251ff4d
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * 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.
+ */
+package org.sonar.server.issue.notification;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.issue.internal.IssueChangeContext;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.notifications.NotificationManager;
+import org.sonar.api.resources.File;
+import org.sonar.api.resources.Project;
+import org.sonar.core.component.ResourceComponent;
+import org.sonar.server.notifications.NotificationService;
+
+import java.util.Date;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+@RunWith(MockitoJUnitRunner.class)
+public class IssueNotificationsTest {
+
+  @Mock
+  NotificationManager manager;
+
+  IssueNotifications issueNotifications;
+
+  @Before
+  public void setUp() throws Exception {
+    issueNotifications = new IssueNotifications(manager, mock(NotificationService.class));
+  }
+
+  // @Test
+  // public void should_send_new_issues() throws Exception {
+  // Date date = DateUtils.parseDateTime("2013-05-18T13:00:03+0200");
+  // Project project = new Project("struts").setAnalysisDate(date);
+  // IssuesBySeverity issuesBySeverity = mock(IssuesBySeverity.class);
+  // when(issuesBySeverity.size()).thenReturn(42);
+  // when(issuesBySeverity.issues("MINOR")).thenReturn(10);
+  // Notification notification = issueNotifications.sendNewIssues(project, issuesBySeverity);
+  //
+  // assertThat(notification.getFieldValue("count")).isEqualTo("42");
+  // assertThat(notification.getFieldValue("count-MINOR")).isEqualTo("10");
+  // assertThat(DateUtils.parseDateTime(notification.getFieldValue("projectDate"))).isEqualTo(date);
+  // Mockito.verify(manager).scheduleForSending(notification);
+  // }
+
+  @Test
+  public void should_send_changes() throws Exception {
+    IssueChangeContext context = IssueChangeContext.createScan(new Date());
+    DefaultIssue issue = new DefaultIssue()
+      .setMessage("the message")
+      .setKey("ABCDE")
+      .setAssignee("freddy")
+      .setFieldChange(context, "resolution", null, "FIXED")
+      .setFieldChange(context, "status", "OPEN", "RESOLVED")
+      .setFieldChange(context, "assignee", "simon", null)
+      .setSendNotifications(true)
+      .setComponentKey("struts:Action")
+      .setProjectKey("struts");
+
+    Notification notification = issueNotifications.sendChanges(issue, "charlie", null, new Project("struts"), null, null, false);
+
+    assertThat(notification.getFieldValue("message")).isEqualTo("the message");
+    assertThat(notification.getFieldValue("key")).isEqualTo("ABCDE");
+    assertThat(notification.getFieldValue("componentKey")).isEqualTo("struts:Action");
+    assertThat(notification.getFieldValue("componentName")).isNull();
+    assertThat(notification.getFieldValue("old.resolution")).isNull();
+    assertThat(notification.getFieldValue("new.resolution")).isEqualTo("FIXED");
+    assertThat(notification.getFieldValue("old.status")).isEqualTo("OPEN");
+    assertThat(notification.getFieldValue("new.status")).isEqualTo("RESOLVED");
+    assertThat(notification.getFieldValue("old.assignee")).isEqualTo("simon");
+    assertThat(notification.getFieldValue("new.assignee")).isNull();
+    Mockito.verify(manager).scheduleForSending(notification);
+  }
+
+  @Test
+  public void should_send_changes_with_comment() throws Exception {
+    DefaultIssue issue = new DefaultIssue()
+      .setMessage("the message")
+      .setKey("ABCDE")
+      .setAssignee("freddy")
+      .setComponentKey("struts:Action")
+      .setProjectKey("struts");
+    Notification notification = issueNotifications.sendChanges(issue, "charlie", null, new Project("struts"), null, "I don't know how to fix it?", false);
+
+    assertThat(notification.getFieldValue("message")).isEqualTo("the message");
+    assertThat(notification.getFieldValue("key")).isEqualTo("ABCDE");
+    assertThat(notification.getFieldValue("comment")).isEqualTo("I don't know how to fix it?");
+    Mockito.verify(manager).scheduleForSending(notification);
+  }
+
+  @Test
+  public void should_send_changes_with_component_name() throws Exception {
+    IssueChangeContext context = IssueChangeContext.createScan(new Date());
+    DefaultIssue issue = new DefaultIssue()
+      .setMessage("the message")
+      .setKey("ABCDE")
+      .setAssignee("freddy")
+      .setFieldChange(context, "resolution", null, "FIXED")
+      .setSendNotifications(true)
+      .setComponentKey("struts:Action.java")
+      .setProjectKey("struts");
+    Notification notification = issueNotifications.sendChanges(issue, "charlie", null, new Project("struts"),
+      new ResourceComponent(File.create("Action.java", "Action.java", null, false).setEffectiveKey("struts:Action.java")), null, false);
+
+    assertThat(notification.getFieldValue("message")).isEqualTo("the message");
+    assertThat(notification.getFieldValue("key")).isEqualTo("ABCDE");
+    assertThat(notification.getFieldValue("componentKey")).isEqualTo("struts:Action.java");
+    assertThat(notification.getFieldValue("componentName")).isEqualTo("Action.java");
+    assertThat(notification.getFieldValue("old.resolution")).isNull();
+    assertThat(notification.getFieldValue("new.resolution")).isEqualTo("FIXED");
+    Mockito.verify(manager).scheduleForSending(notification);
+  }
+
+  @Test
+  public void should_not_send_changes_if_no_diffs() throws Exception {
+    DefaultIssue issue = new DefaultIssue()
+      .setMessage("the message")
+      .setKey("ABCDE")
+      .setComponentKey("struts:Action")
+      .setProjectKey("struts");
+    issueNotifications.sendChanges(issue, "charlie", null, new Project("struts"), null, null, false);
+
+    Mockito.verifyZeroInteractions(manager);
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewFalsePositiveNotificationDispatcherTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewFalsePositiveNotificationDispatcherTest.java
new file mode 100644 (file)
index 0000000..f44b04b
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * 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.
+ */
+package org.sonar.server.issue.notification;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.notifications.*;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class NewFalsePositiveNotificationDispatcherTest {
+  @Mock
+  NotificationManager notifications;
+
+  @Mock
+  NotificationDispatcher.Context context;
+
+  @Mock
+  NotificationChannel emailChannel;
+
+  @Mock
+  NotificationChannel twitterChannel;
+
+  NewFalsePositiveNotificationDispatcher dispatcher;
+
+  @Before
+  public void setUp() {
+    dispatcher = new NewFalsePositiveNotificationDispatcher(notifications);
+  }
+
+  @Test
+  public void test_metadata() throws Exception {
+    NotificationDispatcherMetadata metadata = NewFalsePositiveNotificationDispatcher.newMetadata();
+    assertThat(metadata.getDispatcherKey()).isEqualTo(dispatcher.getKey());
+    assertThat(metadata.getProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION)).isEqualTo("true");
+    assertThat(metadata.getProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION)).isEqualTo("true");
+  }
+
+  @Test
+  public void should_not_dispatch_if_other_notification_type() throws Exception {
+    Notification notification = new Notification("other");
+    dispatcher.performDispatch(notification, context);
+
+    verify(context, never()).addUser(any(String.class), any(NotificationChannel.class));
+  }
+
+  @Test
+  public void should_dispatch_to_subscribers() {
+    Multimap<String, NotificationChannel> recipients = HashMultimap.create();
+    recipients.put("simon", emailChannel);
+    recipients.put("freddy", twitterChannel);
+    recipients.put("godin", twitterChannel);
+    when(notifications.findNotificationSubscribers(dispatcher, "struts")).thenReturn(recipients);
+
+    Notification notification = new Notification("issue-changes").setFieldValue("projectKey", "struts")
+      .setFieldValue("changeAuthor", "godin")
+      .setFieldValue("new.resolution", "FALSE-POSITIVE")
+      .setFieldValue("assignee", "freddy");
+    dispatcher.performDispatch(notification, context);
+
+    verify(context).addUser("simon", emailChannel);
+    verify(context).addUser("freddy", twitterChannel);
+    // do not notify the person who flagged the issue as false-positive
+    verify(context, never()).addUser("godin", twitterChannel);
+    verifyNoMoreInteractions(context);
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesEmailTemplateTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesEmailTemplateTest.java
new file mode 100644 (file)
index 0000000..f658a8f
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * 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.
+ */
+package org.sonar.server.issue.notification;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.config.EmailSettings;
+import org.sonar.api.notifications.Notification;
+import org.sonar.core.i18n.DefaultI18n;
+import org.sonar.plugins.emailnotifications.api.EmailMessage;
+
+import java.util.Locale;
+
+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.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class NewIssuesEmailTemplateTest {
+
+  NewIssuesEmailTemplate template;
+
+  @Mock
+  DefaultI18n i18n;
+
+  @Before
+  public void setUp() {
+    EmailSettings settings = mock(EmailSettings.class);
+    when(settings.getServerBaseURL()).thenReturn("http://nemo.sonarsource.org");
+    template = new NewIssuesEmailTemplate(settings, i18n);
+  }
+
+  @Test
+  public void shouldNotFormatIfNotCorrectNotification() {
+    Notification notification = new Notification("other-notif");
+    EmailMessage message = template.format(notification);
+    assertThat(message).isNull();
+  }
+
+  /**
+   * <pre>
+   * Subject: Project Struts, new issues
+   * From: Sonar
+   *
+   * Project: Foo
+   * 32 new issues
+   *
+   * See it in SonarQube: http://nemo.sonarsource.org/drilldown/measures/org.sonar.foo:foo?metric=new_violations
+   * </pre>
+   */
+  @Test
+  public void shouldFormatCommentAdded() {
+    Notification notification = new Notification("new-issues")
+      .setFieldValue("count", "32")
+      .setFieldValue("count-INFO", "1")
+      .setFieldValue("count-MINOR", "3")
+      .setFieldValue("count-MAJOR", "10")
+      .setFieldValue("count-CRITICAL", "5")
+      .setFieldValue("count-BLOCKER", "0")
+      .setFieldValue("projectName", "Struts")
+      .setFieldValue("projectKey", "org.apache:struts")
+      .setFieldValue("projectUuid", "ABCDE")
+      .setFieldValue("projectDate", "2010-05-18T14:50:45+0000");
+
+    when(i18n.message(any(Locale.class), eq("severity.BLOCKER"), anyString())).thenReturn("Blocker");
+    when(i18n.message(any(Locale.class), eq("severity.CRITICAL"), anyString())).thenReturn("Critical");
+    when(i18n.message(any(Locale.class), eq("severity.MAJOR"), anyString())).thenReturn("Major");
+    when(i18n.message(any(Locale.class), eq("severity.MINOR"), anyString())).thenReturn("Minor");
+    when(i18n.message(any(Locale.class), eq("severity.INFO"), anyString())).thenReturn("Info");
+
+    EmailMessage message = template.format(notification);
+    assertThat(message.getMessageId()).isEqualTo("new-issues/org.apache:struts");
+    assertThat(message.getSubject()).isEqualTo("Struts: new issues");
+
+    // TODO datetime to be completed when test is isolated from JVM timezone
+    assertThat(message.getMessage()).startsWith("" +
+      "Project: Struts\n" +
+      "\n" +
+      "32 new issues\n" +
+      "\n" +
+      "   Blocker: 0   Critical: 5   Major: 10   Minor: 3   Info: 1\n" +
+      "\n" +
+      "See it in SonarQube: http://nemo.sonarsource.org/issues/search#projectUuids=ABCDE|createdAt=2010-05-1");
+  }
+
+  @Test
+  public void shouldNotAddFooterIfMissingProperties() {
+    Notification notification = new Notification("new-issues")
+      .setFieldValue("count", "32")
+      .setFieldValue("projectName", "Struts");
+
+    EmailMessage message = template.format(notification);
+    assertThat(message.getMessage()).doesNotContain("See it");
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationDispatcherTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationDispatcherTest.java
new file mode 100644 (file)
index 0000000..c6fc60c
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+package org.sonar.server.issue.notification;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.notifications.NotificationChannel;
+import org.sonar.api.notifications.NotificationDispatcher;
+import org.sonar.api.notifications.NotificationManager;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.*;
+
+public class NewIssuesNotificationDispatcherTest {
+
+  @Mock
+  private NotificationManager notifications;
+
+  @Mock
+  private NotificationDispatcher.Context context;
+
+  @Mock
+  private NotificationChannel emailChannel;
+
+  @Mock
+  private NotificationChannel twitterChannel;
+
+  private NewIssuesNotificationDispatcher dispatcher;
+
+  @Before
+  public void init() {
+    MockitoAnnotations.initMocks(this);
+    dispatcher = new NewIssuesNotificationDispatcher(notifications);
+  }
+
+  @Test
+  public void shouldNotDispatchIfNotNewViolationsNotification() throws Exception {
+    Notification notification = new Notification("other-notif");
+    dispatcher.performDispatch(notification, context);
+
+    verify(context, never()).addUser(any(String.class), any(NotificationChannel.class));
+  }
+
+  @Test
+  public void shouldDispatchToUsersWhoHaveSubscribedAndFlaggedProjectAsFavourite() {
+    Multimap<String, NotificationChannel> recipients = HashMultimap.create();
+    recipients.put("user1", emailChannel);
+    recipients.put("user2", twitterChannel);
+    when(notifications.findNotificationSubscribers(dispatcher, "struts")).thenReturn(recipients);
+
+    Notification notification = new Notification("new-issues").setFieldValue("projectKey", "struts");
+    dispatcher.performDispatch(notification, context);
+
+    verify(context).addUser("user1", emailChannel);
+    verify(context).addUser("user2", twitterChannel);
+    verifyNoMoreInteractions(context);
+  }
+}
index b2c33c4e60a3d7dd8dc3e17d912de4f9ca4d3d17..de87f67f688f65bf0a97ccaf5a2555bf580960f8 100644 (file)
@@ -28,7 +28,7 @@ import ch.qos.logback.core.read.ListAppender;
 import ch.qos.logback.core.status.Status;
 import ch.qos.logback.core.util.StatusPrinter;
 import org.junit.Test;
-import org.sonar.server.computation.ComputationWorkerLauncher;
+import org.sonar.server.computation.ComputationThreadLauncher;
 
 import java.net.URL;
 
@@ -74,7 +74,7 @@ public class SwitchLogbackAppenderTest {
     configure(getClass().getResource("SwitchLogbackAppenderTest/valid-switch.xml"));
 
     String initialThreadName = Thread.currentThread().getName();
-    Thread.currentThread().setName(ComputationWorkerLauncher.THREAD_NAME_PREFIX + "test");
+    Thread.currentThread().setName(ComputationThreadLauncher.THREAD_NAME_PREFIX + "test");
     try {
       logger.info("hello");
       assertThat(analyisReports.list).hasSize(1);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/util/CloseableIteratorTest.java b/server/sonar-server/src/test/java/org/sonar/server/util/CloseableIteratorTest.java
new file mode 100644 (file)
index 0000000..96913c0
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ * 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.
+ */
+package org.sonar.server.util;
+
+import org.junit.Test;
+
+import java.util.NoSuchElementException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+
+public class CloseableIteratorTest {
+
+  @Test
+  public void iterate() throws Exception {
+    SimpleCloseableIterator it = new SimpleCloseableIterator();
+    assertThat(it.isClosed).isFalse();
+
+    // multiple calls to hasNext() moves only once the cursor
+    assertThat(it.hasNext()).isTrue();
+    assertThat(it.hasNext()).isTrue();
+    assertThat(it.hasNext()).isTrue();
+    assertThat(it.next()).isEqualTo(1);
+    assertThat(it.isClosed).isFalse();
+
+    assertThat(it.hasNext()).isTrue();
+    assertThat(it.next()).isEqualTo(2);
+    assertThat(it.isClosed).isFalse();
+
+    assertThat(it.hasNext()).isFalse();
+    // automatic close
+    assertThat(it.isClosed).isTrue();
+
+    // explicit close does not fail
+    it.close();
+    assertThat(it.isClosed).isTrue();
+  }
+
+  @Test
+  public void call_next_without_hasNext() throws Exception {
+    SimpleCloseableIterator it = new SimpleCloseableIterator();
+    assertThat(it.next()).isEqualTo(1);
+    assertThat(it.next()).isEqualTo(2);
+    try {
+      it.next();
+      fail();
+    } catch (NoSuchElementException expected) {
+
+    }
+  }
+
+  @Test
+  public void automatic_close_if_traversal_error() throws Exception {
+    FailureCloseableIterator it = new FailureCloseableIterator();
+    try {
+      it.next();
+      fail();
+    } catch (IllegalStateException expected) {
+      assertThat(expected).hasMessage("expected failure");
+      assertThat(it.isClosed).isTrue();
+    }
+  }
+
+  @Test
+  public void remove_is_not_supported_by_default() throws Exception {
+    SimpleCloseableIterator it = new SimpleCloseableIterator();
+    try {
+      it.remove();
+      fail();
+    } catch (UnsupportedOperationException expected) {
+      assertThat(it.isClosed).isTrue();
+    }
+  }
+
+  @Test
+  public void remove_can_be_overridden() throws Exception {
+    RemovableCloseableIterator it = new RemovableCloseableIterator();
+    it.remove();
+    assertThat(it.isRemoved).isTrue();
+  }
+
+  static class SimpleCloseableIterator extends CloseableIterator {
+    int count = 0;
+    boolean isClosed = false;
+
+    @Override
+    protected Object doNext() {
+      count++;
+      if (count < 3) {
+        return count;
+      }
+      return null;
+    }
+
+    @Override
+    protected void doClose() {
+      isClosed = true;
+    }
+  }
+
+  static class FailureCloseableIterator extends CloseableIterator {
+    boolean isClosed = false;
+
+    @Override
+    protected Object doNext() {
+      throw new IllegalStateException("expected failure");
+    }
+
+    @Override
+    protected void doClose() {
+      isClosed = true;
+    }
+  }
+
+  static class RemovableCloseableIterator extends CloseableIterator {
+    boolean isClosed = false, isRemoved = false;
+
+    @Override
+    protected Object doNext() {
+      return "foo";
+    }
+
+    @Override
+    protected void doRemove() {
+      isRemoved = true;
+    }
+
+    @Override
+    protected void doClose() {
+      isClosed = true;
+    }
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/util/ObjectInputStreamIteratorTest.java b/server/sonar-server/src/test/java/org/sonar/server/util/ObjectInputStreamIteratorTest.java
new file mode 100644 (file)
index 0000000..28439aa
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+package org.sonar.server.util;
+
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.NoSuchElementException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+
+public class ObjectInputStreamIteratorTest {
+
+  @Test
+  public void read_objects() throws Exception {
+    ByteArrayOutputStream bytesOutput = new ByteArrayOutputStream();
+    ObjectOutputStream objectOutputStream = new ObjectOutputStream(bytesOutput);
+    objectOutputStream.writeObject(new SimpleSerializable("first"));
+    objectOutputStream.writeObject(new SimpleSerializable("second"));
+    objectOutputStream.writeObject(new SimpleSerializable("third"));
+    objectOutputStream.flush();
+    objectOutputStream.close();
+
+    ObjectInputStreamIterator<SimpleSerializable> it = new ObjectInputStreamIterator<>(new ByteArrayInputStream(bytesOutput.toByteArray()));
+    assertThat(it.next().value).isEqualTo("first");
+    assertThat(it.next().value).isEqualTo("second");
+    assertThat(it.next().value).isEqualTo("third");
+    try {
+      it.next();
+      fail();
+    } catch (NoSuchElementException expected) {
+
+    }
+  }
+
+  @Test
+  public void test_error() throws Exception {
+    ByteArrayOutputStream bytesOutput = new ByteArrayOutputStream();
+    ObjectOutputStream objectOutputStream = new ObjectOutputStream(bytesOutput);
+    objectOutputStream.writeObject(new SimpleSerializable("first"));
+    objectOutputStream.writeBoolean(false);
+    objectOutputStream.flush();
+    objectOutputStream.close();
+
+    ObjectInputStreamIterator<SimpleSerializable> it = new ObjectInputStreamIterator<>(new ByteArrayInputStream(bytesOutput.toByteArray()));
+    assertThat(it.next().value).isEqualTo("first");
+    try {
+      it.next();
+      fail();
+    } catch (RuntimeException expected) {
+
+    }
+  }
+
+  static class SimpleSerializable implements Serializable {
+    String value;
+
+    public SimpleSerializable() {
+
+    }
+
+    public SimpleSerializable(String value) {
+      this.value = value;
+    }
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/util/cache/DiskCacheTest.java b/server/sonar-server/src/test/java/org/sonar/server/util/cache/DiskCacheTest.java
new file mode 100644 (file)
index 0000000..b4160f6
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+package org.sonar.server.util.cache;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.utils.System2;
+import org.sonar.server.util.CloseableIterator;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+
+public class DiskCacheTest {
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  @Test
+  public void write_and_read() throws Exception {
+    DiskCache<String> cache = new DiskCache<>(temp.newFile(), System2.INSTANCE);
+    try (CloseableIterator<String> traverse = cache.traverse()) {
+      assertThat(traverse).isEmpty();
+    }
+
+    cache.newAppender()
+      .append("foo")
+      .append("bar")
+      .close();
+    try (CloseableIterator<String> traverse = cache.traverse()) {
+      assertThat(traverse).containsExactly("foo", "bar");
+    }
+  }
+
+  @Test
+  public void fail_if_file_is_not_writable() throws Exception {
+    try {
+      new DiskCache<>(temp.newFolder(), System2.INSTANCE);
+      fail();
+    } catch (IllegalStateException e) {
+      assertThat(e).hasMessageContaining("Fail to write into file");
+    }
+  }
+
+  @Test
+  public void fail_to_serialize() throws Exception {
+    class Unserializable implements Serializable {
+      private void writeObject(ObjectOutputStream out) throws IOException {
+        throw new UnsupportedOperationException("expected error");
+      }
+    }
+    DiskCache<Serializable> cache = new DiskCache<>(temp.newFile(), System2.INSTANCE);
+    try {
+      cache.newAppender().append(new Unserializable());
+      fail();
+    } catch (UnsupportedOperationException e) {
+      assertThat(e).hasMessage("expected error");
+    }
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/util/cache/MemoryCacheTest.java b/server/sonar-server/src/test/java/org/sonar/server/util/cache/MemoryCacheTest.java
new file mode 100644 (file)
index 0000000..c860f4c
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+package org.sonar.server.util.cache;
+
+import com.google.common.collect.ImmutableMap;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.*;
+
+public class MemoryCacheTest {
+
+  CacheLoader<String, String> loader = mock(CacheLoader.class);
+  MemoryCache<String, String> cache = new MemoryCache<>(loader);
+
+  @Test
+  public void getNullable() throws Exception {
+    when(loader.load("foo")).thenReturn("bar");
+    assertThat(cache.getNullable("foo")).isEqualTo("bar");
+    assertThat(cache.getNullable("foo")).isEqualTo("bar");
+    verify(loader, times(1)).load("foo");
+
+    // return null if key not found
+    assertThat(cache.getNullable("not_exists")).isNull();
+
+    // clear cache -> new calls to CacheLoader
+    cache.clear();
+    assertThat(cache.getNullable("foo")).isEqualTo("bar");
+    verify(loader, times(2)).load("foo");
+  }
+
+  @Test
+  public void get_throws_exception_if_not_exists() throws Exception {
+    when(loader.load("foo")).thenReturn("bar");
+    assertThat(cache.get("foo")).isEqualTo("bar");
+    assertThat(cache.get("foo")).isEqualTo("bar");
+    verify(loader, times(1)).load("foo");
+
+    try {
+      cache.get("not_exists");
+      fail();
+    } catch (IllegalArgumentException e) {
+      assertThat(e).hasMessage("Not found: not_exists");
+    }
+  }
+
+  @Test
+  public void getAllNullable() throws Exception {
+    List<String> keys = Arrays.asList("one", "two", "three");
+    Map<String, String> values = new HashMap<>();
+    values.put("one", "un");
+    values.put("two", "deux");
+    values.put("three", null);
+    when(loader.loadAll(keys)).thenReturn(values);
+    assertThat(cache.getAll(keys))
+      .hasSize(3)
+      .containsEntry("one", "un")
+      .containsEntry("two", "deux")
+      .containsEntry("three", null);
+
+    when(loader.loadAll(Arrays.asList("four"))).thenReturn(ImmutableMap.of("four", "quatre"));
+    assertThat(cache.getAll(Arrays.asList("one", "two", "four")))
+      .hasSize(3)
+      .containsEntry("one", "un")
+      .containsEntry("two", "deux")
+      .containsEntry("four", "quatre");
+    verify(loader, times(2)).loadAll(anyCollection());
+  }
+}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/display_component_key_if_no_component_name.txt b/server/sonar-server/src/test/resources/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/display_component_key_if_no_component_name.txt
new file mode 100644 (file)
index 0000000..9f90a7d
--- /dev/null
@@ -0,0 +1,6 @@
+org.apache.struts.Action
+Rule: Avoid Cycles
+Message: Has 3 cycles
+
+
+See it in SonarQube: http://nemo.sonarsource.org/issues/search#issues=ABCDE
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/email_with_action_plan_change.txt b/server/sonar-server/src/test/resources/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/email_with_action_plan_change.txt
new file mode 100644 (file)
index 0000000..f9a4356
--- /dev/null
@@ -0,0 +1,7 @@
+Action
+Rule: Avoid Cycles
+Message: Has 3 cycles
+
+Action Plan changed to ABC 1.0
+
+See it in SonarQube: http://nemo.sonarsource.org/issues/search#issues=ABCDE
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/email_with_assignee_change.txt b/server/sonar-server/src/test/resources/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/email_with_assignee_change.txt
new file mode 100644 (file)
index 0000000..fd4c140
--- /dev/null
@@ -0,0 +1,7 @@
+Action
+Rule: Avoid Cycles
+Message: Has 3 cycles
+
+Assignee changed to louis
+
+See it in SonarQube: http://nemo.sonarsource.org/issues/search#issues=ABCDE
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/email_with_multiple_changes.txt b/server/sonar-server/src/test/resources/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/email_with_multiple_changes.txt
new file mode 100644 (file)
index 0000000..6f4b090
--- /dev/null
@@ -0,0 +1,11 @@
+Action
+Rule: Avoid Cycles
+Message: Has 3 cycles
+
+Comment: How to fix it?
+Assignee changed to louis
+Resolution: FALSE-POSITIVE
+Status: RESOLVED
+Tags: [bug performance]
+
+See it in SonarQube: http://nemo.sonarsource.org/issues/search#issues=ABCDE
index 1a2ad21958e152ff8add3cb8966eb496c038fdc8..0a3c22ef77a193eea1fa55ab569afb97a4b6e7c7 100644 (file)
@@ -73,7 +73,6 @@ import org.sonar.batch.source.CodeColorizers;
 import org.sonar.batch.source.HighlightableBuilder;
 import org.sonar.batch.source.SymbolizableBuilder;
 import org.sonar.core.component.ScanGraph;
-import org.sonar.core.issue.IssueNotifications;
 import org.sonar.core.issue.IssueUpdater;
 import org.sonar.core.issue.workflow.FunctionExecutor;
 import org.sonar.core.issue.workflow.IssueWorkflow;
@@ -154,7 +153,6 @@ public class ProjectScanContainer extends ComponentContainer {
       FunctionExecutor.class,
       IssueWorkflow.class,
       IssueCache.class,
-      IssueNotifications.class,
       DefaultProjectIssues.class,
       IssueChangelogDebtCalculator.class,
 
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/IssueNotifications.java b/sonar-core/src/main/java/org/sonar/core/issue/IssueNotifications.java
deleted file mode 100644 (file)
index c451592..0000000
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * 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.
- */
-package org.sonar.core.issue;
-
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import org.sonar.api.BatchComponent;
-import org.sonar.api.ServerComponent;
-import org.sonar.api.component.Component;
-import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.issue.internal.FieldDiffs;
-import org.sonar.api.issue.internal.IssueChangeContext;
-import org.sonar.api.notifications.Notification;
-import org.sonar.api.notifications.NotificationManager;
-import org.sonar.api.resources.Project;
-import org.sonar.api.rule.Severity;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.utils.DateUtils;
-
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-
-import java.io.Serializable;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-/**
- * Send notifications related to issues.
- *
- * @since 3.6
- */
-public class IssueNotifications implements BatchComponent, ServerComponent {
-
-  private final NotificationManager notificationsManager;
-
-  public IssueNotifications(NotificationManager notificationsManager) {
-    this.notificationsManager = notificationsManager;
-  }
-
-  public Notification sendNewIssues(Project project, IssuesBySeverity newIssues) {
-    Notification notification = newNotification(project, "new-issues")
-      .setDefaultMessage(newIssues.size() + " new issues on " + project.getLongName() + ".\n")
-      .setFieldValue("projectDate", DateUtils.formatDateTime(project.getAnalysisDate()))
-      .setFieldValue("projectUuid", project.getUuid())
-      .setFieldValue("count", String.valueOf(newIssues.size()));
-    for (String severity : Severity.ALL) {
-      notification.setFieldValue("count-" + severity, String.valueOf(newIssues.issues(severity)));
-    }
-    notificationsManager.scheduleForSending(notification);
-    return notification;
-  }
-
-  @CheckForNull
-  public List<Notification> sendChanges(DefaultIssue issue, IssueChangeContext context, @Nullable Rule rule, Component project, @Nullable Component component) {
-    return sendChanges(issue, context, rule, project, component, null);
-  }
-
-  @CheckForNull
-  public List<Notification> sendChanges(DefaultIssue issue, IssueChangeContext context, @Nullable Rule rule, Component project, @Nullable Component component,
-                                        @Nullable String comment) {
-    Map<DefaultIssue, Rule> issues = Maps.newHashMap();
-    issues.put(issue, rule);
-    return sendChanges(issues, context, project, component, comment);
-  }
-
-  @CheckForNull
-  public List<Notification> sendChanges(Map<DefaultIssue, Rule> issues, IssueChangeContext context, Component project, @Nullable Component component, @Nullable String comment) {
-    List<Notification> notifications = Lists.newArrayList();
-    for (Entry<DefaultIssue, Rule> entry : issues.entrySet()) {
-      Notification notification = createChangeNotification(entry.getKey(), context, entry.getValue(), project, component, comment);
-      if (notification != null) {
-        notifications.add(notification);
-      }
-    }
-    if (!notifications.isEmpty()) {
-      notificationsManager.scheduleForSending(notifications);
-    }
-    return notifications;
-  }
-
-  @CheckForNull
-  private Notification createChangeNotification(DefaultIssue issue, IssueChangeContext context, @Nullable Rule rule, Component project,
-    @Nullable Component component, @Nullable String comment) {
-    Notification notification = null;
-    if (comment != null || issue.mustSendNotifications()) {
-      FieldDiffs currentChange = issue.currentChange();
-      notification = newNotification(project, "issue-changes");
-      notification.setFieldValue("key", issue.key());
-      notification.setFieldValue("changeAuthor", context.login());
-      notification.setFieldValue("reporter", issue.reporter());
-      notification.setFieldValue("assignee", issue.assignee());
-      notification.setFieldValue("message", issue.message());
-      notification.setFieldValue("ruleName", ruleName(rule));
-      notification.setFieldValue("componentKey", issue.componentKey());
-      if (component != null) {
-        notification.setFieldValue("componentName", component.longName());
-      }
-      if (comment != null) {
-        notification.setFieldValue("comment", comment);
-      }
-
-      if (currentChange != null) {
-        for (Map.Entry<String, FieldDiffs.Diff> entry : currentChange.diffs().entrySet()) {
-          String type = entry.getKey();
-          FieldDiffs.Diff diff = entry.getValue();
-          Serializable newValue = diff.newValue();
-          Serializable oldValue = diff.oldValue();
-          notification.setFieldValue("old." + type, oldValue != null ? oldValue.toString() : null);
-          notification.setFieldValue("new." + type, newValue != null ? newValue.toString() : null);
-        }
-      }
-    }
-    return notification;
-  }
-
-  @CheckForNull
-  private String ruleName(@Nullable Rule rule) {
-    // this code should definitely be shared in api
-    if (rule == null) {
-      return null;
-    }
-    return rule.getName();
-  }
-
-  private Notification newNotification(Component project, String key) {
-    return new Notification(key)
-      .setFieldValue("projectName", project.longName())
-      .setFieldValue("projectKey", project.key());
-  }
-
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/IssuesBySeverity.java b/sonar-core/src/main/java/org/sonar/core/issue/IssuesBySeverity.java
deleted file mode 100644 (file)
index 85286c3..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.
- */
-package org.sonar.core.issue;
-
-import com.google.common.collect.HashMultiset;
-import com.google.common.collect.Multiset;
-import org.sonar.api.issue.Issue;
-
-public class IssuesBySeverity {
-
-  private final Multiset<String> issuesBySeverity;
-
-  public IssuesBySeverity() {
-    issuesBySeverity = HashMultiset.create();
-  }
-
-  public void add(Issue issue) {
-    issuesBySeverity.add(issue.severity());
-  }
-
-  public int issues(String severity) {
-    return issuesBySeverity.count(severity);
-  }
-
-  public int size() {
-    return issuesBySeverity.size();
-  }
-
-  public boolean isEmpty() {
-    return issuesBySeverity.isEmpty();
-  }
-}
index 7157be3646a8aff3a4ce0d754c3873619652c4de..5c6c46ed641d10aac5f636e4fa3c2462d15f35dd 100644 (file)
@@ -561,7 +561,7 @@ public final class IssueDto implements Serializable {
   /**
    * On batch side, component keys and uuid are useless
    */
-  public static IssueDto toDtoForBatchInsert(DefaultIssue issue, Long componentId, Long rootComponentId, Integer ruleId, long now) {
+  public static IssueDto toDtoForBatchInsert(DefaultIssue issue, long componentId, long projectId, int ruleId, long now) {
     return new IssueDto()
       .setKee(issue.key())
       .setLine(issue.line())
@@ -585,7 +585,7 @@ public final class IssueDto implements Serializable {
       .setModuleUuidPath(issue.moduleUuidPath())
       .setComponentUuid(issue.componentUuid())
       .setProjectUuid(issue.projectUuid())
-      .setProjectId(rootComponentId)
+      .setProjectId(projectId)
       .setProjectKey(issue.projectKey())
       .setActionPlanKey(issue.actionPlanKey())
       .setIssueAttributes(KeyValueFormat.format(issue.attributes()))
index a5fa56cfeb5d885ff8d93dcb406cd0582dc3de34..6ce04317fb0927ad0188ac2829517b3266bcfd30 100644 (file)
@@ -25,9 +25,8 @@ import org.slf4j.LoggerFactory;
 import org.sonar.api.issue.internal.DefaultIssue;
 
 /**
+ * Support concurrent modifications on issues made by analysis and users at the same time
  * See https://jira.codehaus.org/browse/SONAR-4309
- *
- * @since 3.6
  */
 public class UpdateConflictResolver {
 
index 0befc3ad53ff254cd5100a2ff6e27017e5c9e173..b04a4256378d3a5131e9db25908f03ae8e5be8fc 100644 (file)
@@ -50,7 +50,7 @@ public class ResourceIndexerDao {
   /**
    * This method is reentrant. It can be executed even if the project is already indexed.
    */
-  public ResourceIndexerDao indexProject(final int rootProjectId) {
+  public ResourceIndexerDao indexProject(final long rootProjectId) {
     DbSession session = mybatis.openSession(true);
     try {
       indexProject(rootProjectId, session);
@@ -62,7 +62,7 @@ public class ResourceIndexerDao {
     }
   }
 
-  public void indexProject(final int rootProjectId, DbSession session) {
+  public void indexProject(final long rootProjectId, DbSession session) {
     ResourceIndexerMapper mapper = session.getMapper(ResourceIndexerMapper.class);
     doIndexProject(rootProjectId, session, mapper);
   }
@@ -89,7 +89,7 @@ public class ResourceIndexerDao {
     }
   }
 
-  private void doIndexProject(int rootProjectId, SqlSession session, final ResourceIndexerMapper mapper) {
+  private void doIndexProject(long rootProjectId, SqlSession session, final ResourceIndexerMapper mapper) {
     // non indexed resources
     ResourceIndexerQuery query = ResourceIndexerQuery.create()
       .setNonIndexedOnly(true)
index d6db9d6417091716c5b6776bd71153c341f70d81..7d4431703805256f84661bef268d52dafd79f1ed 100644 (file)
@@ -20,7 +20,7 @@
 package org.sonar.core.resource;
 
 final class ResourceIndexerQuery {
-  private Integer rootProjectId = null;
+  private long rootProjectId;
   private String[] scopes = null;
   private String[] qualifiers = null;
   private boolean nonIndexedOnly=false;
@@ -50,11 +50,11 @@ final class ResourceIndexerQuery {
     return this;
   }
 
-  public Integer getRootProjectId() {
+  public long getRootProjectId() {
     return rootProjectId;
   }
 
-  public ResourceIndexerQuery setRootProjectId(Integer i) {
+  public ResourceIndexerQuery setRootProjectId(long i) {
     this.rootProjectId = i;
     return this;
   }
index 2156d2c6b74984c319217a905a47bd802679ac90..616899022318aa44e353ae3b59c33c55232a5180 100644 (file)
@@ -72,7 +72,7 @@
     message, line, effort_to_fix, technical_debt, status, tags,
     resolution, checksum, reporter, assignee, author_login, issue_attributes, issue_creation_date, issue_update_date,
     issue_close_date, created_at, updated_at)
-    VALUES (#{kee,jdbcType=VARCHAR}, #{componentId,jdbcType=INTEGER}, #{projectId,jdbcType=INTEGER}, #{ruleId,jdbcType=INTEGER}, #{actionPlanKey,jdbcType=VARCHAR}, #{severity,jdbcType=VARCHAR}, #{manualSeverity,jdbcType=BOOLEAN},
+    VALUES (#{kee,jdbcType=VARCHAR}, #{componentId,jdbcType=BIGINT}, #{projectId,jdbcType=BIGINT}, #{ruleId,jdbcType=INTEGER}, #{actionPlanKey,jdbcType=VARCHAR}, #{severity,jdbcType=VARCHAR}, #{manualSeverity,jdbcType=BOOLEAN},
     #{message,jdbcType=VARCHAR}, #{line,jdbcType=INTEGER}, #{effortToFix,jdbcType=DOUBLE}, #{debt,jdbcType=INTEGER}, #{status,jdbcType=VARCHAR}, #{tagsString,jdbcType=VARCHAR},
     #{resolution,jdbcType=VARCHAR}, #{checksum,jdbcType=VARCHAR}, #{reporter,jdbcType=VARCHAR}, #{assignee,jdbcType=VARCHAR}, #{authorLogin,jdbcType=VARCHAR}, #{issueAttributes,jdbcType=VARCHAR}, #{issueCreationDate,jdbcType=TIMESTAMP},
     #{issueUpdateDate,jdbcType=TIMESTAMP}, #{issueCloseDate,jdbcType=TIMESTAMP}, #{createdAt,jdbcType=BIGINT}, #{updatedAt,jdbcType=BIGINT})
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/IssueNotificationsTest.java b/sonar-core/src/test/java/org/sonar/core/issue/IssueNotificationsTest.java
deleted file mode 100644 (file)
index 5f7e01e..0000000
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * 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.
- */
-package org.sonar.core.issue;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.runners.MockitoJUnitRunner;
-import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.issue.internal.IssueChangeContext;
-import org.sonar.api.notifications.Notification;
-import org.sonar.api.notifications.NotificationManager;
-import org.sonar.api.resources.File;
-import org.sonar.api.resources.Project;
-import org.sonar.api.utils.DateUtils;
-import org.sonar.core.component.ResourceComponent;
-
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-@RunWith(MockitoJUnitRunner.class)
-public class IssueNotificationsTest {
-
-  @Mock
-  NotificationManager manager;
-
-  IssueNotifications issueNotifications;
-
-  @Before
-  public void setUp() throws Exception {
-    issueNotifications = new IssueNotifications(manager);
-  }
-
-  @Test
-  public void should_send_new_issues() throws Exception {
-    Date date = DateUtils.parseDateTime("2013-05-18T13:00:03+0200");
-    Project project = new Project("struts").setAnalysisDate(date);
-    IssuesBySeverity issuesBySeverity = mock(IssuesBySeverity.class);
-    when(issuesBySeverity.size()).thenReturn(42);
-    when(issuesBySeverity.issues("MINOR")).thenReturn(10);
-    Notification notification = issueNotifications.sendNewIssues(project, issuesBySeverity);
-
-    assertThat(notification.getFieldValue("count")).isEqualTo("42");
-    assertThat(notification.getFieldValue("count-MINOR")).isEqualTo("10");
-    assertThat(DateUtils.parseDateTime(notification.getFieldValue("projectDate"))).isEqualTo(date);
-    Mockito.verify(manager).scheduleForSending(notification);
-  }
-
-  @Test
-  public void should_send_changes() throws Exception {
-    IssueChangeContext context = IssueChangeContext.createScan(new Date());
-    DefaultIssue issue = new DefaultIssue()
-      .setMessage("the message")
-      .setKey("ABCDE")
-      .setAssignee("freddy")
-      .setFieldChange(context, "resolution", null, "FIXED")
-      .setFieldChange(context, "status", "OPEN", "RESOLVED")
-      .setFieldChange(context, "assignee", "simon", null)
-      .setSendNotifications(true)
-      .setComponentKey("struts:Action")
-      .setProjectKey("struts");
-
-    Notification notification = issueNotifications.sendChanges(issue, context, null, new Project("struts"), null).get(0);
-
-    assertThat(notification.getFieldValue("message")).isEqualTo("the message");
-    assertThat(notification.getFieldValue("key")).isEqualTo("ABCDE");
-    assertThat(notification.getFieldValue("componentKey")).isEqualTo("struts:Action");
-    assertThat(notification.getFieldValue("componentName")).isNull();
-    assertThat(notification.getFieldValue("old.resolution")).isNull();
-    assertThat(notification.getFieldValue("new.resolution")).isEqualTo("FIXED");
-    assertThat(notification.getFieldValue("old.status")).isEqualTo("OPEN");
-    assertThat(notification.getFieldValue("new.status")).isEqualTo("RESOLVED");
-    assertThat(notification.getFieldValue("old.assignee")).isEqualTo("simon");
-    assertThat(notification.getFieldValue("new.assignee")).isNull();
-    Mockito.verify(manager).scheduleForSending(eq(Arrays.asList(notification)));
-  }
-
-  @Test
-  public void should_send_changes_with_comment() throws Exception {
-    IssueChangeContext context = IssueChangeContext.createScan(new Date());
-    DefaultIssue issue = new DefaultIssue()
-      .setMessage("the message")
-      .setKey("ABCDE")
-      .setAssignee("freddy")
-      .setComponentKey("struts:Action")
-      .setProjectKey("struts");
-    Notification notification = issueNotifications.sendChanges(issue, context, null, new Project("struts"), null, "I don't know how to fix it?").get(0);
-
-    assertThat(notification.getFieldValue("message")).isEqualTo("the message");
-    assertThat(notification.getFieldValue("key")).isEqualTo("ABCDE");
-    assertThat(notification.getFieldValue("comment")).isEqualTo("I don't know how to fix it?");
-    Mockito.verify(manager).scheduleForSending(eq(Arrays.asList(notification)));
-  }
-
-  @Test
-  public void should_send_changes_with_component_name() throws Exception {
-    IssueChangeContext context = IssueChangeContext.createScan(new Date());
-    DefaultIssue issue = new DefaultIssue()
-      .setMessage("the message")
-      .setKey("ABCDE")
-      .setAssignee("freddy")
-      .setFieldChange(context, "resolution", null, "FIXED")
-      .setSendNotifications(true)
-      .setComponentKey("struts:Action.java")
-      .setProjectKey("struts");
-    Notification notification = issueNotifications.sendChanges(issue, context, null, new Project("struts"),
-      new ResourceComponent(File.create("Action.java", "Action.java", null, false).setEffectiveKey("struts:Action.java"))).get(0);
-
-    assertThat(notification.getFieldValue("message")).isEqualTo("the message");
-    assertThat(notification.getFieldValue("key")).isEqualTo("ABCDE");
-    assertThat(notification.getFieldValue("componentKey")).isEqualTo("struts:Action.java");
-    assertThat(notification.getFieldValue("componentName")).isEqualTo("Action.java");
-    assertThat(notification.getFieldValue("old.resolution")).isNull();
-    assertThat(notification.getFieldValue("new.resolution")).isEqualTo("FIXED");
-    Mockito.verify(manager).scheduleForSending(eq(Arrays.asList(notification)));
-  }
-
-  @Test
-  public void should_not_send_changes_if_no_diffs() throws Exception {
-    IssueChangeContext context = IssueChangeContext.createScan(new Date());
-    DefaultIssue issue = new DefaultIssue()
-      .setMessage("the message")
-      .setKey("ABCDE")
-      .setComponentKey("struts:Action")
-      .setProjectKey("struts");
-    List<Notification> notifications = issueNotifications.sendChanges(issue, context, null, new Project("struts"), null);
-
-    assertThat(notifications).isEmpty();
-    Mockito.verifyZeroInteractions(manager);
-  }
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/issue/IssuesBySeverityTest.java b/sonar-core/src/test/java/org/sonar/core/issue/IssuesBySeverityTest.java
deleted file mode 100644 (file)
index 8c62cb7..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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.
- */
-package org.sonar.core.issue;
-
-import org.junit.Test;
-import org.sonar.api.issue.internal.DefaultIssue;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class IssuesBySeverityTest {
-
-  IssuesBySeverity sut;
-
-  @Test
-  public void add_issue() {
-    sut = new IssuesBySeverity();
-
-    sut.add(new DefaultIssue().setSeverity("MINOR"));
-
-    assertThat(sut.isEmpty()).isFalse();
-    assertThat(sut.size()).isEqualTo(1);
-  }
-
-  @Test
-  public void get_issues_by_severity() {
-    sut = new IssuesBySeverity();
-
-    sut.add(new DefaultIssue().setSeverity("MINOR"));
-    sut.add(new DefaultIssue().setSeverity("MINOR"));
-    sut.add(new DefaultIssue().setSeverity("MAJOR"));
-
-    assertThat(sut.issues("MINOR")).isEqualTo(2);
-    assertThat(sut.issues("MAJOR")).isEqualTo(1);
-  }
-
-  @Test
-  public void get_zero_issues_on_empty_severity() {
-    sut = new IssuesBySeverity();
-
-    sut.add(new DefaultIssue().setSeverity("MAJOR"));
-
-    assertThat(sut.issues("MINOR")).isEqualTo(0);
-  }
-
-  @Test
-  public void is_empty() throws Exception {
-    sut = new IssuesBySeverity();
-
-    assertThat(sut.isEmpty()).isTrue();
-  }
-}
index 232e1eced339219f22c9d021fa82cf3e11ff6d58..46b3f13d4993520e64c2492edaf4aaed14e1c99f 100644 (file)
@@ -98,8 +98,8 @@ public class DbTester extends ExternalResource {
     for (String key : settings.getKeysStartingWith("sonar.jdbc")) {
       LOG.info(key + ": " + settings.getString(key));
     }
-    boolean hasDialect = settings.hasKey("sonar.jdbc.dialect");
-    if (hasDialect) {
+    String dialect = settings.getString("sonar.jdbc.dialect");
+    if (dialect != null && !"h2".equals(dialect)) {
       db = new DefaultDatabase(settings);
     } else {
       db = new H2Database("h2Tests" + DigestUtils.md5Hex(StringUtils.defaultString(schemaPath)), schemaPath == null);
index 8b1e92e7df1be1cb8dc57c13e283bdcd15eafd0d..9ba69fd9cc6dba1032c3a6b988aa8289170b78aa 100644 (file)
@@ -599,7 +599,7 @@ public class DefaultIssue implements Issue {
   }
 
   public DefaultIssue setTags(Collection<String> tags) {
-    this.tags = new LinkedHashSet<String>(tags);
+    this.tags = new LinkedHashSet<>(tags);
     return this;
   }
 }
index 81b720b7e37afbb16fed19df15101e184d04831e..964df41ed0d51c11be7c0ce9bd900c1ace13640a 100644 (file)
@@ -125,4 +125,16 @@ public class System2 implements BatchComponent, ServerComponent {
   public Date newDate() {
     return new Date();
   }
+
+  /**
+   * Closes the object and throws an {@link java.lang.IllegalStateException} on error.
+   * @since 5.1
+   */
+  public void close(AutoCloseable closeable) {
+    try {
+      closeable.close();
+    } catch (Exception e) {
+      throw new IllegalStateException("Fail to close " + closeable, e);
+    }
+  }
 }
diff --git a/sonar-plugin-api/src/main/java/org/sonar/plugins/emailnotifications/api/EmailMessage.java b/sonar-plugin-api/src/main/java/org/sonar/plugins/emailnotifications/api/EmailMessage.java
new file mode 100644 (file)
index 0000000..af3b21f
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * 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.
+ */
+package org.sonar.plugins.emailnotifications.api;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+
+/**
+ * @since 2.10
+ */
+public class EmailMessage {
+
+  private String from;
+  private String to;
+  private String subject;
+  private String message;
+  private String messageId;
+
+  /**
+   * @param from full name of user, who initiated this message or null, if message was initiated by Sonar
+   */
+  public EmailMessage setFrom(String from) {
+    this.from = from;
+    return this;
+  }
+
+  /**
+   * @see #setFrom(String)
+   */
+  public String getFrom() {
+    return from;
+  }
+
+  /**
+   * @param to email address where to send this message
+   */
+  public EmailMessage setTo(String to) {
+    this.to = to;
+    return this;
+  }
+
+  /**
+   * @see #setTo(String)
+   */
+  public String getTo() {
+    return to;
+  }
+
+  /**
+   * @param subject message subject
+   */
+  public EmailMessage setSubject(String subject) {
+    this.subject = subject;
+    return this;
+  }
+
+  /**
+   * @see #setSubject(String)
+   */
+  public String getSubject() {
+    return subject;
+  }
+
+  /**
+   * @param message message body
+   */
+  public EmailMessage setMessage(String message) {
+    this.message = message;
+    return this;
+  }
+
+  /**
+   * @see #setMessage(String)
+   */
+  public String getMessage() {
+    return message;
+  }
+
+  /**
+   * @param messageId id of message for threading
+   */
+  public EmailMessage setMessageId(String messageId) {
+    this.messageId = messageId;
+    return this;
+  }
+
+  /**
+   * @see #setMessageId(String)
+   */
+  public String getMessageId() {
+    return messageId;
+  }
+
+  @Override
+  public String toString() {
+    return ToStringBuilder.reflectionToString(this);
+  }
+
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/plugins/emailnotifications/api/EmailTemplate.java b/sonar-plugin-api/src/main/java/org/sonar/plugins/emailnotifications/api/EmailTemplate.java
new file mode 100644 (file)
index 0000000..09a8a5c
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+package org.sonar.plugins.emailnotifications.api;
+
+import org.sonar.api.ServerExtension;
+import org.sonar.api.notifications.Notification;
+
+/**
+ * @since 2.10
+ */
+public abstract class EmailTemplate implements ServerExtension {
+
+  public abstract EmailMessage format(Notification notification);
+
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/plugins/emailnotifications/api/package-info.java b/sonar-plugin-api/src/main/java/org/sonar/plugins/emailnotifications/api/package-info.java
new file mode 100644 (file)
index 0000000..5a8bed7
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.plugins.emailnotifications.api;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
index 53f6544482a478a8c00febd4015c1494f0b75e69..06d47ce37f03a4656cf5ba9b3f8adb1859ba5704 100644 (file)
@@ -22,6 +22,8 @@ package org.sonar.api.utils;
 import org.apache.commons.lang.SystemUtils;
 import org.junit.Test;
 
+import java.io.Closeable;
+import java.io.IOException;
 import java.util.Map;
 import java.util.Properties;
 
@@ -86,4 +88,35 @@ public class System2Test {
     // well, how to assert that ? Adding a System3 dependency to System2 ? :-)
     System2.INSTANCE.println("foo");
   }
+
+  @Test
+  public void close() throws Exception {
+    class MyCloseable implements Closeable {
+      boolean isClosed = false;
+      @Override
+      public void close() throws IOException {
+        isClosed = true;
+      }
+    }
+
+    MyCloseable closeable = new MyCloseable();
+    System2.INSTANCE.close(closeable);
+    assertThat(closeable.isClosed).isTrue();
+  }
+
+  @Test
+  public void close_throws_exception_on_error() throws Exception {
+    Closeable closeable = new Closeable() {
+      @Override
+      public void close() throws IOException {
+        throw new IOException("expected");
+      }
+    };
+    try {
+      System2.INSTANCE.close(closeable);
+      fail();
+    } catch (IllegalStateException e) {
+      assertThat(e.getCause().getMessage()).isEqualTo("expected");
+    }
+  }
 }