Add plugin JS resource paths to /config/server/info

This requires WebUiPlugins to be bound in the system module.  Existing
plugins that do this binding in the HttpModule are supported by
copying the DynamicSet from the system module.

The Maven plugin archetype is adapted to generate a correct example
for WebUiPlugin bindings.

Bug: issue 3915
Change-Id: Ic976ba1b4a3fc8d08975fd841e5013f7348b32cf
This commit is contained in:
Andrew Bonventre 2016-03-03 16:01:59 -05:00 committed by Jonathan Nieder
parent 8682415768
commit c8a2ed9c6c
14 changed files with 92 additions and 99 deletions

View File

@ -15,6 +15,7 @@
package com.google.gerrit.acceptance.rest.config;
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GerritConfig;
@ -27,6 +28,8 @@ import com.google.gerrit.server.config.AllUsersNameProvider;
import com.google.gerrit.server.config.AnonymousCowardNameProvider;
import com.google.gerrit.server.config.GetServerInfo.ServerInfo;
import java.nio.file.Path;
import java.nio.file.Files;
import org.junit.Test;
public class ServerInfoIT extends AbstractDaemonTest {
@ -107,6 +110,9 @@ public class ServerInfoIT extends AbstractDaemonTest {
// gitweb
assertThat(i.gitweb).isNull();
// plugin
assertThat(i.plugin.jsResourcePaths).isEmpty();
// sshd
assertThat(i.sshd).isNotNull();
@ -117,6 +123,21 @@ public class ServerInfoIT extends AbstractDaemonTest {
assertThat(i.user.anonymousCowardName).isEqualTo("Unnamed User");
}
@Test
@GerritConfig(name = "plugins.allowRemoteAdmin", value = "true")
public void serverConfigWithPlugin() throws Exception {
Path plugins = tempSiteDir.newFolder("plugins").toPath();
Path jsplugin = plugins.resolve("js-plugin-1.js");
Files.write(jsplugin, "Gerrit.install(function(self){});\n".getBytes(UTF_8));
sshSession.exec("gerrit plugin reload");
RestResponse r = adminSession.get("/config/server/info/");
ServerInfo i = newGson().fromJson(r.getReader(), ServerInfo.class);
// plugin
assertThat(i.plugin.jsResourcePaths).hasSize(1);
}
@Test
public void serverConfigWithDefaults() throws Exception {
RestResponse r = adminSession.get("/config/server/info/");
@ -157,6 +178,9 @@ public class ServerInfoIT extends AbstractDaemonTest {
// gitweb
assertThat(i.gitweb).isNull();
// plugin
assertThat(i.plugin.jsResourcePaths).isEmpty();
// sshd
assertThat(i.sshd).isNotNull();

View File

@ -18,6 +18,7 @@ import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.client.rpc.NativeString;
import com.google.gerrit.client.rpc.Natives;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArrayString;
import java.util.HashMap;
import java.util.Map;
@ -68,6 +69,8 @@ public class ServerInfo extends JavaScriptObject {
public static class PluginConfigInfo extends JavaScriptObject {
public final native boolean hasAvatars() /*-{ return this.has_avatars || false; }-*/;
public final native JsArrayString jsResourcePaths() /*-{
return this.js_resource_paths || []; }-*/;
protected PluginConfigInfo() {
}

View File

@ -16,8 +16,6 @@ package com.google.gerrit.httpd;
import static com.google.gerrit.extensions.registration.PrivateInternals_DynamicTypes.registerInParentInjectors;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.webui.WebUiPlugin;
import com.google.gerrit.httpd.auth.become.BecomeAnyAccountModule;
import com.google.gerrit.httpd.auth.container.HttpAuthModule;
import com.google.gerrit.httpd.auth.container.HttpsClientSslCertModule;
@ -71,8 +69,6 @@ public class WebModule extends LifecycleModule {
install(new GitwebModule());
}
DynamicSet.setOf(binder(), WebUiPlugin.class);
install(new AsyncReceiveCommits.Module());
bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(

View File

@ -14,18 +14,14 @@
package com.google.gerrit.httpd.plugins;
import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.server.plugins.AutoRegisterUtil.calculateBindAnnotation;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.gerrit.extensions.annotations.Export;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.webui.JavaScriptPlugin;
import com.google.gerrit.extensions.webui.WebUiPlugin;
import com.google.gerrit.server.plugins.HttpModuleGenerator;
import com.google.gerrit.server.plugins.InvalidPluginException;
import com.google.gerrit.server.plugins.ModuleGenerator;
import com.google.inject.Module;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
@ -37,10 +33,9 @@ import java.util.Map;
import javax.servlet.http.HttpServlet;
class HttpAutoRegisterModuleGenerator extends ServletModule
implements HttpModuleGenerator {
implements ModuleGenerator {
private final Map<String, Class<HttpServlet>> serve = Maps.newHashMap();
private final Multimap<TypeLiteral<?>, Class<?>> listeners = LinkedListMultimap.create();
private String javascript;
@Override
protected void configureServlets() {
@ -58,10 +53,6 @@ class HttpAutoRegisterModuleGenerator extends ServletModule
Annotation n = calculateBindAnnotation(impl);
bind(type).annotatedWith(n).to(impl);
}
if (javascript != null) {
DynamicSet.bind(binder(), WebUiPlugin.class).toInstance(
new JavaScriptPlugin(javascript));
}
}
@Override
@ -88,14 +79,6 @@ class HttpAutoRegisterModuleGenerator extends ServletModule
}
}
@Override
public void export(String javascript) {
checkState(this.javascript == null,
"Multiple JavaScript plugins detected: %s, %s", this.javascript,
javascript);
this.javascript = javascript;
}
@Override
public void listen(TypeLiteral<?> tl, Class<?> clazz) {
listeners.put(tl, clazz);

View File

@ -18,7 +18,7 @@ import com.google.gerrit.httpd.resources.Resource;
import com.google.gerrit.httpd.resources.ResourceKey;
import com.google.gerrit.httpd.resources.ResourceWeigher;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.plugins.HttpModuleGenerator;
import com.google.gerrit.server.plugins.ModuleGenerator;
import com.google.gerrit.server.plugins.ReloadPluginListener;
import com.google.gerrit.server.plugins.StartPluginListener;
import com.google.inject.internal.UniqueAnnotations;
@ -51,7 +51,7 @@ public class HttpPluginModule extends ServletModule {
.annotatedWith(UniqueAnnotations.create())
.to(LfsPluginServlet.class);
bind(HttpModuleGenerator.class)
bind(ModuleGenerator.class)
.to(HttpAutoRegisterModuleGenerator.class);
install(new CacheModule() {

View File

@ -10,7 +10,6 @@ gerrit_plugin(
'Gerrit-ApiType: plugin',
'Gerrit-ApiVersion: ${gerritApiVersion}',
'Gerrit-Module: ${package}.Module',
'Gerrit-HttpModule: ${package}.HttpModule',
],
)

View File

@ -1,29 +0,0 @@
// Copyright (C) 2015 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 ${package};
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.webui.GwtPlugin;
import com.google.gerrit.extensions.webui.WebUiPlugin;
import com.google.gerrit.httpd.plugins.HttpPluginModule;
public class HttpModule extends HttpPluginModule {
@Override
protected void configureServlets() {
DynamicSet.bind(binder(), WebUiPlugin.class)
.toInstance(new GwtPlugin("hello_gwt_plugin"));
}
}

View File

@ -15,7 +15,9 @@
package ${package};
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.webui.GwtPlugin;
import com.google.gerrit.extensions.webui.TopMenu;
import com.google.gerrit.extensions.webui.WebUiPlugin;
import com.google.inject.AbstractModule;
public class Module extends AbstractModule {
@ -23,5 +25,7 @@ public class Module extends AbstractModule {
@Override
protected void configure() {
DynamicSet.bind(binder(), TopMenu.class).to(HelloMenu.class);
DynamicSet.bind(binder(), WebUiPlugin.class)
.toInstance(new GwtPlugin("hello_gwt_plugin"));
}
}

View File

@ -45,6 +45,7 @@ import com.google.gerrit.extensions.webui.FileWebLink;
import com.google.gerrit.extensions.webui.PatchSetWebLink;
import com.google.gerrit.extensions.webui.ProjectWebLink;
import com.google.gerrit.extensions.webui.TopMenu;
import com.google.gerrit.extensions.webui.WebUiPlugin;
import com.google.gerrit.rules.PrologModule;
import com.google.gerrit.rules.RulesCache;
import com.google.gerrit.server.AnonymousUser;
@ -309,6 +310,7 @@ public class GerritGlobalModule extends FactoryModule {
DynamicSet.setOf(binder(), BranchWebLink.class);
DynamicMap.mapOf(binder(), OAuthLoginProvider.class);
DynamicSet.setOf(binder(), AccountExternalIdCreator.class);
DynamicSet.setOf(binder(), WebUiPlugin.class);
factory(UploadValidators.Factory.class);
DynamicSet.setOf(binder(), UploadValidationListener.class);

View File

@ -26,7 +26,9 @@ import com.google.gerrit.extensions.config.DownloadCommand;
import com.google.gerrit.extensions.config.DownloadScheme;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.webui.WebUiPlugin;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.server.EnableSignedPush;
@ -58,6 +60,7 @@ public class GetServerInfo implements RestReadView<ConfigResource> {
private final DynamicMap<DownloadScheme> downloadSchemes;
private final DynamicMap<DownloadCommand> downloadCommands;
private final DynamicMap<CloneCommand> cloneCommands;
private final DynamicSet<WebUiPlugin> plugins;
private final GetArchive.AllowedFormats archiveFormats;
private final AllProjectsName allProjectsName;
private final AllUsersName allUsersName;
@ -75,6 +78,7 @@ public class GetServerInfo implements RestReadView<ConfigResource> {
DynamicMap<DownloadScheme> downloadSchemes,
DynamicMap<DownloadCommand> downloadCommands,
DynamicMap<CloneCommand> cloneCommands,
DynamicSet<WebUiPlugin> webUiPlugins,
GetArchive.AllowedFormats archiveFormats,
AllProjectsName allProjectsName,
AllUsersName allUsersName,
@ -89,6 +93,7 @@ public class GetServerInfo implements RestReadView<ConfigResource> {
this.downloadSchemes = downloadSchemes;
this.downloadCommands = downloadCommands;
this.cloneCommands = cloneCommands;
this.plugins = webUiPlugins;
this.archiveFormats = archiveFormats;
this.allProjectsName = allProjectsName;
this.allUsersName = allUsersName;
@ -270,6 +275,12 @@ public class GetServerInfo implements RestReadView<ConfigResource> {
private PluginConfigInfo getPluginInfo() {
PluginConfigInfo info = new PluginConfigInfo();
info.hasAvatars = toBoolean(avatar.get() != null);
info.jsResourcePaths = new ArrayList<>();
for (WebUiPlugin u : plugins) {
info.jsResourcePaths.add(String.format("plugins/%s/%s",
u.getPluginName(),
u.getJavaScriptResourcePath()));
}
return info;
}
@ -385,6 +396,7 @@ public class GetServerInfo implements RestReadView<ConfigResource> {
public static class PluginConfigInfo {
public Boolean hasAvatars;
public List<String> jsResourcePaths;
}
public static class SshdInfo {

View File

@ -14,6 +14,7 @@
package com.google.gerrit.server.plugins;
import static com.google.gerrit.extensions.webui.JavaScriptPlugin.STATIC_INIT_JS;
import static com.google.gerrit.server.plugins.AutoRegisterUtil.calculateBindAnnotation;
import static com.google.gerrit.server.plugins.PluginGuiceEnvironment.is;
@ -23,7 +24,9 @@ import com.google.common.collect.Sets;
import com.google.gerrit.extensions.annotations.Export;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
import com.google.gerrit.extensions.annotations.Listen;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.webui.JavaScriptPlugin;
import com.google.gerrit.extensions.webui.WebUiPlugin;
import com.google.gerrit.server.plugins.PluginContentScanner.ExtensionMetaData;
import com.google.inject.AbstractModule;
import com.google.inject.Module;
@ -48,10 +51,11 @@ class AutoRegisterModules {
private final PluginContentScanner scanner;
private final ClassLoader classLoader;
private final ModuleGenerator sshGen;
private final HttpModuleGenerator httpGen;
private final ModuleGenerator httpGen;
private Set<Class<?>> sysSingletons;
private Multimap<TypeLiteral<?>, Class<?>> sysListen;
private String initJs;
Module sysModule;
Module sshModule;
@ -70,19 +74,20 @@ class AutoRegisterModules {
: new ModuleGenerator.NOP();
this.httpGen = env.hasHttpModule()
? env.newHttpModuleGenerator()
: new HttpModuleGenerator.NOP();
: new ModuleGenerator.NOP();
}
AutoRegisterModules discover() throws InvalidPluginException {
sysSingletons = Sets.newHashSet();
sysListen = LinkedListMultimap.create();
initJs = null;
sshGen.setPluginName(pluginName);
httpGen.setPluginName(pluginName);
scan();
if (!sysSingletons.isEmpty() || !sysListen.isEmpty()) {
if (!sysSingletons.isEmpty() || !sysListen.isEmpty() || initJs != null) {
sysModule = makeSystemModule();
}
sshModule = sshGen.create();
@ -107,6 +112,10 @@ class AutoRegisterModules {
Annotation n = calculateBindAnnotation(impl);
bind(type).annotatedWith(n).to(impl);
}
if (initJs != null) {
DynamicSet.bind(binder(), WebUiPlugin.class)
.toInstance(new JavaScriptPlugin(initJs));
}
}
};
}
@ -120,18 +129,20 @@ class AutoRegisterModules {
for (ExtensionMetaData listener : extensions.get(Listen.class)) {
listen(listener);
}
exportInitJs();
if (env.hasHttpModule()) {
exportInitJs();
}
}
private void exportInitJs() {
try {
if (scanner.getEntry(JavaScriptPlugin.STATIC_INIT_JS).isPresent()) {
httpGen.export(JavaScriptPlugin.INIT_JS);
if (scanner.getEntry(STATIC_INIT_JS).isPresent()) {
initJs = STATIC_INIT_JS;
}
} catch (IOException e) {
log.warn(String.format("Cannot access %s from plugin %s: "
+ "JavaScript auto-discovered plugin will not be registered",
JavaScriptPlugin.STATIC_INIT_JS, pluginName), e);
STATIC_INIT_JS, pluginName), e);
}
}

View File

@ -1,28 +0,0 @@
// Copyright (C) 2014 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.plugins;
public interface HttpModuleGenerator extends ModuleGenerator {
void export(String javascript);
class NOP extends ModuleGenerator.NOP
implements HttpModuleGenerator {
@Override
public void export(String javascript) {
// do nothing
}
}
}

View File

@ -30,7 +30,7 @@ import org.eclipse.jgit.internal.storage.file.FileSnapshot;
import java.nio.file.Path;
class JsPlugin extends Plugin {
private Injector httpInjector;
private Injector sysInjector;
JsPlugin(String name, Path srcFile, PluginUser pluginUser,
FileSnapshot snapshot) {
@ -52,7 +52,7 @@ class JsPlugin extends Plugin {
public void start(PluginGuiceEnvironment env) throws Exception {
manager = new LifecycleManager();
String fileName = getSrcFile().getFileName().toString();
httpInjector =
sysInjector =
Guice.createInjector(new StandaloneJsPluginModule(getName(), fileName));
manager.start();
}
@ -61,13 +61,13 @@ class JsPlugin extends Plugin {
protected void stop(PluginGuiceEnvironment env) {
if (manager != null) {
manager.stop();
httpInjector = null;
sysInjector = null;
}
}
@Override
public Injector getSysInjector() {
return null;
return sysInjector;
}
@Override
@ -79,7 +79,7 @@ class JsPlugin extends Plugin {
@Override
@Nullable
public Injector getHttpInjector() {
return httpInjector;
return null;
}
@Override

View File

@ -14,6 +14,7 @@
package com.google.gerrit.server.plugins;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.gerrit.extensions.registration.PrivateInternals_DynamicTypes.dynamicItemsOf;
import static com.google.gerrit.extensions.registration.PrivateInternals_DynamicTypes.dynamicMapsOf;
import static com.google.gerrit.extensions.registration.PrivateInternals_DynamicTypes.dynamicSetsOf;
@ -34,6 +35,7 @@ import com.google.gerrit.extensions.registration.PrivateInternals_DynamicTypes;
import com.google.gerrit.extensions.registration.RegistrationHandle;
import com.google.gerrit.extensions.registration.ReloadableRegistrationHandle;
import com.google.gerrit.extensions.systemstatus.ServerInformation;
import com.google.gerrit.extensions.webui.WebUiPlugin;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.server.util.PluginRequestContext;
import com.google.gerrit.server.util.RequestContext;
@ -52,6 +54,8 @@ import com.google.inject.internal.UniqueAnnotations;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@ -85,7 +89,7 @@ public class PluginGuiceEnvironment {
private Module httpModule;
private Provider<ModuleGenerator> sshGen;
private Provider<HttpModuleGenerator> httpGen;
private Provider<ModuleGenerator> httpGen;
private Map<TypeLiteral<?>, DynamicItem<?>> sysItems;
private Map<TypeLiteral<?>, DynamicItem<?>> sshItems;
@ -197,15 +201,27 @@ public class PluginGuiceEnvironment {
public void setHttpInjector(Injector injector) {
httpModule = copy(injector);
httpGen = injector.getProvider(HttpModuleGenerator.class);
httpGen = injector.getProvider(ModuleGenerator.class);
httpItems = dynamicItemsOf(injector);
httpSets = dynamicSetsOf(injector);
httpSets = httpDynamicSetsOf(injector);
httpMaps = dynamicMapsOf(injector);
onStart.addAll(listeners(injector, StartPluginListener.class));
onStop.addAll(listeners(injector, StopPluginListener.class));
onReload.addAll(listeners(injector, ReloadPluginListener.class));
}
private Map<TypeLiteral<?>, DynamicSet<?>> httpDynamicSetsOf(Injector i) {
// Copy binding of DynamicSet<WebUiPlugin> from sysInjector to HTTP.
// This supports older plugins that bound a plugin in the HttpModule.
TypeLiteral<WebUiPlugin> key = TypeLiteral.get(WebUiPlugin.class);
DynamicSet<?> web = sysSets.get(key);
checkNotNull(web, "DynamicSet<WebUiPlugin> exists in sysInjector");
Map<TypeLiteral<?>, DynamicSet<?>> m = new HashMap<>(dynamicSetsOf(i));
m.put(key, web);
return Collections.unmodifiableMap(m);
}
boolean hasHttpModule() {
return httpModule != null;
}
@ -214,7 +230,7 @@ public class PluginGuiceEnvironment {
return httpModule;
}
HttpModuleGenerator newHttpModuleGenerator() {
ModuleGenerator newHttpModuleGenerator() {
return httpGen.get();
}