3 * Copyright (C) 2009-2016 SonarSource SA
4 * mailto:contact 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 com.google.common.base.Optional;
24 import java.util.ArrayList;
25 import java.util.Date;
26 import java.util.List;
28 import org.sonar.api.resources.Qualifiers;
29 import org.sonar.api.resources.Scopes;
30 import org.sonar.api.utils.MessageException;
31 import org.sonar.core.component.ComponentKeys;
32 import org.sonar.db.DbClient;
33 import org.sonar.db.DbSession;
34 import org.sonar.db.component.ComponentDao;
35 import org.sonar.db.component.ComponentDto;
36 import org.sonar.db.component.SnapshotDto;
37 import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
38 import org.sonar.server.computation.task.projectanalysis.batch.BatchReportReader;
39 import org.sonar.server.computation.task.projectanalysis.component.Component;
40 import org.sonar.server.computation.task.projectanalysis.component.ComponentVisitor;
41 import org.sonar.server.computation.task.projectanalysis.component.CrawlerDepthLimit;
42 import org.sonar.server.computation.task.projectanalysis.component.DepthTraversalTypeAwareCrawler;
43 import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolder;
44 import org.sonar.server.computation.task.projectanalysis.component.TypeAwareVisitorAdapter;
45 import org.sonar.server.computation.task.step.ComputationStep;
47 import static com.google.common.collect.FluentIterable.from;
48 import static java.lang.String.format;
49 import static org.sonar.api.utils.DateUtils.formatDateTime;
50 import static org.sonar.db.component.ComponentDtoFunctions.toKey;
53 * Validate project and modules. It will fail in the following cases :
55 * <li>branch is not valid</li>
56 * <li>project or module key is not valid</li>
57 * <li>module key already exists in another project (same module key cannot exists in different projects)</li>
58 * <li>module key is already used as a project key</li>
59 * <li>date of the analysis is before last analysis</li>
62 public class ValidateProjectStep implements ComputationStep {
64 private static final Joiner MESSAGES_JOINER = Joiner.on("\n o ");
66 private final DbClient dbClient;
67 private final BatchReportReader reportReader;
68 private final TreeRootHolder treeRootHolder;
69 private final AnalysisMetadataHolder analysisMetadataHolder;
71 public ValidateProjectStep(DbClient dbClient, BatchReportReader reportReader, TreeRootHolder treeRootHolder, AnalysisMetadataHolder analysisMetadataHolder) {
72 this.dbClient = dbClient;
73 this.reportReader = reportReader;
74 this.treeRootHolder = treeRootHolder;
75 this.analysisMetadataHolder = analysisMetadataHolder;
79 public void execute() {
80 DbSession session = dbClient.openSession(false);
82 Component root = treeRootHolder.getRoot();
83 List<ComponentDto> baseModules = dbClient.componentDao().selectEnabledModulesFromProjectKey(session, root.getKey());
84 Map<String, ComponentDto> baseModulesByKey = from(baseModules).uniqueIndex(toKey());
85 ValidateProjectsVisitor visitor = new ValidateProjectsVisitor(session, dbClient.componentDao(), baseModulesByKey);
86 new DepthTraversalTypeAwareCrawler(visitor).visit(root);
88 if (!visitor.validationMessages.isEmpty()) {
89 throw MessageException.of("Validation of project failed:\n o " + MESSAGES_JOINER.join(visitor.validationMessages));
92 dbClient.closeSession(session);
97 public String getDescription() {
98 return "Validate project";
101 private class ValidateProjectsVisitor extends TypeAwareVisitorAdapter {
102 private final DbSession session;
103 private final ComponentDao componentDao;
104 private final Map<String, ComponentDto> baseModulesByKey;
105 private final List<String> validationMessages = new ArrayList<>();
107 private Component rawProject;
109 public ValidateProjectsVisitor(DbSession session, ComponentDao componentDao, Map<String, ComponentDto> baseModulesByKey) {
110 super(CrawlerDepthLimit.MODULE, ComponentVisitor.Order.PRE_ORDER);
111 this.session = session;
112 this.componentDao = componentDao;
114 this.baseModulesByKey = baseModulesByKey;
118 public void visitProject(Component rawProject) {
119 this.rawProject = rawProject;
120 String rawProjectKey = rawProject.getKey();
122 validateBatchKey(rawProject);
124 Optional<ComponentDto> baseProject = loadBaseComponent(rawProjectKey);
125 validateRootIsProject(baseProject);
126 validateProjectKey(baseProject, rawProjectKey);
127 validateAnalysisDate(baseProject);
130 private void validateRootIsProject(Optional<ComponentDto> baseProject) {
131 if (baseProject.isPresent()) {
132 ComponentDto componentDto = baseProject.get();
133 if (!Qualifiers.PROJECT.equals(componentDto.qualifier()) || !Scopes.PROJECT.equals(componentDto.scope())) {
134 validationMessages.add(format("Component (uuid=%s, key=%s) is not a project", rawProject.getUuid(), rawProject.getKey()));
139 private void validateProjectKey(Optional<ComponentDto> baseProject, String rawProjectKey) {
140 if (baseProject.isPresent() && !baseProject.get().projectUuid().equals(baseProject.get().uuid())) {
141 // Project key is already used as a module of another project
142 ComponentDto anotherBaseProject = componentDao.selectOrFailByUuid(session, baseProject.get().projectUuid());
143 validationMessages.add(format("The project \"%s\" is already defined in SonarQube but as a module of project \"%s\". "
144 + "If you really want to stop directly analysing project \"%s\", please first delete it from SonarQube and then relaunch the analysis of project \"%s\".",
145 rawProjectKey, anotherBaseProject.key(), anotherBaseProject.key(), rawProjectKey));
149 private void validateAnalysisDate(Optional<ComponentDto> baseProject) {
150 if (baseProject.isPresent()) {
151 java.util.Optional<SnapshotDto> snapshotDto = dbClient.snapshotDao().selectLastAnalysisByRootComponentUuid(session, baseProject.get().uuid());
152 long currentAnalysisDate = analysisMetadataHolder.getAnalysisDate();
153 Long lastAnalysisDate = snapshotDto.map(SnapshotDto::getCreatedAt).orElse(null);
154 if (lastAnalysisDate != null && currentAnalysisDate <= lastAnalysisDate) {
155 validationMessages.add(format("Date of analysis cannot be older than the date of the last known analysis on this project. Value: \"%s\". " +
156 "Latest analysis: \"%s\". It's only possible to rebuild the past in a chronological order.",
157 formatDateTime(new Date(currentAnalysisDate)), formatDateTime(new Date(lastAnalysisDate))));
163 public void visitModule(Component rawModule) {
164 String rawProjectKey = rawProject.getKey();
165 String rawModuleKey = rawModule.getKey();
166 validateBatchKey(rawModule);
168 Optional<ComponentDto> baseModule = loadBaseComponent(rawModuleKey);
169 if (!baseModule.isPresent()) {
172 validateModuleIsNotAlreadyUsedAsProject(baseModule.get(), rawProjectKey, rawModuleKey);
173 validateModuleKeyIsNotAlreadyUsedInAnotherProject(baseModule.get(), rawModuleKey);
176 private void validateModuleIsNotAlreadyUsedAsProject(ComponentDto baseModule, String rawProjectKey, String rawModuleKey) {
177 if (baseModule.projectUuid().equals(baseModule.uuid())) {
178 // module is actually a project
179 validationMessages.add(format("The project \"%s\" is already defined in SonarQube but not as a module of project \"%s\". "
180 + "If you really want to stop directly analysing project \"%s\", please first delete it from SonarQube and then relaunch the analysis of project \"%s\".",
181 rawModuleKey, rawProjectKey, rawModuleKey, rawProjectKey));
185 private void validateModuleKeyIsNotAlreadyUsedInAnotherProject(ComponentDto baseModule, String rawModuleKey) {
186 if (!baseModule.projectUuid().equals(baseModule.uuid()) && !baseModule.projectUuid().equals(rawProject.getUuid())) {
187 ComponentDto projectModule = componentDao.selectOrFailByUuid(session, baseModule.projectUuid());
188 validationMessages.add(format("Module \"%s\" is already part of project \"%s\"", rawModuleKey, projectModule.key()));
192 private void validateBatchKey(Component rawComponent) {
193 String batchKey = reportReader.readComponent(rawComponent.getReportAttributes().getRef()).getKey();
194 if (!ComponentKeys.isValidModuleKey(batchKey)) {
195 validationMessages.add(format("\"%s\" is not a valid project or module key. "
196 + "Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.", batchKey));
200 private void validateBranch() {
201 String branch = analysisMetadataHolder.getBranch();
202 if (branch == null) {
205 if (!ComponentKeys.isValidBranch(branch)) {
206 validationMessages.add(format("\"%s\" is not a valid branch name. "
207 + "Allowed characters are alphanumeric, '-', '_', '.' and '/'.", branch));
211 private Optional<ComponentDto> loadBaseComponent(String rawComponentKey) {
212 ComponentDto baseComponent = baseModulesByKey.get(rawComponentKey);
213 if (baseComponent == null) {
214 // Load component from key to be able to detect issue (try to analyze a module, etc.)
215 return componentDao.selectByKey(session, rawComponentKey);
217 return Optional.of(baseComponent);