aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>2015-04-16 10:58:53 +0200
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>2015-04-17 16:24:34 +0200
commit6efaf159c23adb3d65af2749587e0a6fa2272923 (patch)
treef9da85200daeb02542bf023da462b35144b56824 /server
parentf1793ad0c7b6ce5d8847f921cb4abcbf966d2728 (diff)
downloadsonarqube-6efaf159c23adb3d65af2749587e0a6fa2272923.tar.gz
sonarqube-6efaf159c23adb3d65af2749587e0a6fa2272923.zip
SONAR-6377 add WS listing available plugins
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/plugins/ws/AvailablePluginsWsAction.java181
-rw-r--r--server/sonar-server/src/main/resources/org/sonar/server/plugins/ws/example-available_plugins.json53
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AvailablePluginsWsActionTest.java176
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/plugins/ws/AvailablePluginsWsActionTest/properties_per_plugin.json36
5 files changed, 448 insertions, 0 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 110b8ad0149..adea270c55a 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
@@ -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
index 00000000000..ab9ffa620d4
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/AvailablePluginsWsAction.java
@@ -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
index 00000000000..42ed42924da
--- /dev/null
+++ b/server/sonar-server/src/main/resources/org/sonar/server/plugins/ws/example-available_plugins.json
@@ -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
index 00000000000..954653a571e
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AvailablePluginsWsActionTest.java
@@ -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
index 00000000000..4535f49ac88
--- /dev/null
+++ b/server/sonar-server/src/test/resources/org/sonar/server/plugins/ws/AvailablePluginsWsActionTest/properties_per_plugin.json
@@ -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"
+ }
+ ]
+ }
+ }
+ ]
+}