]> source.dussan.org Git - sonarqube.git/blob
78bfa76f1eec19b61f657f3e9fe92d8cfb36f289
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2016 SonarSource SA
4  * mailto:contact 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.base.Joiner;
23 import com.google.common.base.Optional;
24 import java.util.ArrayList;
25 import java.util.Date;
26 import java.util.List;
27 import java.util.Map;
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;
46
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;
51
52 /**
53  * Validate project and modules. It will fail in the following cases :
54  * <ol>
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>
60  * </ol>
61  */
62 public class ValidateProjectStep implements ComputationStep {
63
64   private static final Joiner MESSAGES_JOINER = Joiner.on("\n  o ");
65
66   private final DbClient dbClient;
67   private final BatchReportReader reportReader;
68   private final TreeRootHolder treeRootHolder;
69   private final AnalysisMetadataHolder analysisMetadataHolder;
70
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;
76   }
77
78   @Override
79   public void execute() {
80     DbSession session = dbClient.openSession(false);
81     try {
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);
87
88       if (!visitor.validationMessages.isEmpty()) {
89         throw MessageException.of("Validation of project failed:\n  o " + MESSAGES_JOINER.join(visitor.validationMessages));
90       }
91     } finally {
92       dbClient.closeSession(session);
93     }
94   }
95
96   @Override
97   public String getDescription() {
98     return "Validate project";
99   }
100
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<>();
106
107     private Component rawProject;
108
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;
113
114       this.baseModulesByKey = baseModulesByKey;
115     }
116
117     @Override
118     public void visitProject(Component rawProject) {
119       this.rawProject = rawProject;
120       String rawProjectKey = rawProject.getKey();
121       validateBranch();
122       validateBatchKey(rawProject);
123
124       Optional<ComponentDto> baseProject = loadBaseComponent(rawProjectKey);
125       validateRootIsProject(baseProject);
126       validateProjectKey(baseProject, rawProjectKey);
127       validateAnalysisDate(baseProject);
128     }
129
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()));
135         }
136       }
137     }
138
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));
146       }
147     }
148
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))));
158         }
159       }
160     }
161
162     @Override
163     public void visitModule(Component rawModule) {
164       String rawProjectKey = rawProject.getKey();
165       String rawModuleKey = rawModule.getKey();
166       validateBatchKey(rawModule);
167
168       Optional<ComponentDto> baseModule = loadBaseComponent(rawModuleKey);
169       if (!baseModule.isPresent()) {
170         return;
171       }
172       validateModuleIsNotAlreadyUsedAsProject(baseModule.get(), rawProjectKey, rawModuleKey);
173       validateModuleKeyIsNotAlreadyUsedInAnotherProject(baseModule.get(), rawModuleKey);
174     }
175
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));
182       }
183     }
184
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()));
189       }
190     }
191
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));
197       }
198     }
199
200     private void validateBranch() {
201       String branch = analysisMetadataHolder.getBranch();
202       if (branch == null) {
203         return;
204       }
205       if (!ComponentKeys.isValidBranch(branch)) {
206         validationMessages.add(format("\"%s\" is not a valid branch name. "
207           + "Allowed characters are alphanumeric, '-', '_', '.' and '/'.", branch));
208       }
209     }
210
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);
216       }
217       return Optional.of(baseComponent);
218     }
219   }
220
221 }