]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8857 add parameter "organization" to api/qualityprofiles/restore"
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Tue, 14 Mar 2017 14:38:23 +0000 (15:38 +0100)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Thu, 23 Mar 2017 16:38:34 +0000 (17:38 +0100)
If unset, then the default organization is used.

server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileBackuper.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileBackuperImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileCopier.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileReset.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/OldRestoreAction.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/RestoreAction.java
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/QProfileBackuperMediumTest.java
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/BackupActionTest.java
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/RestoreActionTest.java

index bb99d2a0aa6493818fdbca1f34711af342c82e7b..46ddd1fc49d02aa2596f6bdae737c8ea52128b50 100644 (file)
@@ -140,7 +140,7 @@ import org.sonar.server.projecttag.ws.ProjectTagsWsModule;
 import org.sonar.server.property.InternalPropertiesImpl;
 import org.sonar.server.property.ws.PropertiesWs;
 import org.sonar.server.qualitygate.QualityGateModule;
-import org.sonar.server.qualityprofile.QProfileBackuper;
+import org.sonar.server.qualityprofile.QProfileBackuperImpl;
 import org.sonar.server.qualityprofile.QProfileComparison;
 import org.sonar.server.qualityprofile.QProfileCopier;
 import org.sonar.server.qualityprofile.QProfileExporters;
