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:
		 Martin Fick
					Martin Fick
				
			
				
					committed by
					
						 Zac Livingston
						Zac Livingston
					
				
			
			
				
	
			
			
			 Zac Livingston
						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