Merge changes from topic 'plugin-api'

* changes:
  ServerInfoIT: Use API to install plugin
  Add implementation of PluginApi
  PluginLoader: Ensure that $site/plugins folder exists
  Plugins#ListRequest: Remove parameter from all() method
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/plugin/PluginIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/plugin/PluginIT.java
index cfc6318..58619ad 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/plugin/PluginIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/plugin/PluginIT.java
@@ -15,15 +15,89 @@
 package com.google.gerrit.acceptance.api.plugin;
 
 import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.stream.Collectors.toList;
 
+import com.google.common.collect.ImmutableList;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.GerritConfig;
 import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.common.RawInputUtil;
+import com.google.gerrit.extensions.api.plugins.PluginApi;
+import com.google.gerrit.extensions.api.plugins.Plugins.ListRequest;
+import com.google.gerrit.extensions.common.InstallPluginInput;
+import com.google.gerrit.extensions.common.PluginInfo;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import java.util.List;
 import org.junit.Test;
 
 @NoHttpd
 public class PluginIT extends AbstractDaemonTest {
+  private static final byte[] JS_PLUGIN_CONTENT =
+      "Gerrit.install(function(self){});\n".getBytes(UTF_8);
+  private static final List<String> PLUGINS =
+      ImmutableList.of("plugin-a", "plugin-b", "plugin-c", "plugin-d");
+
   @Test
-  public void noPlugins() throws Exception {
-    assertThat(gApi.plugins().list().get()).isEmpty();
+  @GerritConfig(name = "plugins.allowRemoteAdmin", value = "true")
+  public void pluginManagement() throws Exception {
+    // No plugins are loaded
+    assertThat(list().get()).isEmpty();
+    assertThat(list().all().get()).isEmpty();
+
+    PluginApi test;
+    PluginInfo info;
+
+    // Install all the plugins
+    InstallPluginInput input = new InstallPluginInput();
+    input.raw = RawInputUtil.create(JS_PLUGIN_CONTENT);
+    for (String plugin : PLUGINS) {
+      test = gApi.plugins().install(plugin + ".js", input);
+      assertThat(test).isNotNull();
+      info = test.get();
+      assertThat(info.id).isEqualTo(plugin);
+      assertThat(info.disabled).isNull();
+    }
+    assertPlugins(list().get(), PLUGINS);
+
+    // Disable
+    test = gApi.plugins().name("plugin-a");
+    test.disable();
+    test = gApi.plugins().name("plugin-a");
+    info = test.get();
+    assertThat(info.disabled).isTrue();
+    assertPlugins(list().get(), PLUGINS.subList(1, PLUGINS.size()));
+    assertPlugins(list().all().get(), PLUGINS);
+
+    // Enable
+    test.enable();
+    test = gApi.plugins().name("plugin-a");
+    info = test.get();
+    assertThat(info.disabled).isNull();
+    assertPlugins(list().get(), PLUGINS);
+  }
+
+  @Test
+  public void installNotAllowed() throws Exception {
+    exception.expect(MethodNotAllowedException.class);
+    exception.expectMessage("remote installation is disabled");
+    gApi.plugins().install("test.js", new InstallPluginInput());
+  }
+
+  @Test
+  public void getNonExistingThrowsNotFound() throws Exception {
+    exception.expect(ResourceNotFoundException.class);
+    gApi.plugins().name("does-not-exist");
+  }
+
+  private ListRequest list() throws RestApiException {
+    return gApi.plugins().list();
+  }
+
+  private void assertPlugins(List<PluginInfo> actual, List<String> expected) {
+    List<String> _actual = actual.stream().map(p -> p.id).collect(toList());
+    assertThat(_actual).containsExactlyElementsIn(expected);
   }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
index 2a33140..365a0d8 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
@@ -20,22 +20,20 @@
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.GerritConfig;
 import com.google.gerrit.acceptance.NoHttpd;
-import com.google.gerrit.acceptance.UseSsh;
+import com.google.gerrit.common.RawInputUtil;
 import com.google.gerrit.extensions.client.AccountFieldName;
 import com.google.gerrit.extensions.client.AuthType;
+import com.google.gerrit.extensions.common.InstallPluginInput;
 import com.google.gerrit.extensions.common.ServerInfo;
 import com.google.gerrit.server.config.AllProjectsNameProvider;
 import com.google.gerrit.server.config.AllUsersNameProvider;
 import com.google.gerrit.server.config.AnonymousCowardNameProvider;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.inject.Inject;
-import java.nio.file.Files;
-import java.nio.file.Path;
 import org.junit.Test;
 
 @NoHttpd
 public class ServerInfoIT extends AbstractDaemonTest {
-  @Inject private SitePaths sitePaths;
+  private static final byte[] JS_PLUGIN_CONTENT =
+      "Gerrit.install(function(self){});\n".getBytes(UTF_8);
 
   @Test
   // auth
@@ -132,18 +130,16 @@
   }
 
   @Test
-  @UseSsh
   @GerritConfig(name = "plugins.allowRemoteAdmin", value = "true")
   public void serverConfigWithPlugin() throws Exception {
-    Path plugins = sitePaths.plugins_dir;
-    Files.createDirectory(plugins);
-    Path jsplugin = plugins.resolve("js-plugin-1.js");
-    Files.write(jsplugin, "Gerrit.install(function(self){});\n".getBytes(UTF_8));
-    adminSshSession.exec("gerrit plugin reload");
-
     ServerInfo i = gApi.config().server().getInfo();
+    assertThat(i.plugin.jsResourcePaths).isEmpty();
 
-    // plugin
+    InstallPluginInput input = new InstallPluginInput();
+    input.raw = RawInputUtil.create(JS_PLUGIN_CONTENT);
+    gApi.plugins().install("js-plugin-1.js", input);
+
+    i = gApi.config().server().getInfo();
     assertThat(i.plugin.jsResourcePaths).hasSize(1);
   }
 
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/plugins/PluginApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/plugins/PluginApi.java
new file mode 100644
index 0000000..b6d78a3
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/plugins/PluginApi.java
@@ -0,0 +1,55 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.plugins;
+
+import com.google.gerrit.extensions.common.PluginInfo;
+import com.google.gerrit.extensions.restapi.NotImplementedException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+
+public interface PluginApi {
+  PluginInfo get() throws RestApiException;
+
+  void enable() throws RestApiException;
+
+  void disable() throws RestApiException;
+
+  void reload() throws RestApiException;
+
+  /**
+   * A default implementation which allows source compatibility when adding new methods to the
+   * interface.
+   */
+  class NotImplemented implements PluginApi {
+    @Override
+    public PluginInfo get() throws RestApiException {
+      throw new NotImplementedException();
+    }
+
+    @Override
+    public void enable() throws RestApiException {
+      throw new NotImplementedException();
+    }
+
+    @Override
+    public void disable() throws RestApiException {
+      throw new NotImplementedException();
+    }
+
+    @Override
+    public void reload() throws RestApiException {
+      throw new NotImplementedException();
+    }
+  }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/plugins/Plugins.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/plugins/Plugins.java
index 808a8252..a97073f 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/plugins/Plugins.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/plugins/Plugins.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.extensions.api.plugins;
 
+import com.google.gerrit.extensions.common.InstallPluginInput;
 import com.google.gerrit.extensions.common.PluginInfo;
 import com.google.gerrit.extensions.restapi.NotImplementedException;
 import com.google.gerrit.extensions.restapi.RestApiException;
@@ -24,7 +25,11 @@
 
 public interface Plugins {
 
-  ListRequest list();
+  ListRequest list() throws RestApiException;
+
+  PluginApi name(String name) throws RestApiException;
+
+  PluginApi install(String name, InstallPluginInput input) throws RestApiException;
 
   abstract class ListRequest {
     private boolean all;
@@ -40,8 +45,8 @@
 
     public abstract SortedMap<String, PluginInfo> getAsMap() throws RestApiException;
 
-    public ListRequest all(boolean all) {
-      this.all = all;
+    public ListRequest all() {
+      this.all = true;
       return this;
     }
 
@@ -59,5 +64,15 @@
     public ListRequest list() {
       throw new NotImplementedException();
     }
+
+    @Override
+    public PluginApi name(String name) {
+      throw new NotImplementedException();
+    }
+
+    @Override
+    public PluginApi install(String name, InstallPluginInput input) throws RestApiException {
+      throw new NotImplementedException();
+    }
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/plugins/PluginApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/plugins/PluginApiImpl.java
new file mode 100644
index 0000000..2fc2e50
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/plugins/PluginApiImpl.java
@@ -0,0 +1,72 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.api.plugins;
+
+import com.google.gerrit.extensions.api.plugins.PluginApi;
+import com.google.gerrit.extensions.common.PluginInfo;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.plugins.DisablePlugin;
+import com.google.gerrit.server.plugins.EnablePlugin;
+import com.google.gerrit.server.plugins.GetStatus;
+import com.google.gerrit.server.plugins.PluginResource;
+import com.google.gerrit.server.plugins.ReloadPlugin;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+public class PluginApiImpl implements PluginApi {
+  public interface Factory {
+    PluginApiImpl create(PluginResource resource);
+  }
+
+  private final GetStatus getStatus;
+  private final EnablePlugin enable;
+  private final DisablePlugin disable;
+  private final ReloadPlugin reload;
+  private final PluginResource resource;
+
+  @Inject
+  PluginApiImpl(
+      GetStatus getStatus,
+      EnablePlugin enable,
+      DisablePlugin disable,
+      ReloadPlugin reload,
+      @Assisted PluginResource resource) {
+    this.getStatus = getStatus;
+    this.enable = enable;
+    this.disable = disable;
+    this.reload = reload;
+    this.resource = resource;
+  }
+
+  @Override
+  public PluginInfo get() throws RestApiException {
+    return getStatus.apply(resource);
+  }
+
+  @Override
+  public void enable() throws RestApiException {
+    enable.apply(resource, new EnablePlugin.Input());
+  }
+
+  @Override
+  public void disable() throws RestApiException {
+    disable.apply(resource, new DisablePlugin.Input());
+  }
+
+  @Override
+  public void reload() throws RestApiException {
+    reload.apply(resource, new ReloadPlugin.Input());
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/plugins/PluginsImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/plugins/PluginsImpl.java
index 01ab6ba..994acbf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/plugins/PluginsImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/plugins/PluginsImpl.java
@@ -14,22 +14,44 @@
 
 package com.google.gerrit.server.api.plugins;
 
+import com.google.gerrit.extensions.api.plugins.PluginApi;
 import com.google.gerrit.extensions.api.plugins.Plugins;
+import com.google.gerrit.extensions.common.InstallPluginInput;
 import com.google.gerrit.extensions.common.PluginInfo;
+import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.server.plugins.InstallPlugin;
 import com.google.gerrit.server.plugins.ListPlugins;
+import com.google.gerrit.server.plugins.PluginsCollection;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
+import java.io.IOException;
 import java.util.SortedMap;
 
 @Singleton
 public class PluginsImpl implements Plugins {
+  private final PluginsCollection plugins;
   private final Provider<ListPlugins> listProvider;
+  private final Provider<InstallPlugin> installProvider;
+  private final PluginApiImpl.Factory pluginApi;
 
   @Inject
-  PluginsImpl(Provider<ListPlugins> listProvider) {
+  PluginsImpl(
+      PluginsCollection plugins,
+      Provider<ListPlugins> listProvider,
+      Provider<InstallPlugin> installProvider,
+      PluginApiImpl.Factory pluginApi) {
+    this.plugins = plugins;
     this.listProvider = listProvider;
+    this.installProvider = installProvider;
+    this.pluginApi = pluginApi;
+  }
+
+  @Override
+  public PluginApi name(String name) throws RestApiException {
+    return pluginApi.create(plugins.parse(name));
   }
 
   @Override
@@ -43,4 +65,15 @@
       }
     };
   }
+
+  @Override
+  public PluginApi install(String name, InstallPluginInput input) throws RestApiException {
+    try {
+      Response<PluginInfo> created =
+          installProvider.get().setName(name).apply(TopLevelResource.INSTANCE, input);
+      return pluginApi.create(plugins.parse(created.value().id));
+    } catch (IOException e) {
+      throw new RestApiException("could not install plugin", e);
+    }
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/DisablePlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/DisablePlugin.java
index 8732e3e..a2da580 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/DisablePlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/DisablePlugin.java
@@ -26,8 +26,8 @@
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
 @Singleton
-class DisablePlugin implements RestModifyView<PluginResource, Input> {
-  static class Input {}
+public class DisablePlugin implements RestModifyView<PluginResource, Input> {
+  public static class Input {}
 
   private final PluginLoader loader;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/EnablePlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/EnablePlugin.java
index 41f9349..f29e36b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/EnablePlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/EnablePlugin.java
@@ -29,8 +29,8 @@
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
 @Singleton
-class EnablePlugin implements RestModifyView<PluginResource, Input> {
-  static class Input {}
+public class EnablePlugin implements RestModifyView<PluginResource, Input> {
+  public static class Input {}
 
   private final PluginLoader loader;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/GetStatus.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/GetStatus.java
index 650f9fc..cbd864a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/GetStatus.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/GetStatus.java
@@ -19,7 +19,7 @@
 import com.google.inject.Singleton;
 
 @Singleton
-class GetStatus implements RestReadView<PluginResource> {
+public class GetStatus implements RestReadView<PluginResource> {
   @Override
   public PluginInfo apply(PluginResource resource) {
     return ListPlugins.toPluginInfo(resource.getPlugin());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InstallPlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InstallPlugin.java
index 61aed6a..531e9ac 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InstallPlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InstallPlugin.java
@@ -33,7 +33,7 @@
 import java.util.zip.ZipException;
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
-class InstallPlugin implements RestModifyView<TopLevelResource, InstallPluginInput> {
+public class InstallPlugin implements RestModifyView<TopLevelResource, InstallPluginInput> {
   private final PluginLoader loader;
 
   private String name;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
index dd3dc93..fcc6801 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
@@ -309,7 +309,16 @@
   @Override
   public synchronized void start() {
     removeStalePluginFiles();
-    log.info("Loading plugins from " + pluginsDir.toAbsolutePath());
+    Path absolutePath = pluginsDir.toAbsolutePath();
+    if (!Files.exists(absolutePath)) {
+      log.info(absolutePath + " does not exist; creating");
+      try {
+        Files.createDirectories(absolutePath);
+      } catch (IOException e) {
+        log.error(String.format("Failed to create %s: %s", absolutePath, e.getMessage()));
+      }
+    }
+    log.info("Loading plugins from " + absolutePath);
     srvInfoImpl.state = ServerInformation.State.STARTUP;
     rescan();
     srvInfoImpl.state = ServerInformation.State.RUNNING;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginRestApiModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginRestApiModule.java
index e61c517..5f97134 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginRestApiModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginRestApiModule.java
@@ -19,6 +19,7 @@
 import com.google.gerrit.extensions.api.plugins.Plugins;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.RestApiModule;
+import com.google.gerrit.server.api.plugins.PluginApiImpl;
 import com.google.gerrit.server.api.plugins.PluginsImpl;
 
 public class PluginRestApiModule extends RestApiModule {
@@ -33,5 +34,6 @@
     post(PLUGIN_KIND, "enable").to(EnablePlugin.class);
     post(PLUGIN_KIND, "reload").to(ReloadPlugin.class);
     bind(Plugins.class).to(PluginsImpl.class);
+    factory(PluginApiImpl.Factory.class);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginsCollection.java
index 314415e..a1dc5c1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginsCollection.java
@@ -55,7 +55,11 @@
   @Override
   public PluginResource parse(TopLevelResource parent, IdString id)
       throws ResourceNotFoundException {
-    Plugin p = loader.get(id.get());
+    return parse(id.get());
+  }
+
+  public PluginResource parse(String id) throws ResourceNotFoundException {
+    Plugin p = loader.get(id);
     if (p == null) {
       throw new ResourceNotFoundException(id);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ReloadPlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ReloadPlugin.java
index 426d323..7b464bb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ReloadPlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ReloadPlugin.java
@@ -28,8 +28,8 @@
 
 @RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
 @Singleton
-class ReloadPlugin implements RestModifyView<PluginResource, Input> {
-  static class Input {}
+public class ReloadPlugin implements RestModifyView<PluginResource, Input> {
+  public static class Input {}
 
   private final PluginLoader loader;