Skip to content

Commit

Permalink
SONAR-6113 Short-circuit sending of notifications when there are no s…
Browse files Browse the repository at this point in the history
…ubscribers
  • Loading branch information
Simon Brandhof committed Feb 2, 2015
1 parent 8430a7f commit 7bcccf0
Show file tree
Hide file tree
Showing 33 changed files with 908 additions and 431 deletions.
Expand Up @@ -34,11 +34,12 @@
*/
public class IssueCache extends DiskCache<DefaultIssue> {

// this constructor is used by picocontainer
public IssueCache(TempFolder tempFolder, System2 system2) throws IOException {
super(tempFolder.newFile("issues", ".dat"), system2);
}

IssueCache(File file, System2 system2) {
public IssueCache(File file, System2 system2) {
super(file, system2);
}
}
Expand Up @@ -19,73 +19,78 @@
*/
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 com.google.common.collect.ImmutableSet;
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.IssueCache;
import org.sonar.server.computation.issue.RuleCache;
import org.sonar.server.issue.notification.IssueNotifications;
import org.sonar.server.issue.notification.IssueChangeNotification;
import org.sonar.server.issue.notification.NewIssuesNotification;
import org.sonar.server.notifications.NotificationService;
import org.sonar.server.util.CloseableIterator;

import java.util.Set;

/**
* 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 {
/**
* Types of the notifications sent by this step
*/
static final Set<String> NOTIF_TYPES = ImmutableSet.of(IssueChangeNotification.TYPE, NewIssuesNotification.TYPE);

private final IssueCache issueCache;
private final RuleCache rules;
private final IssueNotifications service;
private final NotificationService service;

public SendIssueNotificationsStep(IssueCache issueCache, RuleCache rules,
IssueNotifications service) {
public SendIssueNotificationsStep(IssueCache issueCache, RuleCache rules, NotificationService service) {
this.issueCache = issueCache;
this.rules = rules;
this.service = service;
}

@Override
public void execute(ComputationContext context) {
NewIssuesStatistics newIssuesStatistics = new NewIssuesStatistics();
if (service.hasProjectSubscribersForTypes(context.getProject().uuid(), NOTIF_TYPES)) {
doExecute(context);
}
}

private void doExecute(ComputationContext context) {
NewIssuesNotification.Stats newIssueStats = new NewIssuesNotification.Stats();
CloseableIterator<DefaultIssue> issues = issueCache.traverse();
try {
while (issues.hasNext()) {
DefaultIssue issue = issues.next();
if (issue.isNew() && issue.resolution() == null) {
newIssuesStatistics.add(issue);
newIssueStats.add(issue);
} else if (issue.isChanged() && issue.mustSendNotifications()) {
service.sendChanges(issue, null, rules.ruleName(issue.ruleKey()),
context.getProject(), /* TODO */null, null, true);
IssueChangeNotification changeNotification = new IssueChangeNotification();
changeNotification.setRuleName(rules.ruleName(issue.ruleKey()));
changeNotification.setIssue(issue);
changeNotification.setProject(context.getProject());
service.deliver(changeNotification);
}
}

} finally {
issues.close();
}
sendNewIssuesStatistics(context, newIssuesStatistics);
sendNewIssuesStatistics(context, newIssueStats);
}

private void sendNewIssuesStatistics(ComputationContext context, NewIssuesStatistics newIssuesStatistics) {
if (!newIssuesStatistics.isEmpty()) {
private void sendNewIssuesStatistics(ComputationContext context, NewIssuesNotification.Stats stats) {
if (stats.size() > 0) {
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);
NewIssuesNotification notification = new NewIssuesNotification();
notification.setProject(project);
notification.setAnalysisDate(context.getAnalysisDate());
notification.setStatistics(project, stats);
service.deliver(notification);
}
}

Expand All @@ -94,24 +99,4 @@ 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();
}
}

}
Expand Up @@ -28,6 +28,7 @@
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.IssueChangeContext;
import org.sonar.api.notifications.NotificationManager;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.Rule;
import org.sonar.core.component.ComponentDto;
Expand All @@ -36,7 +37,7 @@
import org.sonar.core.persistence.DbSession;
import org.sonar.server.db.DbClient;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.issue.notification.IssueNotifications;
import org.sonar.server.issue.notification.IssueChangeNotification;
import org.sonar.server.rule.DefaultRuleFinder;
import org.sonar.server.search.QueryContext;
import org.sonar.server.user.UserSession;
Expand All @@ -62,16 +63,16 @@ public class IssueBulkChangeService {
private final IssueService issueService;
private final IssueStorage issueStorage;
private final DefaultRuleFinder ruleFinder;
private final IssueNotifications issueNotifications;
private final NotificationManager notificationService;
private final List<Action> actions;

public IssueBulkChangeService(DbClient dbClient, IssueService issueService, IssueStorage issueStorage, DefaultRuleFinder ruleFinder,
IssueNotifications issueNotifications, List<Action> actions) {
NotificationManager notificationService, List<Action> actions) {
this.dbClient = dbClient;
this.issueService = issueService;
this.issueStorage = issueStorage;
this.ruleFinder = ruleFinder;
this.issueNotifications = issueNotifications;
this.notificationService = notificationService;
this.actions = actions;
}

Expand Down Expand Up @@ -103,10 +104,12 @@ public IssueBulkChangeResult execute(IssueBulkChangeQuery issueBulkChangeQuery,
String projectKey = issue.projectKey();
if (projectKey != null) {
Rule rule = repository.rule(issue.ruleKey());
issueNotifications.sendChanges((DefaultIssue) issue, issueChangeContext.login(),
rule != null ? rule.getName() : null,
repository.project(projectKey),
repository.component(issue.componentKey()), null, false);
notificationService.scheduleForSending(new IssueChangeNotification()
.setIssue((DefaultIssue) issue)
.setChangeAuthorLogin(issueChangeContext.login())
.setRuleName(rule != null ? rule.getName() : null)
.setProject(repository.project(projectKey))
.setComponent(repository.component(issue.componentKey())));
}
}
concernedProjects.add(issue.projectKey());
Expand Down
Expand Up @@ -29,6 +29,7 @@
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.issue.internal.IssueChangeContext;
import org.sonar.api.notifications.NotificationManager;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.Severity;
import org.sonar.api.rules.Rule;
Expand All @@ -50,7 +51,7 @@
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.issue.actionplan.ActionPlanService;
import org.sonar.server.issue.index.IssueIndex;
import org.sonar.server.issue.notification.IssueNotifications;
import org.sonar.server.issue.notification.IssueChangeNotification;
import org.sonar.server.search.FacetValue;
import org.sonar.server.search.IndexClient;
import org.sonar.server.search.QueryContext;
Expand Down Expand Up @@ -80,7 +81,7 @@ public class IssueService implements ServerComponent {
private final IssueWorkflow workflow;
private final IssueUpdater issueUpdater;
private final IssueStorage issueStorage;
private final IssueNotifications issueNotifications;
private final NotificationManager notificationService;
private final ActionPlanService actionPlanService;
private final RuleFinder ruleFinder;
private final IssueDao deprecatedIssueDao;
Expand All @@ -92,7 +93,7 @@ public IssueService(DbClient dbClient, IndexClient indexClient,
IssueWorkflow workflow,
IssueStorage issueStorage,
IssueUpdater issueUpdater,
IssueNotifications issueNotifications,
NotificationManager notificationService,
ActionPlanService actionPlanService,
RuleFinder ruleFinder,
IssueDao deprecatedIssueDao,
Expand All @@ -105,7 +106,7 @@ public IssueService(DbClient dbClient, IndexClient indexClient,
this.issueUpdater = issueUpdater;
this.actionPlanService = actionPlanService;
this.ruleFinder = ruleFinder;
this.issueNotifications = issueNotifications;
this.notificationService = notificationService;
this.deprecatedIssueDao = deprecatedIssueDao;
this.userFinder = userFinder;
this.userIndex = userIndex;
Expand Down Expand Up @@ -333,11 +334,13 @@ void saveIssue(DbSession session, DefaultIssue issue, IssueChangeContext context
}
issueStorage.save(session, issue);
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, false);
notificationService.scheduleForSending(new IssueChangeNotification()
.setIssue(issue)
.setChangeAuthorLogin(context.login())
.setRuleName(rule != null ? rule.getName() : null)
.setProject(dbClient.componentDao().getByKey(session, projectKey))
.setComponent(dbClient.componentDao().getNullableByKey(session, issue.componentKey()))
.setComment(comment));
}

/**
Expand Down
Expand Up @@ -37,7 +37,7 @@ public class ChangesOnMyIssueNotificationDispatcher extends NotificationDispatch
private NotificationManager notificationManager;

public ChangesOnMyIssueNotificationDispatcher(NotificationManager notificationManager) {
super("issue-changes");
super(IssueChangeNotification.TYPE);
this.notificationManager = notificationManager;
}

Expand Down
@@ -0,0 +1,91 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* SonarQube is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.issue.notification;

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 javax.annotation.Nullable;

import java.io.Serializable;
import java.util.Map;

public class IssueChangeNotification extends Notification {

public static final String TYPE = "issue-changes";

public IssueChangeNotification() {
super(TYPE);
}

public IssueChangeNotification setIssue(DefaultIssue issue) {
setFieldValue("key", issue.key());
setFieldValue("reporter", issue.reporter());
setFieldValue("assignee", issue.assignee());
setFieldValue("message", issue.message());
setFieldValue("componentKey", issue.componentKey());
FieldDiffs currentChange = issue.currentChange();
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();
setFieldValue("old." + type, oldValue != null ? oldValue.toString() : null);
setFieldValue("new." + type, newValue != null ? newValue.toString() : null);
}
}
return this;
}

public IssueChangeNotification setProject(Component project) {
setFieldValue("projectName", project.longName());
setFieldValue("projectKey", project.key());
return this;
}

public IssueChangeNotification setComponent(Component component) {
setFieldValue("componentName", component.longName());
return this;
}

public IssueChangeNotification setChangeAuthorLogin(@Nullable String s) {
if (s != null) {
setFieldValue("changeAuthor", s);
}
return this;
}

public IssueChangeNotification setRuleName(@Nullable String s) {
if (s != null) {
setFieldValue("ruleName", s);
}
return this;
}

public IssueChangeNotification setComment(@Nullable String s) {
if (s != null) {
setFieldValue("comment", s);
}
return this;
}
}
Expand Up @@ -47,7 +47,7 @@ public IssueChangesEmailTemplate(EmailSettings settings, UserFinder userFinder)

@Override
public EmailMessage format(Notification notif) {
if (!"issue-changes".equals(notif.getType())) {
if (!IssueChangeNotification.TYPE.equals(notif.getType())) {
return null;
}

Expand Down

0 comments on commit 7bcccf0

Please sign in to comment.