Browse Source

Computation stack based on an isolated picocontainer

tags/latest-silver-master-#65
Simon Brandhof 9 years ago
parent
commit
72e7da3064
95 changed files with 2165 additions and 1400 deletions
  1. 0
    6
      plugins/sonar-core-plugin/pom.xml
  2. 0
    17
      plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
  3. 0
    100
      plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/SendIssueNotificationsPostJob.java
  4. 0
    174
      plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/SendIssueNotificationsPostJobTest.java
  5. 18
    20
      server/sonar-server/src/main/java/org/sonar/server/computation/AnalysisReportService.java
  6. 51
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/ComputationComponents.java
  7. 1
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/ComputationContext.java
  8. 35
    26
      server/sonar-server/src/main/java/org/sonar/server/computation/ComputationService.java
  9. 23
    6
      server/sonar-server/src/main/java/org/sonar/server/computation/ComputationThread.java
  10. 5
    8
      server/sonar-server/src/main/java/org/sonar/server/computation/ComputationThreadLauncher.java
  11. 0
    102
      server/sonar-server/src/main/java/org/sonar/server/computation/ComputeEngineIssueStorage.java
  12. 40
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/FinalIssues.java
  13. 66
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueComputation.java
  14. 15
    22
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCache.java
  15. 65
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCacheLoader.java
  16. 2
    1
      server/sonar-server/src/main/java/org/sonar/server/computation/issue/package-info.java
  17. 1
    2
      server/sonar-server/src/main/java/org/sonar/server/computation/step/ApplyPermissionsStep.java
  18. 5
    4
      server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationStep.java
  19. 29
    15
      server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java
  20. 1
    2
      server/sonar-server/src/main/java/org/sonar/server/computation/step/DigestReportStep.java
  21. 2
    3
      server/sonar-server/src/main/java/org/sonar/server/computation/step/IndexComponentsStep.java
  22. 1
    3
      server/sonar-server/src/main/java/org/sonar/server/computation/step/IndexIssuesStep.java
  23. 3
    5
      server/sonar-server/src/main/java/org/sonar/server/computation/step/IndexSourceLinesStep.java
  24. 5
    6
      server/sonar-server/src/main/java/org/sonar/server/computation/step/InvalidateBatchCacheStep.java
  25. 131
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistIssuesStep.java
  26. 14
    3
      server/sonar-server/src/main/java/org/sonar/server/computation/step/PurgeDatastoresStep.java
  27. 117
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/step/SendIssueNotificationsStep.java
  28. 18
    10
      server/sonar-server/src/main/java/org/sonar/server/computation/step/SwitchSnapshotStep.java
  29. 3
    3
      server/sonar-server/src/main/java/org/sonar/server/computation/ws/SubmitReportWsAction.java
  30. 5
    4
      server/sonar-server/src/main/java/org/sonar/server/issue/IssueBulkChangeService.java
  31. 5
    4
      server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java
  32. 8
    9
      server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueResultSetIterator.java
  33. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/issue/notification/ChangesOnMyIssueNotificationDispatcher.java
  34. 4
    4
      server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueChangesEmailTemplate.java
  35. 102
    0
      server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueNotifications.java
  36. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewFalsePositiveNotificationDispatcher.java
  37. 1
    3
      server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesEmailTemplate.java
  38. 2
    4
      server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotificationDispatcher.java
  39. 24
    0
      server/sonar-server/src/main/java/org/sonar/server/issue/notification/package-info.java
  40. 1
    2
      server/sonar-server/src/main/java/org/sonar/server/notifications/NotificationService.java
  41. 184
    39
      server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java
  42. 2
    2
      server/sonar-server/src/main/java/org/sonar/server/platform/SwitchLogbackAppender.java
  43. 98
    0
      server/sonar-server/src/main/java/org/sonar/server/util/CloseableIterator.java
  44. 26
    17
      server/sonar-server/src/main/java/org/sonar/server/util/ObjectInputStreamIterator.java
  45. 40
    0
      server/sonar-server/src/main/java/org/sonar/server/util/cache/CacheLoader.java
  46. 104
    0
      server/sonar-server/src/main/java/org/sonar/server/util/cache/DiskCache.java
  47. 84
    0
      server/sonar-server/src/main/java/org/sonar/server/util/cache/MemoryCache.java
  48. 23
    0
      server/sonar-server/src/main/java/org/sonar/server/util/cache/package-info.java
  49. 116
    97
      server/sonar-server/src/test/java/org/sonar/server/computation/AnalysisReportServiceTest.java
  50. 3
    16
      server/sonar-server/src/test/java/org/sonar/server/computation/ComputationComponentsTest.java
  51. 101
    0
      server/sonar-server/src/test/java/org/sonar/server/computation/ComputationThreadLauncherTest.java
  52. 4
    18
      server/sonar-server/src/test/java/org/sonar/server/computation/ComputationThreadTest.java
  53. 0
    82
      server/sonar-server/src/test/java/org/sonar/server/computation/ComputationWorkerLauncherTest.java
  54. 0
    259
      server/sonar-server/src/test/java/org/sonar/server/computation/ComputeEngineIssueStorageTest.java
  55. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/computation/step/ApplyPermissionsStepTest.java
  56. 10
    9
      server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputationStepsTest.java
  57. 21
    5
      server/sonar-server/src/test/java/org/sonar/server/computation/step/DigestReportStepTest.java
  58. 2
    4
      server/sonar-server/src/test/java/org/sonar/server/computation/step/IndexComponentsStepTest.java
  59. 1
    2
      server/sonar-server/src/test/java/org/sonar/server/computation/step/IndexIssuesStepTest.java
  60. 5
    8
      server/sonar-server/src/test/java/org/sonar/server/computation/step/InvalidateBatchCacheStepTest.java
  61. 3
    4
      server/sonar-server/src/test/java/org/sonar/server/computation/step/PurgeDatastoresStepMediumTest.java
  62. 4
    2
      server/sonar-server/src/test/java/org/sonar/server/computation/step/PurgeDatastoresStepTest.java
  63. 12
    35
      server/sonar-server/src/test/java/org/sonar/server/computation/step/SendIssueNotificationsStepTest.java
  64. 4
    15
      server/sonar-server/src/test/java/org/sonar/server/computation/step/SwitchSnapshotStepTest.java
  65. 2
    2
      server/sonar-server/src/test/java/org/sonar/server/computation/ws/SubmitReportWsActionTest.java
  66. 5
    5
      server/sonar-server/src/test/java/org/sonar/server/issue/IssueBulkChangeServiceTest.java
  67. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/issue/notification/ChangesOnMyIssueNotificationDispatcherTest.java
  68. 5
    5
      server/sonar-server/src/test/java/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest.java
  69. 25
    32
      server/sonar-server/src/test/java/org/sonar/server/issue/notification/IssueNotificationsTest.java
  70. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewFalsePositiveNotificationDispatcherTest.java
  71. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesEmailTemplateTest.java
  72. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationDispatcherTest.java
  73. 2
    2
      server/sonar-server/src/test/java/org/sonar/server/platform/SwitchLogbackAppenderTest.java
  74. 150
    0
      server/sonar-server/src/test/java/org/sonar/server/util/CloseableIteratorTest.java
  75. 87
    0
      server/sonar-server/src/test/java/org/sonar/server/util/ObjectInputStreamIteratorTest.java
  76. 81
    0
      server/sonar-server/src/test/java/org/sonar/server/util/cache/DiskCacheTest.java
  77. 92
    0
      server/sonar-server/src/test/java/org/sonar/server/util/cache/MemoryCacheTest.java
  78. 0
    0
      server/sonar-server/src/test/resources/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/display_component_key_if_no_component_name.txt
  79. 0
    0
      server/sonar-server/src/test/resources/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/email_with_action_plan_change.txt
  80. 0
    0
      server/sonar-server/src/test/resources/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/email_with_assignee_change.txt
  81. 0
    0
      server/sonar-server/src/test/resources/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/email_with_multiple_changes.txt
  82. 0
    2
      sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java
  83. 0
    149
      sonar-core/src/main/java/org/sonar/core/issue/IssueNotifications.java
  84. 2
    2
      sonar-core/src/main/java/org/sonar/core/issue/db/IssueDto.java
  85. 1
    2
      sonar-core/src/main/java/org/sonar/core/issue/db/UpdateConflictResolver.java
  86. 3
    3
      sonar-core/src/main/java/org/sonar/core/resource/ResourceIndexerDao.java
  87. 3
    3
      sonar-core/src/main/java/org/sonar/core/resource/ResourceIndexerQuery.java
  88. 1
    1
      sonar-core/src/main/resources/org/sonar/core/issue/db/IssueMapper.xml
  89. 2
    2
      sonar-core/src/test/java/org/sonar/core/persistence/DbTester.java
  90. 1
    1
      sonar-plugin-api/src/main/java/org/sonar/api/issue/internal/DefaultIssue.java
  91. 12
    0
      sonar-plugin-api/src/main/java/org/sonar/api/utils/System2.java
  92. 0
    0
      sonar-plugin-api/src/main/java/org/sonar/plugins/emailnotifications/api/EmailMessage.java
  93. 0
    0
      sonar-plugin-api/src/main/java/org/sonar/plugins/emailnotifications/api/EmailTemplate.java
  94. 1
    0
      sonar-plugin-api/src/main/java/org/sonar/plugins/emailnotifications/api/package-info.java
  95. 33
    0
      sonar-plugin-api/src/test/java/org/sonar/api/utils/System2Test.java

