Support inheritable project specific plugin parameters in the UI

The UI now also displays the inherited value.

Change-Id: I2ec034f919f31afc5687f9da6cbbe05f878d98c1
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 48ef56a..9bfbd4d 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -1298,9 +1298,21 @@
 The type of the configuration parameter, can be `STRING`, `INT`,
 `LONG`, `BOOLEAN` or `LIST`.
 |`value`           |optional|
-The value of the configuration parameter as string.
+The value of the configuration parameter as string. If the parameter
+is inheritable this is the effective value which is deduced from
+`configured_value` and `inherited_value`.
 |`permitted_values`|optional|
 The list of permitted values, only set if the `type` is `LIST`.
+|`inheritable`     |`false` if not set|
+Whether the configuration parameter can be inherited.
+|`configured_value`|optional|
+The value of the configuration parameter that is configured on this
+project, only set if `inheritable` is true.
+|`inherited_value` |optional|
+The inherited value of the configuration parameter, only set if
+`inheritable` is true.
+|`permitted_values` |optional|
+The list of permitted values, only set if the `type` is `LIST`.
 |===============================
 
 [[dashboard-info]]
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
index 7f2c5e4..98b9534 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
@@ -191,6 +191,7 @@
   String patchSetRevision();
   String patchSetUserIdentity();
   String patchSizeCell();
+  String pluginProjectConfigInheritedValue();
   String pluginsTable();
   String posscore();
   String projectActions();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
index 19ae1f3..05ffa9a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
@@ -29,4 +29,6 @@
   String effectiveMaxObjectSizeLimit(String effectiveMaxObjectSizeLimit);
   String globalMaxObjectSizeLimit(String globalMaxObjectSizeLimit);
   String pluginProjectOptionsTitle(String pluginName);
+  String pluginProjectInheritedValue(String value);
+  String pluginProjectInheritedListValue(String value);
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties
index 4c69cf8..6338920 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties
@@ -8,3 +8,6 @@
 effectiveMaxObjectSizeLimit = effective: {0}
 globalMaxObjectSizeLimit = The global max object size limit is set to {0}. The limit cannot be increased on project level.
 pluginProjectOptionsTitle = {0} Plugin Options
+pluginProjectOptionsTitle = {0} Plugin
+pluginProjectInheritedValue = inherited: {0}
+pluginProjectInheritedListValue = INHERIT ({0})
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
index 31f89fb..d836cb5 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
@@ -387,8 +387,21 @@
   private TextBox renderTextBox(LabeledWidgetsGrid g,
       ConfigParameterInfo param, boolean numbersOnly) {
     NpTextBox textBox = numbersOnly ? new NpIntTextBox() : new NpTextBox();
-    textBox.setValue(param.value());
-    g.add(getDisplayName(param), textBox);
+    if (param.inheritable()) {
+      textBox.setValue(param.configuredValue());
+      Label inheritedLabel =
+          new Label(Util.M.pluginProjectInheritedValue(param
+              .inheritedValue()));
+      inheritedLabel.setStyleName(Gerrit.RESOURCES.css()
+          .pluginProjectConfigInheritedValue());
+      HorizontalPanel p = new HorizontalPanel();
+      p.add(textBox);
+      p.add(inheritedLabel);
+      g.add(getDisplayName(param), p);
+    } else {
+      textBox.setValue(param.value());
+      g.add(getDisplayName(param), textBox);
+    }
     saveEnabler.listenTo(textBox);
     return textBox;
   }
