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 static com.google.common.collect.FluentIterable.from;
23 import static java.lang.String.format;
24 import static org.sonar.api.utils.DateUtils.formatDateTime;
26 import java.util.ArrayList;
27 import java.util.Date;
28 import java.util.List;
31 import javax.annotation.Nullable;
33 import org.sonar.api.resources.Qualifiers;
34 import org.sonar.api.resources.Scopes;
35 import org.sonar.api.utils.MessageException;
36 import org.sonar.core.component.ComponentKeys;
37 import org.sonar.db.DbClient;
38 import org.sonar.db.DbSession;
39 import org.sonar.db.component.ComponentDao;
40 import org.sonar.db.component.ComponentDto;
41 import org.sonar.db.component.SnapshotDto;
42 import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
43 import org.sonar.server.computation.task.projectanalysis.batch.BatchReportReader;
44 import org.sonar.server.computation.task.projectanalysis.component.Component;
45 import org.sonar.server.computation.task.projectanalysis.component.ComponentVisitor;
46 import org.sonar.server.computation.task.projectanalysis.component.CrawlerDepthLimit;
47 import org.sonar.server.computation.task.projectanalysis.component.DepthTraversalTypeAwareCrawler;
48 import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolder;
49 import org.sonar.server.computation.task.projectanalysis.component.TypeAwareVisitorAdapter;
50 import org.sonar.server.computation.task.projectanalysis.validation.ValidateIncremental;
51 import org.sonar.server.computation.task.step.ComputationStep;
53 import com.google.common.base.Joiner;
54 import com.google.common.base.Optional;
57 * Validate project and modules. It will fail in the following cases :
59 * <li>branch is not valid</li>
60 * <li>project or module key is not valid</li>
61 * <li>module key already exists in another project (same module key cannot exists in different projects)</li>
62 * <li>module key is already used as a project key</li>
63 * <li>date of the analysis is before last analysis</li>
66 public class ValidateProjectStep implements ComputationStep {
68 private static final Joiner MESSAGES_JOINER = Joiner.on("\n o ");
70 private final DbClient dbClient;
71 private final BatchReportReader reportReader;
72 private final TreeRootHolder treeRootHolder;
73 private final AnalysisMetadataHolder analysisMetadataHolder;
74 private final ValidateIncremental validateIncremental;
76 public ValidateProjectStep(DbClient dbClient, BatchReportReader reportReader, TreeRootHolder treeRootHolder, AnalysisMetadataHolder analysisMetadataHolder) {
77 this(dbClient, reportReader, treeRootHolder, analysisMetadataHolder, null);
80 public ValidateProjectStep(DbClient dbClient, BatchReportReader reportReader, TreeRootHolder treeRootHolder,
81 AnalysisMetadataHolder analysisMetadataHolder, @Nullable ValidateIncremental validateIncremental) {
82 this.dbClient = dbClient;
83 this.reportReader = reportReader;
84 this.treeRootHolder = treeRootHolder;
85 this.analysisMetadataHolder = analysisMetadataHolder;
86 this.validateIncremental = validateIncremental;
90 public void execute() {
91 try (DbSession dbSession = dbClient.openSession(false)) {
92 Component root = treeRootHolder.getRoot();
93 List<ComponentDto> baseModules = dbClient.componentDao().selectEnabledModulesFromProjectKey(dbSession, root.getKey());
94 Map<String, ComponentDto> baseModulesByKey = from(baseModules).uniqueIndex(ComponentDto::getDbKey);
95 ValidateProjectsVisitor visitor = new ValidateProjectsVisitor(dbSession, dbClient.componentDao(), baseModulesByKey);
96 new DepthTraversalTypeAwareCrawler(visitor).visit(root);
98 if (!visitor.validationMessages.isEmpty()) {
99 throw MessageException.of("Validation of project failed:\n o " + MESSAGES_JOINER.join(visitor.validationMessages));
105 public String getDescription() {
106 return "Validate project";
109 private class ValidateProjectsVisitor extends TypeAwareVisitorAdapter {
110 private final DbSession session;
111 private final ComponentDao componentDao;
112 private final Map<String, ComponentDto> baseModulesByKey;
113 private final List<String> validationMessages = new ArrayList<>();
115 private Component rawProject;
117 public ValidateProjectsVisitor(DbSession session, ComponentDao componentDao, Map<String, ComponentDto> baseModulesByKey) {
118 super(CrawlerDepthLimit.MODULE, ComponentVisitor.Order.PRE_ORDER);
119 this.session = session;
120 this.componentDao = componentDao;
122 this.baseModulesByKey = baseModulesByKey;
126 public void visitProject(Component rawProject) {
127 this.rawProject = rawProject;
128 String rawProjectKey = rawProject.getKey();
129 validateIncremental(rawProjectKey);
130 validateNotIncrementalAndFirstAnalysis(rawProjectKey);
131 validateBatchKey(rawProject);
133 Optional<ComponentDto> baseProject = loadBaseComponent(rawProjectKey);
134 validateRootIsProject(baseProject);
135 validateProjectKey(baseProject, rawProjectKey);
136 validateAnalysisDate(baseProject);
139 private void validateRootIsProject(Optional<ComponentDto> baseProject) {
140 if (baseProject.isPresent()) {
141 ComponentDto componentDto = baseProject.get();
142 // the scope field is verified for excluding the project copies generated by portfolios
143 if (!Qualifiers.PROJECT.equals(componentDto.qualifier()) || !Scopes.PROJECT.equals(componentDto.scope())) {
144 validationMessages.add(format("Component (uuid=%s, key=%s) is not a project", rawProject.getUuid(), rawProject.getKey()));
149 private void validateIncremental(String rawProjectKey) {
150 if (analysisMetadataHolder.isIncrementalAnalysis()) {
151 if (validateIncremental == null) {
152 validationMessages.add(format("Can't process an incremental analysis of the project \"%s\" because the incremental plugin is not loaded."
153 + " Please install the plugin or launch a full analysis of the project.", rawProjectKey));
154 } else if (!validateIncremental.execute()) {
155 validationMessages.add(format("The installation of the incremental plugin is invalid. Can't process the incremental analysis "
156 + "of the project \"%s\".", rawProjectKey));
161 private void validateNotIncrementalAndFirstAnalysis(String rawProjectKey) {
162 if (analysisMetadataHolder.isIncrementalAnalysis() && analysisMetadataHolder.isFirstAnalysis()) {
163 validationMessages.add(format("The project \"%s\" hasn't been analysed before and the first analysis can't be incremental."
164 + " Please launch a full analysis of the project.", rawProjectKey));
168 private void validateProjectKey(Optional<ComponentDto> baseProject, String rawProjectKey) {
169 if (baseProject.isPresent() && !baseProject.get().projectUuid().equals(baseProject.get().uuid())) {
170 // Project key is already used as a module of another project
171 ComponentDto anotherBaseProject = componentDao.selectOrFailByUuid(session, baseProject.get().projectUuid());
172 validationMessages.add(format("The project \"%s\" is already defined in SonarQube but as a module of project \"%s\". "
173 + "If you really want to stop directly analysing project \"%s\", please first delete it from SonarQube and then relaunch the analysis of project \"%s\".",
174 rawProjectKey, anotherBaseProject.getDbKey(), anotherBaseProject.getDbKey(), rawProjectKey));
178 private void validateAnalysisDate(Optional<ComponentDto> baseProject) {
179 if (baseProject.isPresent()) {
180 java.util.Optional<SnapshotDto> snapshotDto = dbClient.snapshotDao().selectLastAnalysisByRootComponentUuid(session, baseProject.get().uuid());
181 long currentAnalysisDate = analysisMetadataHolder.getAnalysisDate();
182 Long lastAnalysisDate = snapshotDto.map(SnapshotDto::getCreatedAt).orElse(null);
183 if (lastAnalysisDate != null && currentAnalysisDate <= lastAnalysisDate) {
184 validationMessages.add(format("Date of analysis cannot be older than the date of the last known analysis on this project. Value: \"%s\". " +
185 "Latest analysis: \"%s\". It's only possible to rebuild the past in a chronological order.",
186 formatDateTime(new Date(currentAnalysisDate)), formatDateTime(new Date(lastAnalysisDate))));
192 public void visitModule(Component rawModule) {
193 String rawProjectKey = rawProject.getKey();
194 String rawModuleKey = rawModule.getKey();
195 validateBatchKey(rawModule);
197 Optional<ComponentDto> baseModule = loadBaseComponent(rawModuleKey);
198 if (!baseModule.isPresent()) {
201 validateModuleIsNotAlreadyUsedAsProject(baseModule.get(), rawProjectKey, rawModuleKey);
202 validateModuleKeyIsNotAlreadyUsedInAnotherProject(baseModule.get(), rawModuleKey);
205 private void validateModuleIsNotAlreadyUsedAsProject(ComponentDto baseModule, String rawProjectKey, String rawModuleKey) {
206 if (baseModule.projectUuid().equals(baseModule.uuid())) {
207 // module is actually a project
208 validationMessages.add(format("The project \"%s\" is already defined in SonarQube but not as a module of project \"%s\". "
209 + "If you really want to stop directly analysing project \"%s\", please first delete it from SonarQube and then relaunch the analysis of project \"%s\".",
210 rawModuleKey, rawProjectKey, rawModuleKey, rawProjectKey));
214 private void validateModuleKeyIsNotAlreadyUsedInAnotherProject(ComponentDto baseModule, String rawModuleKey) {
215 if (!baseModule.projectUuid().equals(baseModule.uuid()) && !baseModule.projectUuid().equals(rawProject.getUuid())) {
216 ComponentDto projectModule = componentDao.selectOrFailByUuid(session, baseModule.projectUuid());
217 validationMessages.add(format("Module \"%s\" is already part of project \"%s\"", rawModuleKey, projectModule.getDbKey()));
221 private void validateBatchKey(Component rawComponent) {
222 String batchKey = reportReader.readComponent(rawComponent.getReportAttributes().getRef()).getKey();
223 if (!ComponentKeys.isValidModuleKey(batchKey)) {
224 validationMessages.add(format("\"%s\" is not a valid project or module key. "
225 + "Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.", batchKey));
229 private Optional<ComponentDto> loadBaseComponent(String rawComponentKey) {
230 ComponentDto baseComponent = baseModulesByKey.get(rawComponentKey);
231 if (baseComponent == null) {
232 // Load component from key to be able to detect issue (try to analyze a module, etc.)
233 return componentDao.selectByKey(session, rawComponentKey);
235 return Optional.of(baseComponent);