Move batch user priority to a capability

Instead of using a magical group, use a special capability to
denote users that should get the batch priority behavior.

Change-Id: I3e1f8f3ee39f5dcb2cdad2f9c71c46db25fc30b6
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
index 1fd246aa..2e46791 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
@@ -45,6 +45,9 @@
   /** Can terminate any task using the kill command. */
   public static final String KILL_TASK = "killTask";
 
+  /** Queue a user can access to submit their tasks to. */
+  public static final String PRIORITY = "priority";
+
   /** Maximum result limit per executed query. */
   public static final String QUERY_LIMIT = "queryLimit";
 
@@ -70,6 +73,7 @@
     NAMES_LC.add(CREATE_PROJECT.toLowerCase());
     NAMES_LC.add(FLUSH_CACHES.toLowerCase());
     NAMES_LC.add(KILL_TASK.toLowerCase());
+    NAMES_LC.add(PRIORITY.toLowerCase());
     NAMES_LC.add(QUERY_LIMIT.toLowerCase());
     NAMES_LC.add(START_REPLICATION.toLowerCase());
     NAMES_LC.add(VIEW_CACHES.toLowerCase());
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRule.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRule.java
index 61f5d62..4d5e28f 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRule.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRule.java
@@ -16,10 +16,12 @@
 
 public class PermissionRule implements Comparable<PermissionRule> {
   public static enum Action {
-    ALLOW, DENY;
+    ALLOW, DENY,
+
+    INTERACTIVE, BATCH;
   }
 
-  protected boolean deny;
+  protected Action action = Action.ALLOW;
   protected boolean force;
   protected int min;
   protected int max;
@@ -33,22 +35,22 @@
   }
 
   public Action getAction() {
-    return deny ? Action.DENY : Action.ALLOW;
+    return action;
   }
 
   public void setAction(Action action) {
     if (action == null) {
       throw new NullPointerException("action");
     }
-    setDeny(action == Action.DENY);
+    this.action = action;
   }
 
   public boolean getDeny() {
-    return deny;
+    return action == Action.DENY;
   }
 
   public void setDeny(boolean newDeny) {
-    deny = newDeny;
+    action = Action.DENY;
   }
 
   public Boolean getForce() {
@@ -94,21 +96,35 @@
   }
 
   void mergeFrom(PermissionRule src) {
-    setDeny(getDeny() || src.getDeny());
+    if (getAction() != src.getAction()) {
+      if (getAction() == Action.DENY || src.getAction() == Action.DENY) {
+        setAction(Action.DENY);
+
+      } else if (getAction() == Action.BATCH || src.getAction() == Action.BATCH) {
+        setAction(Action.BATCH);
+
+      }
+    }
+
     setForce(getForce() || src.getForce());
     setRange(Math.min(getMin(), src.getMin()), Math.max(getMax(), src.getMax()));
   }
 
   @Override
   public int compareTo(PermissionRule o) {
-    int cmp = deny(this) - deny(o);
+    int cmp = action(this) - action(o);
     if (cmp == 0) cmp = range(o) - range(this);
     if (cmp == 0) cmp = group(this).compareTo(group(o));
     return cmp;
   }
 
