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.gson.JsonPrimitive;
|
||||||
import com.google.gwtexpui.server.CacheHeaders;
|
import com.google.gwtexpui.server.CacheHeaders;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Injector;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@@ -51,11 +52,16 @@ class ParameterParser {
|
|||||||
ImmutableSet.of("pp", "prettyPrint", "strict", "callback", "alt", "fields");
|
ImmutableSet.of("pp", "prettyPrint", "strict", "callback", "alt", "fields");
|
||||||
|
|
||||||
private final CmdLineParser.Factory parserFactory;
|
private final CmdLineParser.Factory parserFactory;
|
||||||
|
private final Injector injector;
|
||||||
private final DynamicMap<DynamicOptions.DynamicBean> dynamicBeans;
|
private final DynamicMap<DynamicOptions.DynamicBean> dynamicBeans;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ParameterParser(CmdLineParser.Factory pf, DynamicMap<DynamicOptions.DynamicBean> dynamicBeans) {
|
ParameterParser(
|
||||||
|
CmdLineParser.Factory pf,
|
||||||
|
Injector injector,
|
||||||
|
DynamicMap<DynamicOptions.DynamicBean> dynamicBeans) {
|
||||||
this.parserFactory = pf;
|
this.parserFactory = pf;
|
||||||
|
this.injector = injector;
|
||||||
this.dynamicBeans = dynamicBeans;
|
this.dynamicBeans = dynamicBeans;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +69,7 @@ class ParameterParser {
|
|||||||
T param, ListMultimap<String, String> in, HttpServletRequest req, HttpServletResponse res)
|
T param, ListMultimap<String, String> in, HttpServletRequest req, HttpServletResponse res)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
CmdLineParser clp = parserFactory.create(param);
|
CmdLineParser clp = parserFactory.create(param);
|
||||||
DynamicOptions pluginOptions = new DynamicOptions(param, dynamicBeans);
|
DynamicOptions pluginOptions = new DynamicOptions(param, injector, dynamicBeans);
|
||||||
pluginOptions.parseDynamicBeans(clp);
|
pluginOptions.parseDynamicBeans(clp);
|
||||||
pluginOptions.setDynamicBeans();
|
pluginOptions.setDynamicBeans();
|
||||||
pluginOptions.onBeanParseStart();
|
pluginOptions.onBeanParseStart();
|
||||||
|
|||||||
@@ -15,7 +15,9 @@
|
|||||||
package com.google.gerrit.server;
|
package com.google.gerrit.server;
|
||||||
|
|
||||||
import com.google.gerrit.extensions.registration.DynamicMap;
|
import com.google.gerrit.extensions.registration.DynamicMap;
|
||||||
|
import com.google.gerrit.server.plugins.DelegatingClassLoader;
|
||||||
import com.google.gerrit.util.cli.CmdLineParser;
|
import com.google.gerrit.util.cli.CmdLineParser;
|
||||||
|
import com.google.inject.Injector;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -80,15 +82,16 @@ public class DynamicOptions {
|
|||||||
void setDynamicBean(String plugin, DynamicBean dynamicBean);
|
void setDynamicBean(String plugin, DynamicBean dynamicBean);
|
||||||
}
|
}
|
||||||
|
|
||||||
Object bean;
|
protected Object bean;
|
||||||
Map<String, DynamicBean> beansByPlugin;
|
protected Map<String, DynamicBean> beansByPlugin;
|
||||||
|
protected Injector injector;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal: For Gerrit to include options from DynamicBeans, setup a DynamicMap and instantiate
|
* Internal: For Gerrit to include options from DynamicBeans, setup a DynamicMap and instantiate
|
||||||
* this class so the following methods can be called if desired:
|
* this class so the following methods can be called if desired:
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* DynamicOptions pluginOptions = new DynamicOptions(bean, dynamicBeans);
|
* DynamicOptions pluginOptions = new DynamicOptions(bean, injector, dynamicBeans);
|
||||||
* pluginOptions.parseDynamicBeans(clp);
|
* pluginOptions.parseDynamicBeans(clp);
|
||||||
* pluginOptions.setDynamicBeans();
|
* pluginOptions.setDynamicBeans();
|
||||||
* pluginOptions.onBeanParseStart();
|
* pluginOptions.onBeanParseStart();
|
||||||
@@ -98,18 +101,40 @@ public class DynamicOptions {
|
|||||||
* pluginOptions.onBeanParseEnd();
|
* pluginOptions.onBeanParseEnd();
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public DynamicOptions(Object bean, DynamicMap<DynamicBean> dynamicBeans) {
|
public DynamicOptions(Object bean, Injector injector, DynamicMap<DynamicBean> dynamicBeans) {
|
||||||
this.bean = bean;
|
this.bean = bean;
|
||||||
beansByPlugin = new HashMap<>();
|
this.injector = injector;
|
||||||
|
beansByPlugin = new HashMap<String, DynamicBean>();
|
||||||
for (String plugin : dynamicBeans.plugins()) {
|
for (String plugin : dynamicBeans.plugins()) {
|
||||||
Provider<DynamicBean> provider =
|
Provider<DynamicBean> provider =
|
||||||
dynamicBeans.byPlugin(plugin).get(bean.getClass().getCanonicalName());
|
dynamicBeans.byPlugin(plugin).get(bean.getClass().getCanonicalName());
|
||||||
if (provider != null) {
|
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) {
|
public void parseDynamicBeans(CmdLineParser clp) {
|
||||||
for (Entry<String, DynamicBean> e : beansByPlugin.entrySet()) {
|
for (Entry<String, DynamicBean> e : beansByPlugin.entrySet()) {
|
||||||
clp.parseWithPrefix(e.getKey(), e.getValue());
|
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.CmdLineParser;
|
||||||
import com.google.gerrit.util.cli.EndOfOptionsHandler;
|
import com.google.gerrit.util.cli.EndOfOptionsHandler;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Injector;
|
||||||
import java.io.BufferedWriter;
|
import java.io.BufferedWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -86,13 +87,15 @@ public abstract class BaseCommand implements Command {
|
|||||||
|
|
||||||
@Inject private SshScope.Context context;
|
@Inject private SshScope.Context context;
|
||||||
|
|
||||||
@Inject private DynamicMap<DynamicOptions.DynamicBean> dynamicBeans;
|
|
||||||
|
|
||||||
/** Commands declared by a plugin can be scoped by the plugin name. */
|
/** Commands declared by a plugin can be scoped by the plugin name. */
|
||||||
@Inject(optional = true)
|
@Inject(optional = true)
|
||||||
@PluginName
|
@PluginName
|
||||||
private String pluginName;
|
private String pluginName;
|
||||||
|
|
||||||
|
@Inject private Injector injector;
|
||||||
|
|
||||||
|
@Inject private DynamicMap<DynamicOptions.DynamicBean> dynamicBeans = null;
|
||||||
|
|
||||||
/** The task, as scheduled on a worker thread. */
|
/** The task, as scheduled on a worker thread. */
|
||||||
private final AtomicReference<Future<?>> task;
|
private final AtomicReference<Future<?>> task;
|
||||||
|
|
||||||
@@ -197,7 +200,7 @@ public abstract class BaseCommand implements Command {
|
|||||||
*/
|
*/
|
||||||
protected void parseCommandLine(Object options) throws UnloggedFailure {
|
protected void parseCommandLine(Object options) throws UnloggedFailure {
|
||||||
final CmdLineParser clp = newCmdLineParser(options);
|
final CmdLineParser clp = newCmdLineParser(options);
|
||||||
DynamicOptions pluginOptions = new DynamicOptions(options, dynamicBeans);
|
DynamicOptions pluginOptions = new DynamicOptions(options, injector, dynamicBeans);
|
||||||
pluginOptions.parseDynamicBeans(clp);
|
pluginOptions.parseDynamicBeans(clp);
|
||||||
pluginOptions.setDynamicBeans();
|
pluginOptions.setDynamicBeans();
|
||||||
pluginOptions.onBeanParseStart();
|
pluginOptions.onBeanParseStart();
|
||||||
|
|||||||
Reference in New Issue
Block a user