123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377 |
- /*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
- package org.sonar.server.batch;
-
- import com.google.common.base.Function;
- import com.google.common.collect.ArrayListMultimap;
- import com.google.common.collect.Maps;
- import com.google.common.collect.Multimap;
- import java.util.Collections;
- import java.util.Date;
- import java.util.Iterator;
- import java.util.List;
- import java.util.Map;
- import javax.annotation.Nullable;
- import org.sonar.api.resources.Language;
- import org.sonar.api.resources.Languages;
- import org.sonar.api.rule.RuleKey;
- import org.sonar.api.server.ServerSide;
- import org.sonar.api.utils.log.Logger;
- import org.sonar.api.utils.log.Loggers;
- import org.sonar.api.web.UserRole;
- import org.sonar.batch.protocol.input.FileData;
- import org.sonar.batch.protocol.input.ProjectRepositories;
- import org.sonar.core.util.UtcDateUtils;
- import org.sonar.db.component.ComponentDto;
- import org.sonar.db.component.FilePathWithHashDto;
- import org.sonar.core.permission.GlobalPermissions;
- import org.sonar.db.DbSession;
- import org.sonar.db.MyBatis;
- import org.sonar.db.property.PropertyDto;
- import org.sonar.db.qualityprofile.QualityProfileDto;
- import org.sonar.server.db.DbClient;
- import org.sonar.server.exceptions.ForbiddenException;
- import org.sonar.server.qualityprofile.ActiveRule;
- import org.sonar.server.qualityprofile.QProfileFactory;
- import org.sonar.server.qualityprofile.QProfileLoader;
- import org.sonar.server.rule.Rule;
- import org.sonar.server.rule.RuleService;
- import org.sonar.server.rule.index.RuleNormalizer;
- import org.sonar.server.rule.index.RuleQuery;
- import org.sonar.server.search.QueryContext;
- import org.sonar.server.search.Result;
- import org.sonar.server.user.UserSession;
-
- import static com.google.common.collect.Lists.newArrayList;
- import static com.google.common.collect.Maps.newHashMap;
-
- @ServerSide
- public class ProjectRepositoryLoader {
-
- private static final Logger LOG = Loggers.get(ProjectRepositoryLoader.class);
-
- private final DbClient dbClient;
- private final QProfileFactory qProfileFactory;
- private final QProfileLoader qProfileLoader;
- private final RuleService ruleService;
- private final Languages languages;
- private final UserSession userSession;
-
- public ProjectRepositoryLoader(DbClient dbClient, QProfileFactory qProfileFactory, QProfileLoader qProfileLoader, RuleService ruleService,
- Languages languages, UserSession userSession) {
- this.dbClient = dbClient;
- this.qProfileFactory = qProfileFactory;
- this.qProfileLoader = qProfileLoader;
- this.ruleService = ruleService;
- this.languages = languages;
- this.userSession = userSession;
- }
-
- public ProjectRepositories load(ProjectRepositoryQuery query) {
- boolean hasScanPerm = userSession.hasGlobalPermission(GlobalPermissions.SCAN_EXECUTION);
- checkPermission(query.isPreview());
-
- DbSession session = dbClient.openSession(false);
- try {
- ProjectRepositories ref = new ProjectRepositories();
- String projectKey = query.getModuleKey();
- ComponentDto module = dbClient.componentDao().selectNullableByKey(session, query.getModuleKey());
- // Current project/module can be null when analysing a new project
- if (module != null) {
- // Scan permission is enough to analyze all projects but preview permission is limited to projects user can access
- if (query.isPreview() && !userSession.hasProjectPermissionByUuid(UserRole.USER, module.projectUuid())) {
- throw new ForbiddenException("You're not authorized to access to project '" + module.name() + "', please contact your SonarQube administrator.");
- }
-
- ComponentDto project = getProject(module, session);
- if (!project.key().equals(module.key())) {
- addSettings(ref, module.getKey(), getSettingsFromParents(module, hasScanPerm, session));
- projectKey = project.key();
- }
-
- List<ComponentDto> modulesTree = dbClient.componentDao().selectEnabledDescendantModules(session, module.uuid());
- Map<String, String> moduleUuidsByKey = moduleUuidsByKey(module, modulesTree);
- Map<String, Long> moduleIdsByKey = moduleIdsByKey(module, modulesTree);
-
- List<PropertyDto> modulesTreeSettings = dbClient.propertiesDao().selectEnabledDescendantModuleProperties(module.uuid(), session);
- TreeModuleSettings treeModuleSettings = new TreeModuleSettings(moduleUuidsByKey, moduleIdsByKey, modulesTree, modulesTreeSettings, module);
-
- addSettingsToChildrenModules(ref, query.getModuleKey(), Maps.<String, String>newHashMap(), treeModuleSettings, hasScanPerm, session);
- List<FilePathWithHashDto> files = module.isRootProject() ?
- dbClient.componentDao().selectEnabledFilesFromProject(session, module.uuid()) :
- dbClient.componentDao().selectEnabledDescendantFiles(session, module.uuid());
- addFileData(session, ref, modulesTree, files);
-
- // 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
- // ok
- ref.setLastAnalysisDate(new Date());
- } else {
- ref.setLastAnalysisDate(null);
- }
-
- addProfiles(ref, projectKey, query.getProfileName(), session);
- addActiveRules(ref);
- addManualRules(ref);
- return ref;
- } finally {
- MyBatis.closeQuietly(session);
- }
- }
-
- private ComponentDto getProject(ComponentDto module, DbSession session) {
- if (!module.isRootProject()) {
- return dbClient.componentDao().selectNullableByUuid(session, module.projectUuid());
- } else {
- return module;
- }
- }
-
- private Map<String, String> getSettingsFromParents(ComponentDto module, boolean hasScanPerm, DbSession session) {
- List<ComponentDto> parents = newArrayList();
- aggregateParentModules(module, parents, session);
- Collections.reverse(parents);
-
- Map<String, String> parentProperties = newHashMap();
- for (ComponentDto parent : parents) {
- parentProperties.putAll(getPropertiesMap(dbClient.propertiesDao().selectProjectProperties(session, parent.key()), hasScanPerm));
- }
- return parentProperties;
- }
-
- private void aggregateParentModules(ComponentDto component, List<ComponentDto> parents, DbSession session) {
- String moduleUuid = component.moduleUuid();
- if (moduleUuid != null) {
- ComponentDto parent = dbClient.componentDao().selectByUuid(session, moduleUuid);
- if (parent != null) {
- parents.add(parent);
- aggregateParentModules(parent, parents, session);
- }
- }
- }
-
- private void addSettingsToChildrenModules(ProjectRepositories ref, String moduleKey, Map<String, String> parentProperties, TreeModuleSettings treeModuleSettings,
- boolean hasScanPerm, DbSession session) {
- Map<String, String> currentParentProperties = newHashMap();
- currentParentProperties.putAll(parentProperties);
- currentParentProperties.putAll(getPropertiesMap(treeModuleSettings.findModuleSettings(moduleKey), hasScanPerm));
- addSettings(ref, moduleKey, currentParentProperties);
-
- for (ComponentDto childModule : treeModuleSettings.findChildrenModule(moduleKey)) {
- addSettings(ref, childModule.getKey(), currentParentProperties);
- addSettingsToChildrenModules(ref, childModule.getKey(), currentParentProperties, treeModuleSettings, hasScanPerm, session);
- }
- }
-
- private void addSettings(ProjectRepositories ref, String module, Map<String, String> properties) {
- if (!properties.isEmpty()) {
- ref.addSettings(module, properties);
- }
- }
-
- private static Map<String, String> getPropertiesMap(List<PropertyDto> propertyDtos, boolean hasScanPerm) {
- Map<String, String> properties = newHashMap();
- for (PropertyDto propertyDto : propertyDtos) {
- String key = propertyDto.getKey();
- String value = propertyDto.getValue();
- if (isPropertyAllowed(key, hasScanPerm)) {
- properties.put(key, value);
- }
- }
- return properties;
- }
-
- private static boolean isPropertyAllowed(String key, boolean hasScanPerm) {
- return !key.contains(".secured") || hasScanPerm;
- }
-
- private void addProfiles(ProjectRepositories ref, @Nullable String projectKey, @Nullable String profileName, DbSession session) {
- for (Language language : languages.all()) {
- String languageKey = language.getKey();
- QualityProfileDto qualityProfileDto = getProfile(languageKey, projectKey, profileName, session);
- ref.addQProfile(new org.sonar.batch.protocol.input.QProfile(
- qualityProfileDto.getKey(),
- qualityProfileDto.getName(),
- qualityProfileDto.getLanguage(),
- UtcDateUtils.parseDateTime(qualityProfileDto.getRulesUpdatedAt())));
- }
- }
-
- /**
- * First try to find a quality profile matching the given name (if provided) and current language
- * If no profile found, try to find the quality profile set on the project (if provided)
- * If still no profile found, try to find the default profile of the language
- * <p/>
- * Never return null because a default profile should always be set on ech language
- */
- private QualityProfileDto getProfile(String languageKey, @Nullable String projectKey, @Nullable String profileName, DbSession session) {
- QualityProfileDto qualityProfileDto = profileName != null ? qProfileFactory.getByNameAndLanguage(session, profileName, languageKey) : null;
- if (qualityProfileDto == null && projectKey != null) {
- qualityProfileDto = qProfileFactory.getByProjectAndLanguage(session, projectKey, languageKey);
- }
- qualityProfileDto = qualityProfileDto != null ? qualityProfileDto : qProfileFactory.getDefault(session, languageKey);
- if (qualityProfileDto != null) {
- return qualityProfileDto;
- } else {
- throw new IllegalStateException(String.format("No quality profile can been found on language '%s' for project '%s'", languageKey, projectKey));
- }
- }
-
- private void addActiveRules(ProjectRepositories ref) {
- for (org.sonar.batch.protocol.input.QProfile qProfile : ref.qProfiles()) {
- // Load all rules of the profile language (only needed fields are loaded)
- Map<RuleKey, Rule> languageRules = ruleByRuleKey(ruleService.search(new RuleQuery().setLanguages(newArrayList(qProfile.language())),
- new QueryContext(userSession).setLimit(100).setFieldsToReturn(newArrayList(
- RuleNormalizer.RuleField.KEY.field(), RuleNormalizer.RuleField.NAME.field(), RuleNormalizer.RuleField.INTERNAL_KEY.field(), RuleNormalizer.RuleField.TEMPLATE_KEY.field()
- )).setScroll(true))
- .scroll());
- for (Iterator<ActiveRule> activeRuleIterator = qProfileLoader.findActiveRulesByProfile(qProfile.key()); activeRuleIterator.hasNext();) {
- ActiveRule activeRule = activeRuleIterator.next();
- Rule rule = languageRules.get(activeRule.key().ruleKey());
- if (rule == null) {
- // It should never happen, but we need some log in case it happens
- LOG.warn("Rule could not be found on active rule '{}'", activeRule.key());
- } else {
- RuleKey templateKey = rule.templateKey();
- org.sonar.batch.protocol.input.ActiveRule inputActiveRule = new org.sonar.batch.protocol.input.ActiveRule(
- activeRule.key().ruleKey().repository(),
- activeRule.key().ruleKey().rule(),
- templateKey != null ? templateKey.rule() : null,
- rule.name(),
- activeRule.severity(),
- rule.internalKey(),
- qProfile.language());
- for (Map.Entry<String, String> entry : activeRule.params().entrySet()) {
- inputActiveRule.addParam(entry.getKey(), entry.getValue());
- }
- ref.addActiveRule(inputActiveRule);
- }
- }
- }
- }
-
- private Map<RuleKey, Rule> ruleByRuleKey(Iterator<Rule> rules) {
- return Maps.uniqueIndex(rules, MatchRuleKey.INSTANCE);
- }
-
- private void addManualRules(ProjectRepositories ref) {
- Result<Rule> ruleSearchResult = ruleService.search(new RuleQuery().setRepositories(newArrayList(RuleKey.MANUAL_REPOSITORY_KEY)), new QueryContext(userSession).setScroll(true)
- .setFieldsToReturn(newArrayList(RuleNormalizer.RuleField.KEY.field(), RuleNormalizer.RuleField.NAME.field())));
- Iterator<Rule> rules = ruleSearchResult.scroll();
- while (rules.hasNext()) {
- Rule rule = rules.next();
- ref.addActiveRule(new org.sonar.batch.protocol.input.ActiveRule(
- RuleKey.MANUAL_REPOSITORY_KEY,
- rule.key().rule(),
- null, rule.name(),
- null, null, null));
- }
- }
-
- private static void addFileData(DbSession session, ProjectRepositories ref, List<ComponentDto> moduleChildren, List<FilePathWithHashDto> files) {
- Map<String, String> moduleKeysByUuid = newHashMap();
- for (ComponentDto module : moduleChildren) {
- moduleKeysByUuid.put(module.uuid(), module.key());
- }
-
- for (FilePathWithHashDto file : files) {
- // TODO should query E/S to know if blame is missing on this file
- FileData fileData = new FileData(file.getSrcHash(), true);
- ref.addFileData(moduleKeysByUuid.get(file.getModuleUuid()), file.getPath(), fileData);
- }
- }
-
- private void checkPermission(boolean preview) {
- boolean hasScanPerm = userSession.hasGlobalPermission(GlobalPermissions.SCAN_EXECUTION);
- boolean hasPreviewPerm = userSession.hasGlobalPermission(GlobalPermissions.PREVIEW_EXECUTION);
- if (!hasPreviewPerm && !hasScanPerm) {
- throw new ForbiddenException(Messages.NO_PERMISSION);
- }
- if (!preview && !hasScanPerm) {
- throw new ForbiddenException("You're only authorized to execute a local (preview) SonarQube analysis without pushing the results to the SonarQube server. " +
- "Please contact your SonarQube administrator.");
- }
- if (preview && !hasPreviewPerm) {
- throw new ForbiddenException("You're not authorized to execute a preview analysis. Please contact your SonarQube administrator.");
- }
- }
-
- private static Map<String, String> moduleUuidsByKey(ComponentDto module, List<ComponentDto> moduleChildren) {
- Map<String, String> moduleUuidsByKey = newHashMap();
- for (ComponentDto componentDto : moduleChildren) {
- moduleUuidsByKey.put(componentDto.key(), componentDto.uuid());
- }
- return moduleUuidsByKey;
- }
-
- private static Map<String, Long> moduleIdsByKey(ComponentDto module, List<ComponentDto> moduleChildren) {
- Map<String, Long> moduleIdsByKey = newHashMap();
- for (ComponentDto componentDto : moduleChildren) {
- moduleIdsByKey.put(componentDto.key(), componentDto.getId());
- }
- return moduleIdsByKey;
- }
-
- private static class TreeModuleSettings {
-
- private Map<String, Long> moduleIdsByKey;
- private Map<String, String> moduleUuidsByKey;
- private Multimap<Long, PropertyDto> propertiesByModuleId;
- private Multimap<String, ComponentDto> moduleChildrenByModuleUuid;
-
- private TreeModuleSettings(Map<String, String> moduleUuidsByKey, Map<String, Long> moduleIdsByKey, List<ComponentDto> moduleChildren,
- List<PropertyDto> moduleChildrenSettings, ComponentDto module) {
- this.moduleIdsByKey = moduleIdsByKey;
- this.moduleUuidsByKey = moduleUuidsByKey;
- propertiesByModuleId = ArrayListMultimap.create();
- moduleChildrenByModuleUuid = ArrayListMultimap.create();
-
- for (PropertyDto settings : moduleChildrenSettings) {
- propertiesByModuleId.put(settings.getResourceId(), settings);
- }
-
- for (ComponentDto componentDto : moduleChildren) {
- String moduleUuid = componentDto.moduleUuid();
- if (moduleUuid != null) {
- moduleChildrenByModuleUuid.put(moduleUuid, componentDto);
- }
- }
- }
-
- List<PropertyDto> findModuleSettings(String moduleKey) {
- Long moduleId = moduleIdsByKey.get(moduleKey);
- return newArrayList(propertiesByModuleId.get(moduleId));
- }
-
- List<ComponentDto> findChildrenModule(String moduleKey) {
- String moduleUuid = moduleUuidsByKey.get(moduleKey);
- return newArrayList(moduleChildrenByModuleUuid.get(moduleUuid));
- }
- }
-
- private enum MatchRuleKey implements Function<Rule, RuleKey>{
- INSTANCE;
-
- @Override
- public RuleKey apply(@Nullable Rule input) {
- return input != null ? input.key() : null;
- }
- }
- }
|