+ 0
- 6
plugins/sonar-core-plugin/pom.xml View File

@@ -23,12 +23,6 @@
<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>

+ 0
- 17
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java View 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,

+ 0
- 100
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/SendIssueNotificationsPostJob.java View File

@@ -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);
}
}
}

+ 0
- 174
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/SendIssueNotificationsPostJobTest.java View File

@@ -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);
}
}
}

+ 18
- 20
server/sonar-server/src/main/java/org/sonar/server/computation/AnalysisReportService.java View 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());

+ 51
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/ComputationComponents.java View File

@@ -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);
}
}

+ 1
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/ComputationContext.java View File

@@ -85,4 +85,5 @@ public class ComputationContext {
public Date getAnalysisDate() {
return analysisDate;
}

}

+ 35
- 26
server/sonar-server/src/main/java/org/sonar/server/computation/ComputationService.java View 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);
}
}
}

server/sonar-server/src/main/java/org/sonar/server/computation/ComputationWorker.java → server/sonar-server/src/main/java/org/sonar/server/computation/ComputationThread.java View File

@@ -22,20 +22,21 @@ 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 ComputationWorker implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(ComputationWorker.class);
public class ComputationThread implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(ComputationThread.class);

private final AnalysisReportQueue queue;
private final ComputationService service;

