3 * Copyright (C) 2009-2023 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.
20 package org.sonar.ce.task.projectexport.issue;
22 import com.google.common.annotations.VisibleForTesting;
23 import com.google.protobuf.ByteString;
24 import com.google.protobuf.InvalidProtocolBufferException;
25 import com.hazelcast.internal.util.MutableLong;
26 import com.sonarsource.governance.projectdump.protobuf.ProjectDump;
27 import java.util.List;
28 import java.util.Objects;
29 import java.util.Optional;
30 import org.apache.ibatis.cursor.Cursor;
31 import org.slf4j.LoggerFactory;
32 import org.sonar.api.rule.RuleKey;
33 import org.sonar.ce.task.projectexport.component.ComponentRepository;
34 import org.sonar.ce.task.projectexport.rule.Rule;
35 import org.sonar.ce.task.projectexport.rule.RuleRepository;
36 import org.sonar.ce.task.projectexport.steps.DumpElement;
37 import org.sonar.ce.task.projectexport.steps.DumpElement.IssueDumpElement;
38 import org.sonar.ce.task.projectexport.steps.DumpWriter;
39 import org.sonar.ce.task.projectexport.steps.ProjectHolder;
40 import org.sonar.ce.task.projectexport.steps.StreamWriter;
41 import org.sonar.ce.task.step.ComputationStep;
42 import org.sonar.db.DbClient;
43 import org.sonar.db.DbSession;
44 import org.sonar.db.issue.IssueDto;
45 import org.sonar.db.project.ProjectExportMapper;
46 import org.sonar.db.protobuf.DbIssues;
48 import static java.lang.String.format;
50 public class ExportIssuesStep implements ComputationStep {
52 private final DbClient dbClient;
53 private final ProjectHolder projectHolder;
54 private final DumpWriter dumpWriter;
55 private final RuleRegistrar ruleRegistrar;
56 private final ComponentRepository componentRepository;
58 public ExportIssuesStep(DbClient dbClient, ProjectHolder projectHolder, DumpWriter dumpWriter, RuleRepository ruleRepository,
59 ComponentRepository componentRepository) {
60 this.dbClient = dbClient;
61 this.projectHolder = projectHolder;
62 this.dumpWriter = dumpWriter;
63 this.componentRepository = componentRepository;
64 this.ruleRegistrar = new RuleRegistrar(ruleRepository);
68 public String getDescription() {
69 return "Export issues";
73 public void execute(Context context) {
74 MutableLong count = MutableLong.valueOf(0);
76 StreamWriter<ProjectDump.Issue> output = dumpWriter.newStreamWriter(DumpElement.ISSUES);
77 DbSession dbSession = dbClient.openSession(false);
78 Cursor<IssueDto> issueDtoCursor = dbSession.getMapper(ProjectExportMapper.class).scrollIssueForExport(projectHolder.projectDto().getUuid())) {
79 ProjectDump.Issue.Builder issueBuilder = ProjectDump.Issue.newBuilder();
81 .forEach(issueDto -> {
82 ProjectDump.Issue issue = convertToIssue(issueDto, issueBuilder);
86 LoggerFactory.getLogger(getClass()).debug("{} issues exported", count.value);
87 } catch (Exception e) {
88 throw new IllegalStateException(format("Issue export failed after processing %d issues successfully", count.value), e);
92 private ProjectDump.Issue convertToIssue(IssueDto issueDto, ProjectDump.Issue.Builder builder) {
94 String ruleRef = registerRule(issueDto);
98 .setUuid(issueDto.getKee())
99 .setComponentRef(componentRepository.getRef(issueDto.getComponentUuid()))
100 .setType(issueDto.getType())
101 .setMessage(Optional.of(issueDto).map(IssueDto::getMessage).orElse(""))
102 .setLine(Optional.of(issueDto).map(IssueDto::getLine).orElse(0))
103 .setChecksum(Optional.of(issueDto).map(IssueDto::getChecksum).orElse(""))
104 .setStatus(Optional.of(issueDto).map(IssueDto::getStatus).orElse(""))
105 .setResolution(Optional.of(issueDto).map(IssueDto::getResolution).orElse(""))
106 .setSeverity(Optional.of(issueDto).map(IssueDto::getSeverity).orElse(""))
107 .setManualSeverity(issueDto.isManualSeverity())
108 .setGap(Optional.of(issueDto).map(IssueDto::getGap).orElse(IssueDumpElement.NO_GAP))
109 .setEffort(Optional.of(issueDto).map(IssueDto::getEffort).orElse(IssueDumpElement.NO_EFFORT))
110 .setAssignee(Optional.of(issueDto).map(IssueDto::getAssigneeUuid).orElse(""))
111 .setAuthor(Optional.of(issueDto).map(IssueDto::getAuthorLogin).orElse(""))
112 .setTags(Optional.of(issueDto).map(IssueDto::getTagsString).orElse(""))
113 .setIssueCreatedAt(Optional.of(issueDto).map(IssueDto::getIssueCreationTime).orElse(0L))
114 .setIssueUpdatedAt(Optional.of(issueDto).map(IssueDto::getIssueUpdateTime).orElse(0L))
115 .setIssueClosedAt(Optional.of(issueDto).map(IssueDto::getIssueCloseTime).orElse(0L))
116 .setProjectUuid(issueDto.getProjectUuid())
117 .setCodeVariants(Optional.of(issueDto).map(IssueDto::getCodeVariantsString).orElse(""));
118 setLocations(builder, issueDto);
119 setMessageFormattings(builder, issueDto);
120 mergeImpacts(builder, issueDto);
122 return builder.build();
125 private static void mergeImpacts(ProjectDump.Issue.Builder builder, IssueDto issueDto) {
126 issueDto.getImpacts()
128 .map(impactDto -> ProjectDump.Impact.newBuilder()
129 .setSoftwareQuality(ProjectDump.SoftwareQuality.valueOf(impactDto.getSoftwareQuality().name()))
130 .setSeverity(ProjectDump.Severity.valueOf(impactDto.getSeverity().name()))
132 .forEach(builder::addImpacts);
135 private String registerRule(IssueDto issueDto) {
136 String ruleUuid = issueDto.getRuleUuid();
137 RuleKey ruleKey = issueDto.getRuleKey();
138 return ruleRegistrar.register(ruleUuid, ruleKey).ref();
141 private static void setLocations(ProjectDump.Issue.Builder builder, IssueDto issueDto) {
143 byte[] bytes = issueDto.getLocations();
145 // fail fast, ensure we can read data from DB
146 DbIssues.Locations.parseFrom(bytes);
147 builder.setLocations(ByteString.copyFrom(bytes));
149 } catch (InvalidProtocolBufferException e) {
150 throw new IllegalStateException(format("Fail to read locations from DB for issue %s", issueDto.getKee()), e);
154 private static void setMessageFormattings(ProjectDump.Issue.Builder builder, IssueDto issueDto) {
156 byte[] bytes = issueDto.getMessageFormattings();
158 // fail fast, ensure we can read data from DB
159 DbIssues.MessageFormattings messageFormattings = DbIssues.MessageFormattings.parseFrom(bytes);
160 if (messageFormattings != null) {
161 builder.addAllMessageFormattings(dbToDumpMessageFormatting(messageFormattings.getMessageFormattingList()));
164 } catch (InvalidProtocolBufferException e) {
165 throw new IllegalStateException(format("Fail to read message formattings from DB for issue %s", issueDto.getKee()), e);
170 static List<ProjectDump.MessageFormatting> dbToDumpMessageFormatting(List<DbIssues.MessageFormatting> messageFormattingList) {
171 return messageFormattingList.stream()
172 .map(e -> ProjectDump.MessageFormatting.newBuilder()
173 .setStart(e.getStart())
175 .setType(ProjectDump.MessageFormattingType.valueOf(e.getType().name())).build())
179 private static class RuleRegistrar {
180 private final RuleRepository ruleRepository;
181 private Rule previousRule = null;
182 private String previousRuleUuid = null;
184 private RuleRegistrar(RuleRepository ruleRepository) {
185 this.ruleRepository = ruleRepository;
188 public Rule register(String ruleUuid, RuleKey ruleKey) {
189 if (Objects.equals(previousRuleUuid, ruleUuid)) {
192 return lookup(ruleUuid, ruleKey);
195 private Rule lookup(String ruleUuid, RuleKey ruleKey) {
196 this.previousRule = ruleRepository.register(ruleUuid, ruleKey);
197 this.previousRuleUuid = ruleUuid;