]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-1891 Add upload plugin WS 1071/head
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Wed, 29 Jun 2016 10:04:54 +0000 (12:04 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Fri, 1 Jul 2016 16:00:26 +0000 (18:00 +0200)
23 files changed:
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
server/sonar-server/src/main/java/org/sonar/server/updatecenter/UpdateCenterModule.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/updatecenter/ws/UpdateCenterWs.java
server/sonar-server/src/main/java/org/sonar/server/updatecenter/ws/UpdateCenterWsAction.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/updatecenter/ws/UploadAction.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/updatecenter/ws/package-info.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/ws/LocalRequestAdapter.java
server/sonar-server/src/main/java/org/sonar/server/ws/ServletRequest.java
server/sonar-server/src/test/java/org/sonar/server/updatecenter/UpdateCenterModuleTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/updatecenter/ws/UpdateCenterWsTest.java
server/sonar-server/src/test/java/org/sonar/server/updatecenter/ws/UploadActionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/ws/TestRequest.java
server/sonar-server/src/test/java/org/sonar/server/ws/TestResponse.java
server/sonar-server/src/test/java/org/sonar/server/ws/WebServiceEngineTest.java
server/sonar-server/src/test/java/org/sonar/server/ws/WsTester.java
server/sonar-server/src/test/resources/org/sonar/server/updatecenter/ws/UploadActionTest/anotherPlugin.jar [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/updatecenter/ws/UploadActionTest/plugin.jar [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/server/ws/Request.java
sonar-plugin-api/src/main/java/org/sonar/api/server/ws/internal/PartImpl.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/server/ws/internal/SimpleGetRequest.java
sonar-plugin-api/src/main/java/org/sonar/api/server/ws/internal/ValidatingRequest.java
sonar-plugin-api/src/test/java/org/sonar/api/server/ws/RequestTest.java
sonar-plugin-api/src/test/java/org/sonar/api/server/ws/internal/SimpleGetRequestTest.java

index f1dcdddb3a8e262cf42a18b922cf3ff114915181..7313694d9d30e35f384517cac1b9a1f8929ab9ad 100644 (file)
@@ -168,8 +168,6 @@ import org.sonar.server.platform.ws.SystemWs;
 import org.sonar.server.platform.ws.UpgradesAction;
 import org.sonar.server.plugins.PluginDownloader;
 import org.sonar.server.plugins.ServerExtensionInstaller;
-import org.sonar.server.plugins.UpdateCenterClient;
-import org.sonar.server.plugins.UpdateCenterMatrixFactory;
 import org.sonar.server.plugins.privileged.PrivilegedPluginsBootstraper;
 import org.sonar.server.plugins.privileged.PrivilegedPluginsStopper;
 import org.sonar.server.plugins.ws.AvailableAction;
@@ -275,7 +273,7 @@ import org.sonar.server.ui.ws.ComponentNavigationAction;
 import org.sonar.server.ui.ws.GlobalNavigationAction;
 import org.sonar.server.ui.ws.NavigationWs;
 import org.sonar.server.ui.ws.SettingsNavigationAction;
-import org.sonar.server.updatecenter.ws.UpdateCenterWs;
+import org.sonar.server.updatecenter.UpdateCenterModule;
 import org.sonar.server.user.DefaultUserFinder;
 import org.sonar.server.user.DefaultUserService;
 import org.sonar.server.user.DeprecatedUserFinder;
@@ -367,9 +365,7 @@ public class PlatformLevel4 extends PlatformLevel {
       IssueTagCloudWidget.class,
 
       // update center
-      UpdateCenterClient.class,
-      UpdateCenterMatrixFactory.class,
-      UpdateCenterWs.class,
+      UpdateCenterModule.class,
 
       // quality profile
       ActiveRuleIndexer.class,
diff --git a/server/sonar-server/src/main/java/org/sonar/server/updatecenter/UpdateCenterModule.java b/server/sonar-server/src/main/java/org/sonar/server/updatecenter/UpdateCenterModule.java
new file mode 100644 (file)
index 0000000..0164e05
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.updatecenter;
+
+import org.sonar.core.platform.Module;
+import org.sonar.server.plugins.UpdateCenterClient;
+import org.sonar.server.plugins.UpdateCenterMatrixFactory;
+import org.sonar.server.updatecenter.ws.UpdateCenterWs;
+import org.sonar.server.updatecenter.ws.UploadAction;
+
+public class UpdateCenterModule extends Module {
+  @Override
+  protected void configureModule() {
+    add(
+      UpdateCenterClient.class,
+      UpdateCenterMatrixFactory.class,
+      UploadAction.class,
+      UpdateCenterWs.class);
+  }
+}
index 5a5e5cec792c0fda9928d04f90a1499ab18ee788..b311da9ac4e8964d92de46b6a045dd7028b51f83 100644 (file)
@@ -25,6 +25,12 @@ import org.sonar.api.server.ws.WebService;
 
 public class UpdateCenterWs implements WebService {
 
+  private final UpdateCenterWsAction[] actions;
+
+  public UpdateCenterWs(UpdateCenterWsAction... actions) {
+    this.actions = actions;
+  }
+
   @Override
   public void define(Context context) {
     NewController controller = context.createController("api/updatecenter")
@@ -32,6 +38,9 @@ public class UpdateCenterWs implements WebService {
       .setSince("2.10");
 
     defineInstalledPluginsAction(controller);
+    for (UpdateCenterWsAction action : actions) {
+      action.define(controller);
+    }
 
     controller.done();
   }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/updatecenter/ws/UpdateCenterWsAction.java b/server/sonar-server/src/main/java/org/sonar/server/updatecenter/ws/UpdateCenterWsAction.java
new file mode 100644 (file)
index 0000000..95663f6
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.updatecenter.ws;
+
+import org.sonar.server.ws.WsAction;
+
+interface UpdateCenterWsAction extends WsAction {
+  // Marker interface
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/updatecenter/ws/UploadAction.java b/server/sonar-server/src/main/java/org/sonar/server/updatecenter/ws/UploadAction.java
new file mode 100644 (file)
index 0000000..7bdcb3f
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.updatecenter.ws;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import static org.apache.commons.io.IOUtils.closeQuietly;
+import static org.sonar.api.server.ws.Request.Part;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.server.platform.DefaultServerFileSystem;
+import org.sonar.server.user.UserSession;
+
+public class UploadAction implements UpdateCenterWsAction {
+
+  public static final String PARAM_FILE = "file";
+
+  private final UserSession userSession;
+  private final File downloadDir;
+
+  public UploadAction(UserSession userSession, DefaultServerFileSystem fileSystem) {
+    this.userSession = userSession;
+    this.downloadDir = fileSystem.getDownloadedPluginsDir();
+  }
+
+  @Override
+  public void define(WebService.NewController context) {
+    WebService.NewAction action = context.createAction("upload")
+      .setDescription("Upload a plugin.<br /> Requires 'Administer System' permission.")
+      .setSince("6.0")
+      .setPost(true)
+      .setInternal(true)
+      .setHandler(this);
+
+    action.createParam(PARAM_FILE)
+      .setDescription("The jar file of the plugin to install")
+      .setRequired(true);
+  }
+
+  @Override
+  public void handle(Request request, Response response) throws Exception {
+    userSession.checkPermission(GlobalPermissions.SYSTEM_ADMIN);
+
+    Part part = request.mandatoryParamAsPart(PARAM_FILE);
+    String fileName = part.getFileName();
+    checkArgument(fileName.endsWith(".jar"), "Only jar file is allowed");
+    InputStream inputStream = part.getInputStream();
+    try {
+      File destPlugin = new File(downloadDir, fileName);
+      Files.copy(inputStream, destPlugin.toPath(), REPLACE_EXISTING);
+      response.noContent();
+    } finally {
+      closeQuietly(inputStream);
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/updatecenter/ws/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/updatecenter/ws/package-info.java
new file mode 100644 (file)
index 0000000..bc02dde
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.updatecenter.ws;
+
+import javax.annotation.ParametersAreNonnullByDefault;
index f271938dcfbb4912587071a449e27e943669cfc4..98e452668ec2ce3a57d8a010272df1506700b767 100644 (file)
@@ -47,6 +47,11 @@ public class LocalRequestAdapter extends ValidatingRequest {
     return new ByteArrayInputStream(value.getBytes(StandardCharsets.UTF_8));
   }
 
+  @Override
+  protected Part readPart(String key) {
+    throw new UnsupportedOperationException("reading part is not supported yet by local WS calls");
+  }
+
   @Override
   public boolean hasParam(String key) {
     return localRequest.hasParam(key);
index 998b64d70f7447af2323a627f293bd6a9bf1884a..75efe64c1f5ee71d04477e8618b5dfda7e57ab6b 100644 (file)
@@ -30,7 +30,7 @@ import java.io.InputStream;
 import java.util.Map;
 import javax.annotation.CheckForNull;
 import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.Part;
+import org.sonar.api.server.ws.internal.PartImpl;
 import org.sonar.api.server.ws.internal.ValidatingRequest;
 import org.sonarqube.ws.MediaTypes;
 
@@ -73,15 +73,22 @@ public class ServletRequest extends ValidatingRequest {
 
   @Override
   protected InputStream readInputStreamParam(String key) {
+    Part part = readPart(key);
+    return (part == null) ? null : part.getInputStream();
+  }
+
+  @Override
+  @CheckForNull
+  public Part readPart(String key) {
     try {
       if (!isMultipartContent()) {
         return null;
       }
-      Part part = source.getPart(key);
+      javax.servlet.http.Part part = source.getPart(key);
       if (part == null || part.getSize() == 0) {
         return null;
       }
-      return part.getInputStream();
+      return new PartImpl(part.getInputStream(), part.getSubmittedFileName());
     } catch (Exception e) {
       throw new IllegalStateException("Can't read file part", e);
     }
@@ -117,4 +124,5 @@ public class ServletRequest extends ValidatingRequest {
   public String getPath() {
     return source.getRequestURI().replaceFirst(source.getContextPath(), "");
   }
+
 }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/updatecenter/UpdateCenterModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/updatecenter/UpdateCenterModuleTest.java
new file mode 100644 (file)
index 0000000..8bbca3d
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.updatecenter;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Test;
+import org.sonar.core.platform.ComponentContainer;
+
+public class UpdateCenterModuleTest {
+  @Test
+  public void verify_count_of_added_components() {
+    ComponentContainer container = new ComponentContainer();
+    new UpdateCenterModule().configure(container);
+    assertThat(container.size()).isEqualTo(2 + 4);
+  }
+}
index a493b170afa6331f8a5bc3762dbee615a27a0ab4..78b5140f8ff90ff39b35569702d8b9d48c300e4f 100644 (file)
  */
 package org.sonar.server.updatecenter.ws;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.sonar.api.server.ws.RailsHandler;
 import org.sonar.api.server.ws.WebService;
+import org.sonar.server.platform.DefaultServerFileSystem;
 import org.sonar.server.ws.WsTester;
 
-import static org.assertj.core.api.Assertions.assertThat;
-
 public class UpdateCenterWsTest {
 
   WsTester tester;
 
   @Before
   public void setUp() {
-    tester = new WsTester(new UpdateCenterWs());
+    tester = new WsTester(new UpdateCenterWs(
+      new UploadAction(null, mock(DefaultServerFileSystem.class))));
   }
 
   @Test
@@ -42,7 +45,7 @@ public class UpdateCenterWsTest {
     assertThat(controller).isNotNull();
     assertThat(controller.since()).isEqualTo("2.10");
     assertThat(controller.description()).isNotEmpty();
-    assertThat(controller.actions()).hasSize(1);
+    assertThat(controller.actions()).hasSize(2);
   }
 
   @Test
@@ -56,4 +59,15 @@ public class UpdateCenterWsTest {
     assertThat(action.params()).hasSize(1);
   }
 
+  @Test
+  public void define_upload_action() throws Exception {
+    WebService.Controller controller = tester.controller("api/updatecenter");
+
+    WebService.Action action = controller.action("upload");
+    assertThat(action).isNotNull();
+    assertThat(action.handler()).isNotNull();
+    assertThat(action.isInternal()).isTrue();
+    assertThat(action.isPost()).isTrue();
+    assertThat(action.params()).hasSize(1);
+  }
 }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/updatecenter/ws/UploadActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/updatecenter/ws/UploadActionTest.java
new file mode 100644 (file)
index 0000000..1458959
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.updatecenter.ws;
+
+import static java.nio.file.Files.newInputStream;
+import static org.assertj.core.api.Java6Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.core.permission.GlobalPermissions.PROVISIONING;
+import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN;
+import static org.sonar.test.ExceptionCauseMatcher.hasType;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.channels.ClosedChannelException;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.platform.DefaultServerFileSystem;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestResponse;
+import org.sonar.server.ws.WsActionTester;
+
+public class UploadActionTest {
+
+  static final String PLUGIN_NAME = "plugin.jar";
+
+  @Rule
+  public TemporaryFolder folder = new TemporaryFolder();
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Rule
+  public UserSessionRule userSession = UserSessionRule.standalone();
+
+  DefaultServerFileSystem fileSystem = mock(DefaultServerFileSystem.class);
+  File pluginDirectory;
+
+  File plugin = new File(getClass().getResource("UploadActionTest/plugin.jar").getFile());
+  WsActionTester wsTester;
+
+  @Before
+  public void setUp() throws Exception {
+    pluginDirectory = folder.newFolder();
+    when(fileSystem.getDownloadedPluginsDir()).thenReturn(pluginDirectory);
+    wsTester = new WsActionTester(new UploadAction(userSession, fileSystem));
+  }
+
+  @Test
+  public void upload_plugin() throws Exception {
+    setSystemAdminUser();
+
+    TestResponse response = call(newInputStream(plugin.toPath()), PLUGIN_NAME);
+
+    assertThat(response.getStatus()).isEqualTo(204);
+    assertPluginIsUploaded(PLUGIN_NAME);
+  }
+
+  @Test
+  public void erase_existing_plugin_if_already_exists() throws Exception {
+    setSystemAdminUser();
+
+    File plugin1 = new File(getClass().getResource("UploadActionTest/plugin.jar").getFile());
+    call(newInputStream(plugin1.toPath()), PLUGIN_NAME);
+
+    File plugin2 = new File(getClass().getResource("UploadActionTest/anotherPlugin.jar").getFile());
+    call(newInputStream(plugin2.toPath()), PLUGIN_NAME);
+
+    File result = new File(pluginDirectory, PLUGIN_NAME);
+    assertThat(result.exists()).isTrue();
+    assertThat(result.length()).isNotEqualTo(plugin1.length()).isEqualTo(plugin2.length());
+  }
+
+  @Test
+  public void fail_when_plugin_extension_is_not_jar() throws Exception {
+    setSystemAdminUser();
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Only jar file is allowed");
+    call(newInputStream(plugin.toPath()), "plugin.zip");
+  }
+
+  @Test
+  public void fail_when_no_files_param() throws Exception {
+    setSystemAdminUser();
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("The 'file' parameter is missing");
+    wsTester.newRequest().execute();
+  }
+
+  @Test
+  public void input_stream_should_be_closed() throws Exception {
+    setSystemAdminUser();
+
+    InputStream inputStream = newInputStream(plugin.toPath());
+    call(inputStream, PLUGIN_NAME);
+
+    // As the same InputStream is used, it will fail as it should have been called during the first execution of the WS
+    expectedException.expectCause(hasType(ClosedChannelException.class));
+    call(inputStream, PLUGIN_NAME);
+  }
+
+  @Test
+  public void fail_if_not_system_admin() throws Exception {
+    userSession.login().setGlobalPermissions(PROVISIONING);
+
+    expectedException.expect(ForbiddenException.class);
+    call(newInputStream(plugin.toPath()), PLUGIN_NAME);
+  }
+
+  private TestResponse call(InputStream inputStream, String fileName) {
+    return wsTester.newRequest()
+      .setPart("file", inputStream, fileName)
+      .execute();
+  }
+
+  private void setSystemAdminUser() {
+    userSession.login().setGlobalPermissions(SYSTEM_ADMIN);
+  }
+
+  private void assertPluginIsUploaded(String pluginName) {
+    File result = new File(pluginDirectory, pluginName);
+    assertThat(result.exists()).isTrue();
+  }
+
+}
index e02e2c04a5a5e7300428612138f2777e3491c6f9..ac7e439c5a2c9f5bddd339ea580cc1775bb4d581 100644 (file)
@@ -22,15 +22,18 @@ package org.sonar.server.ws;
 import static com.google.common.base.Preconditions.checkNotNull;
 
 import com.google.common.base.Throwables;
+import com.google.common.collect.Maps;
 import java.io.InputStream;
 import java.util.HashMap;
 import java.util.Map;
 import org.apache.commons.io.IOUtils;
+import org.sonar.api.server.ws.internal.PartImpl;
 import org.sonar.api.server.ws.internal.ValidatingRequest;
 
 public class TestRequest extends ValidatingRequest {
 
   private final Map<String, String> params = new HashMap<>();
+  private final Map<String, Part> parts = Maps.newHashMap();
   private String method = "GET";
   private String mimeType = "application/octet-stream";
   private String path;
@@ -49,6 +52,16 @@ public class TestRequest extends ValidatingRequest {
     return IOUtils.toInputStream(value);
   }
 
+  @Override
+  protected Part readPart(String key) {
+    return parts.get(key);
+  }
+
+  public TestRequest setPart(String key, InputStream input, String fileName) {
+    parts.put(key, new PartImpl(input, fileName));
+    return this;
+  }
+
   @Override
   public String method() {
     return method;
index 2dea511d8c323a1262f55cd28b1fc869ccf5f008..5fcdcc523d32da90d1ff79d41b63315dd12f0219 100644 (file)
@@ -42,4 +42,8 @@ public class TestResponse {
   public String getMediaType() {
     return dumbResponse.stream().mediaType();
   }
+
+  public int getStatus() {
+    return dumbResponse.stream().status();
+  }
 }
index 5f30364e2b88f8e59184b1b23796e4d9684bac27..b72623743d15f008d0f0b91e38286560c3617e92 100644 (file)
@@ -23,12 +23,8 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-import com.google.common.collect.Maps;
 import java.io.IOException;
-import java.io.InputStream;
 import java.util.Locale;
-import java.util.Map;
-import javax.annotation.Nullable;
 import org.apache.commons.io.IOUtils;
 import org.junit.After;
 import org.junit.Before;
@@ -73,7 +69,7 @@ public class WebServiceEngineTest {
 
   @Test
   public void execute_request() {
-    ValidatingRequest request = new SimpleRequest("GET", "/api/system/health");
+    ValidatingRequest request = new TestRequest().setMethod("GET").setPath("/api/system/health");
     DumbResponse response = new DumbResponse();
     underTest.execute(request, response);
 
@@ -82,7 +78,7 @@ public class WebServiceEngineTest {
 
   @Test
   public void execute_request_when_path_does_not_begin_with_slash() {
-    ValidatingRequest request = new SimpleRequest("GET", "api/system/health");
+    ValidatingRequest request = new TestRequest().setMethod("GET").setPath("/api/system/health");
     DumbResponse response = new DumbResponse();
     underTest.execute(request, response);
 
@@ -91,7 +87,7 @@ public class WebServiceEngineTest {
 
   @Test
   public void execute_request_with_action_suffix() {
-    ValidatingRequest request = new SimpleRequest("GET", "/api/system/health");
+    ValidatingRequest request = new TestRequest().setMethod("GET").setPath("/api/system/health");
     DumbResponse response = new DumbResponse();
     underTest.execute(request, response);
 
@@ -100,7 +96,7 @@ public class WebServiceEngineTest {
 
   @Test
   public void bad_request_if_action_suffix_is_not_supported() {
-    ValidatingRequest request = new SimpleRequest("GET", "/api/system/health.bat");
+    ValidatingRequest request = new TestRequest().setMethod("GET").setPath("/api/system/health.bat");
     DumbResponse response = new DumbResponse();
     underTest.execute(request, response);
 
@@ -111,7 +107,7 @@ public class WebServiceEngineTest {
 
   @Test
   public void no_content() {
-    ValidatingRequest request = new SimpleRequest("GET", "/api/system/alive");
+    ValidatingRequest request = new TestRequest().setMethod("GET").setPath("/api/system/alive");
     DumbResponse response = new DumbResponse();
     underTest.execute(request, response);
 
@@ -120,7 +116,7 @@ public class WebServiceEngineTest {
 
   @Test
   public void bad_controller() {
-    ValidatingRequest request = new SimpleRequest("GET", "/api/xxx/health");
+    ValidatingRequest request = new TestRequest().setMethod("GET").setPath("/api/xxx/health");
     DumbResponse response = new DumbResponse();
     underTest.execute(request, response);
 
@@ -129,7 +125,7 @@ public class WebServiceEngineTest {
 
   @Test
   public void bad_action() {
-    ValidatingRequest request = new SimpleRequest("GET", "/api/system/xxx");
+    ValidatingRequest request = new TestRequest().setMethod("GET").setPath("/api/system/xxx");
     DumbResponse response = new DumbResponse();
     underTest.execute(request, response);
 
@@ -138,7 +134,7 @@ public class WebServiceEngineTest {
 
   @Test
   public void method_get_not_allowed() {
-    ValidatingRequest request = new SimpleRequest("GET", "/api/system/ping");
+    ValidatingRequest request = new TestRequest().setMethod("GET").setPath("/api/system/ping");
     DumbResponse response = new DumbResponse();
     underTest.execute(request, response);
 
@@ -147,7 +143,7 @@ public class WebServiceEngineTest {
 
   @Test
   public void method_post_required() {
-    ValidatingRequest request = new SimpleRequest("POST", "/api/system/ping");
+    ValidatingRequest request = new TestRequest().setMethod("POST").setPath("/api/system/ping");
     DumbResponse response = new DumbResponse();
     underTest.execute(request, response);
 
@@ -156,7 +152,7 @@ public class WebServiceEngineTest {
 
   @Test
   public void unknown_parameter_is_set() {
-    ValidatingRequest request = new SimpleRequest("GET", "/api/system/fail_with_undeclared_parameter").setParam("unknown", "Unknown");
+    ValidatingRequest request = new TestRequest().setMethod("GET").setPath("/api/system/fail_with_undeclared_parameter").setParam("unknown", "Unknown");
     DumbResponse response = new DumbResponse();
     underTest.execute(request, response);
 
@@ -165,7 +161,7 @@ public class WebServiceEngineTest {
 
   @Test
   public void required_parameter_is_not_set() {
-    ValidatingRequest request = new SimpleRequest("GET", "/api/system/print");
+    ValidatingRequest request = new TestRequest().setMethod("GET").setPath("/api/system/print");
     DumbResponse response = new DumbResponse();
     underTest.execute(request, response);
 
@@ -174,7 +170,7 @@ public class WebServiceEngineTest {
 
   @Test
   public void optional_parameter_is_not_set() {
-    ValidatingRequest request = new SimpleRequest("GET", "/api/system/print").setParam("message", "Hello World");
+    ValidatingRequest request = new TestRequest().setMethod("GET").setPath("/api/system/print").setParam("message", "Hello World");
     DumbResponse response = new DumbResponse();
     underTest.execute(request, response);
 
@@ -183,7 +179,7 @@ public class WebServiceEngineTest {
 
   @Test
   public void optional_parameter_is_set() {
-    ValidatingRequest request = new SimpleRequest("GET", "/api/system/print")
+    ValidatingRequest request = new TestRequest().setMethod("GET").setPath("/api/system/print")
       .setParam("message", "Hello World")
       .setParam("author", "Marcel");
     DumbResponse response = new DumbResponse();
@@ -194,7 +190,7 @@ public class WebServiceEngineTest {
 
   @Test
   public void param_value_is_in_possible_values() {
-    ValidatingRequest request = new SimpleRequest("GET", "/api/system/print")
+    ValidatingRequest request = new TestRequest().setMethod("GET").setPath("/api/system/print")
       .setParam("message", "Hello World")
       .setParam("format", "json");
     DumbResponse response = new DumbResponse();
@@ -205,7 +201,7 @@ public class WebServiceEngineTest {
 
   @Test
   public void param_value_is_not_in_possible_values() {
-    ValidatingRequest request = new SimpleRequest("GET", "api/system/print")
+    ValidatingRequest request = new TestRequest().setMethod("GET").setPath("/api/system/print")
       .setParam("message", "Hello World")
       .setParam("format", "html");
     DumbResponse response = new DumbResponse();
@@ -216,7 +212,7 @@ public class WebServiceEngineTest {
 
   @Test
   public void internal_error() {
-    ValidatingRequest request = new SimpleRequest("GET", "/api/system/fail");
+    ValidatingRequest request = new TestRequest().setMethod("GET").setPath("/api/system/fail");
     DumbResponse response = new DumbResponse();
     underTest.execute(request, response);
 
@@ -228,7 +224,7 @@ public class WebServiceEngineTest {
   @Test
   public void bad_request_with_i18n_message() {
     userSessionRule.setLocale(Locale.ENGLISH);
-    ValidatingRequest request = new SimpleRequest("GET", "api/system/fail_with_i18n_message").setParam("count", "3");
+    ValidatingRequest request = new TestRequest().setMethod("GET").setPath("/api/system/fail_with_i18n_message").setParam("count", "3");
     DumbResponse response = new DumbResponse();
     when(i18n.message(Locale.ENGLISH, "bad.request.reason", "bad.request.reason", 0)).thenReturn("reason #0");
 
@@ -242,7 +238,7 @@ public class WebServiceEngineTest {
 
   @Test
   public void bad_request_with_multiple_messages() {
-    ValidatingRequest request = new SimpleRequest("GET", "api/system/fail_with_multiple_messages").setParam("count", "3");
+    ValidatingRequest request = new TestRequest().setMethod("GET").setPath("/api/system/fail_with_multiple_messages").setParam("count", "3");
     DumbResponse response = new DumbResponse();
 
     underTest.execute(request, response);
@@ -260,7 +256,7 @@ public class WebServiceEngineTest {
   public void bad_request_with_multiple_i18n_messages() {
     userSessionRule.setLocale(Locale.ENGLISH);
 
-    ValidatingRequest request = new SimpleRequest("GET", "api/system/fail_with_multiple_i18n_messages").setParam("count", "3");
+    ValidatingRequest request = new TestRequest().setMethod("GET").setPath("/api/system/fail_with_multiple_i18n_messages").setParam("count", "3");
     DumbResponse response = new DumbResponse();
     when(i18n.message(Locale.ENGLISH, "bad.request.reason", "bad.request.reason", 0)).thenReturn("reason #0");
     when(i18n.message(Locale.ENGLISH, "bad.request.reason", "bad.request.reason", 1)).thenReturn("reason #1");
@@ -286,62 +282,6 @@ public class WebServiceEngineTest {
     assertThat(response.getHeader(name)).isEqualTo(value);
   }
 
-  private static class SimpleRequest extends ValidatingRequest {
-    private final String method;
-    private String path;
-    private Map<String, String> params = Maps.newHashMap();
-
-    private SimpleRequest(String method, String path) {
-      this.method = method;
-      this.path = path;
-    }
-
-    @Override
-    public String method() {
-      return method;
-    }
-
-    @Override
-    public String getMediaType() {
-      return MediaTypes.JSON;
-    }
-
-    @Override
-    public boolean hasParam(String key) {
-      return params.keySet().contains(key);
-    }
-
-    @Override
-    public String getPath() {
-      return path;
-    }
-
-    @Override
-    protected String readParam(String key) {
-      return params.get(key);
-    }
-
-    @Override
-    protected InputStream readInputStreamParam(String key) {
-      String param = readParam(key);
-
-      return param == null ? null : IOUtils.toInputStream(param);
-    }
-
-    public SimpleRequest setParams(Map<String, String> m) {
-      this.params = m;
-      return this;
-    }
-
-    public SimpleRequest setParam(String key, @Nullable String value) {
-      if (value != null) {
-        params.put(key, value);
-      }
-      return this;
-    }
-
-  }
-
   static class SystemWs implements WebService {
     @Override
     public void define(Context context) {
index 7b0e580cdb00b240f0f704cd58297ae30aae1a8c..0d4036364de1782c420c4d3695fdbfbb1dbebcbf 100644 (file)
@@ -37,6 +37,7 @@ import javax.annotation.Nullable;
 import org.apache.commons.io.IOUtils;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
+import org.sonar.api.server.ws.internal.PartImpl;
 import org.sonar.api.server.ws.internal.ValidatingRequest;
 import org.sonar.api.utils.text.JsonWriter;
 import org.sonar.api.utils.text.XmlWriter;
@@ -55,6 +56,7 @@ public class WsTester {
     private String path;
 
     private Map<String, String> params = Maps.newHashMap();
+    private final Map<String, Part> parts = Maps.newHashMap();
 
     private TestRequest(String method) {
       this.method = method;
@@ -109,6 +111,16 @@ public class WsTester {
       return param == null ? null : IOUtils.toInputStream(param);
     }
 
+    @Override
+    protected Part readPart(String key) {
+      return parts.get(key);
+    }
+
+    public TestRequest setPart(String key, InputStream input, String fileName) {
+      parts.put(key, new PartImpl(input, fileName));
+      return this;
+    }
+
     public Result execute() throws Exception {
       TestResponse response = new TestResponse();
       verifyRequest(action(), this);
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/updatecenter/ws/UploadActionTest/anotherPlugin.jar b/server/sonar-server/src/test/resources/org/sonar/server/updatecenter/ws/UploadActionTest/anotherPlugin.jar
new file mode 100644 (file)
index 0000000..c7bc88e
Binary files /dev/null and b/server/sonar-server/src/test/resources/org/sonar/server/updatecenter/ws/UploadActionTest/anotherPlugin.jar differ
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/updatecenter/ws/UploadActionTest/plugin.jar b/server/sonar-server/src/test/resources/org/sonar/server/updatecenter/ws/UploadActionTest/plugin.jar
new file mode 100644 (file)
index 0000000..0895211
Binary files /dev/null and b/server/sonar-server/src/test/resources/org/sonar/server/updatecenter/ws/UploadActionTest/plugin.jar differ
index 50dbabfdc58c8be7a681c2c59ccce5aba2bb0cad..48d9f427b2ef5769fd19a203095dc4dd4595e169 100644 (file)
@@ -19,6 +19,8 @@
  */
 package org.sonar.api.server.ws;
 
+import static com.google.common.base.Preconditions.checkArgument;
+
 import com.google.common.annotations.Beta;
 import com.google.common.base.Splitter;
 import com.google.common.collect.Lists;
@@ -122,6 +124,15 @@ public abstract class Request {
   @CheckForNull
   public abstract InputStream paramAsInputStream(String key);
 
+  @CheckForNull
+  public abstract Part paramAsPart(String key);
+
+  public Part mandatoryParamAsPart(String key) {
+    Part part = paramAsPart(key);
+    checkArgument(part != null, "The '%s' parameter is missing", key);
+    return part;
+  }
+
   /**
    * @deprecated to be dropped in 4.4. Default values are declared in ws metadata
    */
@@ -270,4 +281,13 @@ public abstract class Request {
    * @since 6.0
    */
   public abstract String getPath();
+
+  /**
+   * @since 6.0
+   */
+  public interface Part {
+    InputStream getInputStream();
+
+    String getFileName();
+  }
 }
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/internal/PartImpl.java b/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/internal/PartImpl.java
new file mode 100644 (file)
index 0000000..a0d61f3
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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.api.server.ws.internal;
+
+import java.io.InputStream;
+import org.sonar.api.server.ws.Request;
+
+public class PartImpl implements Request.Part {
+
+  private final InputStream inputStream;
+  private final String fileName;
+
+  public PartImpl(InputStream inputStream, String fileName) {
+    this.inputStream = inputStream;
+    this.fileName = fileName;
+  }
+
+  @Override
+  public InputStream getInputStream() {
+    return inputStream;
+  }
+
+  @Override
+  public String getFileName() {
+    return fileName;
+  }
+}
index 3e583f691a1fcfa744c938cb319fb091c1dd807f..9b2348f48d226cc63ce8c60d7248638cddf1cd79 100644 (file)
@@ -37,6 +37,7 @@ import org.sonar.api.server.ws.Request;
 public class SimpleGetRequest extends Request {
 
   private final Map<String, String> params = Maps.newHashMap();
+  private final Map<String, Part> parts = Maps.newHashMap();
   private String mediaType = "application/json";
   private String path;
 
@@ -82,6 +83,16 @@ public class SimpleGetRequest extends Request {
     return params;
   }
 
+  @Override
+  public Part paramAsPart(String key) {
+    return parts.get(key);
+  }
+
+  public SimpleGetRequest setPart(String key, InputStream input, String fileName) {
+    parts.put(key, new PartImpl(input, fileName));
+    return this;
+  }
+
   @Override
   public LocalConnector localConnector() {
     throw new UnsupportedOperationException();
@@ -96,4 +107,5 @@ public class SimpleGetRequest extends Request {
     this.path = path;
     return this;
   }
+
 }
index 58615ec636e6dd06bcc71b049d66a2b0da7ce66a..fd1c2e2270d2ff3f2e6f27a3c11142c2f863e380 100644 (file)
@@ -19,6 +19,8 @@
  */
 package org.sonar.api.server.ws.internal;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
 import com.google.common.base.CharMatcher;
 import com.google.common.base.Splitter;
 import com.google.common.collect.Lists;
@@ -34,8 +36,6 @@ import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.api.utils.log.Loggers;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-
 /**
  * @since 4.2
  */
@@ -74,6 +74,12 @@ public abstract class ValidatingRequest extends Request {
     return readInputStreamParam(key);
   }
 
+  @Override
+  @CheckForNull
+  public Part paramAsPart(String key) {
+    return readPart(key);
+  }
+
   @CheckForNull
   private String param(String key, boolean validateValue) {
     WebService.Param definition = action.param(key);
@@ -139,6 +145,9 @@ public abstract class ValidatingRequest extends Request {
   @CheckForNull
   protected abstract InputStream readInputStreamParam(String key);
 
+  @CheckForNull
+  protected abstract Part readPart(String key);
+
   private static void validate(String value, WebService.Param definition) {
     Set<String> possibleValues = definition.possibleValues();
     if (possibleValues != null && !possibleValues.contains(value)) {
index a8b939ee1162f5a13554e7cbe693b292feb6f880..8f6613176c542471c905b7968ca8455a5687fe5c 100644 (file)
@@ -32,6 +32,7 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.server.ws.internal.PartImpl;
 import org.sonar.api.server.ws.internal.ValidatingRequest;
 import org.sonar.api.utils.DateUtils;
 
@@ -259,9 +260,30 @@ public class RequestTest {
     assertThat(IOUtils.toString(underTest.setParam("a_string", "foo").paramAsInputStream("a_string"))).isEqualTo("foo");
   }
 
+  @Test
+  public void param_as_part() throws Exception {
+    InputStream inputStream = mock(InputStream.class);
+    underTest.setPart("key", inputStream, "filename");
+
+    Request.Part part = underTest.paramAsPart("key");
+    assertThat(part.getInputStream()).isEqualTo(inputStream);
+    assertThat(part.getFileName()).isEqualTo("filename");
+
+    assertThat(underTest.paramAsPart("unknown")).isNull();
+  }
+
+  @Test
+  public void mandatory_param_as_part() throws Exception {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("The 'required_param' parameter is missing");
+
+    underTest.mandatoryParamAsPart("required_param");
+  }
+
   private static class FakeRequest extends ValidatingRequest {
 
     private final Map<String, String> params = Maps.newHashMap();
+    private final Map<String, Part> parts = Maps.newHashMap();
 
     @Override
     public String method() {
@@ -301,6 +323,16 @@ public class RequestTest {
 
       return param == null ? null : IOUtils.toInputStream(param);
     }
+
+    @Override
+    protected Part readPart(String key) {
+      return parts.get(key);
+    }
+
+    public FakeRequest setPart(String key, InputStream input, String fileName) {
+      parts.put(key, new PartImpl(input, fileName));
+      return this;
+    }
   }
 
   private static class FakeWs implements WebService {
index 9d80da9247f0de0993ca2911d7296b5b3742a976..08f691772623822faeb67451161348ac92a5469b 100644 (file)
  */
 package org.sonar.api.server.ws.internal;
 
-import org.junit.Test;
-
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+import java.io.InputStream;
+import org.junit.Test;
+import org.sonar.api.server.ws.Request;
 
 public class SimpleGetRequestTest {
 
+  SimpleGetRequest underTest = new SimpleGetRequest();
+
   @Test
   public void method() {
-    SimpleGetRequest request = new SimpleGetRequest();
-    assertThat(request.method()).isEqualTo("GET");
+    assertThat(underTest.method()).isEqualTo("GET");
 
-    request.setParam("foo", "bar");
-    assertThat(request.param("foo")).isEqualTo("bar");
-    assertThat(request.param("unknown")).isNull();
+    underTest.setParam("foo", "bar");
+    assertThat(underTest.param("foo")).isEqualTo("bar");
+    assertThat(underTest.param("unknown")).isNull();
   }
 
   @Test
   public void has_param() {
-    SimpleGetRequest request = new SimpleGetRequest();
-    assertThat(request.method()).isEqualTo("GET");
+    assertThat(underTest.method()).isEqualTo("GET");
+
+    underTest.setParam("foo", "bar");
+    assertThat(underTest.hasParam("foo")).isTrue();
+    assertThat(underTest.hasParam("unknown")).isFalse();
+  }
+
+  @Test
+  public void get_part() throws Exception {
+    InputStream inputStream = mock(InputStream.class);
+    underTest.setPart("key", inputStream, "filename");
+
+    Request.Part part = underTest.paramAsPart("key");
+    assertThat(part.getInputStream()).isEqualTo(inputStream);
+    assertThat(part.getFileName()).isEqualTo("filename");
 
-    request.setParam("foo", "bar");
-    assertThat(request.hasParam("foo")).isTrue();
-    assertThat(request.hasParam("unknown")).isFalse();
+    assertThat(underTest.paramAsPart("unknown")).isNull();
   }
 }