Merge "Add a BeanParseListener interface to DynamicOptions."
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/ParameterParser.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/ParameterParser.java
index 3e1913d..6dec9a4 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/ParameterParser.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/ParameterParser.java
@@ -63,7 +63,10 @@
       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 @@
       replyBinaryResult(req, res, BinaryResult.create(msg.toString()).setContentType("text/plain"));
       return false;
     }
+    pluginOptions.onBeanParseEnd();
 
     return true;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/DynamicOptions.java b/gerrit-server/src/main/java/com/google/gerrit/server/DynamicOptions.java
index 6584301..51ad282 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/DynamicOptions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/DynamicOptions.java
@@ -17,7 +17,11 @@
 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:
@@ -44,6 +48,16 @@
   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:
@@ -66,32 +80,67 @@
     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);
       }
     }
   }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
index b828a58..8f4cfad 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
@@ -197,7 +197,10 @@
    */
   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 @@
       msg.write(usage());
       throw new UnloggedFailure(1, msg.toString());
     }
+    pluginOptions.onBeanParseEnd();
   }
 
   protected String usage() {