@@ -275,7 +275,7 @@ public class PlatformLevel4 extends PlatformLevel {
       RuleActivatorContextFactory.class,
       QProfileFactory.class,
       QProfileCopier.class,
-      QProfileBackuper.class,
+      QProfileBackuperImpl.class,
       QProfileReset.class,
       QProfilesWsModule.class,
 
index 1f59325ae7676a877c96788eec8eec0f78ceb8c6..cb8cb04cf2b0a58f05c529d1f8c4d986c415111e 100644 (file)
  */
 package org.sonar.server.qualityprofile;
 
-import com.google.common.base.Joiner;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
 import java.io.Reader;
 import java.io.Writer;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
 import javax.annotation.Nullable;
-import javax.xml.stream.XMLInputFactory;
-import javax.xml.stream.XMLStreamException;
-import org.apache.commons.lang.ObjectUtils;
-import org.apache.commons.lang.StringUtils;
-import org.apache.commons.lang.builder.CompareToBuilder;
-import org.codehaus.staxmate.SMInputFactory;
-import org.codehaus.staxmate.in.SMHierarchicCursor;
-import org.codehaus.staxmate.in.SMInputCursor;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.server.ServerSide;
-import org.sonar.api.utils.text.XmlWriter;
-import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
-import org.sonar.db.qualityprofile.ActiveRuleDto;
-import org.sonar.db.qualityprofile.ActiveRuleParamDto;
+import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.qualityprofile.QualityProfileDto;
 
-@ServerSide
-public class QProfileBackuper {
-
-  private static final Joiner RULE_KEY_JOINER = Joiner.on(", ").skipNulls();
-
-  private final QProfileReset reset;
-  private final DbClient db;
-
-  public QProfileBackuper(QProfileReset reset, DbClient db) {
-    this.reset = reset;
-    this.db = db;
-  }
-
-  public void backup(DbSession dbSession, QualityProfileDto profileDto, Writer writer) {
-    List<ActiveRuleDto> activeRules = db.activeRuleDao().selectByProfileKey(dbSession, profileDto.getKey());
-    activeRules.sort(BackupActiveRuleComparator.INSTANCE);
-    writeXml(dbSession, writer, profileDto, activeRules.iterator());
-  }
+/**
+ * Backup and restore a Quality profile.
+ */
+public interface QProfileBackuper {
 
-  private void writeXml(DbSession dbSession, Writer writer, QualityProfileDto profile, Iterator<ActiveRuleDto> activeRules) {
-    XmlWriter xml = XmlWriter.of(writer).declaration();
-    xml.begin("profile");
-    xml.prop("name", profile.getName());
-    xml.prop("language", profile.getLanguage());
-    xml.begin("rules");
-    while (activeRules.hasNext()) {
-      ActiveRuleDto activeRule = activeRules.next();
-      xml.begin("rule");
-      xml.prop("repositoryKey", activeRule.getKey().ruleKey().repository());
-      xml.prop("key", activeRule.getKey().ruleKey().rule());
-      xml.prop("priority", activeRule.getSeverityString());
-      xml.begin("parameters");
-      for (ActiveRuleParamDto param : db.activeRuleDao().selectParamsByActiveRuleId(dbSession, activeRule.getId())) {
-        xml
-          .begin("parameter")
-          .prop("key", param.getKey())
-          .prop("value", param.getValue())
-          .end();
-      }
-      xml.end("parameters");
-      xml.end("rule");
-    }
-    xml.end("rules").end("profile").close();
-  }
+  void backup(DbSession dbSession, QualityProfileDto profile, Writer backupWriter);
 
   /**
-   * @param reader     the XML backup
-   * @param toProfileName the target profile. If <code>null</code>, then use the
-   *                   lang and name declared in the backup
+   * Restore a profile backup in the specified organization. The parameter {@code overriddenProfileName}
+   * is the name of the profile to be used. If null then name is loaded from backup.
    */
-  public BulkChangeResult restore(DbSession dbSession, Reader reader, @Nullable QProfileName toProfileName) {
-    try {
-      String profileLang = null;
-      String profileName = null;
-      List<RuleActivation> ruleActivations = Lists.newArrayList();
-      SMInputFactory inputFactory = initStax();
-      SMHierarchicCursor rootC = inputFactory.rootElementCursor(reader);
-      rootC.advance(); // <profile>
-      if (!rootC.getLocalName().equals("profile")) {
-        throw new IllegalArgumentException("Backup XML is not valid. Root element must be <profile>.");
-      }
-      SMInputCursor cursor = rootC.childElementCursor();
-      while (cursor.getNext() != null) {
-        String nodeName = cursor.getLocalName();
-        if (StringUtils.equals("name", nodeName)) {
-          profileName = StringUtils.trim(cursor.collectDescendantText(false));
-
-        } else if (StringUtils.equals("language", nodeName)) {
-          profileLang = StringUtils.trim(cursor.collectDescendantText(false));
-
-        } else if (StringUtils.equals("rules", nodeName)) {
-          SMInputCursor rulesCursor = cursor.childElementCursor("rule");
-          ruleActivations = parseRuleActivations(rulesCursor);
-        }
-      }
-
-      QProfileName target = (QProfileName) ObjectUtils.defaultIfNull(toProfileName, new QProfileName(profileLang, profileName));
-      return reset.reset(dbSession, target, ruleActivations);
-    } catch (XMLStreamException e) {
-      throw new IllegalStateException("Fail to restore Quality profile backup", e);
-    }
-  }
-
-  private List<RuleActivation> parseRuleActivations(SMInputCursor rulesCursor) throws XMLStreamException {
-    List<RuleActivation> activations = Lists.newArrayList();
-    Set<RuleKey> activatedKeys = Sets.newHashSet();
-    List<RuleKey> duplicatedKeys = Lists.newArrayList();
-    while (rulesCursor.getNext() != null) {
-      SMInputCursor ruleCursor = rulesCursor.childElementCursor();
-      String repositoryKey = null;
-      String key = null;
-      String severity = null;
-      Map<String, String> parameters = Maps.newHashMap();
-      while (ruleCursor.getNext() != null) {
-        String nodeName = ruleCursor.getLocalName();
-        if (StringUtils.equals("repositoryKey", nodeName)) {
-          repositoryKey = StringUtils.trim(ruleCursor.collectDescendantText(false));
-
-        } else if (StringUtils.equals("key", nodeName)) {
-          key = StringUtils.trim(ruleCursor.collectDescendantText(false));
-
-        } else if (StringUtils.equals("priority", nodeName)) {
-          severity = StringUtils.trim(ruleCursor.collectDescendantText(false));
-
-        } else if (StringUtils.equals("parameters", nodeName)) {
-          SMInputCursor propsCursor = ruleCursor.childElementCursor("parameter");
-          readParameters(propsCursor, parameters);
-        }
-      }
-      RuleKey ruleKey = RuleKey.of(repositoryKey, key);
-      if (activatedKeys.contains(ruleKey)) {
-        duplicatedKeys.add(ruleKey);
-      }
-      activatedKeys.add(ruleKey);
-      RuleActivation activation = new RuleActivation(ruleKey);
-      activation.setSeverity(severity);
-      activation.setParameters(parameters);
-      activations.add(activation);
-    }
-    if (!duplicatedKeys.isEmpty()) {
-      throw new IllegalArgumentException("The quality profile cannot be restored as it contains duplicates for the following rules: " +
-        RULE_KEY_JOINER.join(duplicatedKeys));
-    }
-    return activations;
-  }
-
-  private void readParameters(SMInputCursor propsCursor, Map<String, String> parameters) throws XMLStreamException {
-    while (propsCursor.getNext() != null) {
-      SMInputCursor propCursor = propsCursor.childElementCursor();
-      String key = null;
-      String value = null;
-      while (propCursor.getNext() != null) {
-        String nodeName = propCursor.getLocalName();
-        if (StringUtils.equals("key", nodeName)) {
-          key = StringUtils.trim(propCursor.collectDescendantText(false));
-        } else if (StringUtils.equals("value", nodeName)) {
-          value = StringUtils.trim(propCursor.collectDescendantText(false));
-        }
-      }
-      if (key != null) {
-        parameters.put(key, value);
-      }
-    }
-  }
-
-  private static SMInputFactory initStax() {
-    XMLInputFactory xmlFactory = XMLInputFactory.newInstance();
-    xmlFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
-    xmlFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE);
-    // just so it won't try to load DTD in if there's DOCTYPE
-    xmlFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
-    xmlFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
-    return new SMInputFactory(xmlFactory);
-  }
-
-  private enum BackupActiveRuleComparator implements Comparator<ActiveRuleDto> {
-    INSTANCE;
+  BulkChangeResult restore(DbSession dbSession, Reader backup, OrganizationDto organization, @Nullable String overriddenProfileName);
 
-    @Override
-    public int compare(ActiveRuleDto o1, ActiveRuleDto o2) {
-      return new CompareToBuilder()
-        .append(o1.getKey().ruleKey().repository(), o2.getKey().ruleKey().repository())
-        .append(o1.getKey().ruleKey().rule(), o2.getKey().ruleKey().rule())
-        .toComparison();
-    }
-  }
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileBackuperImpl.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileBackuperImpl.java
new file mode 100644 (file)
index 0000000..807732d
--- /dev/null
@@ -0,0 +1,215 @@
+/*
+ * 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.Joiner;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Nullable;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.builder.CompareToBuilder;
+import org.codehaus.staxmate.SMInputFactory;
+import org.codehaus.staxmate.in.SMHierarchicCursor;
+import org.codehaus.staxmate.in.SMInputCursor;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.utils.text.XmlWriter;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.qualityprofile.ActiveRuleDto;
+import org.sonar.db.qualityprofile.ActiveRuleParamDto;
+import org.sonar.db.qualityprofile.QualityProfileDto;
+
+@ServerSide
+public class QProfileBackuperImpl implements QProfileBackuper {
+
+  private static final Joiner RULE_KEY_JOINER = Joiner.on(", ").skipNulls();
+
+  private final QProfileReset reset;
+  private final DbClient db;
+
+  public QProfileBackuperImpl(QProfileReset reset, DbClient db) {
+    this.reset = reset;
+    this.db = db;
+  }
+
+  @Override
+  public void backup(DbSession dbSession, QualityProfileDto profileDto, Writer writer) {
+    List<ActiveRuleDto> activeRules = db.activeRuleDao().selectByProfileKey(dbSession, profileDto.getKey());
+    activeRules.sort(BackupActiveRuleComparator.INSTANCE);
+    writeXml(dbSession, writer, profileDto, activeRules.iterator());
+  }
+
+  private void writeXml(DbSession dbSession, Writer writer, QualityProfileDto profile, Iterator<ActiveRuleDto> activeRules) {
+    XmlWriter xml = XmlWriter.of(writer).declaration();
+    xml.begin("profile");
+    xml.prop("name", profile.getName());
+    xml.prop("language", profile.getLanguage());
+    xml.begin("rules");
+    while (activeRules.hasNext()) {
+      ActiveRuleDto activeRule = activeRules.next();
+      xml.begin("rule");
+      xml.prop("repositoryKey", activeRule.getKey().ruleKey().repository());
+      xml.prop("key", activeRule.getKey().ruleKey().rule());
+      xml.prop("priority", activeRule.getSeverityString());
+      xml.begin("parameters");
+      for (ActiveRuleParamDto param : db.activeRuleDao().selectParamsByActiveRuleId(dbSession, activeRule.getId())) {
+        xml
+          .begin("parameter")
+          .prop("key", param.getKey())
+          .prop("value", param.getValue())
+          .end();
+      }
+      xml.end("parameters");
+      xml.end("rule");
+    }
+    xml.end("rules").end("profile").close();
+  }
+
+  @Override
+  public BulkChangeResult restore(DbSession dbSession, Reader backup, OrganizationDto organization, @Nullable String overriddenProfileName) {
+    try {
+      String profileLang = null;
+      String profileName = null;
+      List<RuleActivation> ruleActivations = Lists.newArrayList();
+      SMInputFactory inputFactory = initStax();
+      SMHierarchicCursor rootC = inputFactory.rootElementCursor(backup);
+      rootC.advance(); // <profile>
+      if (!"profile".equals(rootC.getLocalName())) {
+        throw new IllegalArgumentException("Backup XML is not valid. Root element must be <profile>.");
+      }
+      SMInputCursor cursor = rootC.childElementCursor();
+      while (cursor.getNext() != null) {
+        String nodeName = cursor.getLocalName();
+        if (StringUtils.equals("name", nodeName)) {
+          profileName = StringUtils.trim(cursor.collectDescendantText(false));
+
+        } else if (StringUtils.equals("language", nodeName)) {
+          profileLang = StringUtils.trim(cursor.collectDescendantText(false));
+
+        } else if (StringUtils.equals("rules", nodeName)) {
+          SMInputCursor rulesCursor = cursor.childElementCursor("rule");
+          ruleActivations = parseRuleActivations(rulesCursor);
+        }
+      }
+
+      QProfileName target = new QProfileName(profileLang, MoreObjects.firstNonNull(overriddenProfileName, profileName));
+      return reset.reset(dbSession, organization, target, ruleActivations);
+    } catch (XMLStreamException e) {
+      throw new IllegalStateException("Fail to restore Quality profile backup", e);
+    }
+  }
+
+  private List<RuleActivation> parseRuleActivations(SMInputCursor rulesCursor) throws XMLStreamException {
+    List<RuleActivation> activations = Lists.newArrayList();
+    Set<RuleKey> activatedKeys = Sets.newHashSet();
+    List<RuleKey> duplicatedKeys = Lists.newArrayList();
+    while (rulesCursor.getNext() != null) {
+      SMInputCursor ruleCursor = rulesCursor.childElementCursor();
+      String repositoryKey = null;
+      String key = null;
+      String severity = null;
+      Map<String, String> parameters = Maps.newHashMap();
+      while (ruleCursor.getNext() != null) {
+        String nodeName = ruleCursor.getLocalName();
+        if (StringUtils.equals("repositoryKey", nodeName)) {
+          repositoryKey = StringUtils.trim(ruleCursor.collectDescendantText(false));
+
+        } else if (StringUtils.equals("key", nodeName)) {
+          key = StringUtils.trim(ruleCursor.collectDescendantText(false));
+
+        } else if (StringUtils.equals("priority", nodeName)) {
+          severity = StringUtils.trim(ruleCursor.collectDescendantText(false));
+
+        } else if (StringUtils.equals("parameters", nodeName)) {
+          SMInputCursor propsCursor = ruleCursor.childElementCursor("parameter");
+          readParameters(propsCursor, parameters);
+        }
+      }
+      RuleKey ruleKey = RuleKey.of(repositoryKey, key);
+      if (activatedKeys.contains(ruleKey)) {
+        duplicatedKeys.add(ruleKey);
+      }
+      activatedKeys.add(ruleKey);
+      RuleActivation activation = new RuleActivation(ruleKey);
+      activation.setSeverity(severity);
+      activation.setParameters(parameters);
+      activations.add(activation);
+    }
+    if (!duplicatedKeys.isEmpty()) {
+      throw new IllegalArgumentException("The quality profile cannot be restored as it contains duplicates for the following rules: " +
+        RULE_KEY_JOINER.join(duplicatedKeys));
+    }
+    return activations;
+  }
+
+  private static void readParameters(SMInputCursor propsCursor, Map<String, String> parameters) throws XMLStreamException {
+    while (propsCursor.getNext() != null) {
+      SMInputCursor propCursor = propsCursor.childElementCursor();
+      String key = null;
+      String value = null;
+      while (propCursor.getNext() != null) {
+        String nodeName = propCursor.getLocalName();
+        if (StringUtils.equals("key", nodeName)) {
+          key = StringUtils.trim(propCursor.collectDescendantText(false));
+        } else if (StringUtils.equals("value", nodeName)) {
+          value = StringUtils.trim(propCursor.collectDescendantText(false));
+        }
+      }
+      if (key != null) {
+        parameters.put(key, value);
+      }
+    }
+  }
+
+  private static SMInputFactory initStax() {
+    XMLInputFactory xmlFactory = XMLInputFactory.newInstance();
+    xmlFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
+    xmlFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE);
+    // just so it won't try to load DTD in if there's DOCTYPE
+    xmlFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
+    xmlFactory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
+    return new SMInputFactory(xmlFactory);
+  }
+
+  private enum BackupActiveRuleComparator implements Comparator<ActiveRuleDto> {
+    INSTANCE;
+
+    @Override
+    public int compare(ActiveRuleDto o1, ActiveRuleDto o2) {
+      return new CompareToBuilder()
+        .append(o1.getKey().ruleKey().repository(), o2.getKey().ruleKey().repository())
+        .append(o1.getKey().ruleKey().rule(), o2.getKey().ruleKey().rule())
+        .toComparison();
+    }
+  }
+}
index 69ea0c002401543ca115bf6a17ccb3cc099dc96e..c164feaabf55780c8059171c4d0327cac73fd2f1 100644 (file)
@@ -31,6 +31,7 @@ import org.sonar.api.server.ServerSide;
 import org.sonar.api.utils.TempFolder;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
+import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.qualityprofile.QualityProfileDto;
 import org.sonar.server.qualityprofile.ws.QProfileWsSupport;
 
@@ -53,27 +54,29 @@ public class QProfileCopier {
     this.qProfileWsSupport = qProfileWsSupport;
   }
 
-  public QualityProfileDto copyToName(DbSession dbSession, String fromProfileKey, String toName) {
-    QualityProfileDto from = db.qualityProfileDao().selectOrFailByKey(dbSession, fromProfileKey);
-    QualityProfileDto to = prepareTarget(dbSession, from, toName);
+  public QualityProfileDto copyToName(DbSession dbSession, String fromKey, String toName) {
+    QualityProfileDto from = db.qualityProfileDao().selectOrFailByKey(dbSession, fromKey);
+    OrganizationDto organization = db.organizationDao().selectByUuid(dbSession, from.getOrganizationUuid())
+      .orElseThrow(() -> new IllegalStateException("Organization with UUID [" + from.getOrganizationUuid() + "] does not exist"));
+    QualityProfileDto to = prepareTarget(dbSession, organization, from, toName);
     File backupFile = temp.newFile();
     try {
       backup(dbSession, from, backupFile);
-      restore(dbSession, backupFile, QProfileName.createFor(to.getLanguage(), to.getName()));
+      restore(dbSession, backupFile, organization, to.getName());
       return to;
     } finally {
       org.sonar.core.util.FileUtils.deleteQuietly(backupFile);
     }
   }
 
-  private QualityProfileDto prepareTarget(DbSession dbSession, QualityProfileDto from, String toName) {
+  private QualityProfileDto prepareTarget(DbSession dbSession, OrganizationDto organization, QualityProfileDto from, String toName) {
     QProfileName toProfileName = new QProfileName(from.getLanguage(), toName);
     verify(from, toProfileName);
-    QualityProfileDto toProfile = db.qualityProfileDao().selectByNameAndLanguage(toProfileName.getName(), toProfileName.getLanguage(), dbSession);
+    QualityProfileDto toProfile = db.qualityProfileDao().selectByNameAndLanguage(organization, toProfileName.getName(), toProfileName.getLanguage(), dbSession);
     if (toProfile == null) {
       // Do not delegate creation to QProfileBackuper because we need to keep
       // the parent-child association, if exists.
-      toProfile = factory.create(dbSession, qProfileWsSupport.getDefaultOrganization(dbSession), toProfileName);
+      toProfile = factory.create(dbSession, organization, toProfileName);
       toProfile.setParentKee(from.getParentKee());
       db.qualityProfileDao().update(dbSession, toProfile);
       dbSession.commit();
@@ -101,9 +104,9 @@ public class QProfileCopier {
     }
   }
 
-  private void restore(DbSession dbSession, File backupFile, QProfileName profileName) {
+  private void restore(DbSession dbSession, File backupFile, OrganizationDto org, String profileName) {
     try (Reader reader = new InputStreamReader(FileUtils.openInputStream(backupFile), UTF_8)) {
-      backuper.restore(dbSession, reader, profileName);
+      backuper.restore(dbSession, reader, org, profileName);
     } catch (IOException e) {
       throw new IllegalStateException("Fail to create temporary backup file: " + backupFile, e);
     }
index 305b1e5b7974c6fbbbc77c7e8b66a6a5ec80068b..1705f16ae37a619d435b73b2179687df32b9f8fd 100644 (file)
@@ -38,6 +38,7 @@ import org.sonar.api.server.ServerSide;
 import org.sonar.api.utils.ValidationMessages;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
+import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.qualityprofile.ActiveRuleDto;
 import org.sonar.db.qualityprofile.ActiveRuleKey;
 import org.sonar.db.qualityprofile.QualityProfileDto;
@@ -106,8 +107,8 @@ public class QProfileReset {
   /**
    * Reset the profile, which is created if it does not exist
    */
-  BulkChangeResult reset(DbSession dbSession, QProfileName profileName, Collection<RuleActivation> activations) {
-    QualityProfileDto profile = factory.getOrCreate(dbSession, qProfileWsSupport.getDefaultOrganization(dbSession), profileName);
+  BulkChangeResult reset(DbSession dbSession, OrganizationDto organization, QProfileName profileName, Collection<RuleActivation> activations) {
+    QualityProfileDto profile = factory.getOrCreate(dbSession, organization, profileName);
     return doReset(dbSession, profile, activations);
   }
 
index ba8b7935fe43fa0076ab544f9660dc16726699fb..0b0eca3e0fdaa25381e14a85d233dc28a252ba3e 100644 (file)
@@ -31,6 +31,7 @@ import org.sonar.api.server.ws.WebService;
 import org.sonar.api.utils.text.JsonWriter;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
+import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.qualityprofile.QualityProfileDto;
 import org.sonar.server.qualityprofile.BulkChangeResult;
 import org.sonar.server.qualityprofile.QProfileBackuper;
@@ -85,7 +86,8 @@ public class OldRestoreAction implements WsAction {
     try (DbSession dbSession = dbClient.openSession(false)) {
       checkArgument(backup != null, "A backup file must be provided");
       reader = new InputStreamReader(backup, StandardCharsets.UTF_8);
-      BulkChangeResult result = backuper.restore(dbSession, reader, null);
+      OrganizationDto defaultOrg = qProfileWsSupport.getOrganizationByKey(dbSession, null);
+      BulkChangeResult result = backuper.restore(dbSession, reader, defaultOrg,null);
       writeResponse(response.newJsonWriter(), result);
     } finally {
       IOUtils.closeQuietly(reader);
index 3c6ef097ac7ed181a7c1458e246c2f6877fc6cea..35d220ea277e944a7a9fea4cdb85420d8f0539a0 100644 (file)
@@ -30,12 +30,16 @@ import org.sonar.api.server.ws.WebService;
 import org.sonar.api.utils.text.JsonWriter;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.permission.OrganizationPermission;
 import org.sonar.db.qualityprofile.QualityProfileDto;
 import org.sonar.server.qualityprofile.BulkChangeResult;
 import org.sonar.server.qualityprofile.QProfileBackuper;
+import org.sonar.server.user.UserSession;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_ORGANIZATION;
 
 public class RestoreAction implements QProfileWsAction {
 
@@ -44,12 +48,14 @@ public class RestoreAction implements QProfileWsAction {
   private final DbClient dbClient;
   private final QProfileBackuper backuper;
   private final Languages languages;
+  private final UserSession userSession;
   private final QProfileWsSupport wsSupport;
 
-  public RestoreAction(DbClient dbClient, QProfileBackuper backuper, Languages languages, QProfileWsSupport wsSupport) {
+  public RestoreAction(DbClient dbClient, QProfileBackuper backuper, Languages languages, UserSession userSession, QProfileWsSupport wsSupport) {
     this.dbClient = dbClient;
     this.backuper = backuper;
     this.languages = languages;
+    this.userSession = userSession;
     this.wsSupport = wsSupport;
   }
 
@@ -67,19 +73,26 @@ public class RestoreAction implements QProfileWsAction {
       .setDescription("A profile backup file in XML format, as generated by api/qualityprofiles/backup " +
         "or the former api/profiles/backup.")
       .setRequired(true);
+
+    QProfileWsSupport.createOrganizationParam(action).setSince("6.4");
   }
 
   @Override
   public void handle(Request request, Response response) throws Exception {
-    wsSupport.checkQProfileAdminPermission();
+    userSession.checkLoggedIn();
 
     InputStream backup = request.paramAsInputStream(PARAM_BACKUP);
+    String organizationKey = request.param(PARAM_ORGANIZATION);
     InputStreamReader reader = null;
 
     try (DbSession dbSession = dbClient.openSession(false)) {
       checkArgument(backup != null, "A backup file must be provided");
       reader = new InputStreamReader(backup, UTF_8);
-      BulkChangeResult result = backuper.restore(dbSession, reader, null);
+
+      OrganizationDto organization = wsSupport.getOrganizationByKey(dbSession, organizationKey);
+      userSession.checkPermission(OrganizationPermission.ADMINISTER_QUALITY_PROFILES, organization);
+
+      BulkChangeResult result = backuper.restore(dbSession, reader, organization, null);
       writeResponse(response.newJsonWriter(), result);
     } finally {
       IOUtils.closeQuietly(reader);
index 54de3e9074a0c1f4665c6065d3729e6052827ff4..27e75f132ff64e1071a3f123efa36a0bc33d981f 100644 (file)
@@ -146,7 +146,7 @@ public class QProfileBackuperMediumTest {
     // Backup file declares profile P1 on xoo
     tester.get(QProfileBackuper.class).restore(dbSession, new StringReader(
       Resources.toString(getClass().getResource("QProfileBackuperMediumTest/restore.xml"), StandardCharsets.UTF_8)),
-      null);
+      organization, null);
 
     // Check in db
     QualityProfileDto profile = db.qualityProfileDao().selectByNameAndLanguage("P1", "xoo", dbSession);
@@ -187,7 +187,7 @@ public class QProfileBackuperMediumTest {
     // restore backup, which activates only x1
     // -> update x1 and deactivate x2
     tester.get(QProfileBackuper.class).restore(dbSession, new StringReader(
-      Resources.toString(getClass().getResource("QProfileBackuperMediumTest/restore.xml"), StandardCharsets.UTF_8)), null);
+      Resources.toString(getClass().getResource("QProfileBackuperMediumTest/restore.xml"), StandardCharsets.UTF_8)), organization, null);
 
     // Check in db
     List<ActiveRuleDto> activeRules = db.activeRuleDao().selectByProfileKey(dbSession, XOO_P1_KEY);
@@ -225,7 +225,7 @@ public class QProfileBackuperMediumTest {
 
     // restore backup of child profile -> overrides x1
     tester.get(QProfileBackuper.class).restore(dbSession, new StringReader(
-      Resources.toString(getClass().getResource("QProfileBackuperMediumTest/restore-child.xml"), StandardCharsets.UTF_8)), null);
+      Resources.toString(getClass().getResource("QProfileBackuperMediumTest/restore-child.xml"), StandardCharsets.UTF_8)), organization, null);
 
     // parent profile is unchanged
     List<ActiveRuleDto> activeRules = db.activeRuleDao().selectByProfileKey(dbSession, XOO_P1_KEY);
@@ -273,7 +273,7 @@ public class QProfileBackuperMediumTest {
 
     // restore backup of parent profile -> update x1 and propagates to child
     tester.get(QProfileBackuper.class).restore(dbSession, new StringReader(
-      Resources.toString(getClass().getResource("QProfileBackuperMediumTest/restore-parent.xml"), StandardCharsets.UTF_8)), null);
+      Resources.toString(getClass().getResource("QProfileBackuperMediumTest/restore-parent.xml"), StandardCharsets.UTF_8)), organization, null);
 
     // parent profile is updated
     List<ActiveRuleDto> activeRules = db.activeRuleDao().selectByProfileKey(dbSession, XOO_P1_KEY);
@@ -322,7 +322,7 @@ public class QProfileBackuperMediumTest {
 
     // backup of child profile contains x2 but not x1
     tester.get(QProfileBackuper.class).restore(dbSession, new StringReader(
-      Resources.toString(getClass().getResource("QProfileBackuperMediumTest/keep_other_inherited_rules.xml"), StandardCharsets.UTF_8)), XOO_P2_NAME);
+      Resources.toString(getClass().getResource("QProfileBackuperMediumTest/keep_other_inherited_rules.xml"), StandardCharsets.UTF_8)), organization, XOO_P2_NAME.getName());
 
     // x1 and x2
     assertThat(db.activeRuleDao().selectByProfileKey(dbSession, XOO_P2_KEY)).hasSize(2);
@@ -332,7 +332,7 @@ public class QProfileBackuperMediumTest {
   public void fail_to_restore_if_not_xml_backup() throws Exception {
     try {
       tester.get(QProfileBackuper.class).restore(dbSession, new StringReader(
-        Resources.toString(getClass().getResource("QProfileBackuperMediumTest/not-xml-backup.txt"), StandardCharsets.UTF_8)), null);
+        Resources.toString(getClass().getResource("QProfileBackuperMediumTest/not-xml-backup.txt"), StandardCharsets.UTF_8)), organization, null);
       fail();
     } catch (IllegalStateException e) {
       assertThat(e).hasMessage("Fail to restore Quality profile backup");
@@ -344,7 +344,7 @@ public class QProfileBackuperMediumTest {
   public void fail_to_restore_if_bad_xml_format() throws Exception {
     try {
       tester.get(QProfileBackuper.class).restore(dbSession, new StringReader(
-        Resources.toString(getClass().getResource("QProfileBackuperMediumTest/bad-xml-backup.xml"), StandardCharsets.UTF_8)), null);
+        Resources.toString(getClass().getResource("QProfileBackuperMediumTest/bad-xml-backup.xml"), StandardCharsets.UTF_8)), organization, null);
       fail();
     } catch (IllegalArgumentException e) {
       assertThat(e).hasMessage("Backup XML is not valid. Root element must be <profile>.");
@@ -355,7 +355,7 @@ public class QProfileBackuperMediumTest {
   public void fail_to_restore_if_duplicate_rule() throws Exception {
     try {
       tester.get(QProfileBackuper.class).restore(dbSession, new StringReader(
-        Resources.toString(getClass().getResource("QProfileBackuperMediumTest/duplicates-xml-backup.xml"), StandardCharsets.UTF_8)), null);
+        Resources.toString(getClass().getResource("QProfileBackuperMediumTest/duplicates-xml-backup.xml"), StandardCharsets.UTF_8)), organization, null);
       fail();
     } catch (IllegalArgumentException e) {
       assertThat(e).hasMessage("The quality profile cannot be restored as it contains duplicates for the following rules: xoo:x1, xoo:x2");
@@ -366,7 +366,7 @@ public class QProfileBackuperMediumTest {
   public void restore_and_override_profile_name() throws Exception {
     tester.get(QProfileBackuper.class).restore(dbSession, new StringReader(
       Resources.toString(getClass().getResource("QProfileBackuperMediumTest/restore.xml"), StandardCharsets.UTF_8)),
-      XOO_P3_NAME);
+      organization, XOO_P3_NAME.getName());
 
     List<ActiveRuleDto> activeRules = db.activeRuleDao().selectByProfileKey(dbSession, XOO_P1_KEY);
     assertThat(activeRules).hasSize(0);
@@ -380,7 +380,7 @@ public class QProfileBackuperMediumTest {
   public void restore_profile_with_zero_rules() throws Exception {
     tester.get(QProfileBackuper.class).restore(dbSession, new StringReader(
       Resources.toString(getClass().getResource("QProfileBackuperMediumTest/empty.xml"), StandardCharsets.UTF_8)),
-      null);
+      organization, null);
 
     dbSession.clearCache();
     assertThat(db.activeRuleDao().selectAll(dbSession)).hasSize(0);
index 66e5aff7fa99ac662cedd11a476f2ae9d5128971..1120cb0830d0030c5179f8fae86c0fd20a7f8d4c 100644 (file)
@@ -33,6 +33,7 @@ import org.sonar.server.language.LanguageTesting;
 import org.sonar.server.organization.DefaultOrganizationProvider;
 import org.sonar.server.organization.TestDefaultOrganizationProvider;
 import org.sonar.server.qualityprofile.QProfileBackuper;
+import org.sonar.server.qualityprofile.QProfileBackuperImpl;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.TestResponse;
 import org.sonar.server.ws.WsActionTester;
@@ -50,7 +51,7 @@ public class BackupActionTest {
   @Rule
   public UserSessionRule userSession = UserSessionRule.standalone();
 
-  private QProfileBackuper backuper = new QProfileBackuper(null, db.getDbClient());
+  private QProfileBackuper backuper = new QProfileBackuperImpl(null, db.getDbClient());
   private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db);
   private QProfileWsSupport wsSupport = new QProfileWsSupport(db.getDbClient(), userSession, defaultOrganizationProvider);
   private Languages languages = LanguageTesting.newLanguages(A_LANGUAGE);
@@ -149,6 +150,13 @@ public class BackupActionTest {
       .execute();
   }
 
+  @Test
+  public void throws_IAE_if_profile_reference_is_not_set() throws Exception {
+    expectedException.expect(IllegalArgumentException.class);
+
+    tester.newRequest().execute();
+  }
+
   private static String xmlForProfileWithoutRules(QualityProfileDto profile) {
     return "<?xml version='1.0' encoding='UTF-8'?>" +
       "<profile>" +
@@ -160,7 +168,7 @@ public class BackupActionTest {
 
   private static QualityProfileDto newProfile(OrganizationDto org) {
     return QualityProfileTesting.newQualityProfileDto()
-      .setLanguage("xoo")
+      .setLanguage(A_LANGUAGE)
       .setOrganizationUuid(org.getUuid());
   }
 }
index 3e9de5eeb423a1e27468ba51dae43089f36fe90f..9bf7869a72ada87e323bcca2a2075780d28c9106 100644 (file)
  */
 package org.sonar.server.qualityprofile.ws;
 
+import java.io.IOException;
 import java.io.Reader;
+import java.io.Writer;
+import javax.annotation.Nullable;
+import org.apache.commons.io.IOUtils;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -30,23 +34,20 @@ import org.sonar.db.DbTester;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.qualityprofile.QualityProfileDto;
 import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.exceptions.UnauthorizedException;
 import org.sonar.server.language.LanguageTesting;
 import org.sonar.server.organization.DefaultOrganizationProvider;
 import org.sonar.server.organization.TestDefaultOrganizationProvider;
 import org.sonar.server.qualityprofile.BulkChangeResult;
 import org.sonar.server.qualityprofile.QProfileBackuper;
-import org.sonar.server.qualityprofile.QProfileName;
 import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestRequest;
 import org.sonar.server.ws.TestResponse;
 import org.sonar.server.ws.WsActionTester;
 import org.sonar.test.JsonAssert;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 import static org.sonar.db.permission.OrganizationPermission.ADMINISTER_QUALITY_PROFILES;
 
 public class RestoreActionTest {
@@ -60,11 +61,11 @@ public class RestoreActionTest {
   @Rule
   public UserSessionRule userSession = UserSessionRule.standalone();
 
-  private QProfileBackuper backuper = mock(QProfileBackuper.class);
+  private TestBackuper backuper = new TestBackuper();
   private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db);
   private QProfileWsSupport wsSupport = new QProfileWsSupport(db.getDbClient(), userSession, defaultOrganizationProvider);
   private Languages languages = LanguageTesting.newLanguages(A_LANGUAGE);
-  private WsActionTester tester = new WsActionTester(new RestoreAction(db.getDbClient(), backuper, languages, wsSupport));
+  private WsActionTester tester = new WsActionTester(new RestoreAction(db.getDbClient(), backuper, languages, userSession, wsSupport));
 
   @Test
   public void test_definition() {
@@ -76,24 +77,58 @@ public class RestoreActionTest {
     assertThat(definition.description()).isNotEmpty();
 
     // parameters
-    assertThat(definition.params()).hasSize(1);
-    assertThat(definition.param("backup").isRequired()).isTrue();
+    assertThat(definition.params()).hasSize(2);
+    WebService.Param backupParam = definition.param("backup");
+    assertThat(backupParam.isRequired()).isTrue();
+    assertThat(backupParam.since()).isNull();
+    WebService.Param orgParam = definition.param("organization");
+    assertThat(orgParam.isRequired()).isFalse();
+    assertThat(orgParam.since()).isEqualTo("6.4");
   }
 
   @Test
-  public void restore_the_uploaded_backup_on_default_organization() throws Exception {
-    QualityProfileDto profile = QualityProfileDto.createFor("P1")
-      .setDefault(false).setLanguage("xoo").setName("Sonar way");
-    BulkChangeResult restoreResult = new BulkChangeResult(profile);
-    when(backuper.restore(any(DbSession.class), any(Reader.class), any(QProfileName.class))).thenReturn(restoreResult);
-
+  public void profile_is_restored_on_default_organization_with_the_name_provided_in_backup() throws Exception {
     logInAsQProfileAdministrator(db.getDefaultOrganization());
-    TestResponse response = restore("<backup/>");
-
-    JsonAssert.assertJson(response.getInput()).isSimilarTo(getClass().getResource("RestoreActionTest/restore_profile.json"));
-    verify(backuper).restore(any(DbSession.class), any(Reader.class), any(QProfileName.class));
+    TestResponse response = restore("<backup/>", null);
+
+    assertThat(backuper.restoredOrganization.getUuid()).isEqualTo(db.getDefaultOrganization().getUuid());
+    assertThat(backuper.restoredBackup).isEqualTo("<backup/>");
+    assertThat(backuper.restoredProfile.getName()).isEqualTo("the-name-in-backup");
+    JsonAssert.assertJson(response.getInput()).isSimilarTo("{" +
+      "  \"profile\": {" +
+      "    \"name\": \"the-name-in-backup\"," +
+      "    \"language\": \"xoo\"," +
+      "    \"languageName\": \"Xoo\"," +
+      "    \"isDefault\": false," +
+      "    \"isInherited\": false" +
+      "  }," +
+      "  \"ruleSuccesses\": 0," +
+      "  \"ruleFailures\": 0" +
+      "}");
   }
 
+  @Test
+  public void profile_is_restored_on_specified_organization_with_the_name_provided_in_backup() throws Exception {
+    OrganizationDto org = db.organizations().insert();
+    logInAsQProfileAdministrator(org);
+    TestResponse response = restore("<backup/>", org.getKey());
+
+    assertThat(backuper.restoredOrganization.getUuid()).isEqualTo(org.getUuid());
+    assertThat(backuper.restoredBackup).isEqualTo("<backup/>");
+    assertThat(backuper.restoredProfile.getName()).isEqualTo("the-name-in-backup");
+    JsonAssert.assertJson(response.getInput()).isSimilarTo("{" +
+      "  \"profile\": {" +
+      "    \"name\": \"the-name-in-backup\"," +
+      "    \"language\": \"xoo\"," +
+      "    \"languageName\": \"Xoo\"," +
+      "    \"isDefault\": false," +
+      "    \"isInherited\": false" +
+      "  }," +
+      "  \"ruleSuccesses\": 0," +
+      "  \"ruleFailures\": 0" +
+      "}");
+
+  }
   @Test
   public void throw_IAE_if_backup_is_missing() throws Exception {
     logInAsQProfileAdministrator(db.getDefaultOrganization());
@@ -107,13 +142,34 @@ public class RestoreActionTest {
   }
 
   @Test
-  public void throw_ForbiddenException_if_not_profile_administrator() throws Exception {
+  public void throw_ForbiddenException_if_not_profile_administrator_of_default_organization() throws Exception {
     userSession.logIn();
 
     expectedException.expect(ForbiddenException.class);
     expectedException.expectMessage("Insufficient privileges");
 
-    restore("<backup/>");
+    restore("<backup/>", null);
+  }
+
+  @Test
+  public void throw_ForbiddenException_if_not_profile_administrator_of_specified_organization() throws Exception {
+    OrganizationDto org = db.organizations().insert();
+    logInAsQProfileAdministrator(db.getDefaultOrganization());
+
+    expectedException.expect(ForbiddenException.class);
+    expectedException.expectMessage("Insufficient privileges");
+
+    restore("<backup/>", org.getKey());
+  }
+
+  @Test
+  public void throw_NotFoundException_if_specified_organization_does_not_exist() throws Exception {
+    userSession.logIn();
+
+    expectedException.expect(NotFoundException.class);
+    expectedException.expectMessage("No organization with key 'missing'");
+
+    restore("<backup/>", "missing");
   }
 
   @Test
@@ -123,7 +179,7 @@ public class RestoreActionTest {
     expectedException.expect(UnauthorizedException.class);
     expectedException.expectMessage("Authentication is required");
 
-    restore("<backup/>");
+    restore("<backup/>", null);
   }
 
   private void logInAsQProfileAdministrator(OrganizationDto org) {
@@ -132,10 +188,43 @@ public class RestoreActionTest {
       .addPermission(ADMINISTER_QUALITY_PROFILES, org);
   }
 
-  private TestResponse restore(String backupContent) {
-    return tester.newRequest()
+  private TestResponse restore(String backupContent, @Nullable String organizationKey) {
+    TestRequest request = tester.newRequest()
       .setMethod("POST")
-      .setParam("backup", backupContent)
-      .execute();
+      .setParam("backup", backupContent);
+    if (organizationKey != null) {
+      request.setParam("organization", organizationKey);
+    }
+    return request.execute();
+  }
+
+  private static class TestBackuper implements QProfileBackuper {
+
+    private String restoredBackup;
+    private OrganizationDto restoredOrganization;
+    private QualityProfileDto restoredProfile;
+
+    @Override
+    public void backup(DbSession dbSession, QualityProfileDto profile, Writer backupWriter) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public BulkChangeResult restore(DbSession dbSession, Reader backup, OrganizationDto organization, @Nullable String overriddenProfileName) {
+      if (restoredProfile != null) {
+        throw new IllegalStateException("Already restored");
+      }
+      try {
+        this.restoredBackup = IOUtils.toString(backup);
+      } catch (IOException e) {
+        throw new IllegalStateException(e);
+      }
+      this.restoredOrganization = organization;
+      restoredProfile = QualityProfileDto.createFor("P1")
+        .setDefault(false)
+        .setLanguage("xoo")
+        .setName(overriddenProfileName != null ? overriddenProfileName : "the-name-in-backup");
+      return new BulkChangeResult(restoredProfile);
+    }
   }
 }