public ComputationWorker(AnalysisReportQueue queue, ComputationService service) {
public ComputationThread(AnalysisReportQueue queue) {
this.queue = queue;
this.service = service;
}

@Override
@@ -48,7 +49,7 @@ public class ComputationWorker implements Runnable {
}
if (report != null) {
try {
service.process(report);
process(report);
} catch (Exception e) {
LOG.error(String.format(
"Failed to process analysis report %d of project %s", report.getId(), report.getProjectKey()), e);
@@ -65,4 +66,20 @@ public class ComputationWorker implements Runnable {
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();
}
}
}

server/sonar-server/src/main/java/org/sonar/server/computation/ComputationWorkerLauncher.java → server/sonar-server/src/main/java/org/sonar/server/computation/ComputationThreadLauncher.java View File

@@ -32,11 +32,10 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

public class ComputationWorkerLauncher implements Startable, ServerComponent, ServerStartHandler {
public class ComputationThreadLauncher implements Startable, ServerComponent, ServerStartHandler {

public static final String THREAD_NAME_PREFIX = "computation-";

private final ComputationService service;
private final AnalysisReportQueue queue;
private final ScheduledExecutorService executorService;

@@ -44,8 +43,7 @@ public class ComputationWorkerLauncher implements Startable, ServerComponent, Se
private final long delayForFirstStart;
private final TimeUnit timeUnit;

public ComputationWorkerLauncher(ComputationService service, AnalysisReportQueue queue) {
this.service = service;
public ComputationThreadLauncher(AnalysisReportQueue queue) {
this.queue = queue;
this.executorService = Executors.newSingleThreadScheduledExecutor(threadFactoryWithSpecificNameForLogging());

@@ -55,14 +53,13 @@ public class ComputationWorkerLauncher implements Startable, ServerComponent, Se
}

@VisibleForTesting
ComputationWorkerLauncher(ComputationService service, AnalysisReportQueue queue, long delayForFirstStart, long delayBetweenTasks, TimeUnit timeUnit) {
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;
this.service = service;
}

@Override
@@ -76,12 +73,12 @@ public class ComputationWorkerLauncher implements Startable, ServerComponent, Se
}

public void startAnalysisTaskNow() {
executorService.execute(new ComputationWorker(queue, service));
executorService.execute(new ComputationThread(queue));
}

@Override
public void onServerStart(Server server) {
executorService.scheduleAtFixedRate(new ComputationWorker(queue, service), delayForFirstStart, delayBetweenTasks, timeUnit);
executorService.scheduleAtFixedRate(new ComputationThread(queue), delayForFirstStart, delayBetweenTasks, timeUnit);
}

/**

+ 0
- 102
server/sonar-server/src/main/java/org/sonar/server/computation/ComputeEngineIssueStorage.java View File

@@ -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();
}
}

+ 40
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/issue/FinalIssues.java View File

@@ -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);
}

}

+ 66
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/issue/IssueComputation.java View File

@@ -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()));
}

}

sonar-core/src/main/java/org/sonar/core/issue/IssuesBySeverity.java → server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCache.java View File

@@ -17,33 +17,26 @@
* 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;
package org.sonar.server.computation.issue;

import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;
import org.sonar.api.issue.Issue;
import org.sonar.api.rule.RuleKey;
import org.sonar.core.rule.RuleDto;
import org.sonar.server.util.cache.MemoryCache;

public class IssuesBySeverity {
import javax.annotation.CheckForNull;

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);
}
/**
* Cache of the rules involved during the current analysis
*/
public class RuleCache extends MemoryCache<RuleKey, RuleDto> {

public int size() {
return issuesBySeverity.size();
public RuleCache(RuleCacheLoader loader) {
super(loader);
}

public boolean isEmpty() {
return issuesBySeverity.isEmpty();
@CheckForNull
public String ruleName(RuleKey key) {
RuleDto rule = get(key);
return rule != null ? rule.getName() : null;
}
}

+ 65
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/issue/RuleCacheLoader.java View File

@@ -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();
}
}
}

plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/package-info.java → server/sonar-server/src/main/java/org/sonar/server/computation/issue/package-info.java View File

@@ -17,7 +17,8 @@
* 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;
package org.sonar.server.computation.issue;

import javax.annotation.ParametersAreNonnullByDefault;

+ 1
- 2
server/sonar-server/src/main/java/org/sonar/server/computation/step/ApplyPermissionsStep.java View 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();
}


+ 5
- 4
server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationStep.java View File

@@ -20,13 +20,14 @@

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();
}

