@@ -156,7 +156,7 @@ public class RuleDao implements Dao { | |||
return mapper(session).selectParamsByRuleKey(key); | |||
} | |||
public List<RuleParamDto> selectRuleParamsByRuleKeys(DbSession session, List<RuleKey> ruleKeys) { | |||
public List<RuleParamDto> selectRuleParamsByRuleKeys(DbSession session, Collection<RuleKey> ruleKeys) { | |||
return executeLargeInputs(ruleKeys, mapper(session)::selectParamsByRuleKeys); | |||
} | |||
@@ -25,9 +25,7 @@ import org.sonar.server.organization.DefaultOrganizationEnforcer; | |||
import org.sonar.server.platform.ServerLifecycleNotifier; | |||
import org.sonar.server.platform.web.RegisterServletFilters; | |||
import org.sonar.server.qualitygate.RegisterQualityGates; | |||
import org.sonar.server.qualityprofile.CachingDefinedQProfileCreationImpl; | |||
import org.sonar.server.qualityprofile.CachingRuleActivator; | |||
import org.sonar.server.qualityprofile.CachingRuleActivatorContextFactory; | |||
import org.sonar.server.qualityprofile.DefinedQProfileInsertImpl; | |||
import org.sonar.server.qualityprofile.DefinedQProfileLoader; | |||
import org.sonar.server.qualityprofile.RegisterQualityProfiles; | |||
import org.sonar.server.rule.RegisterRules; | |||
@@ -58,9 +56,7 @@ public class PlatformLevelStartup extends PlatformLevel { | |||
RegisterRules.class); | |||
add(DefinedQProfileLoader.class); | |||
addIfStartupLeader( | |||
CachingRuleActivatorContextFactory.class, | |||
CachingRuleActivator.class, | |||
CachingDefinedQProfileCreationImpl.class, | |||
DefinedQProfileInsertImpl.class, | |||
RegisterQualityProfiles.class, | |||
RegisterPermissionTemplates.class, | |||
RenameDeprecatedPropertyKeys.class, |
@@ -1,28 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program 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. | |||
* | |||
* This program 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.qualityprofile; | |||
import org.sonar.db.DbClient; | |||
public class CachingDefinedQProfileCreationImpl extends DefinedQProfileCreationImpl implements CachingDefinedQProfileCreation { | |||
public CachingDefinedQProfileCreationImpl(DbClient dbClient, QProfileFactory profileFactory, CachingRuleActivator ruleActivator) { | |||
super(dbClient, profileFactory, ruleActivator); | |||
} | |||
} |
@@ -1,55 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program 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. | |||
* | |||
* This program 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.qualityprofile; | |||
import com.google.common.cache.Cache; | |||
import com.google.common.cache.CacheBuilder; | |||
import java.util.List; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.qualityprofile.QualityProfileDto; | |||
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; | |||
import org.sonar.server.rule.index.RuleIndex; | |||
import org.sonar.server.user.UserSession; | |||
import org.sonar.server.util.TypeValidations; | |||
public class CachingRuleActivator extends RuleActivator { | |||
private final Cache<String, List<QualityProfileDto>> childrenByParentKey = CacheBuilder.newBuilder() | |||
.maximumSize(10_000) | |||
.build(); | |||
public CachingRuleActivator(System2 system2, DbClient db, RuleIndex ruleIndex, CachingRuleActivatorContextFactory contextFactory, TypeValidations typeValidations, | |||
ActiveRuleIndexer activeRuleIndexer, UserSession userSession) { | |||
super(system2, db, ruleIndex, contextFactory, typeValidations, activeRuleIndexer, userSession); | |||
} | |||
@Override | |||
protected List<QualityProfileDto> getChildren(DbSession session, String qualityProfileKey) { | |||
List<QualityProfileDto> res = childrenByParentKey.getIfPresent(qualityProfileKey); | |||
if (res != null) { | |||
return res; | |||
} | |||
res = super.getChildren(session, qualityProfileKey); | |||
childrenByParentKey.put(qualityProfileKey, res); | |||
return res; | |||
} | |||
} |
@@ -1,83 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program 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. | |||
* | |||
* This program 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.qualityprofile; | |||
import com.google.common.cache.Cache; | |||
import com.google.common.cache.CacheBuilder; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
import java.util.Optional; | |||
import java.util.concurrent.ExecutionException; | |||
import org.picocontainer.Startable; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.core.util.stream.MoreCollectors; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.qualityprofile.ActiveRuleDto; | |||
import org.sonar.db.qualityprofile.ActiveRuleKey; | |||
import org.sonar.db.rule.RuleDefinitionDto; | |||
public class CachingRuleActivatorContextFactory extends RuleActivatorContextFactory implements Startable { | |||
private final DbClient dbClient; | |||
private final Map<RuleKey, RuleDefinitionDto> rulesByRuleKey = new HashMap<>(); | |||
private final Cache<String, Map<RuleKey, ActiveRuleDto>> childrenByParentKey = CacheBuilder.newBuilder() | |||
.maximumSize(10) | |||
.build(); | |||
public CachingRuleActivatorContextFactory(DbClient db) { | |||
super(db); | |||
this.dbClient = db; | |||
} | |||
@Override | |||
public void start() { | |||
try (DbSession dbSession = dbClient.openSession(false)) { | |||
dbClient.ruleDao().selectAllDefinitions(dbSession).forEach(rule -> rulesByRuleKey.put(rule.getKey(), rule)); | |||
} | |||
} | |||
@Override | |||
public void stop() { | |||
// nothing to do | |||
} | |||
@Override | |||
Optional<RuleDefinitionDto> getRule(DbSession dbSession, RuleKey ruleKey) { | |||
return Optional.ofNullable(rulesByRuleKey.get(ruleKey)); | |||
} | |||
@Override | |||
Optional<ActiveRuleDto> getActiveRule(DbSession session, ActiveRuleKey key) { | |||
try { | |||
String profileKey = key.qProfile(); | |||
Map<RuleKey, ActiveRuleDto> profileActiveRulesByRuleKey = childrenByParentKey.get( | |||
profileKey, | |||
() -> loadActiveRulesOfQualityProfile(session, profileKey)); | |||
return Optional.ofNullable(profileActiveRulesByRuleKey.get(key.ruleKey())); | |||
} catch (ExecutionException e) { | |||
throw new IllegalStateException(e.getCause()); | |||
} | |||
} | |||
private Map<RuleKey, ActiveRuleDto> loadActiveRulesOfQualityProfile(DbSession session, String profileKey) { | |||
return dbClient.activeRuleDao().selectByProfileKey(session, profileKey).stream() | |||
.collect(MoreCollectors.uniqueIndex(dto -> dto.getKey().ruleKey())); | |||
} | |||
} |
@@ -19,5 +19,10 @@ | |||
*/ | |||
package org.sonar.server.qualityprofile; | |||
public interface CachingDefinedQProfileCreation extends DefinedQProfileCreation { | |||
import java.util.List; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.organization.OrganizationDto; | |||
public interface DefinedQProfileInsert { | |||
void create(DbSession session, DefinedQProfile definedQProfile, OrganizationDto organization, List<ActiveRuleChange> changes); | |||
} |
@@ -0,0 +1,176 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program 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. | |||
* | |||
* This program 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.qualityprofile; | |||
import com.google.common.base.Splitter; | |||
import java.util.Date; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Objects; | |||
import javax.annotation.CheckForNull; | |||
import javax.annotation.Nullable; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.rules.ActiveRuleParam; | |||
import org.sonar.api.server.rule.RuleParamType; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.core.util.UuidFactory; | |||
import org.sonar.core.util.stream.MoreCollectors; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.loadedtemplate.LoadedTemplateDto; | |||
import org.sonar.db.organization.OrganizationDto; | |||
import org.sonar.db.qualityprofile.ActiveRuleDto; | |||
import org.sonar.db.qualityprofile.ActiveRuleKey; | |||
import org.sonar.db.qualityprofile.ActiveRuleParamDto; | |||
import org.sonar.db.qualityprofile.QualityProfileDto; | |||
import org.sonar.db.rule.RuleDefinitionDto; | |||
import org.sonar.db.rule.RuleParamDto; | |||
import org.sonar.server.util.TypeValidations; | |||
import static com.google.common.base.MoreObjects.firstNonNull; | |||
import static com.google.common.base.Preconditions.checkArgument; | |||
import static com.google.common.collect.Lists.newArrayList; | |||
public class DefinedQProfileInsertImpl implements DefinedQProfileInsert { | |||
private final DbClient dbClient; | |||
private final System2 system2; | |||
private final UuidFactory uuidFactory; | |||
private final TypeValidations typeValidations; | |||
private RegisterQualityProfiles.RuleRepository ruleRepository; | |||
public DefinedQProfileInsertImpl(DbClient dbClient, System2 system2, UuidFactory uuidFactory, TypeValidations typeValidations) { | |||
this.dbClient = dbClient; | |||
this.system2 = system2; | |||
this.uuidFactory = uuidFactory; | |||
this.typeValidations = typeValidations; | |||
} | |||
@Override | |||
public void create(DbSession session, DefinedQProfile definedQProfile, OrganizationDto organization, List<ActiveRuleChange> changes) { | |||
initRuleRepository(session); | |||
checkArgument(definedQProfile.getParentQProfileName() == null, | |||
"Inheritance of Quality Profiles is not supported yet"); | |||
// Optional.ofNullable(definedQProfile.getParentQProfileName()) | |||
// .map(parentQProfileName -> dbClient.qualityProfileDao().selectByNameAndLanguage(organization, parentQProfileName.getName(), | |||
// parentQProfileName.getLanguage(), session)) | |||
// .map(parentQualityProfileDto -> dbClient.activeRuleDao().selectByRuleIds()); | |||
Date now = new Date(system2.now()); | |||
QualityProfileDto profileDto = insertQualityProfile(session, definedQProfile, organization, now); | |||
List<ActiveRuleChange> localChanges = definedQProfile.getActiveRules() | |||
.stream() | |||
.map(activeRule -> insertActiveRule(session, profileDto, activeRule, now.getTime())) | |||
.collect(MoreCollectors.toList()); | |||
localChanges.forEach(change -> dbClient.qProfileChangeDao().insert(session, change.toDto(null))); | |||
insertTemplate(session, definedQProfile, organization); | |||
changes.addAll(localChanges); | |||
} | |||
private void initRuleRepository(DbSession session) { | |||
if (ruleRepository == null) { | |||
ruleRepository = new RegisterQualityProfiles.RuleRepository(dbClient, session); | |||
} | |||
} | |||
private QualityProfileDto insertQualityProfile(DbSession session, DefinedQProfile definedQProfile, OrganizationDto organization, Date now) { | |||
QualityProfileDto profileDto = QualityProfileDto.createFor(uuidFactory.create()) | |||
.setName(definedQProfile.getName()) | |||
.setOrganizationUuid(organization.getUuid()) | |||
.setLanguage(definedQProfile.getLanguage()) | |||
.setDefault(definedQProfile.isDefault()) | |||
.setRulesUpdatedAtAsDate(now); | |||
dbClient.qualityProfileDao().insert(session, profileDto); | |||
return profileDto; | |||
} | |||
private ActiveRuleChange insertActiveRule(DbSession session, QualityProfileDto profileDto, org.sonar.api.rules.ActiveRule activeRule, long now) { | |||
RuleKey ruleKey = RuleKey.of(activeRule.getRepositoryKey(), activeRule.getRuleKey()); | |||
RuleDefinitionDto ruleDefinitionDto = ruleRepository.getDefinition(ruleKey) | |||
.orElseThrow(() -> new IllegalStateException("RuleDefinition not found for key " + ruleKey)); | |||
ActiveRuleDto dto = ActiveRuleDto.createFor(profileDto, ruleDefinitionDto); | |||
dto.setSeverity( | |||
firstNonNull( | |||
activeRule.getSeverity().name(), | |||
// context.parentSeverity(), | |||
ruleDefinitionDto.getSeverityString())); | |||
// ActiveRule.Inheritance inheritance = activeRule.getInheritance(); | |||
// if (inheritance != null) { | |||
// activeRule.setInheritance(inheritance.name()); | |||
// } | |||
dto.setUpdatedAt(now); | |||
dto.setCreatedAt(now); | |||
dbClient.activeRuleDao().insert(session, dto); | |||
List<ActiveRuleParamDto> paramDtos = insertActiveRuleParams(session, activeRule, ruleKey, dto); | |||
ActiveRuleChange change = ActiveRuleChange.createFor(ActiveRuleChange.Type.ACTIVATED, ActiveRuleKey.of(profileDto.getKey(), ruleKey)); | |||
change.setSeverity(dto.getSeverityString()); | |||
paramDtos.forEach(paramDto -> change.setParameter(paramDto.getKey(), paramDto.getValue())); | |||
return change; | |||
} | |||
private List<ActiveRuleParamDto> insertActiveRuleParams(DbSession session, org.sonar.api.rules.ActiveRule activeRule, RuleKey ruleKey, ActiveRuleDto activeRuleDto) { | |||
Map<String, String> valuesByParamKey = activeRule.getActiveRuleParams() | |||
.stream() | |||
.collect(MoreCollectors.uniqueIndex(ActiveRuleParam::getParamKey, ActiveRuleParam::getValue)); | |||
return ruleRepository.getRuleParams(ruleKey) | |||
.stream() | |||
.map(param -> { | |||
String activeRuleValue = valuesByParamKey.get(param.getName()); | |||
return createParamDto(param, activeRuleValue == null ? param.getDefaultValue() : activeRuleValue); | |||
}) | |||
.filter(Objects::nonNull) | |||
.peek(paramDto -> dbClient.activeRuleDao().insertParam(session, activeRuleDto, paramDto)) | |||
.collect(MoreCollectors.toList()); | |||
} | |||
@CheckForNull | |||
private ActiveRuleParamDto createParamDto(RuleParamDto param, @Nullable String value) { | |||
if (value == null) { | |||
return null; | |||
} | |||
ActiveRuleParamDto paramDto = ActiveRuleParamDto.createFor(param); | |||
paramDto.setValue(validateParam(param, value)); | |||
return paramDto; | |||
} | |||
@CheckForNull | |||
private String validateParam(RuleParamDto ruleParam, String value) { | |||
RuleParamType ruleParamType = RuleParamType.parse(ruleParam.getType()); | |||
if (ruleParamType.multiple()) { | |||
List<String> values = newArrayList(Splitter.on(",").split(value)); | |||
typeValidations.validate(values, ruleParamType.type(), ruleParamType.values()); | |||
} else { | |||
typeValidations.validate(value, ruleParamType.type(), ruleParamType.values()); | |||
} | |||
return value; | |||
} | |||
private void insertTemplate(DbSession session, DefinedQProfile qualityProfile, OrganizationDto organization) { | |||
LoadedTemplateDto template = new LoadedTemplateDto(organization.getUuid(), qualityProfile.getLoadedTemplateType()); | |||
dbClient.loadedTemplateDao().insert(template, session); | |||
} | |||
} |
@@ -49,6 +49,7 @@ import org.sonar.core.util.stream.MoreCollectors; | |||
import static com.google.common.base.Preconditions.checkArgument; | |||
import static com.google.common.base.Preconditions.checkState; | |||
import static java.lang.String.format; | |||
import static org.apache.commons.lang.StringUtils.isNotEmpty; | |||
import static org.apache.commons.lang.StringUtils.lowerCase; | |||
@@ -95,13 +96,20 @@ public class DefinedQProfileRepositoryImpl implements DefinedQProfileRepository | |||
*/ | |||
private ListMultimap<String, RulesProfile> buildRulesProfilesByLanguage() { | |||
ListMultimap<String, RulesProfile> byLang = ArrayListMultimap.create(); | |||
Profiler profiler = Profiler.create(Loggers.get(getClass())); | |||
for (ProfileDefinition definition : definitions) { | |||
profiler.start(); | |||
ValidationMessages validation = ValidationMessages.create(); | |||
RulesProfile profile = definition.createProfile(validation); | |||
validation.log(LOGGER); | |||
if (profile != null && !validation.hasErrors()) { | |||
checkArgument(isNotEmpty(profile.getName()), "Profile created by Definition %s can't have a blank name", definition); | |||
byLang.put(lowerCase(profile.getLanguage(), Locale.ENGLISH), profile); | |||
if (profile == null) { | |||
profiler.stopDebug(format("Loaded definition %s that return no profile", definition)); | |||
} else { | |||
if (!validation.hasErrors()) { | |||
checkArgument(isNotEmpty(profile.getName()), "Profile created by Definition %s can't have a blank name", definition); | |||
byLang.put(lowerCase(profile.getLanguage(), Locale.ENGLISH), profile); | |||
} | |||
profiler.stopDebug(format("Loaded definition %s for language %s", profile.getName(), profile.getLanguage())); | |||
} | |||
} | |||
return byLang; | |||
@@ -137,7 +145,7 @@ public class DefinedQProfileRepositoryImpl implements DefinedQProfileRepository | |||
.collect(MoreCollectors.uniqueIndex(Map.Entry::getKey, entry -> toQualityProfiles(entry.getValue()), buildersByLanguage.size())); | |||
} | |||
private boolean ensureParentExists(String language, List<DefinedQProfile.Builder> builders) { | |||
private static boolean ensureParentExists(String language, List<DefinedQProfile.Builder> builders) { | |||
Set<String> qProfileNames = builders.stream() | |||
.map(DefinedQProfile.Builder::getName) | |||
.collect(MoreCollectors.toSet(builders.size())); |
@@ -19,18 +19,37 @@ | |||
*/ | |||
package org.sonar.server.qualityprofile; | |||
import com.google.common.annotations.VisibleForTesting; | |||
import com.google.common.collect.ImmutableMap; | |||
import com.google.common.collect.ImmutableSet; | |||
import com.google.common.collect.Sets; | |||
import java.util.ArrayList; | |||
import java.util.Collection; | |||
import java.util.Collections; | |||
import java.util.Comparator; | |||
import java.util.HashMap; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Optional; | |||
import java.util.Set; | |||
import java.util.function.Function; | |||
import java.util.stream.Collectors; | |||
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.utils.log.Profiler; | |||
import org.sonar.core.util.stream.MoreCollectors; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.Pagination; | |||
import org.sonar.db.organization.OrganizationDto; | |||
import org.sonar.db.rule.RuleDefinitionDto; | |||
import org.sonar.db.rule.RuleParamDto; | |||
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; | |||
import static java.lang.String.format; | |||
import static java.util.Objects.requireNonNull; | |||
import static org.sonar.db.Pagination.forPage; | |||
/** | |||
@@ -44,40 +63,46 @@ public class RegisterQualityProfiles { | |||
private final DefinedQProfileRepository definedQProfileRepository; | |||
private final DbClient dbClient; | |||
private final DefinedQProfileCreation definedQProfileCreation; | |||
private final ActiveRuleIndexer activeRuleIndexer; | |||
private final DefinedQProfileInsert definedQProfileInsert; | |||
public RegisterQualityProfiles(DefinedQProfileRepository definedQProfileRepository, | |||
DbClient dbClient, DefinedQProfileCreation definedQProfileCreation, ActiveRuleIndexer activeRuleIndexer) { | |||
DbClient dbClient, DefinedQProfileInsert definedQProfileInsert, ActiveRuleIndexer activeRuleIndexer) { | |||
this.definedQProfileRepository = definedQProfileRepository; | |||
this.dbClient = dbClient; | |||
this.definedQProfileCreation = definedQProfileCreation; | |||
this.activeRuleIndexer = activeRuleIndexer; | |||
this.definedQProfileInsert = definedQProfileInsert; | |||
} | |||
public void start() { | |||
Profiler profiler = Profiler.create(Loggers.get(getClass())).startInfo("Register quality profiles"); | |||
if (definedQProfileRepository.getQProfilesByLanguage().isEmpty()) { | |||
return; | |||
} | |||
try (DbSession session = dbClient.openSession(false)) { | |||
List<ActiveRuleChange> changes = new ArrayList<>(); | |||
definedQProfileRepository.getQProfilesByLanguage().entrySet() | |||
.forEach(entry -> registerPerLanguage(session, entry.getValue(), changes)); | |||
definedQProfileRepository.getQProfilesByLanguage() | |||
.forEach((key, value) -> registerPerLanguage(session, value, changes)); | |||
activeRuleIndexer.index(changes); | |||
profiler.stopDebug(); | |||
} | |||
} | |||
private void registerPerLanguage(DbSession session, List<DefinedQProfile> qualityProfiles, List<ActiveRuleChange> changes) { | |||
qualityProfiles.forEach(qp -> registerPerQualityProfile(session, qp, changes)); | |||
qualityProfiles.stream() | |||
.sorted(new SortByParentName(qualityProfiles)) | |||
.forEach(qp -> registerPerQualityProfile(session, qp, changes)); | |||
session.commit(); | |||
} | |||
private void registerPerQualityProfile(DbSession session, DefinedQProfile qualityProfile, List<ActiveRuleChange> changes) { | |||
LOGGER.info("Register profile {}", qualityProfile.getQProfileName()); | |||
Profiler profiler = Profiler.create(Loggers.get(getClass())); | |||
List<OrganizationDto> organizationDtos; | |||
while (!(organizationDtos = getOrganizationsWithoutQP(session, qualityProfile)).isEmpty()) { | |||
organizationDtos.forEach(organization -> registerPerQualityProfileAndOrganization(session, qualityProfile, organization, changes)); | |||
organizationDtos.forEach(organization -> registerPerQualityProfileAndOrganization(session, qualityProfile, organization, changes, profiler)); | |||
} | |||
} | |||
@@ -86,11 +111,80 @@ public class RegisterQualityProfiles { | |||
qualityProfile.getLoadedTemplateType(), PROCESSED_ORGANIZATIONS_BATCH_SIZE); | |||
} | |||
private void registerPerQualityProfileAndOrganization(DbSession session, DefinedQProfile qualityProfile, OrganizationDto organization, List<ActiveRuleChange> changes) { | |||
LOGGER.debug("Register profile {} for organization {}", qualityProfile.getQProfileName(), organization.getKey()); | |||
private void registerPerQualityProfileAndOrganization(DbSession session, | |||
DefinedQProfile definedQProfile, OrganizationDto organization, List<ActiveRuleChange> changes, Profiler profiler) { | |||
profiler.start(); | |||
definedQProfileInsert.create(session, definedQProfile, organization, changes); | |||
definedQProfileCreation.create(session, qualityProfile, organization, changes); | |||
session.commit(); | |||
profiler.stopDebug(format("Register profile %s for organization %s", definedQProfile.getQProfileName(), organization.getKey())); | |||
} | |||
@VisibleForTesting | |||
static class SortByParentName implements Comparator<DefinedQProfile> { | |||
private final Map<String, DefinedQProfile> buildersByName; | |||
@VisibleForTesting | |||
final Map<String, Integer> depthByBuilder; | |||
@VisibleForTesting | |||
SortByParentName(Collection<DefinedQProfile> builders) { | |||
buildersByName = builders.stream() | |||
.collect(MoreCollectors.uniqueIndex(DefinedQProfile::getName, Function.identity(), builders.size())); | |||
Map<String, Integer> depthByBuilder = new HashMap<>(); | |||
builders.forEach(builder -> depthByBuilder.put(builder.getName(), 0)); | |||
builders.forEach(builder -> increaseDepth(buildersByName, depthByBuilder, builder)); | |||
this.depthByBuilder = ImmutableMap.copyOf(depthByBuilder); | |||
} | |||
private void increaseDepth(Map<String, DefinedQProfile> buildersByName, Map<String, Integer> maps, DefinedQProfile builder) { | |||
Optional.ofNullable(builder.getParentQProfileName()) | |||
.ifPresent(parentQProfileName -> { | |||
DefinedQProfile parent = buildersByName.get(parentQProfileName.getName()); | |||
if (parent.getParentQProfileName() != null) { | |||
increaseDepth(buildersByName, maps, parent); | |||
} | |||
maps.put(builder.getName(), maps.get(parent.getName()) + 1); | |||
}); | |||
} | |||
@Override | |||
public int compare(DefinedQProfile o1, DefinedQProfile o2) { | |||
return depthByBuilder.getOrDefault(o1.getName(), 0) - depthByBuilder.getOrDefault(o2.getName(), 0); | |||
} | |||
} | |||
public static class RuleRepository { | |||
private final Map<RuleKey, RuleDefinitionDto> ruleDefinitions; | |||
private final Map<RuleKey, Set<RuleParamDto>> ruleParams; | |||
public RuleRepository(DbClient dbClient, DbSession session) { | |||
this.ruleDefinitions = dbClient.ruleDao().selectAllDefinitions(session) | |||
.stream() | |||
.collect(Collectors.toMap(RuleDefinitionDto::getKey, Function.identity())); | |||
Map<Integer, RuleKey> ruleIdsByKey = ruleDefinitions.values() | |||
.stream() | |||
.collect(MoreCollectors.uniqueIndex(RuleDefinitionDto::getId, RuleDefinitionDto::getKey)); | |||
this.ruleParams = new HashMap<>(ruleIdsByKey.size()); | |||
dbClient.ruleDao().selectRuleParamsByRuleKeys(session, ruleDefinitions.keySet()) | |||
.forEach(ruleParam -> ruleParams.compute( | |||
ruleIdsByKey.get(ruleParam.getRuleId()), | |||
(key, value) -> { | |||
if (value == null) { | |||
return ImmutableSet.of(ruleParam); | |||
} | |||
return ImmutableSet.copyOf(Sets.union(value, Collections.singleton(ruleParam))); | |||
})); | |||
} | |||
public Optional<RuleDefinitionDto> getDefinition(RuleKey ruleKey) { | |||
return Optional.ofNullable(ruleDefinitions.get(requireNonNull(ruleKey, "RuleKey can't be null"))); | |||
} | |||
public Set<RuleParamDto> getRuleParams(RuleKey ruleKey) { | |||
Set<RuleParamDto> res = ruleParams.get(requireNonNull(ruleKey, "RuleKey can't be null")); | |||
return res == null ? Collections.emptySet() : res; | |||
} | |||
} | |||
} |
@@ -31,6 +31,7 @@ import org.sonar.db.qualityprofile.ActiveRuleKey; | |||
import org.sonar.db.qualityprofile.ActiveRuleParamDto; | |||
import org.sonar.db.qualityprofile.QualityProfileDto; | |||
import org.sonar.db.rule.RuleDefinitionDto; | |||
import org.sonar.db.rule.RuleParamDto; | |||
import static org.sonar.server.ws.WsUtils.checkRequest; | |||
@@ -45,7 +46,7 @@ public class RuleActivatorContextFactory { | |||
RuleActivatorContext create(String profileKey, RuleKey ruleKey, DbSession session) { | |||
RuleActivatorContext context = new RuleActivatorContext(); | |||
QualityProfileDto profile = db.qualityProfileDao().selectByKey(session, profileKey); | |||
QualityProfileDto profile = getQualityProfileDto(session, profileKey); | |||
checkRequest(profile != null, "Quality profile not found: %s", profileKey); | |||
context.setProfile(profile); | |||
return create(ruleKey, session, context); | |||
@@ -68,9 +69,10 @@ public class RuleActivatorContextFactory { | |||
private RuleDefinitionDto initRule(RuleKey ruleKey, RuleActivatorContext context, DbSession dbSession) { | |||
Optional<RuleDefinitionDto> rule = getRule(dbSession, ruleKey); | |||
checkRequest(rule.isPresent(), "Rule not found: %s", ruleKey); | |||
context.setRule(rule.get()); | |||
context.setRuleParams(db.ruleDao().selectRuleParamsByRuleKey(dbSession, rule.get().getKey())); | |||
return rule.get(); | |||
RuleDefinitionDto ruleDefinitionDto = rule.get(); | |||
context.setRule(ruleDefinitionDto); | |||
context.setRuleParams(getRuleParams(dbSession, ruleDefinitionDto)); | |||
return ruleDefinitionDto; | |||
} | |||
private void initActiveRules(String profileKey, RuleKey ruleKey, RuleActivatorContext context, DbSession session, boolean parent) { | |||
@@ -89,10 +91,18 @@ public class RuleActivatorContextFactory { | |||
} | |||
} | |||
QualityProfileDto getQualityProfileDto(DbSession session, String profileKey) { | |||
return db.qualityProfileDao().selectByKey(session, profileKey); | |||
} | |||
Optional<RuleDefinitionDto> getRule(DbSession dbSession, RuleKey ruleKey) { | |||
return Optional.ofNullable(db.ruleDao().selectDefinitionByKey(dbSession, ruleKey).orElse(null)); | |||
} | |||
Collection<RuleParamDto> getRuleParams(DbSession dbSession, RuleDefinitionDto ruleDefinitionDto) { | |||
return db.ruleDao().selectRuleParamsByRuleKey(dbSession, ruleDefinitionDto.getKey()); | |||
} | |||
Optional<ActiveRuleDto> getActiveRule(DbSession session, ActiveRuleKey key) { | |||
return Optional.ofNullable(db.activeRuleDao().selectByKey(session, key).orNull()); | |||
} |
@@ -1,108 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program 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. | |||
* | |||
* This program 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.qualityprofile; | |||
import java.util.Arrays; | |||
import java.util.stream.Collectors; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.qualityprofile.QualityProfileDao; | |||
import org.sonar.db.qualityprofile.QualityProfileDto; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.Matchers.anyBoolean; | |||
import static org.mockito.Matchers.anyString; | |||
import static org.mockito.Matchers.eq; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.times; | |||
import static org.mockito.Mockito.verify; | |||
import static org.mockito.Mockito.verifyNoMoreInteractions; | |||
import static org.mockito.Mockito.when; | |||
public class CachingRuleActivatorTest { | |||
private DbClient dbClient = mock(DbClient.class); | |||
private DbSession dbSession = mock(DbSession.class); | |||
private QualityProfileDao qualityProfileDao = mock(QualityProfileDao.class); | |||
private CachingRuleActivator underTest = new CachingRuleActivator(null, dbClient, null, null, null, null, null); | |||
@Before | |||
public void wire_mocks() throws Exception { | |||
when(dbClient.openSession(anyBoolean())).thenReturn(dbSession); | |||
when(dbClient.qualityProfileDao()).thenReturn(qualityProfileDao); | |||
} | |||
@Test | |||
public void getChildren_caches_that_qp_has_no_children() { | |||
mockSelectChildrenForKey("no_children"); | |||
assertThat(underTest.getChildren(dbSession, "no_children")) | |||
.isEmpty(); | |||
assertThat(underTest.getChildren(dbSession, "no_children")) | |||
.isEmpty(); | |||
assertThat(underTest.getChildren(dbSession, "no_children")) | |||
.isEmpty(); | |||
verify(qualityProfileDao, times(1)).selectChildren(eq(dbSession), anyString()); | |||
} | |||
@Test | |||
public void getChildren_caches_that_sq_has_one_or_more_children() { | |||
mockSelectChildrenForKey("0", "1"); | |||
mockSelectChildrenForKey("1", "2", "3"); | |||
assertThat(underTest.getChildren(dbSession, "0")) | |||
.extracting(QualityProfileDto::getKey) | |||
.containsExactly("1"); | |||
assertThat(underTest.getChildren(dbSession, "0")) | |||
.extracting(QualityProfileDto::getKey) | |||
.containsExactly("1"); | |||
assertThat(underTest.getChildren(dbSession, "0")) | |||
.extracting(QualityProfileDto::getKey) | |||
.containsExactly("1"); | |||
assertThat(underTest.getChildren(dbSession, "1")) | |||
.extracting(QualityProfileDto::getKey) | |||
.containsExactly("2", "3"); | |||
assertThat(underTest.getChildren(dbSession, "1")) | |||
.extracting(QualityProfileDto::getKey) | |||
.containsExactly("2", "3"); | |||
assertThat(underTest.getChildren(dbSession, "1")) | |||
.extracting(QualityProfileDto::getKey) | |||
.containsExactly("2", "3"); | |||
verify(qualityProfileDao, times(1)).selectChildren(dbSession, "0"); | |||
verify(qualityProfileDao, times(1)).selectChildren(dbSession, "1"); | |||
verifyNoMoreInteractions(qualityProfileDao); | |||
} | |||
private void mockSelectChildrenForKey(String key, String... children) { | |||
when(qualityProfileDao.selectChildren(dbSession, key)) | |||
.thenReturn(Arrays.stream(children).map(this::dto).collect(Collectors.toList())) | |||
.thenThrow(new IllegalStateException("selectChildren should be called only once for key " + key)); | |||
} | |||
private QualityProfileDto dto(String key) { | |||
return new QualityProfileDto() { | |||
@Override | |||
public String toString() { | |||
return getKey(); | |||
} | |||
}.setKey(key); | |||
} | |||
} |
@@ -75,12 +75,12 @@ public class DefinedQProfileCreationImplTest { | |||
private DbSession dbSession = dbClient.openSession(false); | |||
private UuidFactory mockedUuidFactory = mock(UuidFactory.class); | |||
private System2 mockedSystem2 = mock(System2.class); | |||
private CachingRuleActivator mockedCachingRuleActivator = mock(CachingRuleActivator.class); | |||
private RuleActivator mockedRuleActivator = mock(RuleActivator.class); | |||
private ActiveRuleIndexer activeRuleIndexer = mock(ActiveRuleIndexer.class); | |||
private DefinedQProfileCreationImpl underTest = new DefinedQProfileCreationImpl( | |||
dbClient, | |||
new QProfileFactory(dbClient, mockedUuidFactory, mockedSystem2, activeRuleIndexer), | |||
mockedCachingRuleActivator); | |||
mockedRuleActivator); | |||
private List<ActiveRuleChange> activeRuleChanges = new ArrayList<>(); | |||
@After | |||
@@ -140,10 +140,10 @@ public class DefinedQProfileCreationImplTest { | |||
String uuid = "uuid 1"; | |||
mockForSingleQPInsert(uuid, date); | |||
QualityProfileDto existing = dbTester.qualityProfiles().insertQualityProfile( | |||
QualityProfileDto.createFor("a key") | |||
.setName(definedQProfile.getName()) | |||
.setLanguage(definedQProfile.getLanguage()) | |||
.setOrganizationUuid(organization.getUuid())); | |||
QualityProfileDto.createFor("a key") | |||
.setName(definedQProfile.getName()) | |||
.setLanguage(definedQProfile.getLanguage()) | |||
.setOrganizationUuid(organization.getUuid())); | |||
underTest.create(dbSession, definedQProfile, organization, activeRuleChanges); | |||
dbSession.commit(); | |||
@@ -264,7 +264,7 @@ public class DefinedQProfileCreationImplTest { | |||
QualityProfileDto qualityProfileDto = t.getArgumentAt(2, QualityProfileDto.class); | |||
callLogs.add(new CallLog(ruleActivation, qualityProfileDto)); | |||
return changesPerCallIt.next(); | |||
}).when(mockedCachingRuleActivator) | |||
}).when(mockedRuleActivator) | |||
.activate(any(DbSession.class), any(RuleActivation.class), any(QualityProfileDto.class)); | |||
} | |||
@@ -25,6 +25,9 @@ import java.util.Collections; | |||
import java.util.Iterator; | |||
import java.util.List; | |||
import java.util.Objects; | |||
import java.util.stream.IntStream; | |||
import javax.annotation.Nullable; | |||
import org.apache.commons.codec.digest.DigestUtils; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
@@ -43,8 +46,11 @@ import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; | |||
import org.sonar.server.tester.UserSessionRule; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.Matchers.anyBoolean; | |||
import static org.mockito.Matchers.anyList; | |||
import static org.mockito.Mockito.doNothing; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.times; | |||
import static org.mockito.Mockito.verify; | |||
import static org.mockito.Mockito.verifyNoMoreInteractions; | |||
@@ -64,11 +70,11 @@ public class RegisterQualityProfilesTest { | |||
private DbClient dbClient = dbTester.getDbClient(); | |||
private DbClient mockedDbClient = mock(DbClient.class); | |||
private ActiveRuleIndexer mockedActiveRuleIndexer = mock(ActiveRuleIndexer.class); | |||
private DummyDefinedQProfileCreation definedQProfileCreation = new DummyDefinedQProfileCreation(); | |||
private DummyDefinedQProfileInsert definedQProfileInsert = new DummyDefinedQProfileInsert(); | |||
private RegisterQualityProfiles underTest = new RegisterQualityProfiles( | |||
definedQProfileRepositoryRule, | |||
dbClient, | |||
definedQProfileCreation, | |||
definedQProfileInsert, | |||
mockedActiveRuleIndexer); | |||
@Test | |||
@@ -86,9 +92,9 @@ public class RegisterQualityProfilesTest { | |||
underTest.start(); | |||
assertThat(definedQProfileCreation.getCallLogs()).isEmpty(); | |||
verify(mockedDbClient).openSession(false); | |||
verify(mockedActiveRuleIndexer).index(Collections.emptyList()); | |||
assertThat(definedQProfileInsert.getCallLogs()).isEmpty(); | |||
verify(mockedDbClient, times(0)).openSession(anyBoolean()); | |||
verify(mockedActiveRuleIndexer, times(0)).index(anyList()); | |||
verifyNoMoreInteractions(mockedDbClient, mockedActiveRuleIndexer); | |||
} | |||
@@ -101,7 +107,7 @@ public class RegisterQualityProfilesTest { | |||
underTest.start(); | |||
assertThat(definedQProfileCreation.getCallLogs()) | |||
assertThat(definedQProfileInsert.getCallLogs()) | |||
.containsExactly( | |||
callLog(definedQProfile, dbTester.getDefaultOrganization()), | |||
callLog(definedQProfile, organization1), | |||
@@ -120,7 +126,7 @@ public class RegisterQualityProfilesTest { | |||
underTest.start(); | |||
assertThat(definedQProfileCreation.getCallLogs()) | |||
assertThat(definedQProfileInsert.getCallLogs()) | |||
.containsExactly(callLog(definedQProfile, org2)); | |||
} | |||
@@ -134,7 +140,7 @@ public class RegisterQualityProfilesTest { | |||
underTest.start(); | |||
assertThat(definedQProfileCreation.getCallLogs()) | |||
assertThat(definedQProfileInsert.getCallLogs()) | |||
.containsExactly(callLog(definedQProfile2, dbTester.getDefaultOrganization()), callLog(definedQProfile1, dbTester.getDefaultOrganization())); | |||
} | |||
@@ -149,11 +155,11 @@ public class RegisterQualityProfilesTest { | |||
ActiveRuleChange ruleChange2 = newActiveRuleChange("2"); | |||
ActiveRuleChange ruleChange3 = newActiveRuleChange("3"); | |||
ActiveRuleChange ruleChange4 = newActiveRuleChange("4"); | |||
definedQProfileCreation.addChangesPerCall(ruleChange1, ruleChange3); | |||
definedQProfileInsert.addChangesPerCall(ruleChange1, ruleChange3); | |||
// no change for second org | |||
definedQProfileCreation.addChangesPerCall(); | |||
definedQProfileCreation.addChangesPerCall(ruleChange2); | |||
definedQProfileCreation.addChangesPerCall(ruleChange4); | |||
definedQProfileInsert.addChangesPerCall(); | |||
definedQProfileInsert.addChangesPerCall(ruleChange2); | |||
definedQProfileInsert.addChangesPerCall(ruleChange4); | |||
ArgumentCaptor<List<ActiveRuleChange>> indexedChangesCaptor = ArgumentCaptor.forClass((Class<List<ActiveRuleChange>>) (Object) List.class); | |||
doNothing().when(mockedActiveRuleIndexer).index(indexedChangesCaptor.capture()); | |||
@@ -163,11 +169,52 @@ public class RegisterQualityProfilesTest { | |||
.containsExactly(ruleChange1, ruleChange3, ruleChange2, ruleChange4); | |||
} | |||
@Test | |||
public void test_SortByParentName_comporator() { | |||
DefinedQProfile[] builderArray = {newBuilder("A1", null), newBuilder("A2", "A1"), newBuilder("A3", null), newBuilder("A4", "A3"), | |||
newBuilder("A5", "A4"), newBuilder("A6", null)}; | |||
List<DefinedQProfile> builders = new ArrayList<>(Arrays.asList(builderArray)); | |||
IntStream.range(0, 100) | |||
.forEach(i -> { | |||
Collections.shuffle(builders); | |||
RegisterQualityProfiles.SortByParentName comparator = new RegisterQualityProfiles.SortByParentName(builders); | |||
assertThat(comparator.depthByBuilder.get("A1")).isEqualTo(0); | |||
assertThat(comparator.depthByBuilder.get("A2")).isEqualTo(1); | |||
assertThat(comparator.depthByBuilder.get("A3")).isEqualTo(0); | |||
assertThat(comparator.depthByBuilder.get("A4")).isEqualTo(1); | |||
assertThat(comparator.depthByBuilder.get("A5")).isEqualTo(2); | |||
assertThat(comparator.depthByBuilder.get("A6")).isEqualTo(0); | |||
builders.sort(comparator); | |||
verifyParentBeforeChild(builderArray, builders, 0, 1); | |||
verifyParentBeforeChild(builderArray, builders, 2, 3); | |||
verifyParentBeforeChild(builderArray, builders, 3, 4); | |||
verifyParentBeforeChild(builderArray, builders, 2, 4); | |||
}); | |||
} | |||
private DefinedQProfile newBuilder(String name, @Nullable String parentName) { | |||
return new DefinedQProfile.Builder() | |||
.setName(name) | |||
.setParentName(parentName) | |||
.build(DigestUtils.getMd5Digest()); | |||
} | |||
private static void verifyParentBeforeChild(DefinedQProfile[] builderArray, List<DefinedQProfile> builders, | |||
int parent, int child) { | |||
assertThat(builders.indexOf(builderArray[parent])) | |||
.describedAs(builderArray[4].getName() + " before " + builderArray[child].getName()) | |||
.isLessThan(builders.indexOf(builderArray[child])); | |||
} | |||
private static ActiveRuleChange newActiveRuleChange(String id) { | |||
return ActiveRuleChange.createFor(ActiveRuleChange.Type.ACTIVATED, ActiveRuleKey.of(id, RuleKey.of(id + "1", id + "2"))); | |||
} | |||
private class DummyDefinedQProfileCreation implements DefinedQProfileCreation { | |||
private class DummyDefinedQProfileInsert implements DefinedQProfileInsert { | |||
private List<List<ActiveRuleChange>> changesPerCall; | |||
private Iterator<List<ActiveRuleChange>> changesPerCallIterator; | |||
private final List<CallLog> callLogs = new ArrayList<>(); |