| // 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; |
| |
| 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; |
| import java.util.Map.Entry; |
| |
| /** Helper class to define and parse options from plugins on ssh and RestAPI commands. */ |
| public class DynamicOptions { |
| /** |
| * To provide additional options, bind a DynamicBean. For example: |
| * |
| * <pre> |
| * bind(com.google.gerrit.server.DynamicOptions.DynamicBean.class) |
| * .annotatedWith(Exports.named(com.google.gerrit.sshd.commands.Query.class)) |
| * .to(MyOptions.class); |
| * </pre> |
| * |
| * To define the additional options, implement this interface. For example: |
| * |
| * <pre> |
| * public class MyOptions implements DynamicOptions.DynamicBean { |
| * {@literal @}Option(name = "--verbose", aliases = {"-v"} |
| * usage = "Make the operation more talkative") |
| * public boolean verbose; |
| * } |
| * </pre> |
| * |
| * The option will be prefixed by the plugin name. In the example above, if the plugin name was |
| * my-plugin, then the --verbose option as used by the caller would be --my-plugin--verbose. |
| */ |
| public interface DynamicBean {} |
| |
| /** |
| * Implement this if your DynamicBean needs an opportunity to act on the Bean directly before or |
| * after argument parsing. |
| */ |
| public interface BeanParseListener extends DynamicBean { |
| void onBeanParseStart(String plugin, Object bean); |
| |
| void onBeanParseEnd(String plugin, Object bean); |
| } |
| |
| /** |
| * The entity which provided additional options may need a way to receive a reference to the |
| * DynamicBean it provided. To do so, the existing class should implement BeanReceiver (a setter) |
| * and then provide some way for the plugin to request its DynamicBean (a getter.) For example: |
| * |
| * <pre> |
| * public class Query extends SshCommand implements DynamicOptions.BeanReceiver { |
| * public void setDynamicBean(String plugin, DynamicOptions.DynamicBean dynamicBean) { |
| * dynamicBeans.put(plugin, dynamicBean); |
| * } |
| * |
| * public DynamicOptions.DynamicBean getDynamicBean(String plugin) { |
| * return dynamicBeans.get(plugin); |
| * } |
| * ... |
| * } |
| * } |
| * </pre> |
| */ |
| public interface BeanReceiver { |
| void setDynamicBean(String plugin, DynamicBean dynamicBean); |
| } |
| |
| 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, injector, dynamicBeans); |
| * pluginOptions.parseDynamicBeans(clp); |
| * pluginOptions.setDynamicBeans(); |
| * pluginOptions.onBeanParseStart(); |
| * |
| * // parse arguments here: clp.parseArgument(argv); |
| * |
| * pluginOptions.onBeanParseEnd(); |
| * </pre> |
| */ |
| public DynamicOptions(Object bean, Injector injector, DynamicMap<DynamicBean> dynamicBeans) { |
| this.bean = bean; |
| this.injector = injector; |
| beansByPlugin = new HashMap<>(); |
| for (String plugin : dynamicBeans.plugins()) { |
| Provider<DynamicBean> provider = |
| dynamicBeans.byPlugin(plugin).get(bean.getClass().getCanonicalName()); |
| if (provider != null) { |
| beansByPlugin.put(plugin, getDynamicBean(bean, provider.get())); |
| } |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| 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()); |
| } |
| } |
| |
| public void setDynamicBeans() { |
| if (bean instanceof BeanReceiver) { |
| BeanReceiver receiver = (BeanReceiver) bean; |
| for (Entry<String, DynamicBean> e : beansByPlugin.entrySet()) { |
| receiver.setDynamicBean(e.getKey(), e.getValue()); |
| } |
| } |
| } |
| |
| public void onBeanParseStart() { |
| for (Entry<String, DynamicBean> e : beansByPlugin.entrySet()) { |
| DynamicBean instance = e.getValue(); |
| if (instance instanceof BeanParseListener) { |
| BeanParseListener listener = (BeanParseListener) instance; |
| listener.onBeanParseStart(e.getKey(), bean); |
| } |
| } |
| } |
| |
| public void onBeanParseEnd() { |
| for (Entry<String, DynamicBean> e : beansByPlugin.entrySet()) { |
| DynamicBean instance = e.getValue(); |
| if (instance instanceof BeanParseListener) { |
| BeanParseListener listener = (BeanParseListener) instance; |
| listener.onBeanParseEnd(e.getKey(), bean); |
| } |
| } |
| } |
| } |