]> source.dussan.org Git - sonarqube.git/blob
77417850dad6ae69be5b64ad7e16c4908bc1d014
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2017 SonarSource SA
4  * mailto:info 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 static com.google.common.collect.FluentIterable.from;
23 import static java.lang.String.format;
24 import static org.sonar.api.utils.DateUtils.formatDateTime;
25
26 import java.util.ArrayList;
27 import java.util.Date;
28 import java.util.List;
29 import java.util.Map;
30
31 import javax.annotation.Nullable;
32
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;
52
53 import com.google.common.base.Joiner;
54 import com.google.common.base.Optional;
55
56 /**
57  * Validate project and modules. It will fail in the following cases :
58  * <ol>
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>
64  * </ol>
65  */
66 public class ValidateProjectStep implements ComputationStep {
67
68   private static final Joiner MESSAGES_JOINER = Joiner.on("\n  o ");
69
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;
75
76   public ValidateProjectStep(DbClient dbClient, BatchReportReader reportReader, TreeRootHolder treeRootHolder, AnalysisMetadataHolder analysisMetadataHolder) {
77     this(dbClient, reportReader, treeRootHolder, analysisMetadataHolder, null);
78   }
79
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;
87   }
88
89   @Override
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);
97
98       if (!visitor.validationMessages.isEmpty()) {
99         throw MessageException.of("Validation of project failed:\n  o " + MESSAGES_JOINER.join(visitor.validationMessages));
100       }
101     }
102   }
103
104   @Override
105   public String getDescription() {
106     return "Validate project";
107   }
108
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<>();
114
115     private Component rawProject;
116
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;
121
122       this.baseModulesByKey = baseModulesByKey;
123     }
124
125     @Override
126     public void visitProject(Component rawProject) {
127       this.rawProject = rawProject;
128       String rawProjectKey = rawProject.getKey();
129       validateIncremental(rawProjectKey);
130       validateNotIncrementalAndFirstAnalysis(rawProjectKey);
131       validateBatchKey(rawProject);
132
133       Optional<ComponentDto> baseProject = loadBaseComponent(rawProjectKey);
134       validateRootIsProject(baseProject);
135       validateProjectKey(baseProject, rawProjectKey);
136       validateAnalysisDate(baseProject);
137     }
138
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()));
145         }
146       }
147     }
148
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));
157         }
158       }
159     }
160
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));
165       }
166     }
167
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));
175       }
176     }
177
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))));
187         }
188       }
189     }
190
191     @Override
192     public void visitModule(Component rawModule) {
193       String rawProjectKey = rawProject.getKey();
194       String rawModuleKey = rawModule.getKey();
195       validateBatchKey(rawModule);
196
197       Optional<ComponentDto> baseModule = loadBaseComponent(rawModuleKey);
198       if (!baseModule.isPresent()) {
199         return;
200       }
201       validateModuleIsNotAlreadyUsedAsProject(baseModule.get(), rawProjectKey, rawModuleKey);
202       validateModuleKeyIsNotAlreadyUsedInAnotherProject(baseModule.get(), rawModuleKey);
203     }
204
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));
211       }
212     }
213
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()));
218       }
219     }
220
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));
226       }
227     }
228
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);
234       }
235       return Optional.of(baseComponent);
236     }
237   }
238
239 }