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.

ProjectRepositoryLoader.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. /*
  2. * SonarQube, open source software quality management tool.
  3. * Copyright (C) 2008-2014 SonarSource
  4. * mailto:contact AT sonarsource DOT com
  5. *
  6. * SonarQube 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. * SonarQube 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.batch;
  21. import com.google.common.base.Function;
  22. import com.google.common.collect.ArrayListMultimap;
  23. import com.google.common.collect.Maps;
  24. import com.google.common.collect.Multimap;
  25. import java.util.Collections;
  26. import java.util.Date;
  27. import java.util.Iterator;
  28. import java.util.List;
  29. import java.util.Map;
  30. import javax.annotation.Nullable;
  31. import org.sonar.api.resources.Language;
  32. import org.sonar.api.resources.Languages;
  33. import org.sonar.api.rule.RuleKey;
  34. import org.sonar.api.server.ServerSide;
  35. import org.sonar.api.utils.log.Logger;
  36. import org.sonar.api.utils.log.Loggers;
  37. import org.sonar.api.web.UserRole;
  38. import org.sonar.batch.protocol.input.FileData;
  39. import org.sonar.batch.protocol.input.ProjectRepositories;
  40. import org.sonar.core.util.UtcDateUtils;
  41. import org.sonar.db.component.ComponentDto;
  42. import org.sonar.db.component.FilePathWithHashDto;
  43. import org.sonar.core.permission.GlobalPermissions;
  44. import org.sonar.db.DbSession;
  45. import org.sonar.db.MyBatis;
  46. import org.sonar.db.property.PropertyDto;
  47. import org.sonar.db.qualityprofile.QualityProfileDto;
  48. import org.sonar.server.db.DbClient;
  49. import org.sonar.server.exceptions.ForbiddenException;
  50. import org.sonar.server.qualityprofile.ActiveRule;
  51. import org.sonar.server.qualityprofile.QProfileFactory;
  52. import org.sonar.server.qualityprofile.QProfileLoader;
  53. import org.sonar.server.rule.Rule;
  54. import org.sonar.server.rule.RuleService;
  55. import org.sonar.server.rule.index.RuleNormalizer;
  56. import org.sonar.server.rule.index.RuleQuery;
  57. import org.sonar.server.search.QueryContext;
  58. import org.sonar.server.search.Result;
  59. import org.sonar.server.user.UserSession;
  60. import static com.google.common.collect.Lists.newArrayList;
  61. import static com.google.common.collect.Maps.newHashMap;
  62. @ServerSide
  63. public class ProjectRepositoryLoader {
  64. private static final Logger LOG = Loggers.get(ProjectRepositoryLoader.class);
  65. private final DbClient dbClient;
  66. private final QProfileFactory qProfileFactory;
  67. private final QProfileLoader qProfileLoader;
  68. private final RuleService ruleService;
  69. private final Languages languages;
  70. private final UserSession userSession;
  71. public ProjectRepositoryLoader(DbClient dbClient, QProfileFactory qProfileFactory, QProfileLoader qProfileLoader, RuleService ruleService,
  72. Languages languages, UserSession userSession) {
  73. this.dbClient = dbClient;
  74. this.qProfileFactory = qProfileFactory;
  75. this.qProfileLoader = qProfileLoader;
  76. this.ruleService = ruleService;
  77. this.languages = languages;
  78. this.userSession = userSession;
  79. }
  80. public ProjectRepositories load(ProjectRepositoryQuery query) {
  81. boolean hasScanPerm = userSession.hasGlobalPermission(GlobalPermissions.SCAN_EXECUTION);
  82. checkPermission(query.isPreview());
  83. DbSession session = dbClient.openSession(false);
  84. try {
  85. ProjectRepositories ref = new ProjectRepositories();
  86. String projectKey = query.getModuleKey();
  87. ComponentDto module = dbClient.componentDao().selectNullableByKey(session, query.getModuleKey());
  88. // Current project/module can be null when analysing a new project
  89. if (module != null) {
  90. // Scan permission is enough to analyze all projects but preview permission is limited to projects user can access
  91. if (query.isPreview() && !userSession.hasProjectPermissionByUuid(UserRole.USER, module.projectUuid())) {
  92. throw new ForbiddenException("You're not authorized to access to project '" + module.name() + "', please contact your SonarQube administrator.");
  93. }
  94. ComponentDto project = getProject(module, session);
  95. if (!project.key().equals(module.key())) {
  96. addSettings(ref, module.getKey(), getSettingsFromParents(module, hasScanPerm, session));
  97. projectKey = project.key();
  98. }
  99. List<ComponentDto> modulesTree = dbClient.componentDao().selectEnabledDescendantModules(session, module.uuid());
  100. Map<String, String> moduleUuidsByKey = moduleUuidsByKey(module, modulesTree);
  101. Map<String, Long> moduleIdsByKey = moduleIdsByKey(module, modulesTree);
  102. List<PropertyDto> modulesTreeSettings = dbClient.propertiesDao().selectEnabledDescendantModuleProperties(module.uuid(), session);
  103. TreeModuleSettings treeModuleSettings = new TreeModuleSettings(moduleUuidsByKey, moduleIdsByKey, modulesTree, modulesTreeSettings, module);
  104. addSettingsToChildrenModules(ref, query.getModuleKey(), Maps.<String, String>newHashMap(), treeModuleSettings, hasScanPerm, session);
  105. List<FilePathWithHashDto> files = module.isRootProject() ?
  106. dbClient.componentDao().selectEnabledFilesFromProject(session, module.uuid()) :
  107. dbClient.componentDao().selectEnabledDescendantFiles(session, module.uuid());
  108. addFileData(session, ref, modulesTree, files);
  109. // FIXME need real value but actually only used to know if there is a previous analysis in local issue tracking mode so any value is
  110. // ok
  111. ref.setLastAnalysisDate(new Date());
  112. } else {
  113. ref.setLastAnalysisDate(null);
  114. }
  115. addProfiles(ref, projectKey, query.getProfileName(), session);
  116. addActiveRules(ref);
  117. addManualRules(ref);
  118. return ref;
  119. } finally {
  120. MyBatis.closeQuietly(session);
  121. }
  122. }
  123. private ComponentDto getProject(ComponentDto module, DbSession session) {
  124. if (!module.isRootProject()) {
  125. return dbClient.componentDao().selectNullableByUuid(session, module.projectUuid());
  126. } else {
  127. return module;
  128. }
  129. }
  130. private Map<String, String> getSettingsFromParents(ComponentDto module, boolean hasScanPerm, DbSession session) {
  131. List<ComponentDto> parents = newArrayList();
  132. aggregateParentModules(module, parents, session);
  133. Collections.reverse(parents);
  134. Map<String, String> parentProperties = newHashMap();
  135. for (ComponentDto parent : parents) {
  136. parentProperties.putAll(getPropertiesMap(dbClient.propertiesDao().selectProjectProperties(session, parent.key()), hasScanPerm));
  137. }
  138. return parentProperties;
  139. }
  140. private void aggregateParentModules(ComponentDto component, List<ComponentDto> parents, DbSession session) {
  141. String moduleUuid = component.moduleUuid();
  142. if (moduleUuid != null) {
  143. ComponentDto parent = dbClient.componentDao().selectByUuid(session, moduleUuid);
  144. if (parent != null) {
  145. parents.add(parent);
  146. aggregateParentModules(parent, parents, session);
  147. }
  148. }
  149. }
  150. private void addSettingsToChildrenModules(ProjectRepositories ref, String moduleKey, Map<String, String> parentProperties, TreeModuleSettings treeModuleSettings,
  151. boolean hasScanPerm, DbSession session) {
  152. Map<String, String> currentParentProperties = newHashMap();
  153. currentParentProperties.putAll(parentProperties);
  154. currentParentProperties.putAll(getPropertiesMap(treeModuleSettings.findModuleSettings(moduleKey), hasScanPerm));
  155. addSettings(ref, moduleKey, currentParentProperties);
  156. for (ComponentDto childModule : treeModuleSettings.findChildrenModule(moduleKey)) {
  157. addSettings(ref, childModule.getKey(), currentParentProperties);
  158. addSettingsToChildrenModules(ref, childModule.getKey(), currentParentProperties, treeModuleSettings, hasScanPerm, session);
  159. }
  160. }
  161. private void addSettings(ProjectRepositories ref, String module, Map<String, String> properties) {
  162. if (!properties.isEmpty()) {
  163. ref.addSettings(module, properties);
  164. }
  165. }
  166. private static Map<String, String> getPropertiesMap(List<PropertyDto> propertyDtos, boolean hasScanPerm) {
  167. Map<String, String> properties = newHashMap();
  168. for (PropertyDto propertyDto : propertyDtos) {
  169. String key = propertyDto.getKey();
  170. String value = propertyDto.getValue();
  171. if (isPropertyAllowed(key, hasScanPerm)) {
  172. properties.put(key, value);
  173. }
  174. }
  175. return properties;
  176. }
  177. private static boolean isPropertyAllowed(String key, boolean hasScanPerm) {
  178. return !key.contains(".secured") || hasScanPerm;
  179. }
  180. private void addProfiles(ProjectRepositories ref, @Nullable String projectKey, @Nullable String profileName, DbSession session) {
  181. for (Language language : languages.all()) {
  182. String languageKey = language.getKey();
  183. QualityProfileDto qualityProfileDto = getProfile(languageKey, projectKey, profileName, session);
  184. ref.addQProfile(new org.sonar.batch.protocol.input.QProfile(
  185. qualityProfileDto.getKey(),
  186. qualityProfileDto.getName(),
  187. qualityProfileDto.getLanguage(),
  188. UtcDateUtils.parseDateTime(qualityProfileDto.getRulesUpdatedAt())));
  189. }
  190. }
  191. /**
  192. * First try to find a quality profile matching the given name (if provided) and current language
  193. * If no profile found, try to find the quality profile set on the project (if provided)
  194. * If still no profile found, try to find the default profile of the language
  195. * <p/>
  196. * Never return null because a default profile should always be set on ech language
  197. */
  198. private QualityProfileDto getProfile(String languageKey, @Nullable String projectKey, @Nullable String profileName, DbSession session) {
  199. QualityProfileDto qualityProfileDto = profileName != null ? qProfileFactory.getByNameAndLanguage(session, profileName, languageKey) : null;
  200. if (qualityProfileDto == null && projectKey != null) {
  201. qualityProfileDto = qProfileFactory.getByProjectAndLanguage(session, projectKey, languageKey);
  202. }
  203. qualityProfileDto = qualityProfileDto != null ? qualityProfileDto : qProfileFactory.getDefault(session, languageKey);
  204. if (qualityProfileDto != null) {
  205. return qualityProfileDto;
  206. } else {
  207. throw new IllegalStateException(String.format("No quality profile can been found on language '%s' for project '%s'", languageKey, projectKey));
  208. }
  209. }
  210. private void addActiveRules(ProjectRepositories ref) {
  211. for (org.sonar.batch.protocol.input.QProfile qProfile : ref.qProfiles()) {
  212. // Load all rules of the profile language (only needed fields are loaded)
  213. Map<RuleKey, Rule> languageRules = ruleByRuleKey(ruleService.search(new RuleQuery().setLanguages(newArrayList(qProfile.language())),
  214. new QueryContext(userSession).setLimit(100).setFieldsToReturn(newArrayList(
  215. RuleNormalizer.RuleField.KEY.field(), RuleNormalizer.RuleField.NAME.field(), RuleNormalizer.RuleField.INTERNAL_KEY.field(), RuleNormalizer.RuleField.TEMPLATE_KEY.field()
  216. )).setScroll(true))
  217. .scroll());
  218. for (Iterator<ActiveRule> activeRuleIterator = qProfileLoader.findActiveRulesByProfile(qProfile.key()); activeRuleIterator.hasNext();) {
  219. ActiveRule activeRule = activeRuleIterator.next();
  220. Rule rule = languageRules.get(activeRule.key().ruleKey());
  221. if (rule == null) {
  222. // It should never happen, but we need some log in case it happens
  223. LOG.warn("Rule could not be found on active rule '{}'", activeRule.key());
  224. } else {
  225. RuleKey templateKey = rule.templateKey();
  226. org.sonar.batch.protocol.input.ActiveRule inputActiveRule = new org.sonar.batch.protocol.input.ActiveRule(
  227. activeRule.key().ruleKey().repository(),
  228. activeRule.key().ruleKey().rule(),
  229. templateKey != null ? templateKey.rule() : null,
  230. rule.name(),
  231. activeRule.severity(),
  232. rule.internalKey(),
  233. qProfile.language());
  234. for (Map.Entry<String, String> entry : activeRule.params().entrySet()) {
  235. inputActiveRule.addParam(entry.getKey(), entry.getValue());
  236. }
  237. ref.addActiveRule(inputActiveRule);
  238. }
  239. }
  240. }
  241. }
  242. private Map<RuleKey, Rule> ruleByRuleKey(Iterator<Rule> rules) {
  243. return Maps.uniqueIndex(rules, MatchRuleKey.INSTANCE);
  244. }
  245. private void addManualRules(ProjectRepositories ref) {
  246. Result<Rule> ruleSearchResult = ruleService.search(new RuleQuery().setRepositories(newArrayList(RuleKey.MANUAL_REPOSITORY_KEY)), new QueryContext(userSession).setScroll(true)
  247. .setFieldsToReturn(newArrayList(RuleNormalizer.RuleField.KEY.field(), RuleNormalizer.RuleField.NAME.field())));
  248. Iterator<Rule> rules = ruleSearchResult.scroll();
  249. while (rules.hasNext()) {
  250. Rule rule = rules.next();
  251. ref.addActiveRule(new org.sonar.batch.protocol.input.ActiveRule(
  252. RuleKey.MANUAL_REPOSITORY_KEY,
  253. rule.key().rule(),
  254. null, rule.name(),
  255. null, null, null));
  256. }
  257. }
  258. private static void addFileData(DbSession session, ProjectRepositories ref, List<ComponentDto> moduleChildren, List<FilePathWithHashDto> files) {
  259. Map<String, String> moduleKeysByUuid = newHashMap();
  260. for (ComponentDto module : moduleChildren) {
  261. moduleKeysByUuid.put(module.uuid(), module.key());
  262. }
  263. for (FilePathWithHashDto file : files) {
  264. // TODO should query E/S to know if blame is missing on this file
  265. FileData fileData = new FileData(file.getSrcHash(), true);
  266. ref.addFileData(moduleKeysByUuid.get(file.getModuleUuid()), file.getPath(), fileData);
  267. }
  268. }
  269. private void checkPermission(boolean preview) {
  270. boolean hasScanPerm = userSession.hasGlobalPermission(GlobalPermissions.SCAN_EXECUTION);
  271. boolean hasPreviewPerm = userSession.hasGlobalPermission(GlobalPermissions.PREVIEW_EXECUTION);
  272. if (!hasPreviewPerm && !hasScanPerm) {
  273. throw new ForbiddenException(Messages.NO_PERMISSION);
  274. }
  275. if (!preview && !hasScanPerm) {
  276. throw new ForbiddenException("You're only authorized to execute a local (preview) SonarQube analysis without pushing the results to the SonarQube server. " +
  277. "Please contact your SonarQube administrator.");
  278. }
  279. if (preview && !hasPreviewPerm) {
  280. throw new ForbiddenException("You're not authorized to execute a preview analysis. Please contact your SonarQube administrator.");
  281. }
  282. }
  283. private static Map<String, String> moduleUuidsByKey(ComponentDto module, List<ComponentDto> moduleChildren) {
  284. Map<String, String> moduleUuidsByKey = newHashMap();
  285. for (ComponentDto componentDto : moduleChildren) {
  286. moduleUuidsByKey.put(componentDto.key(), componentDto.uuid());
  287. }
  288. return moduleUuidsByKey;
  289. }
  290. private static Map<String, Long> moduleIdsByKey(ComponentDto module, List<ComponentDto> moduleChildren) {
  291. Map<String, Long> moduleIdsByKey = newHashMap();
  292. for (ComponentDto componentDto : moduleChildren) {
  293. moduleIdsByKey.put(componentDto.key(), componentDto.getId());
  294. }
  295. return moduleIdsByKey;
  296. }
  297. private static class TreeModuleSettings {
  298. private Map<String, Long> moduleIdsByKey;
  299. private Map<String, String> moduleUuidsByKey;
  300. private Multimap<Long, PropertyDto> propertiesByModuleId;
  301. private Multimap<String, ComponentDto> moduleChildrenByModuleUuid;
  302. private TreeModuleSettings(Map<String, String> moduleUuidsByKey, Map<String, Long> moduleIdsByKey, List<ComponentDto> moduleChildren,
  303. List<PropertyDto> moduleChildrenSettings, ComponentDto module) {
  304. this.moduleIdsByKey = moduleIdsByKey;
  305. this.moduleUuidsByKey = moduleUuidsByKey;
  306. propertiesByModuleId = ArrayListMultimap.create();
  307. moduleChildrenByModuleUuid = ArrayListMultimap.create();
  308. for (PropertyDto settings : moduleChildrenSettings) {
  309. propertiesByModuleId.put(settings.getResourceId(), settings);
  310. }
  311. for (ComponentDto componentDto : moduleChildren) {
  312. String moduleUuid = componentDto.moduleUuid();
  313. if (moduleUuid != null) {
  314. moduleChildrenByModuleUuid.put(moduleUuid, componentDto);
  315. }
  316. }
  317. }
  318. List<PropertyDto> findModuleSettings(String moduleKey) {
  319. Long moduleId = moduleIdsByKey.get(moduleKey);
  320. return newArrayList(propertiesByModuleId.get(moduleId));
  321. }
  322. List<ComponentDto> findChildrenModule(String moduleKey) {
  323. String moduleUuid = moduleUuidsByKey.get(moduleKey);
  324. return newArrayList(moduleChildrenByModuleUuid.get(moduleUuid));
  325. }
  326. }
  327. private enum MatchRuleKey implements Function<Rule, RuleKey>{
  328. INSTANCE;
  329. @Override
  330. public RuleKey apply(@Nullable Rule input) {
  331. return input != null ? input.key() : null;
  332. }
  333. }
  334. }