-  private static int deny(PermissionRule a) {
-    return a.getDeny() ? 1 : 0;
+  private static int action(PermissionRule a) {
+    switch (a.getAction()) {
+      case DENY:
+        return 0;
+      default:
+        return 1 + a.getAction().ordinal();
+    }
   }
 
   private static int range(PermissionRule a) {
@@ -127,8 +143,21 @@
   public String asString(boolean canUseRange) {
     StringBuilder r = new StringBuilder();
 
-    if (getDeny()) {
-      r.append("deny ");
+    switch (getAction()) {
+      case ALLOW:
+        break;
+
+      case DENY:
+        r.append("deny ");
+        break;
+
+      case INTERACTIVE:
+        r.append("interactive ");
+        break;
+
+      case BATCH:
+        r.append("batch ");
+        break;
     }
 
     if (getForce()) {
@@ -157,8 +186,16 @@
     src = src.trim();
 
     if (src.startsWith("deny ")) {
-      rule.setDeny(true);
-      src = src.substring(5).trim();
+      rule.setAction(Action.DENY);
+      src = src.substring("deny ".length()).trim();
+
+    } else if (src.startsWith("interactive ")) {
+      rule.setAction(Action.INTERACTIVE);
+      src = src.substring("interactive ".length()).trim();
+
+    } else if (src.startsWith("batch ")) {
+      rule.setAction(Action.BATCH);
+      src = src.substring("batch ".length()).trim();
     }
 
     if (src.startsWith("+force ")) {
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 0e58123..5e333d8 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
@@ -125,6 +125,7 @@
   createProject, \
   flushCaches, \
   killTask, \
+  priority, \
   queryLimit, \
   startReplication, \
   viewCaches, \
@@ -136,6 +137,7 @@
 createProject = Create Project
 flushCaches = Flush Caches
 killTask = Kill Task
+priority = Priority
 queryLimit = Query Limit
 startReplication = Start Replication
 viewCaches = View Caches
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 3cbece9..a11cd5c 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
@@ -207,7 +207,11 @@
           int min = validRange.getDefaultMin();
           int max = validRange.getDefaultMax();
           newRule.setRange(min, max);
+
+        } else if (GlobalCapability.PRIORITY.equals(value.getName())) {
+          newRule.setAction(PermissionRule.Action.BATCH);
         }
+
         rules.getList().add(newRule);
       }
       groupToAdd.setValue(null);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.java
index ff6ed6a..98e6283 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionRuleEditor.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.client.Dispatcher;
 import com.google.gerrit.client.ui.Hyperlink;
 import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRange;
@@ -112,8 +113,19 @@
     } else {
       min = new RangeBox.Box();
       max = new RangeBox.Box();
-      action.setValue(PermissionRule.Action.ALLOW);
-      action.setAcceptableValues(Arrays.asList(PermissionRule.Action.values()));
+
+      if (GlobalCapability.PRIORITY.equals(permission.getName())) {
+        action.setValue(PermissionRule.Action.INTERACTIVE);
+        action.setAcceptableValues(Arrays.asList(
+            PermissionRule.Action.INTERACTIVE,
+            PermissionRule.Action.BATCH));
+
+      } else {
+        action.setValue(PermissionRule.Action.ALLOW);
+        action.setAcceptableValues(Arrays.asList(
+            PermissionRule.Action.ALLOW,
+            PermissionRule.Action.DENY));
+      }
     }
 
     initWidget(uiBinder.createAndBindUi(this));
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
index bb04071..bc05ec6 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
@@ -27,10 +27,9 @@
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.account.GroupControl;
-import com.google.gerrit.server.config.AuthConfig;
-import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.account.GroupControl;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectControl;
@@ -53,7 +52,6 @@
     SuggestService {
   private static final String MAX_SUFFIX = "\u9fa5";
 
-  private final AuthConfig authConfig;
   private final ProjectControl.Factory projectControlFactory;
   private final ProjectCache projectCache;
   private final AccountCache accountCache;
@@ -65,7 +63,6 @@
 
   @Inject
   SuggestServiceImpl(final Provider<ReviewDb> schema,
-      final AuthConfig authConfig,
       final ProjectControl.Factory projectControlFactory,
       final ProjectCache projectCache, final AccountCache accountCache,
       final GroupControl.Factory groupControlFactory,
@@ -74,7 +71,6 @@
       @GerritServerConfig final Config cfg,
       final GroupCache groupCache) {
     super(schema, currentUser);
-    this.authConfig = authConfig;
     this.projectControlFactory = projectControlFactory;
     this.projectCache = projectCache;
     this.accountCache = accountCache;
@@ -165,7 +161,6 @@
         Set<AccountGroup.UUID> usersGroups = groupsOf(account);
         usersGroups.remove(AccountGroup.ANONYMOUS_USERS);
         usersGroups.remove(AccountGroup.REGISTERED_USERS);
-        usersGroups.remove(authConfig.getBatchUsersGroup());
         for (AccountGroup.UUID myGroup : currentUser.get().getEffectiveGroups()) {
           if (usersGroups.contains(myGroup)) {
             map.put(account.getId(), info);
@@ -178,7 +173,6 @@
         Set<AccountGroup.UUID> usersGroups = groupsOf(account);
         usersGroups.remove(AccountGroup.ANONYMOUS_USERS);
         usersGroups.remove(AccountGroup.REGISTERED_USERS);
-        usersGroups.remove(authConfig.getBatchUsersGroup());
         for (AccountGroup.UUID usersGroup : usersGroups) {
           try {
             if (groupControlFactory.controlFor(usersGroup).isVisible()) {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
index 6870ca4..ee7c794 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
@@ -22,10 +22,10 @@
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.git.QueueProvider;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.gerrit.server.git.WorkQueue.CancelableRunnable;
 import com.google.gerrit.sshd.CommandExecutorQueueProvider;
-import com.google.gerrit.sshd.QueueProvider;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
@@ -142,13 +142,7 @@
   }
 
   private WorkQueue.Executor getExecutor() {
-    WorkQueue.Executor executor;
-    if (userProvider.get().isBatchUser()) {
-      executor = queue.getBatchQueue();
-    } else {
-      executor = queue.getInteractiveQueue();
-    }
-    return executor;
+    return queue.getQueue(userProvider.get().getCapabilities().getQueueType());
   }
 
   @Override
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/SystemConfig.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/SystemConfig.java
index 4b00ca2..c5524ae 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/SystemConfig.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/SystemConfig.java
@@ -62,11 +62,6 @@
   @Column(id = 3, notNull = false)
   public transient String sitePath;
 
-  @Column(id = 8)
-  public AccountGroup.Id batchUsersGroupId;
-  @Column(id = 11)
-  public AccountGroup.UUID batchUsersGroupUUID;
-
 
   // DO NOT LOOK BELOW THIS LINE. These fields have all been deleted,
   // but survive to support schema upgrade code.
@@ -89,6 +84,12 @@
   /** DEPRECATED DO NOT USE */
   @Column(id = 9, notNull = false)
   public AccountGroup.Id ownerGroupId;
+  /** DEPRECATED DO NOT USE */
+  @Column(id = 8, notNull = false)
+  public AccountGroup.Id batchUsersGroupId;
+  /** DEPRECATED DO NOT USE */
+  @Column(id = 11, notNull = false)
+  public AccountGroup.UUID batchUsersGroupUUID;
 
   protected SystemConfig() {
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java
index abe7f20..c1263ef 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/AnonymousUser.java
@@ -18,7 +18,6 @@
 import com.google.gerrit.reviewdb.AccountProjectWatch;
 import com.google.gerrit.reviewdb.Change;
 import com.google.gerrit.server.account.CapabilityControl;
-import com.google.gerrit.server.config.AuthConfig;
 import com.google.inject.Inject;
 
 import java.util.Collection;
@@ -28,8 +27,8 @@
 /** An anonymous user who has not yet authenticated. */
 public class AnonymousUser extends CurrentUser {
   @Inject
-  AnonymousUser(CapabilityControl.Factory capabilityControlFactory, AuthConfig auth) {
-    super(capabilityControlFactory, AccessPath.UNKNOWN, auth);
+  AnonymousUser(CapabilityControl.Factory capabilityControlFactory) {
+    super(capabilityControlFactory, AccessPath.UNKNOWN);
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
index b1b3230..5a68212 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
@@ -18,7 +18,6 @@
 import com.google.gerrit.reviewdb.AccountProjectWatch;
 import com.google.gerrit.reviewdb.Change;
 import com.google.gerrit.server.account.CapabilityControl;
-import com.google.gerrit.server.config.AuthConfig;
 import com.google.inject.servlet.RequestScoped;
 
 import java.util.Collection;
@@ -35,17 +34,14 @@
 public abstract class CurrentUser {
   private final CapabilityControl.Factory capabilityControlFactory;
   private final AccessPath accessPath;
-  protected final AuthConfig authConfig;
 
   private CapabilityControl capabilities;
 
   protected CurrentUser(
       CapabilityControl.Factory capabilityControlFactory,
-      AccessPath accessPath,
-      AuthConfig authConfig) {
+      AccessPath accessPath) {
     this.capabilityControlFactory = capabilityControlFactory;
     this.accessPath = accessPath;
-    this.authConfig = authConfig;
   }
 
   /** How this user is accessing the Gerrit Code Review application. */
@@ -72,11 +68,6 @@
   /** Filters selecting changes the user wants to monitor. */
   public abstract Collection<AccountProjectWatch> getNotificationFilters();
 
-  /** Is the user a non-interactive user? */
-  public boolean isBatchUser() {
-    return getEffectiveGroups().contains(authConfig.getBatchUsersGroup());
-  }
-
   /** Capabilities available to this user account. */
   public CapabilityControl getCapabilities() {
     CapabilityControl ctl = capabilities;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
index 243fe5fc..7c0e51e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
@@ -180,6 +180,7 @@
   private final Realm realm;
   private final AccountCache accountCache;
   private final GroupIncludeCache groupIncludeCache;
+  private final AuthConfig authConfig;
 
   @Nullable
   private final Provider<SocketAddress> remotePeerProvider;
@@ -203,11 +204,12 @@
       final GroupIncludeCache groupIncludeCache,
       @Nullable final Provider<SocketAddress> remotePeerProvider,
       @Nullable final Provider<ReviewDb> dbProvider, final Account.Id id) {
-    super(capabilityControlFactory, accessPath, authConfig);
+    super(capabilityControlFactory, accessPath);
     this.canonicalUrl = canonicalUrl;
     this.realm = realm;
     this.accountCache = accountCache;
     this.groupIncludeCache = groupIncludeCache;
+    this.authConfig = authConfig;
     this.remotePeerProvider = remotePeerProvider;
     this.dbProvider = dbProvider;
     this.accountId = id;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/PeerDaemonUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/PeerDaemonUser.java
index 30eaf6f..a58f7b0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/PeerDaemonUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/PeerDaemonUser.java
@@ -18,7 +18,6 @@
 import com.google.gerrit.reviewdb.AccountProjectWatch;
 import com.google.gerrit.reviewdb.Change;
 import com.google.gerrit.server.account.CapabilityControl;
-import com.google.gerrit.server.config.AuthConfig;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
@@ -40,8 +39,8 @@
 
   @Inject
   protected PeerDaemonUser(CapabilityControl.Factory capabilityControlFactory,
-      AuthConfig authConfig, @Assisted SocketAddress peer) {
-    super(capabilityControlFactory, AccessPath.SSH_COMMAND, authConfig);
+      @Assisted SocketAddress peer) {
+    super(capabilityControlFactory, AccessPath.SSH_COMMAND);
     this.peer = peer;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ReplicationUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/ReplicationUser.java
index cdd0d62..ae7f5ac 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ReplicationUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ReplicationUser.java
@@ -18,7 +18,6 @@
 import com.google.gerrit.reviewdb.AccountProjectWatch;
 import com.google.gerrit.reviewdb.Change;
 import com.google.gerrit.server.account.CapabilityControl;
-import com.google.gerrit.server.config.AuthConfig;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
@@ -40,8 +39,8 @@
 
   @Inject
   protected ReplicationUser(CapabilityControl.Factory capabilityControlFactory,
-      AuthConfig authConfig, @Assisted Set<AccountGroup.UUID> authGroups) {
-    super(capabilityControlFactory, AccessPath.REPLICATION, authConfig);
+      @Assisted Set<AccountGroup.UUID> authGroups) {
+    super(capabilityControlFactory, AccessPath.REPLICATION);
 
     if (authGroups == EVERYTHING_VISIBLE) {
       effectiveGroups = EVERYTHING_VISIBLE;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
index c150bbe..39719ad 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.reviewdb.AccountGroup;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.PeerDaemonUser;
+import com.google.gerrit.server.git.QueueProvider;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.inject.Inject;
@@ -121,6 +122,44 @@
         || canAdministrateServer();
   }
 
+  /** @return which priority queue the user's tasks should be submitted to. */
+  public QueueProvider.QueueType getQueueType() {
+    // If a non-generic group (that is not Anonymous Users or Registered Users)
+    // grants us INTERACTIVE permission, use the INTERACTIVE queue even if
+    // BATCH was otherwise granted. This allows site administrators to grant
+    // INTERACTIVE to Registered Users, and BATCH to 'CI Servers' and have
+    // the 'CI Servers' actually use the BATCH queue while everyone else gets
+    // to use the INTERACTIVE queue without additional grants.
+    //
+    List<PermissionRule> rules = access(GlobalCapability.PRIORITY);
+    boolean batch = false;
+    for (PermissionRule r : rules) {
+      switch (r.getAction()) {
+        case INTERACTIVE:
+          if (!isGenericGroup(r.getGroup())) {
+            return QueueProvider.QueueType.INTERACTIVE;
+          }
+          break;
+
+        case BATCH:
+          batch = true;
+          break;
+      }
+    }
+
+    if (batch) {
+      // If any of our groups matched to the BATCH queue, use it.
+      return QueueProvider.QueueType.BATCH;
+    } else {
+      return QueueProvider.QueueType.INTERACTIVE;
+    }
+  }
+
+  private static boolean isGenericGroup(GroupReference group) {
+    return AccountGroup.ANONYMOUS_USERS.equals(group.getUUID())
+        || AccountGroup.REGISTERED_USERS.equals(group.getUUID());
+  }
+
   /** True if the user has this permission. Works only for non labels. */
   public boolean canPerform(String permissionName) {
     return !access(permissionName).isEmpty();
@@ -172,7 +211,7 @@
     for (Permission permission : section.getPermissions()) {
       for (PermissionRule rule : permission.getRules()) {
         if (matchGroup(rule.getGroup().getUUID())) {
-          if (!rule.getDeny()) {
+          if (rule.getAction() != PermissionRule.Action.DENY) {
             List<PermissionRule> r = res.get(permission.getName());
             if (r == null) {
               r = new ArrayList<PermissionRule>(2);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
index 2452db4..50fc205 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
@@ -16,7 +16,6 @@
 
 import com.google.gerrit.common.auth.openid.OpenIdProviderPattern;
 import com.google.gerrit.reviewdb.AccountExternalId;
-import com.google.gerrit.reviewdb.AccountGroup;
 import com.google.gerrit.reviewdb.AuthType;
 import com.google.gerrit.reviewdb.SystemConfig;
 import com.google.gwtjsonrpc.server.SignedToken;
@@ -43,8 +42,6 @@
   private final boolean cookieSecure;
   private final SignedToken emailReg;
 
-  private final AccountGroup.UUID batchUsersGroup;
-
   private final boolean allowGoogleAccountUpgrade;
 
   @Inject
@@ -59,8 +56,6 @@
     cookieSecure = cfg.getBoolean("auth", "cookiesecure", false);
     emailReg = new SignedToken(5 * 24 * 60 * 60, s.registerEmailPrivateKey);
 
-    batchUsersGroup = s.batchUsersGroupUUID;
-
     if (authType == AuthType.OPENID) {
       allowGoogleAccountUpgrade =
           cfg.getBoolean("auth", "allowgoogleaccountupgrade", false);
@@ -115,11 +110,6 @@
     return allowGoogleAccountUpgrade;
   }
 
-  /** Identity of the group whose service is degraded to lower priority. */
-  public AccountGroup.UUID getBatchUsersGroup() {
-    return batchUsersGroup;
-  }
-
   /** OpenID identities which the server permits for authentication. */
   public List<OpenIdProviderPattern> getAllowedOpenIDs() {
     return allowedOpenIDs;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/QueueProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/QueueProvider.java
new file mode 100644
index 0000000..b539416
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/QueueProvider.java
@@ -0,0 +1,23 @@
+// Copyright (C) 2011 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.git;
+
+public interface QueueProvider {
+  public static enum QueueType {
+    INTERACTIVE, BATCH;
+  }
+
+  public WorkQueue.Executor getQueue(QueueType type);
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SingleGroupUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SingleGroupUser.java
index 67f23e4..ad28b41 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SingleGroupUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SingleGroupUser.java
@@ -35,7 +35,7 @@
 
   SingleGroupUser(CapabilityControl.Factory capabilityControlFactory,
       Set<AccountGroup.UUID> groups) {
-    super(capabilityControlFactory, AccessPath.UNKNOWN, null);
+    super(capabilityControlFactory, AccessPath.UNKNOWN);
     this.groups = groups;
   }
 
@@ -53,9 +53,4 @@
   public Collection<AccountProjectWatch> getNotificationFilters() {
     return Collections.emptySet();
   }
-
-  @Override
-  public boolean isBatchUser() {
-    return false;
-  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
index 80f2bf8..4fa79ef 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
@@ -194,9 +194,6 @@
     final SystemConfig s = SystemConfig.create();
     s.registerEmailPrivateKey = SignedToken.generateRandomKey();
 
-    s.batchUsersGroupId = batchUsers.getId();
-    s.batchUsersGroupUUID = batchUsers.getGroupUUID();
-
     try {
       s.sitePath = site_path.getCanonicalPath();
     } catch (IOException e) {
@@ -269,13 +266,6 @@
     return new PermissionRule(config.resolve(group));
   }
 
-  private PermissionRule rule(ProjectConfig config, AccountGroup group,
-      int min, int max) {
-    PermissionRule rule = new PermissionRule(config.resolve(group));
-    rule.setRange(min, max);
-    return rule;
-  }
-
   private void initVerifiedCategory(final ReviewDb c) throws OrmException {
     final ApprovalCategory cat;
     final ArrayList<ApprovalCategoryValue> vals;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_57.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_57.java
index 2ca24b9..f7ed79e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_57.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_57.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.common.data.PermissionRule.Action;
 import com.google.gerrit.reviewdb.AccountGroup;
 import com.google.gerrit.reviewdb.AccountGroupName;
 import com.google.gerrit.reviewdb.Project;
@@ -115,20 +116,21 @@
         }
 
         AccountGroup batch = db.accountGroups().get(sc.batchUsersGroupId);
-        if (db.accountGroupMembers().byGroup(sc.batchUsersGroupId).toList().isEmpty() &&
-            db.accountGroupIncludes().byGroup(sc.batchUsersGroupId).toList().isEmpty()) {
+        if (batch != null
+            && db.accountGroupMembers().byGroup(sc.batchUsersGroupId).toList().isEmpty()
+            &&  db.accountGroupIncludes().byGroup(sc.batchUsersGroupId).toList().isEmpty()) {
           // If the batch user group is not used, delete it.
           //
-          if (batch != null) {
-            db.accountGroups().delete(Collections.singleton(batch));
+          db.accountGroups().delete(Collections.singleton(batch));
 
-            AccountGroupName name = db.accountGroupNames().get(batch.getNameKey());
-            if (name != null) {
-              db.accountGroupNames().delete(Collections.singleton(name));
-            }
+          AccountGroupName name = db.accountGroupNames().get(batch.getNameKey());
+          if (name != null) {
+            db.accountGroupNames().delete(Collections.singleton(name));
           }
-        } else {
-          // FIXME Assign low priority to this group.
+        } else if (batch != null) {
+          cap.getPermission(GlobalCapability.PRIORITY, true)
+              .getRule(config.resolve(batch), true)
+              .setAction(Action.BATCH);
         }
 
         md.setMessage("Upgrade to Gerrit Code Review schema 57\n");
@@ -159,6 +161,8 @@
     sc.registeredGroupId = new AccountGroup.Id(0);
     sc.wildProjectName = new Project.NameKey("DELETED");
     sc.ownerGroupId = new AccountGroup.Id(0);
+    sc.batchUsersGroupId = new AccountGroup.Id(0);
+    sc.batchUsersGroupUUID = new AccountGroup.UUID("DELETED");
 
     db.systemConfig().update(Collections.singleton(sc));
   }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
index f3b9b9e..bcffa2f 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
@@ -25,13 +25,11 @@
 import com.google.gerrit.reviewdb.AccountProjectWatch;
 import com.google.gerrit.reviewdb.Change;
 import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.SystemConfig;
 import com.google.gerrit.rules.PrologEnvironment;
 import com.google.gerrit.server.AccessPath;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.CapabilityControl;
 import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.ProjectConfig;
@@ -42,10 +40,8 @@
 
 import junit.framework.TestCase;
 
-import org.apache.commons.codec.binary.Base64;
 import org.eclipse.jgit.lib.Config;
 
-import java.io.UnsupportedEncodingException;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -210,20 +206,9 @@
   private final AccountGroup.UUID devs = new AccountGroup.UUID("test.devs");
   private final AccountGroup.UUID fixers = new AccountGroup.UUID("test.fixers");
 
-  private final SystemConfig systemConfig;
-  private final AuthConfig authConfig;
   private final CapabilityControl.Factory capabilityControlFactory;
 
   public RefControlTest() {
-    systemConfig = SystemConfig.create();
-    systemConfig.batchUsersGroupUUID = anonymous;
-    try {
-      byte[] bin = "abcdefghijklmnopqrstuvwxyz".getBytes("UTF-8");
-      systemConfig.registerEmailPrivateKey = Base64.encodeBase64String(bin);
-    } catch (UnsupportedEncodingException err) {
-      throw new RuntimeException("Cannot encode key", err);
-    }
-
     all = new HashMap<Project.NameKey, ProjectState>();
     projectCache = new ProjectCache() {
       @Override
@@ -268,11 +253,8 @@
               CapabilityControl.class));
 
         bind(ProjectCache.class).toInstance(projectCache);
-        bind(SystemConfig.class).toInstance(systemConfig);
-        bind(AuthConfig.class);
       }
     });
-    authConfig = injector.getInstance(AuthConfig.class);
     capabilityControlFactory = injector.getInstance(CapabilityControl.Factory.class);
   }
 
@@ -348,9 +330,7 @@
     private final Set<AccountGroup.UUID> groups;
 
     MockUser(AccountGroup.UUID[] groupId) {
-      super(RefControlTest.this.capabilityControlFactory,
-           AccessPath.UNKNOWN,
-          RefControlTest.this.authConfig);
+      super(RefControlTest.this.capabilityControlFactory, AccessPath.UNKNOWN);
       groups = new HashSet<AccountGroup.UUID>(Arrays.asList(groupId));
       groups.add(registered);
       groups.add(anonymous);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandExecutorProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandExecutorProvider.java
index a66386f..5c6f80a 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandExecutorProvider.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandExecutorProvider.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.sshd;
 
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.git.QueueProvider;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -33,12 +34,6 @@
 
   @Override
   public WorkQueue.Executor get() {
-    WorkQueue.Executor executor;
-    if (user.isBatchUser()) {
-      executor = queues.getBatchQueue();
-    } else {
-      executor = queues.getInteractiveQueue();
-    }
-    return executor;
+    return queues.getQueue(user.getCapabilities().getQueueType());
   }
 }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandExecutorQueueProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandExecutorQueueProvider.java
index d5a3373..bafb9ee 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandExecutorQueueProvider.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/CommandExecutorQueueProvider.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.sshd;
 
 import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.git.QueueProvider;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.inject.Inject;
 
@@ -64,13 +65,13 @@
   }
 
   @Override
-  public WorkQueue.Executor getInteractiveQueue() {
-    return interactiveExecutor;
+  public WorkQueue.Executor getQueue(QueueType type) {
+    switch (type) {
+      case INTERACTIVE:
+        return interactiveExecutor;
+      case BATCH:
+      default:
+        return batchExecutor;
+    }
   }
-
-  @Override
-  public WorkQueue.Executor getBatchQueue() {
-    return batchExecutor;
-  }
-
 }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/QueueProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/QueueProvider.java
deleted file mode 100644
index 282472a..0000000
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/QueueProvider.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.google.gerrit.sshd;
-
-import com.google.gerrit.server.git.WorkQueue;
-
-public interface QueueProvider {
-
-  public WorkQueue.Executor getInteractiveQueue();
-
-  public WorkQueue.Executor getBatchQueue();
-
-}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
index 61e8bfb..e6cb6be 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
@@ -28,6 +28,7 @@
 import com.google.gerrit.server.account.ChangeUserName;
 import com.google.gerrit.server.config.FactoryModule;
 import com.google.gerrit.server.config.GerritRequestModule;
+import com.google.gerrit.server.git.QueueProvider;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.gerrit.server.project.ProjectControl;
 import com.google.gerrit.server.ssh.SshInfo;