Delegate dynamicBeans for external plugins
When dynamicbeans are defined by an external plugin on commands from a different plugin, instantiate the dynamic bean with a delegating classloader which uses the command's classloader as the parent and resolves resources via the dynamicbean's classloader. Also instantiate the dynamicbeans using the injector from the command. Change-Id: If2cff8235a9680eb64c58b77f2d482c5896baf0f
This commit is contained in:

committed by
Zac Livingston

parent
b32d6fcfe4
commit
4c4acd6796
@@ -36,6 +36,7 @@ import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gwtexpui.server.CacheHeaders;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Injector;
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.util.HashSet;
|
||||
@@ -51,11 +52,16 @@ class ParameterParser {
|
||||
ImmutableSet.of("pp", "prettyPrint", "strict", "callback", "alt", "fields");
|
||||
|
||||
private final CmdLineParser.Factory parserFactory;
|
||||
private final Injector injector;
|
||||
private final DynamicMap<DynamicOptions.DynamicBean> dynamicBeans;
|
||||
|
||||
@Inject
|
||||
ParameterParser(CmdLineParser.Factory pf, DynamicMap<DynamicOptions.DynamicBean> dynamicBeans) {
|
||||
ParameterParser(
|
||||
CmdLineParser.Factory pf,
|
||||
Injector injector,
|
||||
DynamicMap<DynamicOptions.DynamicBean> dynamicBeans) {
|
||||
this.parserFactory = pf;
|
||||
this.injector = injector;
|
||||
this.dynamicBeans = dynamicBeans;
|
||||
}
|
||||
|
||||
@@ -63,7 +69,7 @@ class ParameterParser {
|
||||
T param, ListMultimap<String, String> in, HttpServletRequest req, HttpServletResponse res)
|
||||
throws IOException {
|
||||
CmdLineParser clp = parserFactory.create(param);
|
||||
DynamicOptions pluginOptions = new DynamicOptions(param, dynamicBeans);
|
||||
DynamicOptions pluginOptions = new DynamicOptions(param, injector, dynamicBeans);
|
||||
pluginOptions.parseDynamicBeans(clp);
|
||||
pluginOptions.setDynamicBeans();
|
||||
pluginOptions.onBeanParseStart();
|
||||
|
@@ -15,7 +15,9 @@
|
||||
package com.google.gerrit.server;
|
||||
|
||||
import com.google.gerrit.extensions.registration.DynamicMap;
|
||||
import com.google.gerrit.server.plugins.DelegatingClassLoader;
|
||||
import com.google.gerrit.util.cli.CmdLineParser;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Provider;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -80,15 +82,16 @@ public class DynamicOptions {
|
||||
void setDynamicBean(String plugin, DynamicBean dynamicBean);
|
||||
}
|
||||
|
||||
Object bean;
|
||||
Map<String, DynamicBean> beansByPlugin;
|
||||
protected Object bean;
|
||||
protected Map<String, DynamicBean> beansByPlugin;
|
||||
protected Injector injector;
|
||||
|
||||
/**
|
||||
* Internal: For Gerrit to include options from DynamicBeans, setup a DynamicMap and instantiate
|
||||
* this class so the following methods can be called if desired:
|
||||
*
|
||||
* <pre>
|
||||
* DynamicOptions pluginOptions = new DynamicOptions(bean, dynamicBeans);
|
||||
* DynamicOptions pluginOptions = new DynamicOptions(bean, injector, dynamicBeans);
|
||||
* pluginOptions.parseDynamicBeans(clp);
|
||||
* pluginOptions.setDynamicBeans();
|
||||
* pluginOptions.onBeanParseStart();
|
||||
@@ -98,18 +101,40 @@ public class DynamicOptions {
|
||||
* pluginOptions.onBeanParseEnd();
|
||||
* </pre>
|
||||
*/
|
||||
public DynamicOptions(Object bean, DynamicMap<DynamicBean> dynamicBeans) {
|
||||
public DynamicOptions(Object bean, Injector injector, DynamicMap<DynamicBean> dynamicBeans) {
|
||||
this.bean = bean;
|
||||
beansByPlugin = new HashMap<>();
|
||||
this.injector = injector;
|
||||
beansByPlugin = new HashMap<String, DynamicBean>();
|
||||
for (String plugin : dynamicBeans.plugins()) {
|
||||
Provider<DynamicBean> provider =
|
||||
dynamicBeans.byPlugin(plugin).get(bean.getClass().getCanonicalName());
|
||||
if (provider != null) {
|
||||
beansByPlugin.put(plugin, provider.get());
|
||||
beansByPlugin.put(plugin, getDynamicBean(bean, provider.get()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DynamicBean getDynamicBean(Object bean, DynamicBean dynamicBean) {
|
||||
ClassLoader coreCl = getClass().getClassLoader();
|
||||
ClassLoader beanCl = bean.getClass().getClassLoader();
|
||||
if (beanCl != coreCl) { // bean from a plugin?
|
||||
ClassLoader dynamicBeanCl = dynamicBean.getClass().getClassLoader();
|
||||
if (beanCl != dynamicBeanCl) { // in a different plugin?
|
||||
ClassLoader mergedCL = new DelegatingClassLoader(beanCl, dynamicBeanCl);
|
||||
try {
|
||||
return injector
|
||||
.createChildInjector()
|
||||
.getInstance(
|
||||
(Class<DynamicOptions.DynamicBean>)
|
||||
mergedCL.loadClass(dynamicBean.getClass().getCanonicalName()));
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return dynamicBean;
|
||||
}
|
||||
|
||||
public void parseDynamicBeans(CmdLineParser clp) {
|
||||
for (Entry<String, DynamicBean> e : beansByPlugin.entrySet()) {
|
||||
clp.parseWithPrefix(e.getKey(), e.getValue());
|
||||
|
@@ -0,0 +1,69 @@
|
||||
// Copyright (C) 2016 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;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.Enumeration;
|
||||
|
||||
public class DelegatingClassLoader extends ClassLoader {
|
||||
public ClassLoader target;
|
||||
|
||||
public DelegatingClassLoader(ClassLoader parent, ClassLoader target) {
|
||||
super(parent);
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public Class<?> findClass(String name) throws ClassNotFoundException {
|
||||
String path = name.replace('.', '/') + ".class";
|
||||
InputStream resource = target.getResourceAsStream(path);
|
||||
if (resource != null) {
|
||||
try {
|
||||
byte[] bytes = ByteStreams.toByteArray(resource);
|
||||
return defineClass(name, bytes, 0, bytes.length);
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
throw new ClassNotFoundException(name);
|
||||
}
|
||||
|
||||
public URL getResource(String name) {
|
||||
URL rtn = getParent().getResource(name);
|
||||
if (rtn == null) {
|
||||
rtn = target.getResource(name);
|
||||
}
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public Enumeration<URL> getResources(String name) throws IOException {
|
||||
Enumeration<URL> rtn = getParent().getResources(name);
|
||||
if (rtn == null) {
|
||||
rtn = target.getResources(name);
|
||||
}
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public InputStream getResourceAsStream(String name) {
|
||||
InputStream rtn = getParent().getResourceAsStream(name);
|
||||
if (rtn == null) {
|
||||
rtn = target.getResourceAsStream(name);
|
||||
}
|
||||
return rtn;
|
||||
}
|
||||
}
|
@@ -35,6 +35,7 @@ import com.google.gerrit.sshd.SshScope.Context;
|
||||
import com.google.gerrit.util.cli.CmdLineParser;
|
||||
import com.google.gerrit.util.cli.EndOfOptionsHandler;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Injector;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -86,13 +87,15 @@ public abstract class BaseCommand implements Command {
|
||||
|
||||
@Inject private SshScope.Context context;
|
||||
|
||||
@Inject private DynamicMap<DynamicOptions.DynamicBean> dynamicBeans;
|
||||
|
||||
/** Commands declared by a plugin can be scoped by the plugin name. */
|
||||
@Inject(optional = true)
|
||||
@PluginName
|
||||
private String pluginName;
|
||||
|
||||
@Inject private Injector injector;
|
||||
|
||||
@Inject private DynamicMap<DynamicOptions.DynamicBean> dynamicBeans = null;
|
||||
|
||||
/** The task, as scheduled on a worker thread. */
|
||||
private final AtomicReference<Future<?>> task;
|
||||
|
||||
@@ -197,7 +200,7 @@ public abstract class BaseCommand implements Command {
|
||||
*/
|
||||
protected void parseCommandLine(Object options) throws UnloggedFailure {
|
||||
final CmdLineParser clp = newCmdLineParser(options);
|
||||
DynamicOptions pluginOptions = new DynamicOptions(options, dynamicBeans);
|
||||
DynamicOptions pluginOptions = new DynamicOptions(options, injector, dynamicBeans);
|
||||
pluginOptions.parseDynamicBeans(clp);
|
||||
pluginOptions.setDynamicBeans();
|
||||
pluginOptions.onBeanParseStart();
|
||||
|
Reference in New Issue
Block a user