]> source.dussan.org Git - sonarqube.git/blob
c145b30d61b4fd7845ba8c012c0ea91b2337137e
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2017 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 3 of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20 package org.sonar.server.computation.task.projectanalysis.step;
21
22 import com.google.common.collect.ImmutableMap;
23 import com.google.common.collect.ImmutableSet;
24 import java.time.Instant;
25 import java.time.temporal.ChronoUnit;
26 import java.util.Date;
27 import java.util.Map;
28 import java.util.Optional;
29 import java.util.Set;
30 import java.util.function.Predicate;
31 import org.sonar.api.issue.Issue;
32 import org.sonar.api.utils.Duration;
33 import org.sonar.core.issue.DefaultIssue;
34 import org.sonar.core.util.CloseableIterator;
35 import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
36 import org.sonar.server.computation.task.projectanalysis.analysis.Branch;
37 import org.sonar.server.computation.task.projectanalysis.component.Component;
38 import org.sonar.server.computation.task.projectanalysis.component.CrawlerDepthLimit;
39 import org.sonar.server.computation.task.projectanalysis.component.DepthTraversalTypeAwareCrawler;
40 import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolder;
41 import org.sonar.server.computation.task.projectanalysis.component.TypeAwareVisitorAdapter;
42 import org.sonar.server.computation.task.projectanalysis.issue.IssueCache;
43 import org.sonar.server.computation.task.projectanalysis.issue.RuleRepository;
44 import org.sonar.server.computation.task.step.ComputationStep;
45 import org.sonar.server.issue.notification.IssueChangeNotification;
46 import org.sonar.server.issue.notification.MyNewIssuesNotification;
47 import org.sonar.server.issue.notification.NewIssuesNotification;
48 import org.sonar.server.issue.notification.NewIssuesNotificationFactory;
49 import org.sonar.server.issue.notification.NewIssuesStatistics;
50 import org.sonar.server.notification.NotificationService;
51
52 import static org.sonar.server.computation.task.projectanalysis.component.ComponentVisitor.Order.POST_ORDER;
53
54 /**
55  * Reads issues from disk cache and send related notifications. For performance reasons,
56  * the standard notification DB queue is not used as a temporary storage. Notifications
57  * are directly processed by {@link NotificationService}.
58  */
59 public class SendIssueNotificationsStep implements ComputationStep {
60   /**
61    * Types of the notifications sent by this step
62    */
63   static final Set<String> NOTIF_TYPES = ImmutableSet.of(IssueChangeNotification.TYPE, NewIssuesNotification.TYPE, MyNewIssuesNotification.MY_NEW_ISSUES_NOTIF_TYPE);
64
65   private final IssueCache issueCache;
66   private final RuleRepository rules;
67   private final TreeRootHolder treeRootHolder;
68   private final NotificationService service;
69   private final AnalysisMetadataHolder analysisMetadataHolder;
70   private final NewIssuesNotificationFactory newIssuesNotificationFactory;
71   private Map<String, Component> componentsByDbKey;
72
73   public SendIssueNotificationsStep(IssueCache issueCache, RuleRepository rules, TreeRootHolder treeRootHolder,
74     NotificationService service, AnalysisMetadataHolder analysisMetadataHolder,
75     NewIssuesNotificationFactory newIssuesNotificationFactory) {
76     this.issueCache = issueCache;
77     this.rules = rules;
78     this.treeRootHolder = treeRootHolder;
79     this.service = service;
80     this.analysisMetadataHolder = analysisMetadataHolder;
81     this.newIssuesNotificationFactory = newIssuesNotificationFactory;
82   }
83
84   @Override
85   public void execute() {
86     Component project = treeRootHolder.getRoot();
87     if (service.hasProjectSubscribersForTypes(project.getUuid(), NOTIF_TYPES)) {
88       doExecute(project);
89     }
90   }
91
92   private void doExecute(Component project) {
93     long analysisDate = analysisMetadataHolder.getAnalysisDate();
94     Predicate<DefaultIssue> isOnLeakPredicate = i -> i.isNew() && i.creationDate().getTime() >= truncateToSeconds(analysisDate);
95     NewIssuesStatistics newIssuesStats = new NewIssuesStatistics(isOnLeakPredicate);
96     try (CloseableIterator<DefaultIssue> issues = issueCache.traverse()) {
97       processIssues(newIssuesStats, issues, project);
98     }
99     if (newIssuesStats.hasIssuesOnLeak()) {
100       sendNewIssuesNotification(newIssuesStats, project, analysisDate);
101       sendNewIssuesNotificationToAssignees(newIssuesStats, project, analysisDate);
102     }
103   }
104
105   /**
106    * Truncated the analysis date to seconds before comparing it to {@link Issue#creationDate()} is required because
107    * {@link DefaultIssue#setCreationDate(Date)} does it.
108    */
109   private static long truncateToSeconds(long analysisDate) {
110     Instant instant = new Date(analysisDate).toInstant();
111     instant = instant.truncatedTo(ChronoUnit.SECONDS);
112     return Date.from(instant).getTime();
113   }
114
115   private void processIssues(NewIssuesStatistics newIssuesStats, CloseableIterator<DefaultIssue> issues, Component project) {
116     while (issues.hasNext()) {
117       DefaultIssue issue = issues.next();
118       if (issue.isNew() && issue.resolution() == null) {
119         newIssuesStats.add(issue);
120       } else if (issue.isChanged() && issue.mustSendNotifications()) {
121         sendIssueChangeNotification(issue, project);
122       }
123     }
124   }
125
126   private void sendIssueChangeNotification(DefaultIssue issue, Component project) {
127     IssueChangeNotification changeNotification = new IssueChangeNotification();
128     changeNotification.setRuleName(rules.getByKey(issue.ruleKey()).getName());
129     changeNotification.setIssue(issue);
130     changeNotification.setProject(project.getPublicKey(), project.getName(), getBranchName(), project.getUuid());
131     getComponentKey(issue).ifPresent(c -> changeNotification.setComponent(c.getPublicKey(), c.getName()));
132     service.deliver(changeNotification);
133   }
134
135   private void sendNewIssuesNotification(NewIssuesStatistics statistics, Component project, long analysisDate) {
136     NewIssuesStatistics.Stats globalStatistics = statistics.globalStatistics();
137     NewIssuesNotification notification = newIssuesNotificationFactory
138       .newNewIssuesNotication()
139       .setProject(project.getPublicKey(), project.getUuid(), project.getName(), getBranchName())
140       .setProjectVersion(project.getReportAttributes().getVersion())
141       .setAnalysisDate(new Date(analysisDate))
142       .setStatistics(project.getName(), globalStatistics)
143       .setDebt(Duration.create(globalStatistics.effort().getOnLeak()));
144     service.deliver(notification);
145   }
146
147   private void sendNewIssuesNotificationToAssignees(NewIssuesStatistics statistics, Component project, long analysisDate) {
148     statistics.getAssigneesStatistics().entrySet()
149       .stream()
150       .filter(e -> e.getValue().hasIssuesOnLeak())
151       .forEach(e -> {
152         String assignee = e.getKey();
153         NewIssuesStatistics.Stats assigneeStatistics = e.getValue();
154         MyNewIssuesNotification myNewIssuesNotification = newIssuesNotificationFactory
155           .newMyNewIssuesNotification()
156           .setAssignee(assignee);
157         myNewIssuesNotification
158           .setProject(project.getPublicKey(), project.getUuid(), project.getName(), getBranchName())
159           .setProjectVersion(project.getReportAttributes().getVersion())
160           .setAnalysisDate(new Date(analysisDate))
161           .setStatistics(project.getName(), assigneeStatistics)
162           .setDebt(Duration.create(assigneeStatistics.effort().getOnLeak()));
163
164         service.deliver(myNewIssuesNotification);
165       });
166   }
167
168   private Optional<Component> getComponentKey(DefaultIssue issue) {
169     if (componentsByDbKey == null) {
170       final ImmutableMap.Builder<String, Component> builder = ImmutableMap.builder();
171       new DepthTraversalTypeAwareCrawler(
172         new TypeAwareVisitorAdapter(CrawlerDepthLimit.LEAVES, POST_ORDER) {
173           @Override
174           public void visitAny(Component component) {
175             builder.put(component.getKey(), component);
176           }
177         }).visit(this.treeRootHolder.getRoot());
178       this.componentsByDbKey = builder.build();
179     }
180     return Optional.ofNullable(componentsByDbKey.get(issue.componentKey()));
181   }
182
183   @Override
184   public String getDescription() {
185     return "Send issue notifications";
186   }
187
188   private String getBranchName() {
189     return analysisMetadataHolder.getBranch().filter(b -> !b.isMain()).map(Branch::getName).orElse(null);
190   }
191
192 }