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.Predicate;
23 import java.util.Collection;
24 import java.util.Date;
26 import java.util.Optional;
27 import java.util.function.Function;
28 import java.util.stream.Collectors;
29 import javax.annotation.Nonnull;
30 import org.apache.commons.io.FilenameUtils;
31 import org.apache.commons.lang.StringUtils;
32 import org.sonar.api.resources.Qualifiers;
33 import org.sonar.api.resources.Scopes;
34 import org.sonar.api.utils.System2;
35 import org.sonar.db.DbClient;
36 import org.sonar.db.DbSession;
37 import org.sonar.db.component.ComponentDto;
38 import org.sonar.db.component.ComponentUpdateDto;
39 import org.sonar.server.computation.task.projectanalysis.component.Component;
40 import org.sonar.server.computation.task.projectanalysis.component.CrawlerDepthLimit;
41 import org.sonar.server.computation.task.projectanalysis.component.DbIdsRepositoryImpl;
42 import org.sonar.server.computation.task.projectanalysis.component.MutableDbIdsRepository;
43 import org.sonar.server.computation.task.projectanalysis.component.MutableDisabledComponentsHolder;
44 import org.sonar.server.computation.task.projectanalysis.component.PathAwareCrawler;
45 import org.sonar.server.computation.task.projectanalysis.component.PathAwareVisitor;
46 import org.sonar.server.computation.task.projectanalysis.component.PathAwareVisitorAdapter;
47 import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolder;
48 import org.sonar.server.computation.task.step.ComputationStep;
50 import static com.google.common.collect.FluentIterable.from;
51 import static org.sonar.core.util.stream.Collectors.toList;
52 import static org.sonar.db.component.ComponentDto.UUID_PATH_SEPARATOR;
53 import static org.sonar.db.component.ComponentDto.formatUuidPathFromParent;
54 import static org.sonar.server.computation.task.projectanalysis.component.ComponentVisitor.Order.PRE_ORDER;
57 * Persist report components
58 * Also feed the components cache {@link DbIdsRepositoryImpl} with component ids
60 public class PersistComponentsStep implements ComputationStep {
61 private final DbClient dbClient;
62 private final TreeRootHolder treeRootHolder;
63 private final MutableDbIdsRepository dbIdsRepository;
64 private final System2 system2;
65 private final MutableDisabledComponentsHolder disabledComponentsHolder;
67 public PersistComponentsStep(DbClient dbClient, TreeRootHolder treeRootHolder,
68 MutableDbIdsRepository dbIdsRepository, System2 system2,
69 MutableDisabledComponentsHolder disabledComponentsHolder) {
70 this.dbClient = dbClient;
71 this.treeRootHolder = treeRootHolder;
72 this.dbIdsRepository = dbIdsRepository;
73 this.system2 = system2;
74 this.disabledComponentsHolder = disabledComponentsHolder;
78 public String getDescription() {
79 return "Persist components";
83 public void execute() {
84 DbSession dbSession = dbClient.openSession(false);
86 String projectUuid = treeRootHolder.getRoot().getUuid();
88 // safeguard, reset all rows to b-changed=false
89 dbClient.componentDao().resetBChangedForRootComponentUuid(dbSession, projectUuid);
91 Map<String, ComponentDto> existingDtosByKeys = indexExistingDtosByKey(dbSession);
92 // Insert or update the components in database. They are removed from existingDtosByKeys
94 new PathAwareCrawler<>(new PersistComponentStepsVisitor(existingDtosByKeys, dbSession))
95 .visit(treeRootHolder.getRoot());
97 disableRemainingComponents(dbSession, existingDtosByKeys.values());
101 dbClient.closeSession(dbSession);
105 private void disableRemainingComponents(DbSession dbSession, Collection<ComponentDto> dtos) {
107 .filter(ComponentDto::isEnabled)
109 ComponentUpdateDto update = ComponentUpdateDto.copyFrom(c)
112 dbClient.componentDao().update(dbSession, update);
114 disabledComponentsHolder.setUuids(dtos.stream().map(ComponentDto::uuid).collect(toList(dtos.size())));
118 * Returns a mutable map of the components currently persisted in database for the project, including
119 * disabled components.
121 private Map<String, ComponentDto> indexExistingDtosByKey(DbSession session) {
122 return dbClient.componentDao().selectAllComponentsFromProjectKey(session, treeRootHolder.getRoot().getKey())
124 .collect(Collectors.toMap(ComponentDto::key, Function.identity()));
127 private class PersistComponentStepsVisitor extends PathAwareVisitorAdapter<ComponentDtoHolder> {
129 private final Map<String, ComponentDto> existingComponentDtosByKey;
130 private final DbSession dbSession;
132 public PersistComponentStepsVisitor(Map<String, ComponentDto> existingComponentDtosByKey, DbSession dbSession) {
134 CrawlerDepthLimit.LEAVES,
136 new SimpleStackElementFactory<ComponentDtoHolder>() {
138 public ComponentDtoHolder createForAny(Component component) {
139 return new ComponentDtoHolder();
143 public ComponentDtoHolder createForFile(Component file) {
144 // no need to create holder for file since they are always leaves of the Component tree
149 public ComponentDtoHolder createForProjectView(Component projectView) {
150 // no need to create holder for file since they are always leaves of the Component tree
154 this.existingComponentDtosByKey = existingComponentDtosByKey;
155 this.dbSession = dbSession;
159 public void visitProject(Component project, Path<ComponentDtoHolder> path) {
160 ComponentDto dto = createForProject(project);
161 path.current().setDto(persistAndPopulateCache(project, dto));
165 public void visitModule(Component module, Path<ComponentDtoHolder> path) {
166 ComponentDto dto = createForModule(module, path);
167 path.current().setDto(persistAndPopulateCache(module, dto));
171 public void visitDirectory(Component directory, Path<ComponentDtoHolder> path) {
172 ComponentDto dto = createForDirectory(directory, path);
173 path.current().setDto(persistAndPopulateCache(directory, dto));
177 public void visitFile(Component file, Path<ComponentDtoHolder> path) {
178 ComponentDto dto = createForFile(file, path);
179 persistAndPopulateCache(file, dto);
183 public void visitView(Component view, Path<ComponentDtoHolder> path) {
184 ComponentDto dto = createForView(view);
185 path.current().setDto(persistAndPopulateCache(view, dto));
189 public void visitSubView(Component subView, Path<ComponentDtoHolder> path) {
190 ComponentDto dto = createForSubView(subView, path);
191 path.current().setDto(persistAndPopulateCache(subView, dto));
195 public void visitProjectView(Component projectView, Path<ComponentDtoHolder> path) {
196 ComponentDto dto = createForProjectView(projectView, path);
197 persistAndPopulateCache(projectView, dto);
200 private ComponentDto persistAndPopulateCache(Component component, ComponentDto dto) {
201 ComponentDto projectDto = persistComponent(dto);
202 addToCache(component, projectDto);
206 private ComponentDto persistComponent(ComponentDto componentDto) {
207 ComponentDto existingComponent = existingComponentDtosByKey.remove(componentDto.getKey());
208 if (existingComponent == null) {
209 dbClient.componentDao().insert(dbSession, componentDto);
212 Optional<ComponentUpdateDto> update = compareForUpdate(existingComponent, componentDto);
213 if (update.isPresent()) {
214 ComponentUpdateDto updateDto = update.get();
215 dbClient.componentDao().update(dbSession, updateDto);
217 // update the fields in memory in order the PathAwareVisitor.Path
219 existingComponent.setCopyComponentUuid(updateDto.getBCopyComponentUuid());
220 existingComponent.setDescription(updateDto.getBDescription());
221 existingComponent.setEnabled(updateDto.isBEnabled());
222 existingComponent.setLanguage(updateDto.getBLanguage());
223 existingComponent.setLongName(updateDto.getBLongName());
224 existingComponent.setModuleUuid(updateDto.getBModuleUuid());
225 existingComponent.setModuleUuidPath(updateDto.getBModuleUuidPath());
226 existingComponent.setName(updateDto.getBName());
227 existingComponent.setPath(updateDto.getBPath());
228 existingComponent.setQualifier(updateDto.getBQualifier());
230 return existingComponent;
233 private void addToCache(Component component, ComponentDto componentDto) {
234 dbIdsRepository.setComponentId(component, componentDto.getId());
238 public ComponentDto createForProject(Component project) {
239 ComponentDto res = createBase(project);
241 res.setScope(Scopes.PROJECT);
242 res.setQualifier(Qualifiers.PROJECT);
243 res.setName(project.getName());
244 res.setLongName(res.name());
245 res.setDescription(project.getDescription());
247 res.setProjectUuid(res.uuid());
248 res.setRootUuid(res.uuid());
249 res.setUuidPath(ComponentDto.UUID_PATH_OF_ROOT);
250 res.setModuleUuidPath(UUID_PATH_SEPARATOR + res.uuid() + UUID_PATH_SEPARATOR);
255 public ComponentDto createForModule(Component module, PathAwareVisitor.Path<ComponentDtoHolder> path) {
256 ComponentDto res = createBase(module);
258 res.setScope(Scopes.PROJECT);
259 res.setQualifier(Qualifiers.MODULE);
260 res.setName(module.getName());
261 res.setLongName(res.name());
262 res.setPath(module.getReportAttributes().getPath());
263 res.setDescription(module.getDescription());
265 setRootAndParentModule(res, path);
270 public ComponentDto createForDirectory(Component directory, PathAwareVisitor.Path<ComponentDtoHolder> path) {
271 ComponentDto res = createBase(directory);
273 res.setScope(Scopes.DIRECTORY);
274 res.setQualifier(Qualifiers.DIRECTORY);
275 res.setName(directory.getReportAttributes().getPath());
276 res.setLongName(directory.getReportAttributes().getPath());
277 res.setPath(directory.getReportAttributes().getPath());
279 setParentModuleProperties(res, path);
284 public ComponentDto createForFile(Component file, PathAwareVisitor.Path<ComponentDtoHolder> path) {
285 ComponentDto res = createBase(file);
287 res.setScope(Scopes.FILE);
288 res.setQualifier(getFileQualifier(file));
289 res.setName(FilenameUtils.getName(file.getReportAttributes().getPath()));
290 res.setLongName(file.getReportAttributes().getPath());
291 res.setPath(file.getReportAttributes().getPath());
292 res.setLanguage(file.getFileAttributes().getLanguageKey());
294 setParentModuleProperties(res, path);
299 private ComponentDto createForView(Component view) {
300 ComponentDto res = createBase(view);
302 res.setScope(Scopes.PROJECT);
303 res.setQualifier(Qualifiers.VIEW);
304 res.setName(view.getName());
305 res.setDescription(view.getDescription());
306 res.setLongName(res.name());
308 res.setProjectUuid(res.uuid());
309 res.setRootUuid(res.uuid());
310 res.setUuidPath(ComponentDto.UUID_PATH_OF_ROOT);
311 res.setModuleUuidPath(UUID_PATH_SEPARATOR + res.uuid() + UUID_PATH_SEPARATOR);
316 private ComponentDto createForSubView(Component subView, PathAwareVisitor.Path<ComponentDtoHolder> path) {
317 ComponentDto res = createBase(subView);
319 res.setScope(Scopes.PROJECT);
320 res.setQualifier(Qualifiers.SUBVIEW);
321 res.setName(subView.getName());
322 res.setDescription(subView.getDescription());
323 res.setLongName(res.name());
325 setRootAndParentModule(res, path);
330 private ComponentDto createForProjectView(Component projectView, PathAwareVisitor.Path<ComponentDtoHolder> path) {
331 ComponentDto res = createBase(projectView);
333 res.setScope(Scopes.FILE);
334 res.setQualifier(Qualifiers.PROJECT);
335 res.setName(projectView.getName());
336 res.setLongName(res.name());
337 res.setCopyComponentUuid(projectView.getProjectViewAttributes().getProjectUuid());
339 setRootAndParentModule(res, path);
344 private ComponentDto createBase(Component component) {
345 String componentKey = component.getKey();
346 String componentUuid = component.getUuid();
348 ComponentDto componentDto = new ComponentDto();
349 componentDto.setUuid(componentUuid);
350 componentDto.setKey(componentKey);
351 componentDto.setDeprecatedKey(componentKey);
352 componentDto.setEnabled(true);
353 componentDto.setCreatedAt(new Date(system2.now()));
358 * Applies to a node of type either MODULE, SUBVIEW, PROJECT_VIEW
360 private static void setRootAndParentModule(ComponentDto res, PathAwareVisitor.Path<ComponentDtoHolder> path) {
361 ComponentDto rootDto = path.root().getDto();
362 res.setRootUuid(rootDto.uuid());
363 res.setProjectUuid(rootDto.uuid());
365 ComponentDto parentModule = path.parent().getDto();
366 res.setUuidPath(formatUuidPathFromParent(parentModule));
367 res.setModuleUuid(parentModule.uuid());
368 res.setModuleUuidPath(parentModule.moduleUuidPath() + res.uuid() + UUID_PATH_SEPARATOR);
372 * Applies to a node of type either DIRECTORY or FILE
374 private static void setParentModuleProperties(ComponentDto componentDto, PathAwareVisitor.Path<ComponentDtoHolder> path) {
375 componentDto.setProjectUuid(path.root().getDto().uuid());
377 ComponentDto parentModule = from(path.getCurrentPath())
378 .filter(ParentModulePathElement.INSTANCE)
381 .getElement().getDto();
382 componentDto.setUuidPath(formatUuidPathFromParent(path.parent().getDto()));
383 componentDto.setRootUuid(parentModule.uuid());
384 componentDto.setModuleUuid(parentModule.uuid());
385 componentDto.setModuleUuidPath(parentModule.moduleUuidPath());
389 private static Optional<ComponentUpdateDto> compareForUpdate(ComponentDto existing, ComponentDto target) {
390 boolean hasDifferences = !StringUtils.equals(existing.getCopyResourceUuid(), target.getCopyResourceUuid()) ||
391 !StringUtils.equals(existing.description(), target.description()) ||
392 !existing.isEnabled() ||
393 !StringUtils.equals(existing.language(), target.language()) ||
394 !StringUtils.equals(existing.longName(), target.longName()) ||
395 !StringUtils.equals(existing.moduleUuid(), target.moduleUuid()) ||
396 !StringUtils.equals(existing.moduleUuidPath(), target.moduleUuidPath()) ||
397 !StringUtils.equals(existing.name(), target.name()) ||
398 !StringUtils.equals(existing.path(), target.path()) ||
399 !StringUtils.equals(existing.qualifier(), target.qualifier());
401 ComponentUpdateDto update = null;
402 if (hasDifferences) {
403 update = ComponentUpdateDto
407 return Optional.ofNullable(update);
410 private static String getFileQualifier(Component component) {
411 return component.getFileAttributes().isUnitTest() ? Qualifiers.UNIT_TEST_FILE : Qualifiers.FILE;
414 private static class ComponentDtoHolder {
415 private ComponentDto dto;
417 public ComponentDto getDto() {
421 public void setDto(ComponentDto dto) {
426 private enum ParentModulePathElement implements Predicate<PathAwareVisitor.PathElement<ComponentDtoHolder>> {
430 public boolean apply(@Nonnull PathAwareVisitor.PathElement<ComponentDtoHolder> input) {
431 return input.getComponent().getType() == Component.Type.MODULE
432 || input.getComponent().getType() == Component.Type.PROJECT;