]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6399 WS to export a quality profile for external tools
authorJean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com>
Tue, 14 Apr 2015 14:53:27 +0000 (16:53 +0200)
committerJean-Baptiste Lievremont <jean-baptiste.lievremont@sonarsource.com>
Fri, 17 Apr 2015 15:23:28 +0000 (17:23 +0200)
server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileBackuper.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/LanguageParamUtils.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/QProfileBackupAction.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/QProfileExportAction.java [new file with mode: 0644]
server/sonar-server/src/main/resources/org/sonar/server/qualityprofile/ws/example-exporters.json [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/QProfileExportActionTest.java [new file with mode: 0644]

index adea270c55a3ca3bfd88b661ccb8f605e603dadc..d3fdaff68bb7211ca70f4d0d8dc23a7819573482 100644 (file)
@@ -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);
index 87e0ab6929a0170f6e86ef787a347d84bf099c74..a5a2146e9e3aad1cb42f4979d03e9f78272e45d9 100644 (file)
@@ -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;
index e4f04ce4a99613919aee8be5c866074f9d6593e5..fe7d71ff6136603d901061f107dea0b014db94c2 100644 (file)
@@ -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
index f7d6557a1c51f70a10aca1fce1a167cc01d2bb77..0c0aadf68cca5b8658357dc85b8dbc91d73063ea 100644 (file)
@@ -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 (file)
index 0000000..7174e36
--- /dev/null
@@ -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 (file)
index 0000000..86d3e20
--- /dev/null
@@ -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 (file)
index 0000000..6033875
--- /dev/null
@@ -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();
+
+  }
+}