]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6315 support parent in DefinedQProfileRepository
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 28 Apr 2017 15:08:00 +0000 (17:08 +0200)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 12 May 2017 09:18:19 +0000 (11:18 +0200)
fail if a profile references a non existing parent

server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfile.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileRepositoryImpl.java

index 278caad1a8ee8a03f2065cac2bfc382b77e14d41..af4dfff22fe1e2386e3c81e30635edac60f35684 100644 (file)
@@ -23,6 +23,8 @@ import com.google.common.collect.ImmutableList;
 import java.security.MessageDigest;
 import java.util.ArrayList;
 import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 import org.sonar.api.profiles.ProfileDefinition;
 
@@ -41,12 +43,14 @@ public final class DefinedQProfile {
   private final boolean isDefault;
   private final String loadedTemplateType;
   private final List<org.sonar.api.rules.ActiveRule> activeRules;
+  private final QProfileName parentQProfileName;
 
   private DefinedQProfile(Builder builder, MessageDigest messageDigest) {
     this.qProfileName = new QProfileName(builder.language, builder.getName());
     this.isDefault = builder.declaredDefault || builder.computedDefault;
     this.loadedTemplateType = computeLoadedTemplateType(this.qProfileName, messageDigest);
     this.activeRules = ImmutableList.copyOf(builder.activeRules);
+    this.parentQProfileName = builder.parentName == null ? null : new QProfileName(builder.language, builder.parentName);
   }
 
   private static String computeLoadedTemplateType(QProfileName qProfileName, MessageDigest messageDigest) {
@@ -78,12 +82,18 @@ public final class DefinedQProfile {
     return activeRules;
   }
 
+  @CheckForNull
+  public QProfileName getParentQProfileName() {
+    return parentQProfileName;
+  }
+
   static final class Builder {
     private String language;
     private String name;
     private boolean declaredDefault;
     private boolean computedDefault;
     private final List<org.sonar.api.rules.ActiveRule> activeRules = new ArrayList<>();
+    private String parentName;
 
     public Builder setLanguage(String language) {
       this.language = language;
@@ -118,6 +128,16 @@ public final class DefinedQProfile {
       return this;
     }
 
+    public Builder setParentName(@Nullable String parentName) {
+      this.parentName = parentName;
+      return this;
+    }
+
+    @CheckForNull
+    public String getParentName() {
+      return parentName;
+    }
+
     DefinedQProfile build(MessageDigest messageDigest) {
       return new DefinedQProfile(this, messageDigest);
     }
index 97b76dc86629dc3f1de84e3437254708a0fe6ebc..4dde1a32ef22845bb79ea015199573dea18d74dc 100644 (file)
  */
 package org.sonar.server.qualityprofile;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Multimaps;
 import java.security.MessageDigest;
 import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.function.Function;
 import javax.annotation.Nullable;
 import org.apache.commons.codec.digest.DigestUtils;
 import org.sonar.api.profiles.ProfileDefinition;
@@ -128,9 +133,24 @@ public class DefinedQProfileRepositoryImpl implements DefinedQProfileRepository
       .entrySet()
       .stream()
       .filter(DefinedQProfileRepositoryImpl::ensureAtMostOneDeclaredDefault)
+      .filter(entry -> ensureParentExists(entry.getKey(), entry.getValue()))
       .collect(MoreCollectors.uniqueIndex(Map.Entry::getKey, entry -> toQualityProfiles(entry.getValue()), buildersByLanguage.size()));
   }
 
+  private boolean ensureParentExists(String language, List<DefinedQProfile.Builder> builders) {
+    Set<String> qProfileNames = builders.stream()
+      .map(DefinedQProfile.Builder::getName)
+      .collect(MoreCollectors.toSet(builders.size()));
+    builders
+      .forEach(builder -> {
+        String parentName = builder.getParentName();
+        checkState(parentName == null || qProfileNames.contains(parentName),
+          "Quality profile with name %s references Quality profile with name %s as its parent, but it does not exist for language %s",
+          builder.getName(), builder.getParentName(), language);
+      });
+    return true;
+  }
+
   /**
    * Creates {@link DefinedQProfile.Builder} for each unique quality profile name for a given language.
    * Builders will have the following properties populated:
@@ -150,7 +170,7 @@ public class DefinedQProfileRepositoryImpl implements DefinedQProfileRepository
     for (RulesProfile rulesProfile : rulesProfilesByLanguage.getValue()) {
       qualityProfileBuildersByName.compute(
         rulesProfile.getName(),
-        (name, existingBuilder) -> updateOrCreateBuilder(language, existingBuilder, rulesProfile, name));
+        (name, existingBuilder) -> updateOrCreateBuilder(language, existingBuilder, rulesProfile));
     }
     return ImmutableList.copyOf(qualityProfileBuildersByName.values());
   }
@@ -167,12 +187,13 @@ public class DefinedQProfileRepositoryImpl implements DefinedQProfileRepository
     return true;
   }
 
-  private static DefinedQProfile.Builder updateOrCreateBuilder(String language, @Nullable DefinedQProfile.Builder existingBuilder, RulesProfile rulesProfile, String name) {
+  private static DefinedQProfile.Builder updateOrCreateBuilder(String language, @Nullable DefinedQProfile.Builder existingBuilder, RulesProfile rulesProfile) {
     DefinedQProfile.Builder builder = existingBuilder;
     if (builder == null) {
       builder = new DefinedQProfile.Builder()
         .setLanguage(language)
-        .setName(name);
+        .setName(rulesProfile.getName())
+        .setParentName(rulesProfile.getParentName());
     }
     Boolean defaultProfile = rulesProfile.getDefaultProfile();
     boolean declaredDefault = defaultProfile != null && defaultProfile;
@@ -194,7 +215,45 @@ public class DefinedQProfileRepositoryImpl implements DefinedQProfileRepository
     }
     MessageDigest md5Digest = DigestUtils.getMd5Digest();
     return builders.stream()
+      .sorted(new SortByParentName(builders))
       .map(builder -> builder.build(md5Digest))
       .collect(MoreCollectors.toList(builders.size()));
   }
+
+  @VisibleForTesting
+  static class SortByParentName implements Comparator<DefinedQProfile.Builder> {
+    private final Map<String, DefinedQProfile.Builder> buildersByName;
+    @VisibleForTesting
+    final Map<String, Integer> depthByBuilder;
+
+    @VisibleForTesting
+    SortByParentName(Collection<DefinedQProfile.Builder> builders) {
+      this.buildersByName = builders.stream()
+        .collect(MoreCollectors.uniqueIndex(DefinedQProfile.Builder::getName, Function.identity(), builders.size()));
+      this.depthByBuilder = buildDepthByBuilder(buildersByName, builders);
+    }
+
+    private static Map<String, Integer> buildDepthByBuilder(Map<String, DefinedQProfile.Builder> buildersByName, Collection<DefinedQProfile.Builder> builders) {
+      Map<String, Integer> depthByBuilder = new HashMap<>();
+      builders.forEach(builder -> depthByBuilder.put(builder.getName(), 0));
+      builders
+        .stream()
+        .filter(builder -> builder.getParentName() != null)
+        .forEach(builder -> increaseDepth(buildersByName, depthByBuilder, builder));
+      return ImmutableMap.copyOf(depthByBuilder);
+    }
+
+    private static void increaseDepth(Map<String, DefinedQProfile.Builder> buildersByName, Map<String, Integer> maps, DefinedQProfile.Builder builder) {
+      DefinedQProfile.Builder parent = buildersByName.get(builder.getParentName());
+      if (parent.getParentName() != null) {
+        increaseDepth(buildersByName, maps, parent);
+      }
+      maps.put(builder.getName(), maps.get(parent.getName()) + 1);
+    }
+
+    @Override
+    public int compare(DefinedQProfile.Builder o1, DefinedQProfile.Builder o2) {
+      return depthByBuilder.getOrDefault(o1.getName(), 0) - depthByBuilder.getOrDefault(o2.getName(), 0);
+    }
+  }
 }