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