aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorJean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com>2015-04-14 16:53:27 +0200
committerJean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com>2015-04-17 17:23:28 +0200
commit51e596c8fff5742e3292d002c3fbc02e9575cc65 (patch)
treea01e57915b9bfb2cf3b0eb5f0a9ef8bd326ff4db /server
parent20499b26a45114913f94387be92e9c473c5c7170 (diff)
downloadsonarqube-51e596c8fff5742e3292d002c3fbc02e9575cc65.tar.gz
sonarqube-51e596c8fff5742e3292d002c3fbc02e9575cc65.zip
SONAR-6399 WS to export a quality profile for external tools
Diffstat (limited to 'server')
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileBackuper.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/LanguageParamUtils.java9
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/QProfileBackupAction.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/QProfileExportAction.java136
-rw-r--r--server/sonar-server/src/main/resources/org/sonar/server/qualityprofile/ws/example-exporters.json8
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/QProfileExportActionTest.java198
7 files changed, 356 insertions, 1 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java
index adea270c55a..d3fdaff68bb 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java
@@ -595,6 +595,8 @@ class ServerComponents {
pico.addSingleton(QProfileChangeParentAction.class);
pico.addSingleton(QProfileChangelogAction.class);
pico.addSingleton(QProfileCompareAction.class);
+ pico.addSingleton(QProfileExportAction.class);
+ pico.addSingleton(QProfileExportersAction.class);
pico.addSingleton(QProfilesWs.class);
pico.addSingleton(ProfilesWs.class);
pico.addSingleton(RuleActivationActions.class);
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileBackuper.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileBackuper.java
index 87e0ab6929a..a5a2146e9e3 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileBackuper.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileBackuper.java
@@ -50,6 +50,8 @@ import java.util.Set;
public class QProfileBackuper implements ServerComponent {
+ public static final String MEDIA_TYPE_XML = "text/xml";
+
private final QProfileReset reset;
private final DbClient db;
private final IndexClient index;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/LanguageParamUtils.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/LanguageParamUtils.java
index e4f04ce4a99..fe7d71ff613 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/LanguageParamUtils.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/LanguageParamUtils.java
@@ -33,6 +33,15 @@ class LanguageParamUtils {
// Utility class
}
+ static String getExampleValue(Languages languages) {
+ Language[] languageArray = languages.all();
+ if (languageArray.length > 0) {
+ return languageArray[0].getKey();
+ } else {
+ return "";
+ }
+ }
+
static Collection<String> getLanguageKeys(Languages languages) {
return Collections2.transform(Arrays.asList(languages.all()), new NonNullInputFunction<Language, String>() {
@Override
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/QProfileBackupAction.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/QProfileBackupAction.java
index f7d6557a1c5..0c0aadf68cc 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/QProfileBackupAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/QProfileBackupAction.java
@@ -65,7 +65,7 @@ public class QProfileBackupAction implements BaseQProfileWsAction {
@Override
public void handle(Request request, Response response) throws Exception {
Stream stream = response.stream();
- stream.setMediaType("text/xml");
+ stream.setMediaType(QProfileBackuper.MEDIA_TYPE_XML);
OutputStreamWriter writer = new OutputStreamWriter(stream.output(), Charsets.UTF_8);
DbSession session = dbClient.openSession(false);
try {
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/QProfileExportAction.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/QProfileExportAction.java
new file mode 100644
index 00000000000..7174e369b38
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/QProfileExportAction.java
@@ -0,0 +1,136 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.ws;
+
+import com.google.common.collect.Lists;
+import org.apache.commons.io.IOUtils;
+import org.sonar.api.profiles.ProfileExporter;
+import org.sonar.api.resources.Language;
+import org.sonar.api.resources.Languages;
+import org.sonar.api.server.ws.*;
+import org.sonar.api.server.ws.Response.Stream;
+import org.sonar.api.server.ws.WebService.NewAction;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.qualityprofile.db.QualityProfileDto;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.qualityprofile.QProfileBackuper;
+import org.sonar.server.qualityprofile.QProfileExporters;
+import org.sonar.server.qualityprofile.QProfileFactory;
+
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.List;
+
+public class QProfileExportAction implements BaseQProfileWsAction {
+
+ private static final String PARAM_PROFILE_NAME = "name";
+ private static final String PARAM_LANGUAGE = "language";
+ private static final String PARAM_FORMAT = "format";
+
+ private final DbClient dbClient;
+
+ private final QProfileFactory profileFactory;
+
+ private final QProfileBackuper backuper;
+
+ private final QProfileExporters exporters;
+
+ private final Languages languages;
+
+ public QProfileExportAction(DbClient dbClient, QProfileFactory profileFactory, QProfileBackuper backuper, QProfileExporters exporters, Languages languages) {
+ this.dbClient = dbClient;
+ this.profileFactory = profileFactory;
+ this.backuper = backuper;
+ this.exporters = exporters;
+ this.languages = languages;
+ }
+
+ @Override
+ public void define(WebService.NewController controller) {
+ NewAction create = controller.createAction("export")
+ .setSince("5.2")
+ .setDescription("Export a quality profile.")
+ .setHandler(this);
+
+ create.createParam(PARAM_PROFILE_NAME)
+ .setDescription("The name of the quality profile to export. If left empty, will export the default profile for the language.")
+ .setExampleValue("My Sonar way");
+
+ create.createParam(PARAM_LANGUAGE)
+ .setDescription("The language for the quality profile.")
+ .setExampleValue(LanguageParamUtils.getExampleValue(languages))
+ .setPossibleValues(LanguageParamUtils.getLanguageKeys(languages))
+ .setRequired(true);
+
+ List<String> exporterKeys = Lists.newArrayList();
+ for (Language lang: languages.all()) {
+ for(ProfileExporter exporter: exporters.exportersForLanguage(lang.getKey())) {
+ exporterKeys.add(exporter.getKey());
+ }
+ }
+ if (!exporterKeys.isEmpty()) {
+ create.createParam(PARAM_FORMAT)
+ .setDescription("Output format. If left empty, the same format as api/qualityprofiles/backup is used.")
+ .setPossibleValues(exporterKeys);
+ }
+ }
+
+ @Override
+ public void handle(Request request, Response response) throws Exception {
+ String name = request.param(PARAM_PROFILE_NAME);
+ String language = request.mandatoryParam(PARAM_LANGUAGE);
+ String format = null;
+ if (!exporters.exportersForLanguage(language).isEmpty()) {
+ format = request.param(PARAM_FORMAT);
+ }
+
+ DbSession dbSession = dbClient.openSession(false);
+ Stream stream = response.stream();
+ OutputStream output = stream.output();
+ Writer writer = new OutputStreamWriter(output);
+
+ try {
+ QualityProfileDto profile = null;
+ if (name == null) {
+ profile = profileFactory.getDefault(dbSession, language);
+ } else {
+ profile = profileFactory.getByNameAndLanguage(dbSession, name, language);
+ }
+ if (profile == null) {
+ throw new NotFoundException(String.format("Could not find profile with name '%s' for language '%s'", name, language));
+ }
+
+ String profileKey = profile.getKey();
+ if (format == null) {
+ stream.setMediaType(QProfileBackuper.MEDIA_TYPE_XML);
+ backuper.backup(profileKey, writer);
+ } else {
+ stream.setMediaType(exporters.mimeType(format));
+ exporters.export(profileKey, format, writer);
+ }
+ } finally {
+ IOUtils.closeQuietly(writer);
+ IOUtils.closeQuietly(output);
+ dbSession.close();
+ }
+ }
+}
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/qualityprofile/ws/example-exporters.json b/server/sonar-server/src/main/resources/org/sonar/server/qualityprofile/ws/example-exporters.json
new file mode 100644
index 00000000000..86d3e20568d
--- /dev/null
+++ b/server/sonar-server/src/main/resources/org/sonar/server/qualityprofile/ws/example-exporters.json
@@ -0,0 +1,8 @@
+{
+ "exporters": [
+ {"key": "pmd", "name": "PMD", "languages": ["java"]},
+ {"key": "checkstyle", "name": "Checkstyle", "languages": ["java"]},
+ {"key": "js-lint", "name": "JS Lint", "languages": ["js"]},
+ {"key": "android-lint", "name": "Android Lint", "languages": ["xml", "java"]}
+ ]
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/QProfileExportActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/QProfileExportActionTest.java
new file mode 100644
index 00000000000..6033875c4dd
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/QProfileExportActionTest.java
@@ -0,0 +1,198 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.ws;
+
+import com.google.common.collect.Sets;
+import org.apache.commons.lang.StringUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.mockito.Matchers;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.sonar.api.profiles.ProfileExporter;
+import org.sonar.api.profiles.RulesProfile;
+import org.sonar.api.server.ws.WebService.Action;
+import org.sonar.api.utils.System2;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.persistence.DbTester;
+import org.sonar.core.qualityprofile.db.QualityProfileDao;
+import org.sonar.core.qualityprofile.db.QualityProfileDto;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.language.LanguageTesting;
+import org.sonar.server.qualityprofile.*;
+import org.sonar.server.qualityprofile.index.ActiveRuleIndex;
+import org.sonar.server.search.IndexClient;
+import org.sonar.server.ws.WsTester;
+import org.sonar.server.ws.WsTester.Result;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class QProfileExportActionTest {
+
+ @ClassRule
+ public static final DbTester db = new DbTester();
+
+ WsTester wsTester;
+
+ QualityProfileDao qualityProfileDao;
+
+ DbClient dbClient;
+
+ DbSession session;
+
+ QProfileBackuper backuper;
+
+ QProfileExporters exporters;
+
+ @Before
+ public void before() throws Exception {
+ qualityProfileDao = new QualityProfileDao(db.myBatis(), mock(System2.class));
+ dbClient = new DbClient(db.database(), db.myBatis(), qualityProfileDao);
+ session = dbClient.openSession(false);
+ backuper = mock(QProfileBackuper.class);
+
+ db.truncateTables();
+
+ ProfileExporter exporter1 = newExporter("polop");
+ ProfileExporter exporter2 = newExporter("palap");
+
+ IndexClient indexClient = mock(IndexClient.class);
+ ActiveRuleIndex activeRuleIndex = mock(ActiveRuleIndex.class);
+ when(activeRuleIndex.findByProfile(Matchers.anyString())).thenReturn(Sets.<ActiveRule>newHashSet().iterator());
+
+ when(indexClient.get(ActiveRuleIndex.class)).thenReturn(activeRuleIndex);
+ exporters = new QProfileExporters(new QProfileLoader(dbClient, indexClient), null, null, new ProfileExporter[] {exporter1, exporter2}, null);
+ wsTester = new WsTester(new QProfilesWs(mock(RuleActivationActions.class),
+ mock(BulkRuleActivationActions.class),
+ mock(ProjectAssociationActions.class),
+ new QProfileExportAction(dbClient, new QProfileFactory(dbClient), backuper, exporters, LanguageTesting.newLanguages("xoo"))));
+ }
+
+ @After
+ public void after() throws Exception {
+ session.close();
+ }
+
+ private ProfileExporter newExporter(final String key) {
+ return new ProfileExporter(key, StringUtils.capitalize(key)) {
+ @Override
+ public String getMimeType() {
+ return "text/plain+" + key;
+ }
+
+ @Override
+ public void exportProfile(RulesProfile profile, Writer writer) {
+ try {
+ writer.write(String.format("Profile %s exported by %s", profile.getName(), key));
+ } catch (IOException ioe) {
+ throw new RuntimeException(ioe);
+ }
+ }
+ };
+ }
+
+ @Test
+ public void export_without_format() throws Exception {
+ QualityProfileDto profile = QProfileTesting.newXooP1();
+ qualityProfileDao.insert(session, profile);
+ session.commit();
+
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ invocation.getArgumentAt(1, Writer.class).write("As exported by SQ !");
+ return null;
+ }
+ }).when(backuper).backup(Matchers.eq(profile.getKey()), Matchers.any(Writer.class));
+
+ Result result = wsTester.newGetRequest("api/qualityprofiles", "export").setParam("language", profile.getLanguage()).setParam("name", profile.getName()).execute();
+
+ assertThat(result.outputAsString()).isEqualTo("As exported by SQ !");
+ }
+
+ @Test
+ public void export_with_format() throws Exception {
+ QualityProfileDto profile = QProfileTesting.newXooP1();
+ qualityProfileDao.insert(session, profile);
+ session.commit();
+
+ Result result = wsTester.newGetRequest("api/qualityprofiles", "export")
+ .setParam("language", profile.getLanguage()).setParam("name", profile.getName()).setParam("format", "polop").execute();
+
+ assertThat(result.outputAsString()).isEqualTo("Profile " + profile.getName() + " exported by polop");
+ }
+
+ @Test
+ public void export_default_profile() throws Exception {
+ QualityProfileDto profile1 = QProfileTesting.newXooP1();
+ QualityProfileDto profile2 = QProfileTesting.newXooP2().setDefault(true);
+ qualityProfileDao.insert(session, profile1, profile2);
+ session.commit();
+
+ Result result = wsTester.newGetRequest("api/qualityprofiles", "export")
+ .setParam("language", "xoo").setParam("format", "polop").execute();
+
+ assertThat(result.outputAsString()).isEqualTo("Profile " + profile2.getName() + " exported by polop");
+ }
+
+ @Test(expected = NotFoundException.class)
+ public void fail_on_unknown_profile() throws Exception {
+ wsTester.newGetRequest("api/qualityprofiles", "export")
+ .setParam("language", "xoo").setParam("format", "polop").execute();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void fail_on_unknown_exporter() throws Exception {
+ QualityProfileDto profile = QProfileTesting.newXooP1();
+ qualityProfileDao.insert(session, profile);
+ session.commit();
+
+ wsTester.newGetRequest("api/qualityprofiles", "export")
+ .setParam("language", "xoo").setParam("format", "unknown").execute();
+ }
+
+ @Test
+ public void do_not_fail_when_no_exporters() throws Exception {
+ exporters = new QProfileExporters(null, null, null, new ProfileExporter[0], null);
+ wsTester = new WsTester(new QProfilesWs(mock(RuleActivationActions.class),
+ mock(BulkRuleActivationActions.class),
+ mock(ProjectAssociationActions.class),
+ new QProfileExportAction(dbClient, new QProfileFactory(dbClient), backuper, exporters, LanguageTesting.newLanguages("xoo"))));
+
+ Action export = wsTester.controller("api/qualityprofiles").action("export");
+ assertThat(export.params()).hasSize(2);
+
+ QualityProfileDto profile = QProfileTesting.newXooP1();
+ qualityProfileDao.insert(session, profile);
+ session.commit();
+
+ wsTester.newGetRequest("api/qualityprofiles", "export").setParam("language", "xoo").setParam("name", profile.getName()).execute();
+
+ }
+}