@@ -405,13 +418,29 @@
   private ListBox renderListBox(LabeledWidgetsGrid g,
       ConfigParameterInfo param) {
     ListBox listBox = new ListBox();
-    for (int i = 0; i < param.permittedValues().length(); i++) {
-      String sv = param.permittedValues().get(i);
-      listBox.addItem(sv);
-      if (sv.equals(param.value())) {
-        listBox.setSelectedIndex(i);
+    if (param.inheritable()) {
+      listBox.addItem(
+          Util.M.pluginProjectInheritedListValue(param.inheritedValue()));
+      if (param.configuredValue() == null) {
+        listBox.setSelectedIndex(0);
+      }
+      for (int i = 0; i < param.permittedValues().length(); i++) {
+        String pv = param.permittedValues().get(i);
+        listBox.addItem(pv);
+        if (pv.equals(param.configuredValue())) {
+          listBox.setSelectedIndex(i + 1);
+        }
+      }
+    } else {
+      for (int i = 0; i < param.permittedValues().length(); i++) {
+        String pv = param.permittedValues().get(i);
+        listBox.addItem(pv);
+        if (pv.equals(param.value())) {
+          listBox.setSelectedIndex(i);
+        }
       }
     }
+
     g.add(getDisplayName(param), listBox);
     saveEnabler.listenTo(listBox);
     return listBox;
@@ -480,7 +509,10 @@
           values.put(e2.getKey(), Boolean.toString(((CheckBox) widget).getValue()));
         } else if (widget instanceof ListBox) {
           ListBox listBox = (ListBox) widget;
-          String value = listBox.getValue(listBox.getSelectedIndex());
+          // the inherited value is at index 0,
+          // if it is selected no value should be set on this project
+          String value = listBox.getSelectedIndex() > 0
+              ? listBox.getValue(listBox.getSelectedIndex()) : null;
           values.put(e2.getKey(), value);
         }
       }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
index c0e13e4..77daccf 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
@@ -1623,3 +1623,8 @@
   padding-top: 5px;
   padding-left: 5px;
 }
+
+.pluginProjectConfigInheritedValue {
+  padding-top: 5px;
+  padding-left: 5px;
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
index f1bc33b..381c189 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
@@ -151,6 +151,9 @@
     public final native String displayName() /*-{ return this.display_name; }-*/;
     public final native String type() /*-{ return this.type; }-*/;
     public final native String value() /*-{ return this.value; }-*/;
+    public final native boolean inheritable() /*-{ return this.inheritable ? true : false; }-*/;
+    public final native String configuredValue() /*-{ return this.configured_value; }-*/;
+    public final native String inheritedValue() /*-{ return this.inherited_value; }-*/;
     public final native JsArrayString permittedValues()  /*-{ return this.permitted_values; }-*/;
 
     protected ConfigParameterInfo() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java
index d093833..8076eaa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java
@@ -28,33 +28,61 @@
   }
 
   private final String displayName;
+  private final boolean inheritable;
   private final String defaultValue;
   private final Type type;
   private final List<String> permittedValues;
 
   public ProjectConfigEntry(String displayName, String defaultValue) {
-    this(displayName, defaultValue, Type.STRING, null);
+    this(displayName, defaultValue, false);
+  }
+
+  public ProjectConfigEntry(String displayName, String defaultValue,
+      boolean inheritable) {
+    this(displayName, defaultValue, Type.STRING, null, inheritable);
   }
 
   public ProjectConfigEntry(String displayName, int defaultValue) {
-    this(displayName, Integer.toString(defaultValue), Type.INT, null);
+    this(displayName, defaultValue, false);
+  }
+
+  public ProjectConfigEntry(String displayName, int defaultValue,
+      boolean inheritable) {
+    this(displayName, Integer.toString(defaultValue), Type.INT, null,
+        inheritable);
   }
 
   public ProjectConfigEntry(String displayName, long defaultValue) {
-    this(displayName, Long.toString(defaultValue), Type.LONG, null);
+    this(displayName, defaultValue, false);
   }
 
