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