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