+  public ProjectConfigEntry(String displayName, long defaultValue,
+      boolean inheritable) {
+    this(displayName, Long.toString(defaultValue), Type.LONG, null, inheritable);
+  }
+
+  // For inheritable boolean use 'LIST' type with InheritableBoolean
   public ProjectConfigEntry(String displayName, boolean defaultValue) {
-    this(displayName, Boolean.toString(defaultValue), Type.BOOLEAN, null);
+    this(displayName, Boolean.toString(defaultValue), Type.BOOLEAN, null, false);
   }
 
   public ProjectConfigEntry(String displayName, String defaultValue,
       List<String> permittedValues) {
-    this(displayName, defaultValue, Type.LIST, permittedValues);
+    this(displayName, defaultValue, permittedValues, false);
+  }
+
+  public ProjectConfigEntry(String displayName, String defaultValue,
+      List<String> permittedValues, boolean inheritable) {
+    this(displayName, defaultValue, Type.LIST, permittedValues, inheritable);
   }
 
   public <T extends Enum<?>> ProjectConfigEntry(String displayName,
       T defaultValue, Class<T> permittedValues) {
+    this(displayName, defaultValue, permittedValues, false);
+  }
+
+  public <T extends Enum<?>> ProjectConfigEntry(String displayName,
+      T defaultValue, Class<T> permittedValues, boolean inheritable) {
     this(displayName, defaultValue.name(), Type.LIST, Lists.transform(
         Arrays.asList(permittedValues.getEnumConstants()),
         new Function<Enum<?>, String>() {
@@ -62,21 +90,26 @@
           public String apply(Enum<?> e) {
             return e.name();
           }
-        }));
+        }), inheritable);
   }
 
   private ProjectConfigEntry(String displayName, String defaultValue,
-      Type type, List<String> permittedValues) {
+      Type type, List<String> permittedValues, boolean inheritable) {
     this.displayName = displayName;
     this.defaultValue = defaultValue;
     this.type = type;
     this.permittedValues = permittedValues;
+    this.inheritable = inheritable;
   }
 
   public String getDisplayName() {
     return displayName;
   }
 
+  public boolean isInheritable() {
+    return inheritable;
+  }
+
   public String getDefaultValue() {
     return defaultValue;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java
index 891301b..18e713c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java
@@ -25,6 +25,7 @@
 import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
 import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gerrit.server.actions.ActionInfo;
+import com.google.gerrit.server.config.AllProjectsNameProvider;
 import com.google.gerrit.server.config.PluginConfig;
 import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.gerrit.server.config.ProjectConfigEntry;
@@ -57,6 +58,7 @@
       TransferConfig config,
       DynamicMap<ProjectConfigEntry> pluginConfigEntries,
       PluginConfigFactory cfgFactory,
+      AllProjectsNameProvider allProjects,
       DynamicMap<RestView<ProjectResource>> views) {
     ProjectState projectState = control.getProjectState();
     Project p = control.getProject();
@@ -113,7 +115,8 @@
     }
 
     pluginConfig =
