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.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;
29 import java.util.Collection;
30 import java.util.Date;
32 import java.util.Optional;
34 import java.util.function.Function;
35 import java.util.stream.Collectors;
37 import javax.annotation.CheckForNull;
38 import javax.annotation.Nonnull;
39 import javax.annotation.Nullable;
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.BranchPersisterDelegate;
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;
65 import com.google.common.base.Predicate;
68 * Persist report components
69 * Also feed the components cache {@link DbIdsRepositoryImpl} with component ids
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;
79 private final BranchPersisterDelegate branchPersister;
81 public PersistComponentsStep(DbClient dbClient, TreeRootHolder treeRootHolder,
82 MutableDbIdsRepository dbIdsRepository, System2 system2,
83 MutableDisabledComponentsHolder disabledComponentsHolder, AnalysisMetadataHolder analysisMetadataHolder) {
84 this(dbClient, treeRootHolder, dbIdsRepository, system2, disabledComponentsHolder, analysisMetadataHolder, null);
87 public PersistComponentsStep(DbClient dbClient, TreeRootHolder treeRootHolder,
88 MutableDbIdsRepository dbIdsRepository, System2 system2,
89 MutableDisabledComponentsHolder disabledComponentsHolder, AnalysisMetadataHolder analysisMetadataHolder,
90 @Nullable BranchPersisterDelegate branchPersister) {
91 this.dbClient = dbClient;
92 this.treeRootHolder = treeRootHolder;
93 this.dbIdsRepository = dbIdsRepository;
94 this.system2 = system2;
95 this.disabledComponentsHolder = disabledComponentsHolder;
96 this.analysisMetadataHolder = analysisMetadataHolder;
97 this.branchPersister = branchPersister;
101 public String getDescription() {
102 return "Persist components";
106 public void execute() {
107 try (DbSession dbSession = dbClient.openSession(false)) {
108 ofNullable(branchPersister).ifPresent(p -> p.persist(dbSession));
110 String projectUuid = treeRootHolder.getRoot().getUuid();
112 // safeguard, reset all rows to b-changed=false
113 dbClient.componentDao().resetBChangedForRootComponentUuid(dbSession, projectUuid);
115 Map<String, ComponentDto> existingDtosByKeys = indexExistingDtosByKey(dbSession);
116 boolean isRootPrivate = isRootPrivate(treeRootHolder.getRoot(), existingDtosByKeys);
117 String mainBranchProjectUuid = loadProjectUuidOfMainBranch();
119 // Insert or update the components in database. They are removed from existingDtosByKeys
121 new PathAwareCrawler<>(new PersistComponentStepsVisitor(existingDtosByKeys, dbSession, mainBranchProjectUuid))
122 .visit(treeRootHolder.getRoot());
124 disableRemainingComponents(dbSession, existingDtosByKeys.values());
125 ensureConsistentVisibility(dbSession, projectUuid, isRootPrivate);
132 * See {@link ComponentDto#mainBranchProjectUuid} : value is null on main branches, otherwise it is
133 * the uuid of the main branch.
136 private String loadProjectUuidOfMainBranch() {
137 Optional<Branch> branch = analysisMetadataHolder.getBranch();
138 if (branch.isPresent() && !branch.get().isMain()) {
139 return analysisMetadataHolder.getProject().getUuid();
144 private void disableRemainingComponents(DbSession dbSession, Collection<ComponentDto> dtos) {
145 Set<String> uuids = dtos.stream()
146 .filter(ComponentDto::isEnabled)
147 .map(ComponentDto::uuid)
148 .collect(MoreCollectors.toSet(dtos.size()));
149 dbClient.componentDao().updateBEnabledToFalse(dbSession, uuids);
150 disabledComponentsHolder.setUuids(uuids);
153 private void ensureConsistentVisibility(DbSession dbSession, String projectUuid, boolean isRootPrivate) {
154 dbClient.componentDao().setPrivateForRootComponentUuid(dbSession, projectUuid, isRootPrivate);
157 private static boolean isRootPrivate(Component root, Map<String, ComponentDto> existingDtosByKeys) {
158 String rootKey = root.getKey();
159 ComponentDto rootDto = existingDtosByKeys.get(rootKey);
160 if (rootDto == null) {
161 if (Component.Type.VIEW == root.getType()) {
164 throw new IllegalStateException(String.format("The project '%s' is not stored in the database, during a project analysis.", rootKey));
166 return rootDto.isPrivate();
170 * Returns a mutable map of the components currently persisted in database for the project, including
171 * disabled components.
173 private Map<String, ComponentDto> indexExistingDtosByKey(DbSession session) {
174 return dbClient.componentDao().selectAllComponentsFromProjectKey(session, treeRootHolder.getRoot().getKey())
176 .collect(Collectors.toMap(ComponentDto::getDbKey, Function.identity()));
179 private class PersistComponentStepsVisitor extends PathAwareVisitorAdapter<ComponentDtoHolder> {
181 private final Map<String, ComponentDto> existingComponentDtosByKey;
182 private final DbSession dbSession;
184 private final String mainBranchProjectUuid;
186 PersistComponentStepsVisitor(Map<String, ComponentDto> existingComponentDtosByKey, DbSession dbSession, @Nullable String mainBranchProjectUuid) {
188 CrawlerDepthLimit.LEAVES,
190 new SimpleStackElementFactory<ComponentDtoHolder>() {
192 public ComponentDtoHolder createForAny(Component component) {
193 return new ComponentDtoHolder();
197 public ComponentDtoHolder createForFile(Component file) {
198 // no need to create holder for file since they are always leaves of the Component tree
203 public ComponentDtoHolder createForProjectView(Component projectView) {
204 // no need to create holder for file since they are always leaves of the Component tree
208 this.existingComponentDtosByKey = existingComponentDtosByKey;
209 this.dbSession = dbSession;
210 this.mainBranchProjectUuid = mainBranchProjectUuid;
214 public void visitProject(Component project, Path<ComponentDtoHolder> path) {
215 ComponentDto dto = createForProject(project);
216 path.current().setDto(persistAndPopulateCache(project, dto));
220 public void visitModule(Component module, Path<ComponentDtoHolder> path) {
221 ComponentDto dto = createForModule(module, path);
222 path.current().setDto(persistAndPopulateCache(module, dto));
226 public void visitDirectory(Component directory, Path<ComponentDtoHolder> path) {
227 ComponentDto dto = createForDirectory(directory, path);
228 path.current().setDto(persistAndPopulateCache(directory, dto));
232 public void visitFile(Component file, Path<ComponentDtoHolder> path) {
233 ComponentDto dto = createForFile(file, path);
234 persistAndPopulateCache(file, dto);
238 public void visitView(Component view, Path<ComponentDtoHolder> path) {
239 ComponentDto dto = createForView(view);
240 path.current().setDto(persistAndPopulateCache(view, dto));
244 public void visitSubView(Component subView, Path<ComponentDtoHolder> path) {
245 ComponentDto dto = createForSubView(subView, path);
246 path.current().setDto(persistAndPopulateCache(subView, dto));
250 public void visitProjectView(Component projectView, Path<ComponentDtoHolder> path) {
251 ComponentDto dto = createForProjectView(projectView, path);
252 persistAndPopulateCache(projectView, dto);
255 private ComponentDto persistAndPopulateCache(Component component, ComponentDto dto) {
256 ComponentDto projectDto = persistComponent(dto);
257 addToCache(component, projectDto);
261 private ComponentDto persistComponent(ComponentDto componentDto) {
262 ComponentDto existingComponent = existingComponentDtosByKey.remove(componentDto.getDbKey());
263 if (existingComponent == null) {
264 dbClient.componentDao().insert(dbSession, componentDto);
267 Optional<ComponentUpdateDto> update = compareForUpdate(existingComponent, componentDto);
268 if (update.isPresent()) {
269 ComponentUpdateDto updateDto = update.get();
270 dbClient.componentDao().update(dbSession, updateDto);
272 // update the fields in memory in order the PathAwareVisitor.Path
274 existingComponent.setCopyComponentUuid(updateDto.getBCopyComponentUuid());
275 existingComponent.setDescription(updateDto.getBDescription());
276 existingComponent.setEnabled(updateDto.isBEnabled());
277 existingComponent.setUuidPath(updateDto.getBUuidPath());
278 existingComponent.setLanguage(updateDto.getBLanguage());
279 existingComponent.setLongName(updateDto.getBLongName());
280 existingComponent.setModuleUuid(updateDto.getBModuleUuid());
281 existingComponent.setModuleUuidPath(updateDto.getBModuleUuidPath());
282 existingComponent.setName(updateDto.getBName());
283 existingComponent.setPath(updateDto.getBPath());
284 existingComponent.setQualifier(updateDto.getBQualifier());
286 return existingComponent;
289 private void addToCache(Component component, ComponentDto componentDto) {
290 dbIdsRepository.setComponentId(component, componentDto.getId());
293 public ComponentDto createForProject(Component project) {
294 ComponentDto res = createBase(project);
296 res.setScope(Scopes.PROJECT);
297 res.setQualifier(Qualifiers.PROJECT);
298 res.setName(project.getName());
299 res.setLongName(res.name());
300 res.setDescription(project.getDescription());
302 res.setProjectUuid(res.uuid());
303 res.setRootUuid(res.uuid());
304 res.setUuidPath(UUID_PATH_OF_ROOT);
305 res.setModuleUuidPath(UUID_PATH_SEPARATOR + res.uuid() + UUID_PATH_SEPARATOR);
310 public ComponentDto createForModule(Component module, PathAwareVisitor.Path<ComponentDtoHolder> path) {
311 ComponentDto res = createBase(module);
313 res.setScope(Scopes.PROJECT);
314 res.setQualifier(Qualifiers.MODULE);
315 res.setName(module.getName());
316 res.setLongName(res.name());
317 res.setPath(module.getReportAttributes().getPath());
318 res.setDescription(module.getDescription());
320 setRootAndParentModule(res, path);
325 public ComponentDto createForDirectory(Component directory, PathAwareVisitor.Path<ComponentDtoHolder> path) {
326 ComponentDto res = createBase(directory);
328 res.setScope(Scopes.DIRECTORY);
329 res.setQualifier(Qualifiers.DIRECTORY);
330 res.setName(directory.getReportAttributes().getPath());
331 res.setLongName(directory.getReportAttributes().getPath());
332 res.setPath(directory.getReportAttributes().getPath());
334 setParentModuleProperties(res, path);
339 public ComponentDto createForFile(Component file, PathAwareVisitor.Path<ComponentDtoHolder> path) {
340 ComponentDto res = createBase(file);
342 res.setScope(Scopes.FILE);
343 res.setQualifier(getFileQualifier(file));
344 res.setName(FilenameUtils.getName(file.getReportAttributes().getPath()));
345 res.setLongName(file.getReportAttributes().getPath());
346 res.setPath(file.getReportAttributes().getPath());
347 res.setLanguage(file.getFileAttributes().getLanguageKey());
349 setParentModuleProperties(res, path);
354 private ComponentDto createForView(Component view) {
355 ComponentDto res = createBase(view);
357 res.setScope(Scopes.PROJECT);
358 res.setQualifier(view.getViewAttributes().getType().getQualifier());
359 res.setName(view.getName());
360 res.setDescription(view.getDescription());
361 res.setLongName(res.name());
363 res.setProjectUuid(res.uuid());
364 res.setRootUuid(res.uuid());
365 res.setUuidPath(UUID_PATH_OF_ROOT);
366 res.setModuleUuidPath(UUID_PATH_SEPARATOR + res.uuid() + UUID_PATH_SEPARATOR);
371 private ComponentDto createForSubView(Component subView, PathAwareVisitor.Path<ComponentDtoHolder> path) {
372 ComponentDto res = createBase(subView);
374 res.setScope(Scopes.PROJECT);
375 res.setQualifier(Qualifiers.SUBVIEW);
376 res.setName(subView.getName());
377 res.setDescription(subView.getDescription());
378 res.setLongName(res.name());
379 res.setCopyComponentUuid(subView.getSubViewAttributes().getOriginalViewUuid());
381 setRootAndParentModule(res, path);
386 private ComponentDto createForProjectView(Component projectView, PathAwareVisitor.Path<ComponentDtoHolder> path) {
387 ComponentDto res = createBase(projectView);
389 res.setScope(Scopes.FILE);
390 res.setQualifier(Qualifiers.PROJECT);
391 res.setName(projectView.getName());
392 res.setLongName(res.name());
393 res.setCopyComponentUuid(projectView.getProjectViewAttributes().getProjectUuid());
395 setRootAndParentModule(res, path);
400 private ComponentDto createBase(Component component) {
401 String componentKey = component.getKey();
402 String componentUuid = component.getUuid();
404 ComponentDto componentDto = new ComponentDto();
405 componentDto.setOrganizationUuid(analysisMetadataHolder.getOrganization().getUuid());
406 componentDto.setUuid(componentUuid);
407 componentDto.setDbKey(componentKey);
408 componentDto.setDeprecatedKey(componentKey);
409 componentDto.setMainBranchProjectUuid(mainBranchProjectUuid);
410 componentDto.setEnabled(true);
411 componentDto.setCreatedAt(new Date(system2.now()));
417 * Applies to a node of type either MODULE, SUBVIEW, PROJECT_VIEW
419 private void setRootAndParentModule(ComponentDto res, PathAwareVisitor.Path<ComponentDtoHolder> path) {
420 ComponentDto rootDto = path.root().getDto();
421 res.setRootUuid(rootDto.uuid());
422 res.setProjectUuid(rootDto.uuid());
424 ComponentDto parentModule = path.parent().getDto();
425 res.setUuidPath(formatUuidPathFromParent(parentModule));
426 res.setModuleUuid(parentModule.uuid());
427 res.setModuleUuidPath(parentModule.moduleUuidPath() + res.uuid() + UUID_PATH_SEPARATOR);
432 * Applies to a node of type either DIRECTORY or FILE
434 private static void setParentModuleProperties(ComponentDto componentDto, PathAwareVisitor.Path<ComponentDtoHolder> path) {
435 componentDto.setProjectUuid(path.root().getDto().uuid());
437 ComponentDto parentModule = from(path.getCurrentPath())
438 .filter(ParentModulePathElement.INSTANCE)
441 .getElement().getDto();
442 componentDto.setUuidPath(formatUuidPathFromParent(path.parent().getDto()));
443 componentDto.setRootUuid(parentModule.uuid());
444 componentDto.setModuleUuid(parentModule.uuid());
445 componentDto.setModuleUuidPath(parentModule.moduleUuidPath());
449 private static Optional<ComponentUpdateDto> compareForUpdate(ComponentDto existing, ComponentDto target) {
450 boolean hasDifferences = !StringUtils.equals(existing.getCopyResourceUuid(), target.getCopyResourceUuid()) ||
451 !StringUtils.equals(existing.description(), target.description()) ||
452 !existing.isEnabled() ||
453 !StringUtils.equals(existing.getUuidPath(), target.getUuidPath()) ||
454 !StringUtils.equals(existing.language(), target.language()) ||
455 !StringUtils.equals(existing.longName(), target.longName()) ||
456 !StringUtils.equals(existing.moduleUuid(), target.moduleUuid()) ||
457 !StringUtils.equals(existing.moduleUuidPath(), target.moduleUuidPath()) ||
458 !StringUtils.equals(existing.name(), target.name()) ||
459 !StringUtils.equals(existing.path(), target.path()) ||
460 !StringUtils.equals(existing.qualifier(), target.qualifier());
462 ComponentUpdateDto update = null;
463 if (hasDifferences) {
464 update = ComponentUpdateDto
468 return ofNullable(update);
471 private static String getFileQualifier(Component component) {
472 return component.getFileAttributes().isUnitTest() ? Qualifiers.UNIT_TEST_FILE : Qualifiers.FILE;
475 private static class ComponentDtoHolder {
476 private ComponentDto dto;
478 public ComponentDto getDto() {
482 public void setDto(ComponentDto dto) {
487 private enum ParentModulePathElement implements Predicate<PathAwareVisitor.PathElement<ComponentDtoHolder>> {
491 public boolean apply(@Nonnull PathAwareVisitor.PathElement<ComponentDtoHolder> input) {
492 return input.getComponent().getType() == Component.Type.MODULE
493 || input.getComponent().getType() == Component.Type.PROJECT;