server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationStepRegistry.java → server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java View File

@@ -21,34 +21,48 @@
package org.sonar.server.computation.step;

import com.google.common.collect.Lists;
import org.sonar.api.ServerComponent;
import org.sonar.server.source.IndexSourceLinesStep;
import org.sonar.server.computation.ComputationComponents;

import java.util.Arrays;
import java.util.List;

public class ComputationStepRegistry implements ServerComponent {
public class ComputationSteps {

private final List<ComputationStep> steps;

public ComputationStepRegistry(ComputationStep... s) {
this.steps = order(s,
/**
* 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,
ApplyPermissionsStep.class,
PersistIssuesStep.class,
SwitchSnapshotStep.class,
InvalidatePreviewCacheStep.class,
IndexComponentsStep.class,
PurgeDatastoresStep.class,
InvalidateBatchCacheStep.class,

// ES indexing is done after all db changes
ApplyPermissionsStep.class,
IndexIssuesStep.class,
IndexSourceLinesStep.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> steps() {
return steps;
public List<ComputationStep> orderedSteps() {
return orderedSteps;
}

private List<ComputationStep> order(ComputationStep[] steps, Class<? extends ComputationStep>... classes) {
private static List<ComputationStep> order(ComputationStep[] steps) {
List<ComputationStep> result = Lists.newArrayList();
for (Class<? extends ComputationStep> clazz : classes) {
for (Class<? extends ComputationStep> clazz : orderedStepClasses()) {
result.add(find(steps, clazz));
}
return result;
@@ -60,6 +74,6 @@ public class ComputationStepRegistry implements ServerComponent {
return step;
}
}
throw new IllegalStateException("Component not found in picocontainer: " + clazz);
throw new IllegalStateException("Component not found: " + clazz + ". Check " + ComputationComponents.class);
}
}

+ 1
- 2
server/sonar-server/src/main/java/org/sonar/server/computation/step/DigestReportStep.java View 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);
}


+ 2
- 3
server/sonar-server/src/main/java/org/sonar/server/computation/step/IndexComponentsStep.java View 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

+ 1
- 3
server/sonar-server/src/main/java/org/sonar/server/computation/step/IndexIssuesStep.java View File

@@ -20,14 +20,12 @@

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();
}

server/sonar-server/src/main/java/org/sonar/server/source/IndexSourceLinesStep.java → server/sonar-server/src/main/java/org/sonar/server/computation/step/IndexSourceLinesStep.java View File

@@ -17,11 +17,9 @@
* 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;
package org.sonar.server.computation.step;

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 {
@@ -33,13 +31,13 @@ public class IndexSourceLinesStep implements ComputationStep {
}

@Override
public void execute(DbSession session, ComputationContext context) {
public void execute(ComputationContext context) {
indexer.index();
}

@Override
public String getDescription() {
return "Put source code into search index";
return "Index source lines";
}

}

server/sonar-server/src/main/java/org/sonar/server/computation/step/InvalidatePreviewCacheStep.java → server/sonar-server/src/main/java/org/sonar/server/computation/step/InvalidateBatchCacheStep.java View File

@@ -20,30 +20,29 @@

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 {
public class InvalidateBatchCacheStep implements ComputationStep {
private final PropertiesDao propertiesDao;

public InvalidatePreviewCacheStep(PropertiesDao propertiesDao) {
public InvalidateBatchCacheStep(PropertiesDao propertiesDao) {
this.propertiesDao = propertiesDao;
}

@Override
public void execute(DbSession session, ComputationContext context) {
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, session);
propertiesDao.setProperty(property);
}

@Override
public String getDescription() {
return "Invalidate preview cache";
return "Invalidate batch cache";
}
}

+ 131
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/step/PersistIssuesStep.java View File

@@ -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";
}
}

+ 14
- 3
server/sonar-server/src/main/java/org/sonar/server/computation/step/PurgeDatastoresStep.java View 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

+ 117
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/step/SendIssueNotificationsStep.java View File

@@ -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();
}
}

}

+ 18
- 10
server/sonar-server/src/main/java/org/sonar/server/computation/step/SwitchSnapshotStep.java View 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);


+ 3
- 3
server/sonar-server/src/main/java/org/sonar/server/computation/ws/SubmitReportWsAction.java View 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;
}

+ 5
- 4
server/sonar-server/src/main/java/org/sonar/server/issue/IssueBulkChangeService.java View 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());

+ 5
- 4
server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java View 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());
}


+ 8
- 9
server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueResultSetIterator.java View File

@@ -19,21 +19,20 @@
*/
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

plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/ChangesOnMyIssueNotificationDispatcher.java → server/sonar-server/src/main/java/org/sonar/server/issue/notification/ChangesOnMyIssueNotificationDispatcher.java View File

@@ -17,7 +17,7 @@
* 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;
package org.sonar.server.issue.notification;

import com.google.common.base.Objects;
import com.google.common.collect.Multimap;

plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplate.java → server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueChangesEmailTemplate.java View File

@@ -17,7 +17,7 @@
* 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;
package org.sonar.server.issue.notification;

import com.google.common.base.Strings;
import org.apache.commons.lang.StringUtils;
@@ -28,12 +28,11 @@ 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".
*
* @since 3.6
*/
public class IssueChangesEmailTemplate extends EmailTemplate {

@@ -85,7 +84,8 @@ public class IssueChangesEmailTemplate extends EmailTemplate {
appendField(sb, "Tags", formatTagChange(notif.getFieldValue("old.tags")), formatTagChange(notif.getFieldValue("new.tags")));
}

private static String formatTagChange(String tags) {
@CheckForNull
private static String formatTagChange(@Nullable String tags) {
if (tags == null) {
return null;
} else {

+ 102
- 0
server/sonar-server/src/main/java/org/sonar/server/issue/notification/IssueNotifications.java View File

@@ -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;
}

}

plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/NewFalsePositiveNotificationDispatcher.java → server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewFalsePositiveNotificationDispatcher.java View File

@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonar.plugins.core.issue.notification;
package org.sonar.server.issue.notification;

import com.google.common.base.Objects;
import com.google.common.collect.Multimap;

plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/NewIssuesEmailTemplate.java → server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesEmailTemplate.java View File

@@ -17,7 +17,7 @@
* 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;
package org.sonar.server.issue.notification;

import com.google.common.collect.Lists;
import org.sonar.api.config.EmailSettings;
@@ -36,8 +36,6 @@ import java.util.Locale;

/**
* Creates email message for notification "new-issues".
*
* @since 2.10
*/
public class NewIssuesEmailTemplate extends EmailTemplate {


plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/NewIssuesNotificationDispatcher.java → server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotificationDispatcher.java View File

@@ -17,7 +17,7 @@
* 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;
package org.sonar.server.issue.notification;

import com.google.common.collect.Multimap;
import org.sonar.api.notifications.*;
@@ -26,9 +26,7 @@ import java.util.Collection;
import java.util.Map;

/**
* This dispatcher means: "notify me when new issues are introduced during project scan".
*
* @since 2.14
* This dispatcher means: "notify me when new issues are introduced during project analysis"
*/
public class NewIssuesNotificationDispatcher extends NotificationDispatcher {


+ 24
- 0
server/sonar-server/src/main/java/org/sonar/server/issue/notification/package-info.java View File

@@ -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;

+ 1
- 2
server/sonar-server/src/main/java/org/sonar/server/notifications/NotificationService.java View 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() {

+ 184
- 39
server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java View 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();
}
}

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/platform/SwitchLogbackAppender.java View 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);

+ 98
- 0
server/sonar-server/src/main/java/org/sonar/server/util/CloseableIterator.java View File

@@ -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;

}

server/sonar-server/src/main/java/org/sonar/server/computation/ComputeEngineIssueStorageFactory.java → server/sonar-server/src/main/java/org/sonar/server/util/ObjectInputStreamIterator.java View File

@@ -17,28 +17,37 @@
* 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;

package org.sonar.server.computation;
import com.google.common.base.Throwables;
import org.apache.commons.io.IOUtils;

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;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;

public class ComputeEngineIssueStorageFactory implements ServerComponent {
private final MyBatis myBatis;
private final DbClient dbClient;
private final RuleFinder ruleFinder;
public class ObjectInputStreamIterator<E> extends CloseableIterator<E> {

public ComputeEngineIssueStorageFactory(MyBatis myBatis, DbClient dbClient, RuleFinder ruleFinder) {
this.myBatis = myBatis;
this.dbClient = dbClient;
this.ruleFinder = ruleFinder;
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);
}
}

public IssueStorage newComputeEngineIssueStorage(ComponentDto project) {
return new ComputeEngineIssueStorage(myBatis, dbClient, ruleFinder, project);
@Override
protected void doClose() {
IOUtils.closeQuietly(stream);
}
}

+ 40
- 0
server/sonar-server/src/main/java/org/sonar/server/util/cache/CacheLoader.java View File

@@ -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);
}

+ 104
- 0
server/sonar-server/src/main/java/org/sonar/server/util/cache/DiskCache.java View File

@@ -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);
}
}
}

