3 * Copyright (C) 2009-2017 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.server.computation.task.projectanalysis.step;
22 import com.google.common.base.Joiner;
23 import java.util.Date;
24 import java.util.List;
25 import java.util.Optional;
26 import javax.annotation.CheckForNull;
27 import javax.annotation.Nullable;
28 import org.apache.commons.lang.StringUtils;
29 import org.sonar.api.utils.MessageException;
30 import org.sonar.ce.queue.CeTask;
31 import org.sonar.core.component.ComponentKeys;
32 import org.sonar.core.platform.PluginInfo;
33 import org.sonar.core.platform.PluginRepository;
34 import org.sonar.core.util.stream.MoreCollectors;
35 import org.sonar.db.DbClient;
36 import org.sonar.db.DbSession;
37 import org.sonar.db.component.ComponentDto;
38 import org.sonar.db.organization.OrganizationDto;
39 import org.sonar.db.qualityprofile.QProfileDto;
40 import org.sonar.scanner.protocol.output.ScannerReport;
41 import org.sonar.scanner.protocol.output.ScannerReport.Metadata.Plugin;
42 import org.sonar.scanner.protocol.output.ScannerReport.Metadata.QProfile;
43 import org.sonar.server.computation.task.projectanalysis.analysis.MutableAnalysisMetadataHolder;
44 import org.sonar.server.computation.task.projectanalysis.analysis.Organization;
45 import org.sonar.server.computation.task.projectanalysis.analysis.Project;
46 import org.sonar.server.computation.task.projectanalysis.analysis.ScannerPlugin;
47 import org.sonar.server.computation.task.projectanalysis.batch.BatchReportReader;
48 import org.sonar.server.computation.task.projectanalysis.component.BranchLoader;
49 import org.sonar.server.computation.task.step.ComputationStep;
50 import org.sonar.server.organization.DefaultOrganizationProvider;
51 import org.sonar.server.qualityprofile.QualityProfile;
53 import static java.util.stream.Collectors.toMap;
54 import static com.google.common.base.Preconditions.checkState;
55 import static com.google.common.collect.Maps.transformValues;
56 import static java.lang.String.format;
57 import static org.apache.commons.lang.StringUtils.isNotEmpty;
58 import static org.sonar.core.util.stream.MoreCollectors.toList;
61 * Feed analysis metadata holder with metadata from the analysis report.
63 public class LoadReportAnalysisMetadataHolderStep implements ComputationStep {
64 private final CeTask ceTask;
65 private final BatchReportReader reportReader;
66 private final MutableAnalysisMetadataHolder analysisMetadata;
67 private final DefaultOrganizationProvider defaultOrganizationProvider;
68 private final DbClient dbClient;
69 private final BranchLoader branchLoader;
70 private final PluginRepository pluginRepository;
72 public LoadReportAnalysisMetadataHolderStep(CeTask ceTask, BatchReportReader reportReader, MutableAnalysisMetadataHolder analysisMetadata,
73 DefaultOrganizationProvider defaultOrganizationProvider, DbClient dbClient, BranchLoader branchLoader, PluginRepository pluginRepository) {
75 this.reportReader = reportReader;
76 this.analysisMetadata = analysisMetadata;
77 this.defaultOrganizationProvider = defaultOrganizationProvider;
78 this.dbClient = dbClient;
79 this.branchLoader = branchLoader;
80 this.pluginRepository = pluginRepository;
84 public void execute() {
85 ScannerReport.Metadata reportMetadata = reportReader.readMetadata();
87 loadMetadata(reportMetadata);
88 Organization organization = loadOrganization(reportMetadata);
89 loadProject(reportMetadata, organization);
90 loadIncrementalMode(reportMetadata);
91 loadQualityProfiles(reportMetadata, organization);
92 branchLoader.load(reportMetadata);
95 private void loadMetadata(ScannerReport.Metadata reportMetadata) {
96 analysisMetadata.setAnalysisDate(reportMetadata.getAnalysisDate());
97 analysisMetadata.setRootComponentRef(reportMetadata.getRootComponentRef());
98 analysisMetadata.setCrossProjectDuplicationEnabled(reportMetadata.getCrossProjectDuplicationActivated());
101 private void loadProject(ScannerReport.Metadata reportMetadata, Organization organization) {
102 String reportProjectKey = projectKeyFromReport(reportMetadata);
103 checkProjectKeyConsistency(reportProjectKey);
104 ComponentDto dto = toProject(reportProjectKey);
105 if (!dto.getOrganizationUuid().equals(organization.getUuid())) {
106 throw MessageException.of(format("Project is not in the expected organization: %s", organization.getKey()));
108 if (dto.getMainBranchProjectUuid() != null) {
109 throw MessageException.of("Project should not reference a branch");
111 analysisMetadata.setProject(new Project(dto.uuid(), dto.getDbKey(), dto.name()));
114 private Organization loadOrganization(ScannerReport.Metadata reportMetadata) {
115 Organization organization = toOrganization(ceTask.getOrganizationUuid());
116 checkOrganizationKeyConsistency(reportMetadata, organization);
117 analysisMetadata.setOrganization(organization);
121 private void loadQualityProfiles(ScannerReport.Metadata reportMetadata, Organization organization) {
122 checkQualityProfilesConsistency(reportMetadata, organization);
123 analysisMetadata.setQProfilesByLanguage(reportMetadata.getQprofilesPerLanguage().values().stream()
125 QProfile::getLanguage,
126 qp -> new QualityProfile(qp.getKey(), qp.getName(), qp.getLanguage(), new Date(qp.getRulesUpdatedAt())))));
127 analysisMetadata.setScannerPluginsByKey(reportMetadata.getPluginsByKey().values().stream()
130 p -> new ScannerPlugin(p.getKey(), getBasePluginKey(p), p.getUpdatedAt()))));
134 private String getBasePluginKey(Plugin p) {
135 PluginInfo pluginInfo = pluginRepository.getPluginInfo(p.getKey());
136 if (pluginInfo == null) {
137 // May happen if plugin was uninstalled between start of scanner analysis and now.
138 // But it doesn't matter since all active rules are removed anyway, so no issues will be reported
141 return pluginInfo.getBasePlugin();
144 private void loadIncrementalMode(ScannerReport.Metadata reportMetadata) {
145 analysisMetadata.setIncrementalAnalysis(reportMetadata.getIncremental());
149 * Check that the Quality profiles sent by scanner correctly relate to the project organization.
151 private void checkQualityProfilesConsistency(ScannerReport.Metadata metadata, Organization organization) {
152 List<String> profileKeys = metadata.getQprofilesPerLanguage().values().stream()
153 .map(QProfile::getKey)
154 .collect(toList(metadata.getQprofilesPerLanguage().size()));
155 try (DbSession dbSession = dbClient.openSession(false)) {
156 List<QProfileDto> profiles = dbClient.qualityProfileDao().selectByUuids(dbSession, profileKeys);
157 String badKeys = profiles.stream()
158 .filter(p -> !p.getOrganizationUuid().equals(organization.getUuid()))
159 .map(QProfileDto::getKee)
160 .collect(MoreCollectors.join(Joiner.on(", ")));
161 if (!badKeys.isEmpty()) {
162 throw MessageException.of(format("Quality profiles with following keys don't exist in organization [%s]: %s", organization.getKey(), badKeys));
167 private void checkProjectKeyConsistency(String reportProjectKey) {
168 String componentKey = ceTask.getComponentKey();
169 if (componentKey == null) {
170 throw MessageException.of(format(
171 "Compute Engine task component key is null. Project with UUID %s must have been deleted since report was uploaded. Can not proceed.",
172 ceTask.getComponentUuid()));
174 if (!componentKey.equals(reportProjectKey)) {
175 throw MessageException.of(format(
176 "ProjectKey in report (%s) is not consistent with projectKey under which the report as been submitted (%s)",
182 private void checkOrganizationKeyConsistency(ScannerReport.Metadata reportMetadata, Organization organization) {
183 String organizationKey = reportMetadata.getOrganizationKey();
184 String resolveReportOrganizationKey = resolveReportOrganizationKey(organizationKey);
185 if (!resolveReportOrganizationKey.equals(organization.getKey())) {
186 if (reportBelongsToDefaultOrganization(organizationKey)) {
187 throw MessageException.of(format(
188 "Report does not specify an OrganizationKey but it has been submitted to another organization (%s) than the default one (%s)",
189 organization.getKey(),
190 defaultOrganizationProvider.get().getKey()));
192 throw MessageException.of(format(
193 "OrganizationKey in report (%s) is not consistent with organizationKey under which the report as been submitted (%s)",
194 resolveReportOrganizationKey,
195 organization.getKey()));
200 private String resolveReportOrganizationKey(@Nullable String organizationKey) {
201 if (reportBelongsToDefaultOrganization(organizationKey)) {
202 return defaultOrganizationProvider.get().getKey();
204 return organizationKey;
207 private static boolean reportBelongsToDefaultOrganization(@Nullable String organizationKey) {
208 return organizationKey == null || organizationKey.isEmpty();
211 private Organization toOrganization(String organizationUuid) {
212 try (DbSession dbSession = dbClient.openSession(false)) {
213 Optional<OrganizationDto> organizationDto = dbClient.organizationDao().selectByUuid(dbSession, organizationUuid);
214 checkState(organizationDto.isPresent(), "Organization with uuid '%s' can't be found", organizationUuid);
215 return Organization.from(organizationDto.get());
219 private ComponentDto toProject(String projectKey) {
220 try (DbSession dbSession = dbClient.openSession(false)) {
221 com.google.common.base.Optional<ComponentDto> opt = dbClient.componentDao().selectByKey(dbSession, projectKey);
222 checkState(opt.isPresent(), "Project with key '%s' can't be found", projectKey);
227 private static String projectKeyFromReport(ScannerReport.Metadata reportMetadata) {
228 String deprecatedBranch = reportMetadata.getDeprecatedBranch();
229 if (StringUtils.isNotEmpty(deprecatedBranch)) {
230 return ComponentKeys.createKey(reportMetadata.getProjectKey(), deprecatedBranch);
232 return reportMetadata.getProjectKey();
236 public String getDescription() {
237 return "Load analysis metadata";