You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PersistComponentsStep.java 17KB

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