]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6377 add WS listing available plugins 228/head
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Thu, 16 Apr 2015 08:58:53 +0000 (10:58 +0200)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 17 Apr 2015 14:24:34 +0000 (16:24 +0200)
server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java
server/sonar-server/src/main/java/org/sonar/server/plugins/ws/AvailablePluginsWsAction.java [new file with mode: 0644]
server/sonar-server/src/main/resources/org/sonar/server/plugins/ws/example-available_plugins.json [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AvailablePluginsWsActionTest.java [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/plugins/ws/AvailablePluginsWsActionTest/properties_per_plugin.json [new file with mode: 0644]

index 110b8ad014930fdabdae4dc637ec4ce68830bd75..adea270c55a3ca3bfd88b661ccb8f605e603dadc 100644 (file)
@@ -216,6 +216,7 @@ import org.sonar.server.plugins.ServerPluginJarsInstaller;
 import org.sonar.server.plugins.ServerPluginRepository;
 import org.sonar.server.plugins.UpdateCenterClient;
 import org.sonar.server.plugins.UpdateCenterMatrixFactory;
+import org.sonar.server.plugins.ws.AvailablePluginsWsAction;
 import org.sonar.server.plugins.ws.InstalledPluginsWsAction;
 import org.sonar.server.plugins.ws.PluginsWs;
 import org.sonar.server.properties.ProjectSettingsFactory;
@@ -881,6 +882,7 @@ class ServerComponents {
 
     // Plugins WS
     pico.addSingleton(InstalledPluginsWsAction.class);
+    pico.addSingleton(AvailablePluginsWsAction.class);
     pico.addSingleton(PluginsWs.class);
 
     // Compute engine
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/AvailablePluginsWsAction.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/AvailablePluginsWsAction.java
new file mode 100644 (file)
index 0000000..ab9ffa6
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * 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.plugins.ws;
+
+import com.google.common.base.Function;
+import com.google.common.io.Resources;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.server.plugins.UpdateCenterMatrixFactory;
+import org.sonar.updatecenter.common.Artifact;
+import org.sonar.updatecenter.common.Plugin;
+import org.sonar.updatecenter.common.PluginUpdate;
+import org.sonar.updatecenter.common.Release;
+
+import java.util.List;
+
+import static com.google.common.collect.Iterables.filter;
+import static com.google.common.collect.Iterables.transform;
+
+public class AvailablePluginsWsAction implements PluginsWsAction {
+
+  private static final boolean DO_NOT_FORCE_REFRESH = false;
+  private static final String PROPERTY_KEY = "key";
+  private static final String PROPERTY_NAME = "name";
+  private static final String PROPERTY_DESCRIPTION = "description";
+  private static final String PROPERTY_ORGANIZATION_NAME = "organizationName";
+  private static final String PROPERTY_ORGANIZATION_URL = "organizationUrl";
+  private static final String PROPERTY_URL = "url";
+  private static final String PROPERTY_TERMS_AND_CONDITIONS_URL = "termsAndConditionsUrl";
+  private static final String OBJECT_UPDATE = "update";
+  private static final String OBJECT_ARCHIVE = "archive";
+  private static final String OBJECT_RELEASE = "release";
+  private static final String PROPERTY_VERSION = "version";
+  private static final String PROPERTY_DATE = "date";
+  private static final String PROPERTY_STATUS = "status";
+  private static final String ARRAY_REQUIRES = "requires";
+  private static final String ARRAY_PLUGINS = "plugins";
+  private static final String PROPERTY_CATEGORY = "category";
+  private static final String PROPERTY_LICENSE = "license";
+
+  private final UpdateCenterMatrixFactory updateCenterFactory;
+
+  public AvailablePluginsWsAction(UpdateCenterMatrixFactory updateCenterFactory) {
+    this.updateCenterFactory = updateCenterFactory;
+  }
+
+  @Override
+  public void define(WebService.NewController controller) {
+    controller.createAction("available")
+      .setDescription("Get the list of all the plugins available for installation on the SonarQube instance, sorted by name." +
+        "<br/>" +
+        "Update status values are: [COMPATIBLE, INCOMPATIBLE, REQUIRES_UPGRADE, DEPS_REQUIRE_UPGRADE]")
+      .setSince("5.2")
+      .setHandler(this)
+      .setResponseExample(Resources.getResource(this.getClass(), "example-available_plugins.json"));
+  }
+
+  @Override
+  public void handle(Request request, Response response) throws Exception {
+    JsonWriter jsonWriter = response.newJsonWriter();
+    jsonWriter.beginObject();
+
+    writePlugins(jsonWriter);
+
+    jsonWriter.close();
+  }
+
+  private void writePlugins(JsonWriter jsonWriter) {
+    jsonWriter.name(ARRAY_PLUGINS);
+    jsonWriter.beginArray();
+    for (PluginUpdate pluginUpdate : retrieveAvailablePlugins()) {
+      writePluginUpdate(jsonWriter, pluginUpdate);
+    }
+    jsonWriter.endArray();
+    jsonWriter.endObject();
+  }
+
+  private void writePluginUpdate(JsonWriter jsonWriter, PluginUpdate pluginUpdate) {
+    jsonWriter.beginObject();
+    Plugin plugin = pluginUpdate.getPlugin();
+
+    jsonWriter.prop(PROPERTY_KEY, plugin.getKey());
+    jsonWriter.prop(PROPERTY_NAME, plugin.getName());
+    jsonWriter.prop(PROPERTY_CATEGORY, plugin.getCategory());
+    jsonWriter.prop(PROPERTY_DESCRIPTION, plugin.getDescription());
+    jsonWriter.prop(PROPERTY_LICENSE, plugin.getLicense());
+    jsonWriter.prop(PROPERTY_TERMS_AND_CONDITIONS_URL, plugin.getTermsConditionsUrl());
+    jsonWriter.prop(PROPERTY_ORGANIZATION_NAME, plugin.getOrganization());
+    jsonWriter.prop(PROPERTY_ORGANIZATION_URL, plugin.getOrganizationUrl());
+
+    writeRelease(jsonWriter, pluginUpdate.getRelease());
+
+    writeUpdate(jsonWriter, pluginUpdate);
+
+    jsonWriter.endObject();
+  }
+
+  private List<PluginUpdate> retrieveAvailablePlugins() {
+    return updateCenterFactory.getUpdateCenter(DO_NOT_FORCE_REFRESH).findAvailablePlugins();
+  }
+
+  private void writeRelease(JsonWriter jsonWriter, Release release) {
+    jsonWriter.name(OBJECT_RELEASE);
+    jsonWriter.beginObject();
+    jsonWriter.prop(PROPERTY_VERSION, release.getVersion().toString());
+    jsonWriter.propDate(PROPERTY_DATE, release.getDate());
+    writeArchive(jsonWriter, release);
+    jsonWriter.endObject();
+  }
+
+  private void writeArchive(JsonWriter jsonWriter, Release release) {
+    jsonWriter.name(OBJECT_ARCHIVE);
+    jsonWriter.beginObject();
+    jsonWriter.prop(PROPERTY_NAME, release.getFilename());
+    jsonWriter.prop(PROPERTY_URL, release.getDownloadUrl());
+    jsonWriter.endObject();
+  }
+
+  private void writeUpdate(JsonWriter jsonWriter, PluginUpdate pluginUpdate) {
+    jsonWriter.name(OBJECT_UPDATE);
+    jsonWriter.beginObject();
+    jsonWriter.prop(PROPERTY_STATUS, toJSon(pluginUpdate.getStatus()));
+
+    jsonWriter.name(ARRAY_REQUIRES);
+    jsonWriter.beginArray();
+    Release release = pluginUpdate.getRelease();
+    for (Plugin child : filter(transform(release.getOutgoingDependencies(), ReleaseToArtifact.INSTANCE), Plugin.class)) {
+      jsonWriter.beginObject();
+      jsonWriter.prop(PROPERTY_KEY, child.getKey());
+      jsonWriter.prop(AvailablePluginsWsAction.PROPERTY_NAME, child.getName());
+      jsonWriter.prop(PROPERTY_DESCRIPTION, child.getDescription());
+      jsonWriter.endObject();
+    }
+    jsonWriter.endArray();
+
+    jsonWriter.endObject();
+  }
+
+  private static String toJSon(PluginUpdate.Status status) {
+    switch (status) {
+      case COMPATIBLE:
+        return "COMPATIBLE";
+      case INCOMPATIBLE:
+        return "INCOMPATIBLE";
+      case REQUIRE_SONAR_UPGRADE:
+        return "REQUIRES_UPGRADE";
+      case DEPENDENCIES_REQUIRE_SONAR_UPGRADE:
+        return "DEPS_REQUIRE_UPGRADE";
+      default:
+        throw new IllegalArgumentException("Unsupported value of PluginUpdate.Status " + status);
+    }
+  }
+
+  private enum ReleaseToArtifact implements Function<Release, Artifact> {
+    INSTANCE;
+
+    @Override
+    public Artifact apply(Release input) {
+      return input.getArtifact();
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/plugins/ws/example-available_plugins.json b/server/sonar-server/src/main/resources/org/sonar/server/plugins/ws/example-available_plugins.json
new file mode 100644 (file)
index 0000000..42ed429
--- /dev/null
@@ -0,0 +1,53 @@
+{
+  "plugins": [
+    {
+      "key": "abap",
+      "name": "ABAP",
+      "category": "Languages",
+      "description": "Enable analysis and reporting on ABAP projects",
+      "license": "Commercial",
+      "organizationName": "SonarSource",
+      "organizationUrl": "http://www.sonarsource.com",
+      "termsAndConditionsUrl": "http://dist.sonarsource.com/SonarSource_Terms_And_Conditions.pdf",
+      "release": {
+        "version": "3.2",
+        "date": "2015-03-10",
+        "archive": {
+          "name": "sonar-abap-plugin-3.2.jar",
+          "url": "http://dist.sonarsource.com/abap/download/sonar-abap-plugin-3.2.jar"
+        }
+      },
+      "update": {
+        "status": "COMPATIBLE",
+        "requires": []
+      }
+    },
+    {
+      "key": "android",
+      "name": "Android",
+      "category": "Languages",
+      "description": "Import Android Lint reports.",
+      "license": "GNU LGPL 3",
+      "organizationName": "SonarSource and Jerome Van Der Linden, Stephane Nicolas, Florian Roncari, Thomas Bores",
+      "organizationUrl": "http://www.sonarsource.com",
+      "release": {
+        "version": "1.0",
+        "date": "2014-03-31",
+        "archive": {
+          "name": "sonar-android-plugin-1.0.jar",
+          "url": "http://repository.codehaus.org/org/codehaus/sonar-plugins/android/sonar-android-plugin/1.0/sonar-android-plugin-1.0.jar"
+        }
+      },
+      "update": {
+        "status": "COMPATIBLE",
+        "requires": [
+          {
+            "key": "java",
+            "name": "Java",
+            "description": "SonarQube rule engine."
+          }
+        ]
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AvailablePluginsWsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AvailablePluginsWsActionTest.java
new file mode 100644 (file)
index 0000000..954653a
--- /dev/null
@@ -0,0 +1,176 @@
+/*
+ * 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.plugins.ws;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.server.plugins.UpdateCenterMatrixFactory;
+import org.sonar.server.ws.WsTester;
+import org.sonar.updatecenter.common.Plugin;
+import org.sonar.updatecenter.common.PluginUpdate;
+import org.sonar.updatecenter.common.Release;
+import org.sonar.updatecenter.common.UpdateCenter;
+
+import java.net.URL;
+
+import static com.google.common.collect.ImmutableList.of;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+import static org.sonar.test.JsonAssert.assertJson;
+import static org.sonar.updatecenter.common.PluginUpdate.Status.COMPATIBLE;
+import static org.sonar.updatecenter.common.PluginUpdate.Status.DEPENDENCIES_REQUIRE_SONAR_UPGRADE;
+import static org.sonar.updatecenter.common.PluginUpdate.Status.INCOMPATIBLE;
+import static org.sonar.updatecenter.common.PluginUpdate.Status.REQUIRE_SONAR_UPGRADE;
+import static org.sonar.updatecenter.common.Version.create;
+
+public class AvailablePluginsWsActionTest {
+  private static final String DUMMY_CONTROLLER_KEY = "dummy";
+  private static final String JSON_EMPTY_PLUGIN_LIST =
+    "{" +
+      "  \"plugins\":" + "[]" +
+      "}";
+  private static final Plugin PLUGIN_1 = new Plugin("p_key_1").setName("p_name_1");
+  private static final Plugin PLUGIN_2 = new Plugin("p_key_2").setName("p_name_2").setDescription("p_desc_2");
+
+  @Mock
+  private UpdateCenterMatrixFactory updateCenterFactory;
+  @Mock
+  private UpdateCenter updateCenter;
+  @InjectMocks
+  private AvailablePluginsWsAction underTest;
+
+  private Request request = mock(Request.class);
+  private WsTester.TestResponse response = new WsTester.TestResponse();
+
+  @Before
+  public void createAndWireMocksTogether() throws Exception {
+    initMocks(this);
+    when(updateCenterFactory.getUpdateCenter(anyBoolean())).thenReturn(updateCenter);
+  }
+
+  @Test
+  public void action_available_is_defined() throws Exception {
+    WsTester wsTester = new WsTester();
+    WebService.NewController newController = wsTester.context().createController(DUMMY_CONTROLLER_KEY);
+
+    underTest.define(newController);
+    newController.done();
+
+    WebService.Controller controller = wsTester.controller(DUMMY_CONTROLLER_KEY);
+    assertThat(controller.actions()).extracting("key").containsExactly("available");
+
+    WebService.Action action = controller.actions().iterator().next();
+    assertThat(action.isPost()).isFalse();
+    assertThat(action.description()).isNotEmpty();
+    assertThat(action.responseExample()).isNotNull();
+  }
+
+  @Test
+  public void empty_array_is_returned_when_there_is_no_plugin_available() throws Exception {
+    underTest.handle(request, response);
+
+    assertJson(response.outputAsString()).setStrictArrayOrder(true).isSimilarTo(JSON_EMPTY_PLUGIN_LIST);
+  }
+
+  @Test
+  public void verify_properties_displayed_in_json_per_plugin() throws Exception {
+    Plugin plugin = new Plugin("p_key")
+      .setName("p_name")
+      .setCategory("p_category")
+      .setDescription("p_description")
+      .setLicense("p_license")
+      .setOrganization("p_orga_name")
+      .setOrganizationUrl("p_orga_url")
+      .setTermsConditionsUrl("p_t_and_c_url");
+    Release pluginRelease = release(plugin, "1.12.1")
+      .setDate(DateUtils.parseDate("2015-04-16"))
+      .setDownloadUrl("http://p_file.jar")
+      .addOutgoingDependency(release(PLUGIN_1, "0.3.6"))
+      .addOutgoingDependency(release(PLUGIN_2, "1.0.0"));
+    when(updateCenter.findAvailablePlugins()).thenReturn(of(
+      pluginUpdate(pluginRelease, COMPATIBLE)
+      ));
+
+    underTest.handle(request, response);
+
+    assertJson(response.outputAsString()).isSimilarTo(resource("properties_per_plugin.json"));
+  }
+
+  private Release release(Plugin plugin1, String version) {
+    return new Release(plugin1, create(version));
+  }
+
+  @Test
+  public void status_COMPATIBLE_is_displayed_COMPATIBLE_in_JSON() throws Exception {
+    checkStatusDisplayedInJson(COMPATIBLE, "COMPATIBLE");
+  }
+
+  @Test
+  public void status_INCOMPATIBLE_is_displayed_INCOMPATIBLE_in_JSON() throws Exception {
+    checkStatusDisplayedInJson(INCOMPATIBLE, "INCOMPATIBLE");
+  }
+
+  @Test
+  public void status_REQUIRE_SONAR_UPGRADE_is_displayed_REQUIRES_UPGRADE_in_JSON() throws Exception {
+    checkStatusDisplayedInJson(REQUIRE_SONAR_UPGRADE, "REQUIRES_UPGRADE");
+  }
+
+  @Test
+  public void status_DEPENDENCIES_REQUIRE_SONAR_UPGRADE_is_displayed_DEPS_REQUIRE_UPGRADE_in_JSON() throws Exception {
+    checkStatusDisplayedInJson(DEPENDENCIES_REQUIRE_SONAR_UPGRADE, "DEPS_REQUIRE_UPGRADE");
+  }
+
+  private void checkStatusDisplayedInJson(PluginUpdate.Status status, String expectedValue) throws Exception {
+    when(updateCenter.findAvailablePlugins()).thenReturn(of(
+      pluginUpdate(release(PLUGIN_1, "1.0.0"), status)
+      ));
+
+    underTest.handle(request, response);
+
+    assertJson(response.outputAsString()).isSimilarTo(
+      "{" +
+        "  \"plugins\": [" +
+        "    {" +
+        "      \"update\": {" +
+        "        \"status\": \"" + expectedValue + "\"" +
+        "      }" +
+        "    }" +
+        "  ]" +
+        "}"
+      );
+  }
+
+  private static PluginUpdate pluginUpdate(Release pluginRelease, PluginUpdate.Status compatible) {
+    return PluginUpdate.createWithStatus(pluginRelease, compatible);
+  }
+
+  private static URL resource(String s) {
+    Class<AvailablePluginsWsActionTest> clazz = AvailablePluginsWsActionTest.class;
+    return clazz.getResource(clazz.getSimpleName() + "/" + s);
+  }
+}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ws/AvailablePluginsWsActionTest/properties_per_plugin.json b/server/sonar-server/src/test/resources/org/sonar/server/plugins/ws/AvailablePluginsWsActionTest/properties_per_plugin.json
new file mode 100644 (file)
index 0000000..4535f49
--- /dev/null
@@ -0,0 +1,36 @@
+{
+  "plugins": [
+    {
+      "key": "p_key",
+      "name": "p_name",
+      "category": "p_category",
+      "description": "p_description",
+      "license": "p_license",
+      "organizationName": "p_orga_name",
+      "organizationUrl": "p_orga_url",
+      "termsAndConditionsUrl": "p_t_and_c_url",
+      "release": {
+        "version": "1.12.1",
+        "date": "2015-04-16",
+        "archive": {
+          "name": "p_file.jar",
+          "url": "http://p_file.jar"
+        }
+      },
+      "update": {
+        "status": "COMPATIBLE",
+        "requires": [
+          {
+            "key": "p_key_1",
+            "name": "p_name_1"
+          },
+          {
+            "key": "p_key_2",
+            "name": "p_name_2",
+            "description": "p_desc_2"
+          }
+        ]
+      }
+    }
+  ]
+}