]> source.dussan.org Git - sonarqube.git/blob
9e84defc794ee56094dc7cb56695867dfc1598b0
[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.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;
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   @Nullable
79   private final BranchPersisterDelegate branchPersister;
80
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);
85   }
86
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;
98   }
99
100   @Override
101   public String getDescription() {
102     return "Persist components";
103   }
104
105   @Override
106   public void execute() {
107     try (DbSession dbSession = dbClient.openSession(false)) {
108       ofNullable(branchPersister).ifPresent(p -> p.persist(dbSession));
109
110       String projectUuid = treeRootHolder.getRoot().getUuid();
111
112       // safeguard, reset all rows to b-changed=false
113       dbClient.componentDao().resetBChangedForRootComponentUuid(dbSession, projectUuid);
114
115       Map<String, ComponentDto> existingDtosByKeys = indexExistingDtosByKey(dbSession);
116       boolean isRootPrivate = isRootPrivate(treeRootHolder.getRoot(), existingDtosByKeys);
117       String mainBranchProjectUuid = loadProjectUuidOfMainBranch();
118
119       // Insert or update the components in database. They are removed from existingDtosByKeys
120       // at the same time.
121       new PathAwareCrawler<>(new PersistComponentStepsVisitor(existingDtosByKeys, dbSession, mainBranchProjectUuid))
122         .visit(treeRootHolder.getRoot());
123
124       disableRemainingComponents(dbSession, existingDtosByKeys.values());
125       ensureConsistentVisibility(dbSession, projectUuid, isRootPrivate);
126
127       dbSession.commit();
128     }
129   }
130
131   /**
132    * See {@link ComponentDto#mainBranchProjectUuid} : value is null on main branches, otherwise it is
133    * the uuid of the main branch.
134    */
135   @CheckForNull
136   private String loadProjectUuidOfMainBranch() {
137     Optional<Branch> branch = analysisMetadataHolder.getBranch();
138     if (branch.isPresent() && !branch.get().isMain()) {
139       return analysisMetadataHolder.getProject().getUuid();
140     }
141     return null;
142   }
143
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);
151   }
152
153   private void ensureConsistentVisibility(DbSession dbSession, String projectUuid, boolean isRootPrivate) {
154     dbClient.componentDao().setPrivateForRootComponentUuid(dbSession, projectUuid, isRootPrivate);
155   }
156
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()) {
162         return false;
163       }
164       throw new IllegalStateException(String.format("The project '%s' is not stored in the database, during a project analysis.", rootKey));
165     }
166     return rootDto.isPrivate();
167   }
168
169   /**
170    * Returns a mutable map of the components currently persisted in database for the project, including
171    * disabled components.
172    */
173   private Map<String, ComponentDto> indexExistingDtosByKey(DbSession session) {
174     return dbClient.componentDao().selectAllComponentsFromProjectKey(session, treeRootHolder.getRoot().getKey())
175       .stream()
176       .collect(Collectors.toMap(ComponentDto::getDbKey, Function.identity()));
177   }
178
179   private class PersistComponentStepsVisitor extends PathAwareVisitorAdapter<ComponentDtoHolder> {
180
181     private final Map<String, ComponentDto> existingComponentDtosByKey;
182     private final DbSession dbSession;
183     @Nullable
184     private final String mainBranchProjectUuid;
185
186     PersistComponentStepsVisitor(Map<String, ComponentDto> existingComponentDtosByKey, DbSession dbSession, @Nullable String mainBranchProjectUuid) {
187       super(
188         CrawlerDepthLimit.LEAVES,
189         PRE_ORDER,
190         new SimpleStackElementFactory<ComponentDtoHolder>() {
191           @Override
192           public ComponentDtoHolder createForAny(Component component) {
193             return new ComponentDtoHolder();
194           }
195
196           @Override
197           public ComponentDtoHolder createForFile(Component file) {
198             // no need to create holder for file since they are always leaves of the Component tree
199             return null;
200           }
201
202           @Override
203           public ComponentDtoHolder createForProjectView(Component projectView) {
204             // no need to create holder for file since they are always leaves of the Component tree
205             return null;
206           }
207         });
208       this.existingComponentDtosByKey = existingComponentDtosByKey;
209       this.dbSession = dbSession;
210       this.mainBranchProjectUuid = mainBranchProjectUuid;
211     }
212
213     @Override
214     public void visitProject(Component project, Path<ComponentDtoHolder> path) {
215       ComponentDto dto = createForProject(project);
216       path.current().setDto(persistAndPopulateCache(project, dto));
217     }
218
219     @Override
220     public void visitModule(Component module, Path<ComponentDtoHolder> path) {
221       ComponentDto dto = createForModule(module, path);
222       path.current().setDto(persistAndPopulateCache(module, dto));
223     }
224
225     @Override
226     public void visitDirectory(Component directory, Path<ComponentDtoHolder> path) {
227       ComponentDto dto = createForDirectory(directory, path);
228       path.current().setDto(persistAndPopulateCache(directory, dto));
229     }
230
231     @Override
232     public void visitFile(Component file, Path<ComponentDtoHolder> path) {
233       ComponentDto dto = createForFile(file, path);
234       persistAndPopulateCache(file, dto);
235     }
236
237     @Override
238     public void visitView(Component view, Path<ComponentDtoHolder> path) {
239       ComponentDto dto = createForView(view);
240       path.current().setDto(persistAndPopulateCache(view, dto));
241     }
242
243     @Override
244     public void visitSubView(Component subView, Path<ComponentDtoHolder> path) {
245       ComponentDto dto = createForSubView(subView, path);
246       path.current().setDto(persistAndPopulateCache(subView, dto));
247     }
248
249     @Override
250     public void visitProjectView(Component projectView, Path<ComponentDtoHolder> path) {
251       ComponentDto dto = createForProjectView(projectView, path);
252       persistAndPopulateCache(projectView, dto);
253     }
254
255     private ComponentDto persistAndPopulateCache(Component component, ComponentDto dto) {
256       ComponentDto projectDto = persistComponent(dto);
257       addToCache(component, projectDto);
258       return projectDto;
259     }
260
261     private ComponentDto persistComponent(ComponentDto componentDto) {
262       ComponentDto existingComponent = existingComponentDtosByKey.remove(componentDto.getDbKey());
263       if (existingComponent == null) {
264         dbClient.componentDao().insert(dbSession, componentDto);
265         return componentDto;
266       }
267       Optional<ComponentUpdateDto> update = compareForUpdate(existingComponent, componentDto);
268       if (update.isPresent()) {
269         ComponentUpdateDto updateDto = update.get();
270         dbClient.componentDao().update(dbSession, updateDto);
271
272         // update the fields in memory in order the PathAwareVisitor.Path
273         // to be up-to-date
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());
285       }
286       return existingComponent;
287     }
288
289     private void addToCache(Component component, ComponentDto componentDto) {
290       dbIdsRepository.setComponentId(component, componentDto.getId());
291     }
292
293     public ComponentDto createForProject(Component project) {
294       ComponentDto res = createBase(project);
295
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());
301
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);
306
307       return res;
308     }
309
310     public ComponentDto createForModule(Component module, PathAwareVisitor.Path<ComponentDtoHolder> path) {
311       ComponentDto res = createBase(module);
312
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());
319
320       setRootAndParentModule(res, path);
321
322       return res;
323     }
324
325     public ComponentDto createForDirectory(Component directory, PathAwareVisitor.Path<ComponentDtoHolder> path) {
326       ComponentDto res = createBase(directory);
327
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());
333
334       setParentModuleProperties(res, path);
335
336       return res;
337     }
338
339     public ComponentDto createForFile(Component file, PathAwareVisitor.Path<ComponentDtoHolder> path) {
340       ComponentDto res = createBase(file);
341
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());
348
349       setParentModuleProperties(res, path);
350
351       return res;
352     }
353
354     private ComponentDto createForView(Component view) {
355       ComponentDto res = createBase(view);
356
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());
362
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);
367
368       return res;
369     }
370
371     private ComponentDto createForSubView(Component subView, PathAwareVisitor.Path<ComponentDtoHolder> path) {
372       ComponentDto res = createBase(subView);
373
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());
380
381       setRootAndParentModule(res, path);
382
383       return res;
384     }
385
386     private ComponentDto createForProjectView(Component projectView, PathAwareVisitor.Path<ComponentDtoHolder> path) {
387       ComponentDto res = createBase(projectView);
388
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());
394
395       setRootAndParentModule(res, path);
396
397       return res;
398     }
399
400     private ComponentDto createBase(Component component) {
401       String componentKey = component.getKey();
402       String componentUuid = component.getUuid();
403
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()));
412
413       return componentDto;
414     }
415
416     /**
417      * Applies to a node of type either MODULE, SUBVIEW, PROJECT_VIEW
418      */
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());
423
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);
428     }
429   }
430
431   /**
432    * Applies to a node of type either DIRECTORY or FILE
433    */
434   private static void setParentModuleProperties(ComponentDto componentDto, PathAwareVisitor.Path<ComponentDtoHolder> path) {
435     componentDto.setProjectUuid(path.root().getDto().uuid());
436
437     ComponentDto parentModule = from(path.getCurrentPath())
438       .filter(ParentModulePathElement.INSTANCE)
439       .first()
440       .get()
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());
446
447   }
448
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());
461
462     ComponentUpdateDto update = null;
463     if (hasDifferences) {
464       update = ComponentUpdateDto
465         .copyFrom(target)
466         .setBChanged(true);
467     }
468     return ofNullable(update);
469   }
470
471   private static String getFileQualifier(Component component) {
472     return component.getFileAttributes().isUnitTest() ? Qualifiers.UNIT_TEST_FILE : Qualifiers.FILE;
473   }
474
475   private static class ComponentDtoHolder {
476     private ComponentDto dto;
477
478     public ComponentDto getDto() {
479       return dto;
480     }
481
482     public void setDto(ComponentDto dto) {
483       this.dto = dto;
484     }
485   }
486
487   private enum ParentModulePathElement implements Predicate<PathAwareVisitor.PathElement<ComponentDtoHolder>> {
488     INSTANCE;
489
490     @Override
491     public boolean apply(@Nonnull PathAwareVisitor.PathElement<ComponentDtoHolder> input) {
492       return input.getComponent().getType() == Component.Type.MODULE
493         || input.getComponent().getType() == Component.Type.PROJECT;
494     }
495   }
496
497 }