+ 84
- 0
server/sonar-server/src/main/java/org/sonar/server/util/cache/MemoryCache.java View File

@@ -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();
}
}

+ 23
- 0
server/sonar-server/src/main/java/org/sonar/server/util/cache/package-info.java View File

@@ -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;

+ 116
- 97
server/sonar-server/src/test/java/org/sonar/server/computation/AnalysisReportServiceTest.java View File

@@ -17,100 +17,119 @@
* 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);
// }
// }
//
//}

server/sonar-server/src/test/java/org/sonar/server/computation/ComputeEngineIssueStorageFactoryTest.java → server/sonar-server/src/test/java/org/sonar/server/computation/ComputationComponentsTest.java View File

@@ -17,29 +17,16 @@
* 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;
public class ComputationComponentsTest {

@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);
public void nonStepComponents() throws Exception {
assertThat(ComputationComponents.nonStepComponents()).isNotEmpty();
}
}

+ 101
- 0
server/sonar-server/src/test/java/org/sonar/server/computation/ComputationThreadLauncherTest.java View File

@@ -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);
// }
//}

server/sonar-server/src/test/java/org/sonar/server/computation/ComputationWorkerTest.java → server/sonar-server/src/test/java/org/sonar/server/computation/ComputationThreadTest.java View File

