From: Sébastien Lesaint Date: Thu, 16 Apr 2015 08:58:53 +0000 (+0200) Subject: SONAR-6377 add WS listing available plugins X-Git-Tag: 5.2-RC1~2221 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=6efaf159c23adb3d65af2749587e0a6fa2272923;p=sonarqube.git SONAR-6377 add WS listing available plugins --- 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." + + "
" + + "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 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 { + 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 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" + } + ] + } + } + ] +}