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