Add top level /config REST API

/config/server/ part is hard coded for now, but we may add more
config specific resource names later.

/config/server/capabilities retrieves the known server wide
capabilities list. The list has to be loaded dynamically from
the server to permit plugins to declare their own capabilities.

Change-Id: I83692bd021925d37659bfbc88b3e3b67b92c18c1
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
new file mode 100644
index 0000000..65e18a7
--- /dev/null
+++ b/Documentation/rest-api-config.txt
@@ -0,0 +1,123 @@
+Gerrit Code Review - /config/ REST API
+======================================
+
+This page describes the config related REST endpoints.
+Please also take note of the general information on the
+link:rest-api.html[REST API].
+
+[[config-endpoints]]
+Config Endpoints
+---------------
+
+[[list-capabilities]]
+List Capabilities
+~~~~~~~~~~~~~~~~~
+[verse]
+'GET /config/server/capabilities'
+
+Lists the capabilities that are available in system. There are two kinds of
+capabilities: core and plugin-owned capabilities.
+
+As result a map is returned that maps the capability names to
+capability descriptions. The entries in the map are sorted
+by capability name.
+
+.Request
+----
+  GET /config/server/capabilities/ HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "accessDatabase": {
+      "kind": "gerritcodereview#capability",
+      "id": "accessDatabase",
+      "name": "Access Database"
+    },
+    "administrateServer": {
+      "kind": "gerritcodereview#capability",
+      "id": "administrateServer",
+      "name": "Administrate Server"
+    },
+    "createAccount": {
+      "kind": "gerritcodereview#capability",
+      "id": "createAccount",
+      "name": "Create Account"
+    },
+    "createGroup": {
+      "kind": "gerritcodereview#capability",
+      "id": "createGroup",
+      "name": "Create Group"
+    },
+    "createProject": {
+      "kind": "gerritcodereview#capability",
+      "id": "createProject",
+      "name": "Create Project"
+    },
+    "emailReviewers": {
+      "kind": "gerritcodereview#capability",
+      "id": "emailReviewers",
+      "name": "Email Reviewers"
+    },
+    "flushCaches": {
+      "kind": "gerritcodereview#capability",
+      "id": "flushCaches",
+      "name": "Flush Caches"
+    },
+    "killTask": {
+      "kind": "gerritcodereview#capability",
+      "id": "killTask",
+      "name": "Kill Task"
+    },
+    "priority": {
+      "kind": "gerritcodereview#capability",
+      "id": "priority",
+      "name": "Priority"
+    },
+    "queryLimit": {
+      "kind": "gerritcodereview#capability",
+      "id": "queryLimit",
+      "name": "Query Limit"
+    },
+    "runGC": {
+      "kind": "gerritcodereview#capability",
+      "id": "runGC",
+      "name": "Run Garbage Collection"
+    },
+    "startReplication": {
+      "kind": "gerritcodereview#capability",
+      "id": "startReplication",
+     "name": "Start Replication"
+    },
+    "streamEvents": {
+      "kind": "gerritcodereview#capability",
+      "id": "streamEvents",
+      "name": "Stream Events"
+    },
+    "viewCaches": {
+      "kind": "gerritcodereview#capability",
+      "id": "viewCaches",
+      "name": "View Caches"
+    },
+    "viewConnections": {
+      "kind": "gerritcodereview#capability",
+      "id": "viewConnections",
+      "name": "View Connections"
+    },
+    "viewQueue": {
+      "kind": "gerritcodereview#capability",
+      "id": "viewQueue",
+      "name": "View Queue"
+    }
+  }
+----
+
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/rest-api.txt b/Documentation/rest-api.txt
index ccc8604..c0dde1c 100644
--- a/Documentation/rest-api.txt
+++ b/Documentation/rest-api.txt
@@ -13,6 +13,8 @@
   Account related REST endpoints
 link:rest-api-changes.html[/changes/]::
   Change related REST endpoints