-        getPluginConfig(control.getProjectState(), pluginConfigEntries, cfgFactory);
+        getPluginConfig(control.getProjectState(), pluginConfigEntries,
+            cfgFactory, allProjects);
 
     actions = Maps.newTreeMap();
     for (UiAction.Description d : UiActions.from(
@@ -126,17 +129,29 @@
 
   private Map<String, Map<String, ConfigParameterInfo>> getPluginConfig(
       ProjectState project, DynamicMap<ProjectConfigEntry> pluginConfigEntries,
-      PluginConfigFactory cfgFactory) {
+      PluginConfigFactory cfgFactory, AllProjectsNameProvider allProjects) {
     TreeMap<String, Map<String, ConfigParameterInfo>> pluginConfig = new TreeMap<>();
     for (Entry<ProjectConfigEntry> e : pluginConfigEntries) {
       PluginConfig cfg =
           cfgFactory.getFromProjectConfig(project, e.getPluginName());
       ProjectConfigEntry configEntry = e.getProvider().get();
+      String configuredValue = cfg.getString(e.getExportName());
       ConfigParameterInfo p = new ConfigParameterInfo();
       p.displayName = configEntry.getDisplayName();
       p.type = configEntry.getType();
-      p.value = cfg.getString(e.getExportName(), configEntry.getDefaultValue());
       p.permittedValues = configEntry.getPermittedValues();
+      if (configEntry.isInheritable()
+          && !allProjects.get().equals(project.getProject().getNameKey())) {
+        PluginConfig cfgWithInheritance =
+            cfgFactory.getFromProjectConfigWithInheritance(project,
+                e.getPluginName());
+        p.inheritable = true;
+        p.value = cfgWithInheritance.getString(e.getExportName(), configEntry.getDefaultValue());
+        p.configuredValue = configuredValue;
+        p.inheritedValue = getInheritedValue(project, cfgFactory, e);
+      } else {
+        p.value = configuredValue != null ? configuredValue : configEntry.getDefaultValue();
+      }
       Map<String, ConfigParameterInfo> pc = pluginConfig.get(e.getPluginName());
       if (pc == null) {
         pc = new TreeMap<>();
@@ -147,6 +162,22 @@
     return !pluginConfig.isEmpty() ? pluginConfig : null;
   }
 
+  private String getInheritedValue(ProjectState project,
+      PluginConfigFactory cfgFactory, Entry<ProjectConfigEntry> e) {
+    ProjectConfigEntry configEntry = e.getProvider().get();
+    ProjectState parent = Iterables.getFirst(project.parents(), null);
+    String inheritedValue = configEntry.getDefaultValue();
+    if (parent != null) {
+      PluginConfig parentCfgWithInheritance =
+          cfgFactory.getFromProjectConfigWithInheritance(parent,
+              e.getPluginName());
+      inheritedValue =
+          parentCfgWithInheritance.getString(e.getExportName(),
+              configEntry.getDefaultValue());
+    }
+    return inheritedValue;
+  }
+
   public static class InheritedBooleanInfo {
     public Boolean value;
     public InheritableBoolean configuredValue;
@@ -163,6 +194,9 @@
     public String displayName;
     public ProjectConfigEntry.Type type;
     public String value;
+    public Boolean inheritable;
+    public String configuredValue;
+    public String inheritedValue;
     public List<String> permittedValues;
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java
index 1ee1d00..88ac83f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.extensions.restapi.RestView;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.AllProjectsNameProvider;
 import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.gerrit.server.config.ProjectConfigEntry;
 import com.google.gerrit.server.git.TransferConfig;
@@ -29,16 +30,19 @@
   private final TransferConfig config;
   private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
   private final PluginConfigFactory cfgFactory;
+  private final AllProjectsNameProvider allProjects;
   private final DynamicMap<RestView<ProjectResource>> views;
 
   @Inject
   public GetConfig(TransferConfig config,
       DynamicMap<ProjectConfigEntry> pluginConfigEntries,
       PluginConfigFactory cfgFactory,
+      AllProjectsNameProvider allProjects,
       DynamicMap<RestView<ProjectResource>> views,
       Provider<CurrentUser> currentUser) {
     this.config = config;
     this.pluginConfigEntries = pluginConfigEntries;
+    this.allProjects = allProjects;
     this.cfgFactory = cfgFactory;
     this.views = views;
   }
@@ -46,6 +50,6 @@
   @Override
   public ConfigInfo apply(ProjectResource resource) {
     return new ConfigInfo(resource.getControl(), config,
-        pluginConfigEntries, cfgFactory, views);
+        pluginConfigEntries, cfgFactory, allProjects, views);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
index 0cfdbd2..e50c751 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
@@ -26,6 +26,7 @@
 import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
 import com.google.gerrit.reviewdb.client.Project.SubmitType;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.AllProjectsNameProvider;
 import com.google.gerrit.server.config.PluginConfig;
 import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.gerrit.server.config.ProjectConfigEntry;
@@ -67,6 +68,7 @@
   private final TransferConfig config;
   private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
   private final PluginConfigFactory cfgFactory;
+  private final AllProjectsNameProvider allProjects;
   private final DynamicMap<RestView<ProjectResource>> views;
   private final Provider<CurrentUser> currentUser;
 
@@ -78,6 +80,7 @@
       TransferConfig config,
       DynamicMap<ProjectConfigEntry> pluginConfigEntries,
       PluginConfigFactory cfgFactory,
+      AllProjectsNameProvider allProjects,
       DynamicMap<RestView<ProjectResource>> views,
       Provider<CurrentUser> currentUser) {
     this.metaDataUpdateFactory = metaDataUpdateFactory;
@@ -87,6 +90,7 @@
     this.config = config;
     this.pluginConfigEntries = pluginConfigEntries;
     this.cfgFactory = cfgFactory;
+    this.allProjects = allProjects;
     this.views = views;
     this.currentUser = currentUser;
   }
@@ -162,9 +166,8 @@
       }
 
       ProjectState state = projectStateFactory.create(projectConfig);
-      return new ConfigInfo(
-          state.controlFor(currentUser.get()),
-          config, pluginConfigEntries, cfgFactory, views);
+      return new ConfigInfo(state.controlFor(currentUser.get()), config,
+          pluginConfigEntries, cfgFactory, allProjects, views);
     } catch (ConfigInvalidException err) {
       throw new ResourceConflictException("Cannot read project " + projectName, err);
     } catch (IOException err) {