@@ -24,20 +24,17 @@ 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 {
public class ComputationThreadTest {

private ComputationWorker sut;
private ComputationService service;
private ComputationThread sut;
private AnalysisReportQueue queue;

@Before
public void before() {
this.service = mock(ComputationService.class);
this.queue = mock(AnalysisReportQueue.class);
this.sut = new ComputationWorker(queue, service);
this.sut = new ComputationThread(queue);
}

@Test
@@ -45,7 +42,6 @@ public class ComputationWorkerTest {
sut.run();

verify(queue).pop();
verify(service, never()).process(any(AnalysisReportDto.class));
}

@Test
@@ -56,21 +52,11 @@ public class ComputationWorkerTest {
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);
when(queue.pop()).thenThrow(new IllegalStateException());

sut.run();
}

+ 0
- 82
server/sonar-server/src/test/java/org/sonar/server/computation/ComputationWorkerLauncherTest.java View File

@@ -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);
}
}

+ 0
- 259
server/sonar-server/src/test/java/org/sonar/server/computation/ComputeEngineIssueStorageTest.java View File

@@ -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;
}
}
}

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/computation/step/ApplyPermissionsStepTest.java View 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();
}
}

server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputationStepRegistryTest.java → server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputationStepsTest.java View File

@@ -21,39 +21,40 @@
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 {
public class ComputationStepsTest {

@Test
public void ordered_steps() throws Exception {
ComputationStepRegistry registry = new ComputationStepRegistry(
ComputationSteps registry = new ComputationSteps(
// unordered
mock(ApplyPermissionsStep.class),
mock(DigestReportStep.class),
mock(IndexSourceLinesStep.class),
mock(InvalidatePreviewCacheStep.class),
mock(InvalidateBatchCacheStep.class),
mock(PersistIssuesStep.class),
mock(IndexIssuesStep.class),
mock(SwitchSnapshotStep.class),
mock(PurgeDatastoresStep.class),
mock(SendIssueNotificationsStep.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);
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 ComputationStepRegistry(mock(DigestReportStep.class));
new ComputationSteps(mock(DigestReportStep.class));
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessage("Component not found in picocontainer: " + ApplyPermissionsStep.class.toString());
assertThat(e).hasMessageContaining("Component not found");
}
}
}

+ 21
- 5
server/sonar-server/src/test/java/org/sonar/server/computation/step/DigestReportStepTest.java View File

@@ -17,13 +17,31 @@
* 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));
}

+ 2
- 4
server/sonar-server/src/test/java/org/sonar/server/computation/step/IndexComponentsStepTest.java View 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);
}

}

+ 1
- 2
server/sonar-server/src/test/java/org/sonar/server/computation/step/IndexIssuesStepTest.java View 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();

