]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6315 use an insert only code for RegisterQualityProfiles
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 28 Apr 2017 09:33:43 +0000 (11:33 +0200)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 12 May 2017 09:18:19 +0000 (11:18 +0200)
14 files changed:
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDao.java
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingDefinedQProfileCreation.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingDefinedQProfileCreationImpl.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingRuleActivator.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingRuleActivatorContextFactory.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileInsert.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileInsertImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileRepositoryImpl.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RegisterQualityProfiles.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RuleActivatorContextFactory.java
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/CachingRuleActivatorTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileCreationImplTest.java
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/RegisterQualityProfilesTest.java

index 5a5744e5e4f08debcbb90d645e2a1d2fbd09d3e0..2a98022add914c4c02d2f5b44300e0fa324ed1d2 100644 (file)
@@ -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);
   }
 
index 2359dbb98118f5ab36091419599f7d45d16a14b6..588ae28ead9266a7ed8c65594746e2dfb927f84c 100644 (file)
@@ -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,
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingDefinedQProfileCreation.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingDefinedQProfileCreation.java
deleted file mode 100644 (file)
index 427672e..0000000
+++ /dev/null
@@ -1,23 +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;
-
-public interface CachingDefinedQProfileCreation extends DefinedQProfileCreation {
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingDefinedQProfileCreationImpl.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingDefinedQProfileCreationImpl.java
deleted file mode 100644 (file)
index 1d4e755..0000000
+++ /dev/null
@@ -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);
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingRuleActivator.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingRuleActivator.java
deleted file mode 100644 (file)
index 452ef1f..0000000
+++ /dev/null
@@ -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;
-  }
-
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingRuleActivatorContextFactory.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/CachingRuleActivatorContextFactory.java
deleted file mode 100644 (file)
index befd973..0000000
+++ /dev/null
@@ -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()));
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileInsert.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileInsert.java
new file mode 100644 (file)
index 0000000..0eafd84
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * 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.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);
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileInsertImpl.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileInsertImpl.java
new file mode 100644 (file)
index 0000000..cf0dfad
--- /dev/null
@@ -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);
+  }
+}
index 4dde1a32ef22845bb79ea015199573dea18d74dc..05ed2e133c4c5f0b53efbabb5b2a8e5853477173 100644 (file)
@@ -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()));
index c003b4d5ba2aef7ac0a2e3233e583d1494976aca..95601f99af64edb213ef76967fbfcbd8f4a353f8 100644 (file)
  */
 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;
+    }
+  }
 }
index d7edffbfe7cd76eee90531886c17c174d48c07d1..04e42453c82c3f871463f87896b507ec4df7a8e0 100644 (file)
@@ -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());
   }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/CachingRuleActivatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/CachingRuleActivatorTest.java
deleted file mode 100644 (file)
index 66664b4..0000000
+++ /dev/null
@@ -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);
-  }
-}
index 0f449bd6f1e88192a3919ea4906d5576ef59139e..20add6673d48163bff7ded30c040f125cfe37857 100644 (file)
@@ -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));
   }
 
index 28abb7f9052c1de2b6ab5f1142cd4fe2c19c3f8b..9a0cc03abcb1b08bbeb44d348492015c81208ca8 100644 (file)
@@ -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<>();