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