server/sonar-server/src/test/java/org/sonar/server/computation/step/InvalidatePreviewCacheStepTest.java → server/sonar-server/src/test/java/org/sonar/server/computation/step/InvalidateBatchCacheStepTest.java View File

@@ -24,33 +24,30 @@ 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;
public class InvalidateBatchCacheStepTest {
private InvalidateBatchCacheStep sut;
private PropertiesDao propertiesDao;

@Before
public void before() {
this.propertiesDao = mock(PropertiesDao.class);
this.sut = new InvalidatePreviewCacheStep(propertiesDao);
this.sut = new InvalidateBatchCacheStep(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);
sut.execute(context);

verify(propertiesDao).setProperty(any(PropertyDto.class), eq(session));
verify(propertiesDao).setProperty(any(PropertyDto.class));
}
}

+ 3
- 4
server/sonar-server/src/test/java/org/sonar/server/computation/step/PurgeDatastoresStepMediumTest.java View 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

+ 4
- 2
server/sonar-server/src/test/java/org/sonar/server/computation/step/PurgeDatastoresStepTest.java View 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));
}

sonar-core/src/test/java/org/sonar/core/issue/IssuesBySeverityTest.java → server/sonar-server/src/test/java/org/sonar/server/computation/step/SendIssueNotificationsStepTest.java View File

@@ -17,52 +17,29 @@
* 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;
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 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);
}
public class SendIssueNotificationsStepTest {

@Test
public void get_issues_by_severity() {
sut = new IssuesBySeverity();
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"));
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();
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);
}
}

+ 4
- 15
server/sonar-server/src/test/java/org/sonar/server/computation/step/SwitchSnapshotStepTest.java View 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);
}
}

+ 2
- 2
server/sonar-server/src/test/java/org/sonar/server/computation/ws/SubmitReportWsActionTest.java View 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

+ 5
- 5
server/sonar-server/src/test/java/org/sonar/server/issue/IssueBulkChangeServiceTest.java View 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);
}


plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/ChangesOnMyIssueNotificationDispatcherTest.java → server/sonar-server/src/test/java/org/sonar/server/issue/notification/ChangesOnMyIssueNotificationDispatcherTest.java View File

@@ -17,7 +17,7 @@
* 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;
package org.sonar.server.issue.notification;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;

plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplateTest.java → server/sonar-server/src/test/java/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest.java View File

@@ -17,7 +17,7 @@
* 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;
package org.sonar.server.issue.notification;

import com.google.common.io.Resources;
import org.apache.commons.codec.Charsets;
@@ -71,7 +71,7 @@ public class IssueChangesEmailTemplateTest {

String message = email.getMessage();
String expected = Resources.toString(Resources.getResource(
"org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplateTest/email_with_assignee_change.txt"),
"org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/email_with_assignee_change.txt"),
Charsets.UTF_8
);
expected = StringUtils.remove(expected, '\r');
@@ -91,7 +91,7 @@ public class IssueChangesEmailTemplateTest {

String message = email.getMessage();
String expected = Resources.toString(Resources.getResource(
"org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplateTest/email_with_action_plan_change.txt"),
"org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/email_with_action_plan_change.txt"),
Charsets.UTF_8
);
expected = StringUtils.remove(expected, '\r');
@@ -110,7 +110,7 @@ public class IssueChangesEmailTemplateTest {

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"),
"org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/display_component_key_if_no_component_name.txt"),
Charsets.UTF_8
);
expected = StringUtils.remove(expected, '\r');
@@ -133,7 +133,7 @@ public class IssueChangesEmailTemplateTest {

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);
"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();

sonar-core/src/test/java/org/sonar/core/issue/IssueNotificationsTest.java → server/sonar-server/src/test/java/org/sonar/server/issue/notification/IssueNotificationsTest.java View File

@@ -17,7 +17,7 @@
* 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;
package org.sonar.server.issue.notification;

import org.junit.Before;
import org.junit.Test;
@@ -31,17 +31,13 @@ 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 org.sonar.server.notifications.NotificationService;

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 {
@@ -53,23 +49,23 @@ public class IssueNotificationsTest {

@Before
public void setUp() throws Exception {
issueNotifications = new IssueNotifications(manager);
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_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 {
@@ -85,7 +81,7 @@ public class IssueNotificationsTest {
.setComponentKey("struts:Action")
.setProjectKey("struts");

Notification notification = issueNotifications.sendChanges(issue, context, null, new Project("struts"), null).get(0);
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");
@@ -97,24 +93,23 @@ public class IssueNotificationsTest {
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)));
Mockito.verify(manager).scheduleForSending(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);
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(eq(Arrays.asList(notification)));
Mockito.verify(manager).scheduleForSending(notification);
}

@Test
@@ -128,8 +123,8 @@ public class IssueNotificationsTest {
.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);
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");
@@ -137,20 +132,18 @@ public class IssueNotificationsTest {
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)));
Mockito.verify(manager).scheduleForSending(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);
issueNotifications.sendChanges(issue, "charlie", null, new Project("struts"), null, null, false);

