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