Add a BeanParseListener interface to DynamicOptions.

This interface extends the DynamicBean interface allowing beans to
access the Bean immediately before or after option parsing is done on the
DynamicBeans.  The plumbing is added to both the SSH and RestAPI layers
to actually call this bean during parsing.

Change-Id: Id39edfedc9612c42b8c35470e52cacc05d86bf43
This commit is contained in:
Martin Fick
2016-09-13 10:08:14 -06:00
committed by Zac Livingston
parent 4f083a841d
commit 156c9c9fac
3 changed files with 74 additions and 17 deletions

View File

@@ -63,7 +63,10 @@ class ParameterParser {
T param, ListMultimap<String, String> in, HttpServletRequest req, HttpServletResponse res)
throws IOException {
CmdLineParser clp = parserFactory.create(param);
DynamicOptions.parse(dynamicBeans, clp, param);
DynamicOptions pluginOptions = new DynamicOptions(param, dynamicBeans);
pluginOptions.parseDynamicBeans(clp);
pluginOptions.setDynamicBeans();
pluginOptions.onBeanParseStart();
try {
clp.parseOptionMap(in);
} catch (CmdLineException | NumberFormatException e) {
@@ -84,6 +87,7 @@ class ParameterParser {
replyBinaryResult(req, res, BinaryResult.create(msg.toString()).setContentType("text/plain"));
return false;
}
pluginOptions.onBeanParseEnd();
return true;
}

View File

@@ -17,7 +17,11 @@ package com.google.gerrit.server;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.util.cli.CmdLineParser;
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:
@@ -43,6 +47,16 @@ public class DynamicOptions {
*/
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)
@@ -66,32 +80,67 @@ public class DynamicOptions {
void setDynamicBean(String plugin, DynamicBean dynamicBean);
}
Object bean;
Map<String, DynamicBean> beansByPlugin;
/**
* To include options from DynamicBeans, setup a DynamicMap and call this parse method. For
* example:
* 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>
* DynamicMap.mapOf(binder(), DynamicOptions.DynamicBean.class);
* DynamicOptions pluginOptions = new DynamicOptions(bean, dynamicBeans);
* pluginOptions.parseDynamicBeans(clp);
* pluginOptions.setDynamicBeans();
* pluginOptions.onBeanParseStart();
*
* ...
* // parse arguments here: clp.parseArgument(argv);
*
* protected void parseCommandLine(Object options) throws UnloggedFailure {
* final CmdLineParser clp = newCmdLineParser(options);
* DynamicOptions.parse(dynamicBeans, clp, options);
* ...
* }
* pluginOptions.onBeanParseEnd();
* </pre>
*/
public static void parse(DynamicMap<DynamicBean> dynamicBeans, CmdLineParser clp, Object bean) {
public DynamicOptions(Object bean, DynamicMap<DynamicBean> dynamicBeans) {
this.bean = bean;
beansByPlugin = new HashMap<String, DynamicBean>();
for (String plugin : dynamicBeans.plugins()) {
Provider<DynamicBean> provider =
dynamicBeans.byPlugin(plugin).get(bean.getClass().getCanonicalName());
if (provider != null) {
DynamicBean dynamicBean = provider.get();
clp.parseWithPrefix(plugin, dynamicBean);
if (bean instanceof BeanReceiver) {
((BeanReceiver) bean).setDynamicBean(plugin, dynamicBean);
}
beansByPlugin.put(plugin, provider.get());
}
}
}
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);
}
}
}

View File

@@ -197,7 +197,10 @@ public abstract class BaseCommand implements Command {
*/
protected void parseCommandLine(Object options) throws UnloggedFailure {
final CmdLineParser clp = newCmdLineParser(options);
DynamicOptions.parse(dynamicBeans, clp, options);
DynamicOptions pluginOptions = new DynamicOptions(options, dynamicBeans);
pluginOptions.parseDynamicBeans(clp);
pluginOptions.setDynamicBeans();
pluginOptions.onBeanParseStart();
try {
clp.parseArgument(argv);
} catch (IllegalArgumentException | CmdLineException err) {
@@ -212,6 +215,7 @@ public abstract class BaseCommand implements Command {
msg.write(usage());
throw new UnloggedFailure(1, msg.toString());
}
pluginOptions.onBeanParseEnd();
}
protected String usage() {