]> source.dussan.org Git - sonarqube.git/blob
216843767c01ac108e9e432ef4626b03b91db727
[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.util.Optional.ofNullable;
24 import static org.sonar.db.component.ComponentDto.UUID_PATH_OF_ROOT;
25 import static org.sonar.db.component.ComponentDto.UUID_PATH_SEPARATOR;
26 import static org.sonar.db.component.ComponentDto.formatUuidPathFromParent;
27 import static org.sonar.server.computation.task.projectanalysis.component.ComponentVisitor.Order.PRE_ORDER;
28
29 import java.util.Collection;
30 import java.util.Date;
31 import java.util.Map;
32 import java.util.Optional;
33 import java.util.Set;
34 import java.util.function.Function;
35 import java.util.stream.Collectors;
36
37 import javax.annotation.CheckForNull;
38 import javax.annotation.Nonnull;
39 import javax.annotation.Nullable;
40
41 import org.apache.commons.io.FilenameUtils;
42 import org.apache.commons.lang.StringUtils;
43 import org.sonar.api.resources.Qualifiers;
44 import org.sonar.api.resources.Scopes;
45 import org.sonar.api.utils.System2;
46 import org.sonar.core.util.stream.MoreCollectors;
47 import org.sonar.db.DbClient;
48 import org.sonar.db.DbSession;
49 import org.sonar.db.component.ComponentDto;
50 import org.sonar.db.component.ComponentUpdateDto;
51 import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
52 import org.sonar.server.computation.task.projectanalysis.analysis.Branch;
53 import org.sonar.server.computation.task.projectanalysis.component.BranchPersister;
54 import org.sonar.server.computation.task.projectanalysis.component.Component;
55 import org.sonar.server.computation.task.projectanalysis.component.CrawlerDepthLimit;
56 import org.sonar.server.computation.task.projectanalysis.component.DbIdsRepositoryImpl;
57 import org.sonar.server.computation.task.projectanalysis.component.MutableDbIdsRepository;
58 import org.sonar.server.computation.task.projectanalysis.component.MutableDisabledComponentsHolder;
59 import org.sonar.server.computation.task.projectanalysis.component.PathAwareCrawler;
60 import org.sonar.server.computation.task.projectanalysis.component.PathAwareVisitor;
61 import org.sonar.server.computation.task.projectanalysis.component.PathAwareVisitorAdapter;
62 import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolder;
63 import org.sonar.server.computation.task.step.ComputationStep;
64
65 import com.google.common.base.Predicate;
66
67 /**
68  * Persist report components
69  * Also feed the components cache {@link DbIdsRepositoryImpl} with component ids
70  */
71 public class PersistComponentsStep implements ComputationStep {
72   private final DbClient dbClient;
73   private final TreeRootHolder treeRootHolder;
74   private final MutableDbIdsRepository dbIdsRepository;
75   private final System2 system2;
76   private final MutableDisabledComponentsHolder disabledComponentsHolder;
77   private final AnalysisMetadataHolder analysisMetadataHolder;
78   private final BranchPersister branchPersister;
79
80   public PersistComponentsStep(DbClient dbClient, TreeRootHolder treeRootHolder,
81     MutableDbIdsRepository dbIdsRepository, System2 system2,
82     MutableDisabledComponentsHolder disabledComponentsHolder, AnalysisMetadataHolder analysisMetadataHolder,
83     BranchPersister branchPersister) {
84     this.dbClient = dbClient;
85     this.treeRootHolder = treeRootHolder;
86     this.dbIdsRepository = dbIdsRepository;
87     this.system2 = system2;
88     this.disabledComponentsHolder = disabledComponentsHolder;
89     this.analysisMetadataHolder = analysisMetadataHolder;
90     this.branchPersister = branchPersister;
91   }
92
93   @Override
94   public String getDescription() {
95     return "Persist components";
96   }
97
98   @Override
99   public void execute() {
100     try (DbSession dbSession = dbClient.openSession(false)) {
101       branchPersister.persist(dbSession);
102
103       String projectUuid = treeRootHolder.getRoot().getUuid();
104
105       // safeguard, reset all rows to b-changed=false
106       dbClient.componentDao().resetBChangedForRootComponentUuid(dbSession, projectUuid);
107
108       Map<String, ComponentDto> existingDtosByKeys = indexExistingDtosByKey(dbSession);
109       boolean isRootPrivate = isRootPrivate(treeRootHolder.getRoot(), existingDtosByKeys);
110       String mainBranchProjectUuid = loadProjectUuidOfMainBranch();
111
112       // Insert or update the components in database. They are removed from existingDtosByKeys
113       // at the same time.
114       new PathAwareCrawler<>(new PersistComponentStepsVisitor(existingDtosByKeys, dbSession, mainBranchProjectUuid))
115         .visit(treeRootHolder.getRoot());
116
117       disableRemainingComponents(dbSession, existingDtosByKeys.values());
118       ensureConsistentVisibility(dbSession, projectUuid, isRootPrivate);
119
120       dbSession.commit();
121     }
122   }
123
124   /**
125    * See {@link ComponentDto#mainBranchProjectUuid} : value is null on main branches, otherwise it is
126    * the uuid of the main branch.
127    */
128   @CheckForNull
129   private String loadProjectUuidOfMainBranch() {
130     Optional<Branch> branch = analysisMetadataHolder.getBranch();
131     if (branch.isPresent() && !branch.get().isMain()) {
132       return analysisMetadataHolder.getProject().getUuid();
133     }
134     return null;
135   }
136
137   private void disableRemainingComponents(DbSession dbSession, Collection<ComponentDto> dtos) {
138     Set<String> uuids = dtos.stream()
139       .filter(ComponentDto::isEnabled)
140       .map(ComponentDto::uuid)
141       .collect(MoreCollectors.toSet(dtos.size()));
142     dbClient.componentDao().updateBEnabledToFalse(dbSession, uuids);
143     disabledComponentsHolder.setUuids(uuids);
144   }
145
146   private void ensureConsistentVisibility(DbSession dbSession, String projectUuid, boolean isRootPrivate) {
147     dbClient.componentDao().setPrivateForRootComponentUuid(dbSession, projectUuid, isRootPrivate);
148   }
149
150   private static boolean isRootPrivate(Component root, Map<String, ComponentDto> existingDtosByKeys) {
151     String rootKey = root.getKey();
152     ComponentDto rootDto = existingDtosByKeys.get(rootKey);
153     if (rootDto == null) {
154       if (Component.Type.VIEW == root.getType()) {
155         return false;
156       }
157       throw new IllegalStateException(String.format("The project '%s' is not stored in the database, during a project analysis.", rootKey));
158     }
159     return rootDto.isPrivate();
160   }
161
162   /**
163    * Returns a mutable map of the components currently persisted in database for the project, including
164    * disabled components.
165    */
166   private Map<String, ComponentDto> indexExistingDtosByKey(DbSession session) {
167     return dbClient.componentDao().selectAllComponentsFromProjectKey(session, treeRootHolder.getRoot().getKey())
168       .stream()
169       .collect(Collectors.toMap(ComponentDto::getDbKey, Function.identity()));
170   }
171
172   private class PersistComponentStepsVisitor extends PathAwareVisitorAdapter<ComponentDtoHolder> {
173
174     private final Map<String, ComponentDto> existingComponentDtosByKey;
175     private final DbSession dbSession;
176     @Nullable
177     private final String mainBranchProjectUuid;
178
179     PersistComponentStepsVisitor(Map<String, ComponentDto> existingComponentDtosByKey, DbSession dbSession, @Nullable String mainBranchProjectUuid) {
180       super(
181         CrawlerDepthLimit.LEAVES,
182         PRE_ORDER,
183         new SimpleStackElementFactory<ComponentDtoHolder>() {
184           @Override
185           public ComponentDtoHolder createForAny(Component component) {
186             return new ComponentDtoHolder();
187           }
188
189           @Override
190           public ComponentDtoHolder createForFile(Component file) {
191             // no need to create holder for file since they are always leaves of the Component tree
192             return null;
193           }
194
195           @Override
196           public ComponentDtoHolder createForProjectView(Component projectView) {
197             // no need to create holder for file since they are always leaves of the Component tree
198             return null;
199           }
200         });
201       this.existingComponentDtosByKey = existingComponentDtosByKey;
202       this.dbSession = dbSession;
203       this.mainBranchProjectUuid = mainBranchProjectUuid;
204     }
205
206     @Override
207     public void visitProject(Component project, Path<ComponentDtoHolder> path) {
208       ComponentDto dto = createForProject(project);
209       path.current().setDto(persistAndPopulateCache(project, dto));
210     }
211
212     @Override
213     public void visitModule(Component module, Path<ComponentDtoHolder> path) {
214       ComponentDto dto = createForModule(module, path);
215       path.current().setDto(persistAndPopulateCache(module, dto));
216     }
217
218     @Override
219     public void visitDirectory(Component directory, Path<ComponentDtoHolder> path) {
220       ComponentDto dto = createForDirectory(directory, path);
221       path.current().setDto(persistAndPopulateCache(directory, dto));
222     }
223
224     @Override
225     public void visitFile(Component file, Path<ComponentDtoHolder> path) {
226       ComponentDto dto = createForFile(file, path);
227       persistAndPopulateCache(file, dto);
228     }
229
230     @Override
231     public void visitView(Component view, Path<ComponentDtoHolder> path) {
232       ComponentDto dto = createForView(view);
233       path.current().setDto(persistAndPopulateCache(view, dto));
234     }
235
236     @Override
237     public void visitSubView(Component subView, Path<ComponentDtoHolder> path) {
238       ComponentDto dto = createForSubView(subView, path);
239       path.current().setDto(persistAndPopulateCache(subView, dto));
240     }
241
242     @Override
243     public void visitProjectView(Component projectView, Path<ComponentDtoHolder> path) {
244       ComponentDto dto = createForProjectView(projectView, path);
245       persistAndPopulateCache(projectView, dto);
246     }
247
248     private ComponentDto persistAndPopulateCache(Component component, ComponentDto dto) {
249       ComponentDto projectDto = persistComponent(dto);
250       addToCache(component, projectDto);
251       return projectDto;
252     }
253
254     private ComponentDto persistComponent(ComponentDto componentDto) {
255       ComponentDto existingComponent = existingComponentDtosByKey.remove(componentDto.getDbKey());
256       if (existingComponent == null) {
257         dbClient.componentDao().insert(dbSession, componentDto);
258         return componentDto;
259       }
260       Optional<ComponentUpdateDto> update = compareForUpdate(existingComponent, componentDto);
261       if (update.isPresent()) {
262         ComponentUpdateDto updateDto = update.get();
263         dbClient.componentDao().update(dbSession, updateDto);
264
265         // update the fields in memory in order the PathAwareVisitor.Path
266         // to be up-to-date
267         existingComponent.setCopyComponentUuid(updateDto.getBCopyComponentUuid());
268         existingComponent.setDescription(updateDto.getBDescription());
269         existingComponent.setEnabled(updateDto.isBEnabled());
270         existingComponent.setUuidPath(updateDto.getBUuidPath());
271         existingComponent.setLanguage(updateDto.getBLanguage());
272         existingComponent.setLongName(updateDto.getBLongName());
273         existingComponent.setModuleUuid(updateDto.getBModuleUuid());
274         existingComponent.setModuleUuidPath(updateDto.getBModuleUuidPath());
275         existingComponent.setName(updateDto.getBName());
276         existingComponent.setPath(updateDto.getBPath());
277         existingComponent.setQualifier(updateDto.getBQualifier());
278       }
279       return existingComponent;
280     }
281
282     private void addToCache(Component component, ComponentDto componentDto) {
283       dbIdsRepository.setComponentId(component, componentDto.getId());
284     }
285
286     public ComponentDto createForProject(Component project) {
287       ComponentDto res = createBase(project);
288
289       res.setScope(Scopes.PROJECT);
290       res.setQualifier(Qualifiers.PROJECT);
291       res.setName(project.getName());
292       res.setLongName(res.name());
293       res.setDescription(project.getDescription());
294
295       res.setProjectUuid(res.uuid());
296       res.setRootUuid(res.uuid());
297       res.setUuidPath(UUID_PATH_OF_ROOT);
298       res.setModuleUuidPath(UUID_PATH_SEPARATOR + res.uuid() + UUID_PATH_SEPARATOR);
299
300       return res;
301     }
302
303     public ComponentDto createForModule(Component module, PathAwareVisitor.Path<ComponentDtoHolder> path) {
304       ComponentDto res = createBase(module);
305
306       res.setScope(Scopes.PROJECT);
307       res.setQualifier(Qualifiers.MODULE);
308       res.setName(module.getName());
309       res.setLongName(res.name());
310       res.setPath(module.getReportAttributes().getPath());
311       res.setDescription(module.getDescription());
312
313       setRootAndParentModule(res, path);
314
315       return res;
316     }
317
318     public ComponentDto createForDirectory(Component directory, PathAwareVisitor.Path<ComponentDtoHolder> path) {
319       ComponentDto res = createBase(directory);
320
321       res.setScope(Scopes.DIRECTORY);
322       res.setQualifier(Qualifiers.DIRECTORY);
323       res.setName(directory.getReportAttributes().getPath());
324       res.setLongName(directory.getReportAttributes().getPath());
325       res.setPath(directory.getReportAttributes().getPath());
326
327       setParentModuleProperties(res, path);
328
329       return res;
330     }
331
332     public ComponentDto createForFile(Component file, PathAwareVisitor.Path<ComponentDtoHolder> path) {
333       ComponentDto res = createBase(file);
334
335       res.setScope(Scopes.FILE);
336       res.setQualifier(getFileQualifier(file));
337       res.setName(FilenameUtils.getName(file.getReportAttributes().getPath()));
338       res.setLongName(file.getReportAttributes().getPath());
339       res.setPath(file.getReportAttributes().getPath());
340       res.setLanguage(file.getFileAttributes().getLanguageKey());
341
342       setParentModuleProperties(res, path);
343
344       return res;
345     }
346
347     private ComponentDto createForView(Component view) {
348       ComponentDto res = createBase(view);
349
350       res.setScope(Scopes.PROJECT);
351       res.setQualifier(view.getViewAttributes().getType().getQualifier());
352       res.setName(view.getName());
353       res.setDescription(view.getDescription());
354       res.setLongName(res.name());
355
356       res.setProjectUuid(res.uuid());
357       res.setRootUuid(res.uuid());
358       res.setUuidPath(UUID_PATH_OF_ROOT);
359       res.setModuleUuidPath(UUID_PATH_SEPARATOR + res.uuid() + UUID_PATH_SEPARATOR);
360
361       return res;
362     }
363
364     private ComponentDto createForSubView(Component subView, PathAwareVisitor.Path<ComponentDtoHolder> path) {
365       ComponentDto res = createBase(subView);
366
367       res.setScope(Scopes.PROJECT);
368       res.setQualifier(Qualifiers.SUBVIEW);
369       res.setName(subView.getName());
370       res.setDescription(subView.getDescription());
371       res.setLongName(res.name());
372       res.setCopyComponentUuid(subView.getSubViewAttributes().getOriginalViewUuid());
373
374       setRootAndParentModule(res, path);
375
376       return res;
377     }
378
379     private ComponentDto createForProjectView(Component projectView, PathAwareVisitor.Path<ComponentDtoHolder> path) {
380       ComponentDto res = createBase(projectView);
381
382       res.setScope(Scopes.FILE);
383       res.setQualifier(Qualifiers.PROJECT);
384       res.setName(projectView.getName());
385       res.setLongName(res.name());
386       res.setCopyComponentUuid(projectView.getProjectViewAttributes().getProjectUuid());
387
388       setRootAndParentModule(res, path);
389
390       return res;
391     }
392
393     private ComponentDto createBase(Component component) {
394       String componentKey = component.getKey();
395       String componentUuid = component.getUuid();
396
397       ComponentDto componentDto = new ComponentDto();
398       componentDto.setOrganizationUuid(analysisMetadataHolder.getOrganization().getUuid());
399       componentDto.setUuid(componentUuid);
400       componentDto.setDbKey(componentKey);
401       componentDto.setDeprecatedKey(componentKey);
402       componentDto.setMainBranchProjectUuid(mainBranchProjectUuid);
403       componentDto.setEnabled(true);
404       componentDto.setCreatedAt(new Date(system2.now()));
405
406       return componentDto;
407     }
408
409     /**
410      * Applies to a node of type either MODULE, SUBVIEW, PROJECT_VIEW
411      */
412     private void setRootAndParentModule(ComponentDto res, PathAwareVisitor.Path<ComponentDtoHolder> path) {
413       ComponentDto rootDto = path.root().getDto();
414       res.setRootUuid(rootDto.uuid());
415       res.setProjectUuid(rootDto.uuid());
416
417       ComponentDto parentModule = path.parent().getDto();
418       res.setUuidPath(formatUuidPathFromParent(parentModule));
419       res.setModuleUuid(parentModule.uuid());
420       res.setModuleUuidPath(parentModule.moduleUuidPath() + res.uuid() + UUID_PATH_SEPARATOR);
421     }
422   }
423
424   /**
425    * Applies to a node of type either DIRECTORY or FILE
426    */
427   private static void setParentModuleProperties(ComponentDto componentDto, PathAwareVisitor.Path<ComponentDtoHolder> path) {
428     componentDto.setProjectUuid(path.root().getDto().uuid());
429
430     ComponentDto parentModule = from(path.getCurrentPath())
431       .filter(ParentModulePathElement.INSTANCE)
432       .first()
433       .get()
434       .getElement().getDto();
435     componentDto.setUuidPath(formatUuidPathFromParent(path.parent().getDto()));
436     componentDto.setRootUuid(parentModule.uuid());
437     componentDto.setModuleUuid(parentModule.uuid());
438     componentDto.setModuleUuidPath(parentModule.moduleUuidPath());
439
440   }
441
442   private static Optional<ComponentUpdateDto> compareForUpdate(ComponentDto existing, ComponentDto target) {
443     boolean hasDifferences = !StringUtils.equals(existing.getCopyResourceUuid(), target.getCopyResourceUuid()) ||
444       !StringUtils.equals(existing.description(), target.description()) ||
445       !existing.isEnabled() ||
446       !StringUtils.equals(existing.getUuidPath(), target.getUuidPath()) ||
447       !StringUtils.equals(existing.language(), target.language()) ||
448       !StringUtils.equals(existing.longName(), target.longName()) ||
449       !StringUtils.equals(existing.moduleUuid(), target.moduleUuid()) ||
450       !StringUtils.equals(existing.moduleUuidPath(), target.moduleUuidPath()) ||
451       !StringUtils.equals(existing.name(), target.name()) ||
452       !StringUtils.equals(existing.path(), target.path()) ||
453       !StringUtils.equals(existing.qualifier(), target.qualifier());
454
455     ComponentUpdateDto update = null;
456     if (hasDifferences) {
457       update = ComponentUpdateDto
458         .copyFrom(target)
459         .setBChanged(true);
460     }
461     return ofNullable(update);
462   }
463
464   private static String getFileQualifier(Component component) {
465     return component.getFileAttributes().isUnitTest() ? Qualifiers.UNIT_TEST_FILE : Qualifiers.FILE;
466   }
467
468   private static class ComponentDtoHolder {
469     private ComponentDto dto;
470
471     public ComponentDto getDto() {
472       return dto;
473     }
474
475     public void setDto(ComponentDto dto) {
476       this.dto = dto;
477     }
478   }
479
480   private enum ParentModulePathElement implements Predicate<PathAwareVisitor.PathElement<ComponentDtoHolder>> {
481     INSTANCE;
482
483     @Override
484     public boolean apply(@Nonnull PathAwareVisitor.PathElement<ComponentDtoHolder> input) {
485       return input.getComponent().getType() == Component.Type.MODULE
486         || input.getComponent().getType() == Component.Type.PROJECT;
487     }
488   }
489
490 }