+link:rest-api-config.html[/config/]::
+  Config related REST endpoints
 link:rest-api-groups.html[/groups/]::
   Group related REST endpoints
 link:rest-api-projects.html[/projects/]::
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java
index 904c5c7..ee6cc95 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.reviewdb.client.Project;
 
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 public class ProjectAccess {
@@ -28,6 +29,7 @@
   protected boolean isConfigVisible;
   protected boolean canUpload;
   protected LabelTypes labelTypes;
+  protected Map<String, String> capabilities;
 
   public ProjectAccess() {
   }
@@ -112,4 +114,12 @@
   public void setLabelTypes(LabelTypes labelTypes) {
     this.labelTypes = labelTypes;
   }
+
+  public Map<String, String> getCapabilities() {
+    return capabilities;
+  }
+
+  public void setCapabilities(Map<String, String> capabilities) {
+    this.capabilities = capabilities;
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
index 344104d..16be43c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
@@ -93,9 +93,8 @@
 
   public AccessSectionEditor(ProjectAccess access) {
     projectAccess = access;
-
-    permissionSelector =
-        new ValueListBox<String>(PermissionNameRenderer.INSTANCE);
+    permissionSelector = new ValueListBox<String>(
+        new PermissionNameRenderer(access.getCapabilities()));
     permissionSelector.addValueChangeHandler(new ValueChangeHandler<String>() {
       @Override
       public void onValueChange(ValueChangeEvent<String> event) {
@@ -222,7 +221,7 @@
     List<String> perms = new ArrayList<String>();
 
     if (AccessSection.GLOBAL_CAPABILITIES.equals(value.getName())) {
-      for (String varName : Util.C.capabilityNames().keySet()) {
+      for (String varName : projectAccess.getCapabilities().keySet()) {
         addPermission(varName, perms);
       }
     } else if (RefConfigSection.isValid(value.getName())) {
@@ -282,7 +281,7 @@
     @Override
     public PermissionEditor create(int index) {
       PermissionEditor subEditor =
-          new PermissionEditor(projectAccess.getProjectName(), readOnly, value,
+          new PermissionEditor(projectAccess, readOnly, value,
               projectAccess.getLabelTypes());
       permissionContainer.insert(subEditor, index);
       return subEditor;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
index f4c0b55..e505860 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
@@ -125,8 +125,6 @@
   String refErrorPrintable();
   String errorsMustBeFixed();
 
-  Map<String, String> capabilityNames();
-
   String sectionTypeReference();
   String sectionTypeSection();
   Map<String, String> sectionNames();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
index fe75b83..2f2345d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -144,43 +144,6 @@
 refErrorPrintable = References may contain only printable characters
 errorsMustBeFixed = Errors must be fixed before committing changes.
 
-# Capability Names
-capabilityNames = \
-  accessDatabase, \
-  administrateServer, \
-  createAccount, \
-  createGroup, \
-  createProject, \
-  emailReviewers, \
-  flushCaches, \
-  killTask, \
-  priority, \
-  queryLimit, \
-  runAs, \
-  runGC, \
-  startReplication, \
-  streamEvents, \
-  viewCaches, \
-  viewConnections, \
-  viewQueue
-accessDatabase = Access Database
-administrateServer = Administrate Server
-createAccount = Create Account
-createGroup = Create Group
-createProject = Create Project
-emailReviewers = Email Reviewers
-flushCaches = Flush Caches
-killTask = Kill Task
-priority = Priority
-queryLimit = Query Limit
-runAs = Run As
-runGC = Run Garbage Collection
-startReplication = Start Replication
-streamEvents = Stream Events
-viewCaches = View Caches
-viewConnections = View Connections
-viewQueue = View Queue
-
 # Section Names
 sectionTypeReference = Reference:
 sectionTypeSection = Section:
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java
index 9848c18..c2487cf 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java
@@ -24,6 +24,7 @@
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRange;
 import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.common.data.ProjectAccess;
 import com.google.gerrit.common.data.RefConfigSection;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.core.client.GWT;
@@ -107,17 +108,19 @@
   private PermissionRange.WithDefaults validRange;
   private boolean isDeleted;
 
-  public PermissionEditor(Project.NameKey projectName,
+  public PermissionEditor(ProjectAccess projectAccess,
       boolean readOnly,
       AccessSection section,
       LabelTypes labelTypes) {
     this.readOnly = readOnly;
     this.section = section;
-    this.projectName = projectName;
+    this.projectName = projectAccess.getProjectName();
     this.labelTypes = labelTypes;
 
-    normalName = new ValueLabel<String>(PermissionNameRenderer.INSTANCE);
-    deletedName = new ValueLabel<String>(PermissionNameRenderer.INSTANCE);
+    PermissionNameRenderer nameRenderer =
+        new PermissionNameRenderer(projectAccess.getCapabilities());
+    normalName = new ValueLabel<String>(nameRenderer);
+    deletedName = new ValueLabel<String>(nameRenderer);
 
     initWidget(uiBinder.createAndBindUi(this));
     groupToAdd.setProject(projectName);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java
index ad3473c..6ee44a2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java
@@ -22,33 +22,48 @@
 import java.util.Map;
 
 class PermissionNameRenderer implements Renderer<String> {
-  static final PermissionNameRenderer INSTANCE = new PermissionNameRenderer();
-
-  private static final Map<String, String> all;
+  private static final Map<String, String> permissions;
 
   static {
-    all = new HashMap<String, String>();
-    for (Map.Entry<String, String> e : Util.C.capabilityNames().entrySet()) {
-      all.put(e.getKey(), e.getValue());
-      all.put(e.getKey().toLowerCase(), e.getValue());
-    }
+    permissions = new HashMap<String, String>();
     for (Map.Entry<String, String> e : Util.C.permissionNames().entrySet()) {
-      all.put(e.getKey(), e.getValue());
-      all.put(e.getKey().toLowerCase(), e.getValue());
+      permissions.put(e.getKey(), e.getValue());
+      permissions.put(e.getKey().toLowerCase(), e.getValue());
     }
   }
 
+  private final Map<String, String> fromServer;
+
+  PermissionNameRenderer(Map<String, String> allFromOutside) {
+    fromServer = allFromOutside;
+  }
+
   @Override
   public String render(String varName) {
     if (Permission.isLabel(varName)) {
       return Util.M.label(new Permission(varName).getLabel());
     }
 
-    String desc = all.get(varName);
-    if (desc == null) {
-      desc = all.get(varName.toLowerCase());
+    String desc = permissions.get(varName);
+    if (desc != null) {
+      return desc;
     }
-    return desc != null ? desc : varName;
+
+    desc = fromServer.get(varName);
+    if (desc != null) {
+      return desc;
+    }
+
+    desc = permissions.get(varName.toLowerCase());
+    if (desc != null) {
+      return desc;
+    }
+
+    desc = fromServer.get(varName.toLowerCase());
+    if (desc != null) {
+      return desc;
+    }
+    return varName;
   }
 
   @Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
index 96824f3..28f5d85 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
@@ -18,7 +18,12 @@
 import static com.google.gerrit.common.ProjectAccessUtil.removeEmptyPermissionsAndSections;
 
 import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.config.CapabilityInfo;
+import com.google.gerrit.client.config.ConfigServerApi;
+import com.google.gerrit.client.rpc.CallbackGroup;
 import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.rpc.ScreenLoadCallback;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.common.data.AccessSection;
@@ -33,6 +38,7 @@
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.uibinder.client.UiHandler;
 import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.rpc.AsyncCallback;
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.HTMLPanel;
 import com.google.gwt.user.client.ui.Label;
@@ -41,8 +47,10 @@
 import com.google.gwtexpui.globalkey.client.NpTextArea;
 
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 public class ProjectAccessScreen extends ProjectScreen {
@@ -90,6 +98,8 @@
 
   private ProjectAccess access;
 
+  private NativeMap<CapabilityInfo> capabilityMap;
+
   public ProjectAccessScreen(final Project.NameKey toShow) {
     super(toShow);
   }
@@ -107,18 +117,36 @@
   @Override
   protected void onLoad() {
     super.onLoad();
+    CallbackGroup cbs = new CallbackGroup();
+    ConfigServerApi.capabilities(
+        cbs.add(new AsyncCallback<NativeMap<CapabilityInfo>>() {
+          @Override
+          public void onSuccess(NativeMap<CapabilityInfo> result) {
+            capabilityMap = result;
+          }
+
+          @Override
+          public void onFailure(Throwable caught) {
+            // Handled by ScreenLoadCallback.onFailure().
+          }
+        }));
     Util.PROJECT_SVC.projectAccess(getProjectKey(),
-        new ScreenLoadCallback<ProjectAccess>(this) {
+        cbs.addGwtjsonrpc(new ScreenLoadCallback<ProjectAccess>(this) {
           @Override
           public void preDisplay(ProjectAccess access) {
             displayReadOnly(access);
           }
-        });
+        }));
     savedPanel = ACCESS;
   }
 
   private void displayReadOnly(ProjectAccess access) {
     this.access = access;
+    Map<String, String> allCapabilities = new HashMap<String, String>();
+    for (CapabilityInfo c : Natives.asList(capabilityMap.values())) {
+      allCapabilities.put(c.id(), c.name());
+    }
+    this.access.setCapabilities(allCapabilities);
     accessEditor.setEditing(false);
     UIObject.setVisible(editTools, !access.getOwnerOf().isEmpty() || access.canUpload());
     edit.setEnabled(!access.getOwnerOf().isEmpty() || access.canUpload());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/CapabilityInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/CapabilityInfo.java
new file mode 100644
index 0000000..45abbd6
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/CapabilityInfo.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2013 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.client.config;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class CapabilityInfo extends JavaScriptObject {
+  public final native String id() /*-{ return this.id; }-*/;
+  public final native String name() /*-{ return this.name; }-*/;
+
+  protected CapabilityInfo() {
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/ConfigServerApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/ConfigServerApi.java
new file mode 100644
index 0000000..b283a0d
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/ConfigServerApi.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2013 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.client.config;
+
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.RestApi;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+/**
+ * A collection of static methods which work on the Gerrit REST API for server
+ * configuration.
+ */
+public class ConfigServerApi {
+  /** map of the server wide capabilities (core & plugins). */
+  public static void capabilities(AsyncCallback<NativeMap<CapabilityInfo>> cb) {
+    new RestApi("/config/server/capabilities/").get(cb);
+  }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
index bd25faf..47219ca 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
@@ -27,6 +27,7 @@
 import com.google.gerrit.httpd.rpc.account.AccountsRestApiServlet;
 import com.google.gerrit.httpd.rpc.change.ChangesRestApiServlet;
 import com.google.gerrit.httpd.rpc.change.DeprecatedChangeQueryServlet;
+import com.google.gerrit.httpd.rpc.config.ConfigRestApiServlet;
 import com.google.gerrit.httpd.rpc.group.GroupsRestApiServlet;
 import com.google.gerrit.httpd.rpc.project.ProjectsRestApiServlet;
 import com.google.gerrit.reviewdb.client.Change;
@@ -102,6 +103,7 @@
     serveRegex("^/(?:a/)?tools/(.*)$").with(ToolServlet.class);
     serveRegex("^/(?:a/)?accounts/(.*)$").with(AccountsRestApiServlet.class);
     serveRegex("^/(?:a/)?changes/(.*)$").with(ChangesRestApiServlet.class);
+    serveRegex("^/(?:a/)?config/(.*)$").with(ConfigRestApiServlet.class);
     serveRegex("^/(?:a/)?groups/(.*)?$").with(GroupsRestApiServlet.class);
     serveRegex("^/(?:a/)?projects/(.*)?$").with(ProjectsRestApiServlet.class);
 
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/config/ConfigRestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/config/ConfigRestApiServlet.java
new file mode 100644
index 0000000..f951ad3
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/config/ConfigRestApiServlet.java
@@ -0,0 +1,32 @@
+// Copyright (C) 2013 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.httpd.rpc.config;
+
+import com.google.gerrit.httpd.restapi.RestApiServlet;
+import com.google.gerrit.server.config.ConfigCollection;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+@Singleton
+public class ConfigRestApiServlet extends RestApiServlet {
+  private static final long serialVersionUID = 1L;
+
+  @Inject
+  ConfigRestApiServlet(RestApiServlet.Globals globals,
+      Provider<ConfigCollection> configCollection) {
+    super(globals, configCollection);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilitiesCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilitiesCollection.java
new file mode 100644
index 0000000..3a8bcc5
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilitiesCollection.java
@@ -0,0 +1,52 @@
+// Copyright (C) 2013 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.config;
+
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.ChildCollection;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class CapabilitiesCollection implements
+    ChildCollection<ConfigResource, CapabilityResource> {
+  private final DynamicMap<RestView<CapabilityResource>> views;
+  private final Provider<ListCapabilities> list;
+
+  @Inject
+  CapabilitiesCollection(DynamicMap<RestView<CapabilityResource>> views,
+      Provider<ListCapabilities> list) {
+    this.views = views;
+    this.list = list;
+  }
+
+  @Override
+  public RestView<ConfigResource> list() throws ResourceNotFoundException {
+    return list.get();
+  }
+
+  @Override
+  public CapabilityResource parse(ConfigResource parent, IdString id)
+      throws ResourceNotFoundException {
+    throw new ResourceNotFoundException(id);
+  }
+
+  @Override
+  public DynamicMap<RestView<CapabilityResource>> views() {
+    return views;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityConstants.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityConstants.java
new file mode 100644
index 0000000..c0a014c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityConstants.java
@@ -0,0 +1,42 @@
+// Copyright (C) 2013 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.config;
+
+import org.eclipse.jgit.nls.NLS;
+import org.eclipse.jgit.nls.TranslationBundle;
+
+public class CapabilityConstants extends TranslationBundle {
+  public static CapabilityConstants get() {
+    return NLS.getBundleFor(CapabilityConstants.class);
+  }
+
+  public String accessDatabase;
+  public String administrateServer;
+  public String createAccount;
+  public String createGroup;
+  public String createProject;
+  public String emailReviewers;
+  public String flushCaches;
+  public String killTask;
+  public String priority;
+  public String queryLimit;
+  public String runAs;
+  public String runGC;
+  public String startReplication;
+  public String streamEvents;
+  public String viewCaches;
+  public String viewConnections;
+  public String viewQueue;
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityResource.java
new file mode 100644
index 0000000..7e3c87e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityResource.java
@@ -0,0 +1,23 @@
+// Copyright (C) 2013 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.config;
+
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.inject.TypeLiteral;
+
+public class CapabilityResource extends ConfigResource {
+  public static final TypeLiteral<RestView<CapabilityResource>> CAPABILITY_KIND =
+      new TypeLiteral<RestView<CapabilityResource>>() {};
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigCollection.java
new file mode 100644
index 0000000..5ed007a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigCollection.java
@@ -0,0 +1,52 @@
+// Copyright (C) 2013 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.config;
+
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestCollection;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.inject.Inject;
+
+public class ConfigCollection implements
+    RestCollection<TopLevelResource, ConfigResource> {
+  private final DynamicMap<RestView<ConfigResource>> views;
+
+  @Inject
+  ConfigCollection(DynamicMap<RestView<ConfigResource>> views) {
+    this.views = views;
+  }
+
+  @Override
+  public RestView<TopLevelResource> list() throws ResourceNotFoundException {
+    throw new ResourceNotFoundException();
+  }
+
+  @Override
+  public DynamicMap<RestView<ConfigResource>> views() {
+    return views;
+  }
+
+  @Override
+  public ConfigResource parse(TopLevelResource root, IdString id)
+      throws ResourceNotFoundException {
+    if (id.equals("server")) {
+      return new ConfigResource();
+    }
+    throw new ResourceNotFoundException(id);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigResource.java
new file mode 100644
index 0000000..ec0e0c2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigResource.java
@@ -0,0 +1,24 @@
+// Copyright (C) 2013 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.config;
+
+import com.google.gerrit.extensions.restapi.RestResource;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.inject.TypeLiteral;
+
+public class ConfigResource implements RestResource {
+  public static final TypeLiteral<RestView<ConfigResource>> CONFIG_KIND =
+      new TypeLiteral<RestView<ConfigResource>>() {};
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 9370a89..160b33f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -240,6 +240,7 @@
     install(new AuditModule());
     install(new com.google.gerrit.server.account.Module());
     install(new com.google.gerrit.server.change.Module());
+    install(new com.google.gerrit.server.config.Module());
     install(new com.google.gerrit.server.group.Module());
     install(new com.google.gerrit.server.project.Module());
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java
new file mode 100644
index 0000000..d92dfa7
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java
@@ -0,0 +1,54 @@
+// Copyright (C) 2013 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.config;
+
+import com.google.common.collect.Maps;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+
+import java.util.Map;
+
+/** List capabilities visible to the calling user. */
+public class ListCapabilities implements RestReadView<ConfigResource> {
+  @Override
+  public Map<String, CapabilityInfo> apply(ConfigResource resource)
+      throws AuthException, BadRequestException, ResourceConflictException,
+      IllegalArgumentException, SecurityException, IllegalAccessException,
+      NoSuchFieldException {
+    Map<String, CapabilityInfo> output = Maps.newTreeMap();
+    Class<? extends CapabilityConstants> bundleClass =
+        CapabilityConstants.get().getClass();
+    CapabilityConstants c = CapabilityConstants.get();
+    for (String id : GlobalCapability.getAllNames()) {
+      String name = (String) bundleClass.getField(id).get(c);
+      output.put(id, new CapabilityInfo(id, name));
+    }
+    return output;
+  }
+
+  public static class CapabilityInfo {
+    final String kind = "gerritcodereview#capability";
+    public String id;
+    public String name;
+
+    public CapabilityInfo(String id, String name) {
+      this.id = id;
+      this.name = name;
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java
new file mode 100644
index 0000000..81c2de9
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2013 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.config;
+
+import static com.google.gerrit.server.config.ConfigResource.CONFIG_KIND;
+import static com.google.gerrit.server.config.CapabilityResource.CAPABILITY_KIND;
+
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.RestApiModule;
+
+public class Module extends RestApiModule {
+  @Override
+  protected void configure() {
+    DynamicMap.mapOf(binder(), CONFIG_KIND);
+    DynamicMap.mapOf(binder(), CAPABILITY_KIND);
+    child(CONFIG_KIND, "capabilities").to(CapabilitiesCollection.class);
+  }
+}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/config/CapabilityConstants.properties b/gerrit-server/src/main/resources/com/google/gerrit/server/config/CapabilityConstants.properties
new file mode 100644
index 0000000..d19fd22
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/config/CapabilityConstants.properties
@@ -0,0 +1,17 @@
+accessDatabase = Access Database
+administrateServer = Administrate Server
+createAccount = Create Account
+createGroup = Create Group
+createProject = Create Project
+emailReviewers = Email Reviewers
+flushCaches = Flush Caches
+killTask = Kill Task
+priority = Priority
+queryLimit = Query Limit
+runAs = Run As
+runGC = Run Garbage Collection
+startReplication = Start Replication
+streamEvents = Stream Events
+viewCaches = View Caches
+viewConnections = View Connections
+viewQueue = View Queue
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/config/ListCapabilitiesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/config/ListCapabilitiesTest.java
new file mode 100644
index 0000000..f97ca55
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/config/ListCapabilitiesTest.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2013 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.config;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.server.config.ListCapabilities.CapabilityInfo;
+
+import org.junit.Test;
+
+import java.util.Map;
+
+public class ListCapabilitiesTest {
+  @Test
+  public void testList() throws Exception {
+    Map<String, CapabilityInfo> m =
+        new ListCapabilities().apply(new ConfigResource());
+    for (String id : GlobalCapability.getAllNames()) {
+      assertTrue("contains " + id, m.containsKey(id));
+      assertEquals(id, m.get(id).id);
+      assertNotNull(id + " has name", m.get(id).name);
+    }
+  }
+}