If unset, then the default organization is used.
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;
RuleActivatorContextFactory.class,
QProfileFactory.class,
QProfileCopier.class,
- QProfileBackuper.class,
+ QProfileBackuperImpl.class,
QProfileReset.class,
QProfilesWsModule.class,
*/
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();
- }
- }
}
--- /dev/null
+/*
+ * 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();
+ }
+ }
+}
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;
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();
}
}
- 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);
}
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;
/**
* 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);
}
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;
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);
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 {
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;
}
.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);
// 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);
// 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);
// 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);
// 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);
// 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);
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");
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>.");
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");
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);
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);
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;
@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);
.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>" +
private static QualityProfileDto newProfile(OrganizationDto org) {
return QualityProfileTesting.newQualityProfileDto()
- .setLanguage("xoo")
+ .setLanguage(A_LANGUAGE)
.setOrganizationUuid(org.getUuid());
}
}
*/
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;
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 {
@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() {
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());
}
@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
expectedException.expect(UnauthorizedException.class);
expectedException.expectMessage("Authentication is required");
- restore("<backup/>");
+ restore("<backup/>", null);
}
private void logInAsQProfileAdministrator(OrganizationDto org) {
.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);
+ }
}
}