assertThat(notifications).isEmpty();
Mockito.verifyZeroInteractions(manager);
}
}

plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/NewFalsePositiveNotificationDispatcherTest.java → server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewFalsePositiveNotificationDispatcherTest.java View File

@@ -17,7 +17,7 @@
* 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;
package org.sonar.server.issue.notification;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;

plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/NewIssuesEmailTemplateTest.java → server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesEmailTemplateTest.java View File

@@ -17,7 +17,7 @@
* 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;
package org.sonar.server.issue.notification;

import org.junit.Before;
import org.junit.Test;

plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/NewIssuesNotificationDispatcherTest.java → server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationDispatcherTest.java View File

@@ -17,7 +17,7 @@
* 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;
package org.sonar.server.issue.notification;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;

+ 2
- 2
server/sonar-server/src/test/java/org/sonar/server/platform/SwitchLogbackAppenderTest.java View 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);

+ 150
- 0
server/sonar-server/src/test/java/org/sonar/server/util/CloseableIteratorTest.java View File

@@ -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;
}
}
}

+ 87
- 0
server/sonar-server/src/test/java/org/sonar/server/util/ObjectInputStreamIteratorTest.java View File

@@ -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;
}
}
}

+ 81
- 0
server/sonar-server/src/test/java/org/sonar/server/util/cache/DiskCacheTest.java View File

@@ -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");
}
}
}

+ 92
- 0
server/sonar-server/src/test/java/org/sonar/server/util/cache/MemoryCacheTest.java View File

@@ -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());
}
}

plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplateTest/display_component_key_if_no_component_name.txt → server/sonar-server/src/test/resources/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/display_component_key_if_no_component_name.txt View File


plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplateTest/email_with_action_plan_change.txt → server/sonar-server/src/test/resources/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/email_with_action_plan_change.txt View File


plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplateTest/email_with_assignee_change.txt → server/sonar-server/src/test/resources/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/email_with_assignee_change.txt View File


plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/issue/notification/IssueChangesEmailTemplateTest/email_with_multiple_changes.txt → server/sonar-server/src/test/resources/org/sonar/server/issue/notification/IssueChangesEmailTemplateTest/email_with_multiple_changes.txt View File


+ 0
- 2
sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java View 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,


+ 0
- 149
sonar-core/src/main/java/org/sonar/core/issue/IssueNotifications.java View File

@@ -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());
}

}

+ 2
- 2
sonar-core/src/main/java/org/sonar/core/issue/db/IssueDto.java View 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()))

+ 1
- 2
sonar-core/src/main/java/org/sonar/core/issue/db/UpdateConflictResolver.java View 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 {


+ 3
- 3
sonar-core/src/main/java/org/sonar/core/resource/ResourceIndexerDao.java View 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)

+ 3
- 3
sonar-core/src/main/java/org/sonar/core/resource/ResourceIndexerQuery.java View 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;
}

+ 1
- 1
sonar-core/src/main/resources/org/sonar/core/issue/db/IssueMapper.xml View 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})

+ 2
- 2
sonar-core/src/test/java/org/sonar/core/persistence/DbTester.java View 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);

+ 1
- 1
sonar-plugin-api/src/main/java/org/sonar/api/issue/internal/DefaultIssue.java View 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;
}
}

+ 12
- 0
sonar-plugin-api/src/main/java/org/sonar/api/utils/System2.java View 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);
}
}
}

plugins/sonar-email-notifications-plugin/src/main/java/org/sonar/plugins/emailnotifications/api/EmailMessage.java → sonar-plugin-api/src/main/java/org/sonar/plugins/emailnotifications/api/EmailMessage.java View File


plugins/sonar-email-notifications-plugin/src/main/java/org/sonar/plugins/emailnotifications/api/EmailTemplate.java → sonar-plugin-api/src/main/java/org/sonar/plugins/emailnotifications/api/EmailTemplate.java View File


plugins/sonar-email-notifications-plugin/src/main/java/org/sonar/plugins/emailnotifications/api/package-info.java → sonar-plugin-api/src/main/java/org/sonar/plugins/emailnotifications/api/package-info.java View File

@@ -21,3 +21,4 @@
package org.sonar.plugins.emailnotifications.api;

import javax.annotation.ParametersAreNonnullByDefault;


+ 33
- 0
sonar-plugin-api/src/test/java/org/sonar/api/utils/System2Test.java View 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");
}
}
}

Loading…
Cancel
Save