Merge "Implement Buck driven build"
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUser.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUser.java
index a13e2f1..30f45fa 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUser.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUser.java
@@ -14,7 +14,9 @@
 
 package com.googlesource.gerrit.plugins.serviceuser;
 
+import com.google.common.base.Function;
 import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -22,17 +24,41 @@
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.account.CreateAccount;
 import com.google.gerrit.server.config.ConfigResource;
+import com.google.gerrit.server.config.PluginConfig;
 import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.ProjectLevelConfig;
+import com.google.gerrit.server.project.ProjectCache;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.assistedinject.Assisted;
 
 import com.googlesource.gerrit.plugins.serviceuser.CreateServiceUser.Input;
 
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.PersonIdent;
+
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
 @RequiresCapability(CreateServiceUserCapability.ID)
 public class CreateServiceUser implements RestModifyView<ConfigResource, Input> {
+  private static final String USER = "user";
+  private static final String KEY_CREATED_BY = "createdBy";
+  private static final String KEY_CREATED_AT = "createdAt";
+
   static class Input {
     String username;
     String sshKey;
@@ -42,37 +68,80 @@
     CreateServiceUser create(String username);
   }
 
-  private final PluginConfigFactory cfg;
-  private final String pluginName;
+  private final PluginConfig cfg;
   private final CreateAccount.Factory createAccountFactory;
   private final String username;
+  private final List<String> blockedNames;
+  private final Provider<CurrentUser> userProvider;
+  private final MetaDataUpdate.User metaDataUpdateFactory;
+  private final Project.NameKey allProjects;
+  private final ProjectLevelConfig storage;
+  private final DateFormat rfc2822DateFormatter;
 
   @Inject
-  CreateServiceUser(PluginConfigFactory cfg, @PluginName String pluginName,
-      CreateAccount.Factory createAccountFactory, @Assisted String username) {
-    this.cfg = cfg;
-    this.pluginName = pluginName;
+  CreateServiceUser(PluginConfigFactory cfgFactory,
+      @PluginName String pluginName,
+      CreateAccount.Factory createAccountFactory,
+      Provider<CurrentUser> userProvider,
+      @GerritPersonIdent PersonIdent gerritIdent,
+      MetaDataUpdate.User metaDataUpdateFactory,
+      ProjectCache projectCache,
+      @Assisted String username) {
+    this.cfg = cfgFactory.getFromGerritConfig(pluginName);
     this.createAccountFactory = createAccountFactory;
     this.username = username;
+    this.blockedNames =
+        Lists.transform(Arrays.asList(cfg.getStringList("block")),
+            new Function<String, String>() {
+              @Override
+              public String apply(String blockedName) {
+                return blockedName.toLowerCase();
+              }
+            });
+    this.userProvider = userProvider;
+    this.metaDataUpdateFactory = metaDataUpdateFactory;
+    this.storage = projectCache.getAllProjects().getConfig(pluginName + ".db");
+    this.allProjects = projectCache.getAllProjects().getProject().getNameKey();
+    this.rfc2822DateFormatter =
+        new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
+    this.rfc2822DateFormatter.setCalendar(Calendar.getInstance(
+        gerritIdent.getTimeZone(), Locale.US));
   }
 
   @Override
   public Object apply(ConfigResource resource, Input input)
       throws BadRequestException, ResourceConflictException,
-      UnprocessableEntityException, OrmException {
+      UnprocessableEntityException, OrmException, IOException {
     if (input == null) {
       input = new Input();
     }
-    if (input.username != null && !username.equals(input.username)) {
+    if (!username.equals(input.username)) {
       throw new BadRequestException("username must match URL");
     }
     if (Strings.isNullOrEmpty(input.sshKey)) {
       throw new BadRequestException("sshKey not set");
     }
 
+    if (blockedNames.contains(username.toLowerCase())) {
+      throw new BadRequestException("The username '" + username
+          + "' is not allowed as name for service users.");
+    }
+
     CreateAccount.Input in =
-        new ServiceUserInput(username, input.sshKey,
-            cfg.getFromGerritConfig(pluginName));
-    return createAccountFactory.create(username).apply(TopLevelResource.INSTANCE, in);
+        new ServiceUserInput(username, input.sshKey, cfg);
+    Object response = createAccountFactory.create(username)
+            .apply(TopLevelResource.INSTANCE, in);
+
+    Config db = storage.get();
+    db.setString(USER, username, KEY_CREATED_BY,
+        userProvider.get().getUserName());
+    db.setString(USER, username, KEY_CREATED_AT,
+        rfc2822DateFormatter.format(new Date()));
+
+    MetaDataUpdate md = metaDataUpdateFactory.create(allProjects);
+    md.setMessage("Create Service User '" + username + "'\n");
+    storage.commit(md);
+
+    return response;
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUserCommand.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUserCommand.java
index e151105..d86c93e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUserCommand.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUserCommand.java
@@ -14,12 +14,9 @@
 
 package com.googlesource.gerrit.plugins.serviceuser;
 
-import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.extensions.restapi.TopLevelResource;
-import com.google.gerrit.server.account.CreateAccount;
-import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.gerrit.server.config.ConfigResource;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
 import com.google.gwtorm.server.OrmException;
@@ -44,21 +41,15 @@
   private String sshKey;
 
   @Inject
-  private CreateAccount.Factory createAccountFactory;
-
-  @Inject
-  private PluginConfigFactory cfg;
-
-  @Inject
-  private @PluginName String pluginName;
+  private CreateServiceUser.Factory createServiceUser;
 
   @Override
   protected void run() throws OrmException, IOException, UnloggedFailure {
-    CreateAccount.Input input =
-        new ServiceUserInput(username, readSshKey(),
-            cfg.getFromGerritConfig(pluginName));
+    CreateServiceUser.Input input = new CreateServiceUser.Input();
+    input.sshKey = readSshKey();
+
     try {
-      createAccountFactory.create(username).apply(TopLevelResource.INSTANCE, input);
+      createServiceUser.create(username).apply(new ConfigResource(), input);
     } catch (RestApiException e) {
       throw die(e.getMessage());
     }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUserMenu.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUserMenu.java
index 3dac6d1..51cc8d9 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUserMenu.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/CreateServiceUserMenu.java
@@ -14,6 +14,7 @@
 
 package com.googlesource.gerrit.plugins.serviceuser;
 
+import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.webui.TopMenu;
 import com.google.gerrit.server.CurrentUser;
@@ -21,7 +22,6 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
@@ -36,7 +36,7 @@
       Provider<CurrentUser> userProvider) {
     this.pluginName = pluginName;
     this.userProvider = userProvider;
-    menuEntries = new ArrayList<TopMenu.MenuEntry>();
+    menuEntries = Lists.newArrayList();
     if (canCreateServiceUser()) {
       menuEntries.add(new MenuEntry("People", Collections
           .singletonList(new MenuItem("Create Service User", "", "", MENU_ID))));
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/GetMessages.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/GetMessages.java
new file mode 100644
index 0000000..1ce5ad7
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/GetMessages.java
@@ -0,0 +1,47 @@
+// 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.googlesource.gerrit.plugins.serviceuser;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.config.ConfigResource;
+import com.google.gerrit.server.config.PluginConfig;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.inject.Inject;
+
+public class GetMessages implements RestReadView<ConfigResource> {
+
+  private final PluginConfig cfg;
+
+  @Inject
+  public GetMessages(PluginConfigFactory cfgFactory,
+      @PluginName String pluginName) {
+    this.cfg = cfgFactory.getFromGerritConfig(pluginName);
+  }
+
+  @Override
+  public MessagesInfo apply(ConfigResource rsrc) {
+    MessagesInfo info = new MessagesInfo();
+    info.info = Strings.emptyToNull(cfg.getString("infoMessage"));
+    info.onSuccess = Strings.emptyToNull(cfg.getString("onSuccessMessage"));
+    return info;
+  }
+
+  public class MessagesInfo {
+    String info;
+    String onSuccess;
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/Module.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/Module.java
index 697b573..4a45a89 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/Module.java
@@ -38,6 +38,7 @@
         bind(ServiceUserCollection.class);
         child(CONFIG_KIND, "serviceusers").to(ServiceUserCollection.class);
         install(new FactoryModuleBuilder().build(CreateServiceUser.Factory.class));
+        get(CONFIG_KIND, "messages").to(GetMessages.class);
       }
     });
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/client/CreateServiceUserForm.java b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/client/CreateServiceUserForm.java
index 2dfbe18..2363efa 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serviceuser/client/CreateServiceUserForm.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serviceuser/client/CreateServiceUserForm.java
@@ -43,6 +43,7 @@
   private DialogBox dialogBox;
   private TextBox usernameTxt;
   private TextArea sshKeyTxt;
+  private String onSuccessMessage;
 
   @Override
   public void onModuleLoad() {
@@ -50,7 +51,7 @@
     dialogBox.setText("Create Service User");
     dialogBox.setAnimationEnabled(true);
 
-    Panel p = new VerticalPanel();
+    final VerticalPanel p = new VerticalPanel();
     p.setStyleName("panel");
 
     Panel usernamePanel = new VerticalPanel();
@@ -154,6 +155,24 @@
           createButton.setEnabled(false);
         }
     }, ClickEvent.getType());
+
+    new RestApi("config").id("server").view("serviceuser", "messages")
+        .get(new AsyncCallback<MessagesInfo>() {
+          @Override
+          public void onSuccess(MessagesInfo info) {
+            onSuccessMessage = info.getOnSuccessMessage();
+
+            String infoMessage = info.getInfoMessage();
+            if (infoMessage != null && !"".equals(infoMessage)) {
+              p.insert(new HTML(onSuccessMessage), 0);
+            }
+          }
+
+          @Override
+          public void onFailure(Throwable caught) {
+            // never invoked
+          }
+    });
   }
 
   private void doCreate() {
@@ -185,6 +204,11 @@
             successDialog.hide();
           }
         });
+
+        if (onSuccessMessage != null && !"".equals(onSuccessMessage)) {
+          p.add(new HTML(onSuccessMessage));
+        }
+
         p.add(okButton);
         successDialog.add(p);
 
@@ -204,6 +228,14 @@
     sshKeyTxt.setValue("");
   }
 
+  private static class MessagesInfo extends JavaScriptObject {
+    public final native String getOnSuccessMessage() /*-{ return this.on_success }-*/;
+    public final native String getInfoMessage() /*-{ return this.info }-*/;
+
+    protected MessagesInfo() {
+    }
+  }
+
   private static class ServiceUserInput extends JavaScriptObject {
     final native void ssh_key(String s) /*-{ this.ssh_key = s; }-*/;
 
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
index d428721..bb91d0e 100644
--- a/src/main/resources/Documentation/about.md
+++ b/src/main/resources/Documentation/about.md
@@ -17,8 +17,36 @@
 groups](config.html#group). This allows to automatically assign or
 block certain access rights for the service users.
 
+For each created service user the plugin stores some
+[properties](#properties).
+
 <a id="webui"></a>
 Create Service User in WebUI
 ----------------------------
 In the `People` top menu there is a menu item `Create Service User`
 that opens a dialog for creating a service user.
+
+<a id="properties"></a>
+Service User Properties
+-----------------------
+The service user properties are stored in the `refs/meta/config` branch
+of the `All-Projects` project in the file `@PLUGIN@.db`, which is a
+Git config file:
+
+```
+  [user "build-bot"]
+    createdBy = jdoe
+    createdAt = Wed, 13 Nov 2013 14:31:11 +0100
+  [user "voter"]
+    createdBy = jroe
+    createdAt = Wed, 13 Nov 2013 14:45:00 +0100
+```
+
+<a id="createdBy">
+`user.<service-user-name>.createdBy`
+: The username of the user who created the service user.
+
+<a id="createdAt">
+`user.<service-user-name>.createdAt`
+: The date when the service user was created.
+
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 793158d..ff65add 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -9,8 +9,25 @@
     group = Service Users
 ```
 
+<a id="block">
+`plugin.@PLUGIN@.block`
+:	A username which is forbidden to be used as name for a service
+	user. The blocked username is case insensitive. Multiple
+	usernames can be blocked by specifying multiple
+	`plugin.@PLUGIN@.block` entries.
+
 <a id="group">
 `plugin.@PLUGIN@.group`
 :	A group to which newly created service users should be
-    automatically added. Multiple groups can be specified by having
-    multiple `plugin.@PLUGIN@.group` entries.
+	automatically added. Multiple groups can be specified by having
+	multiple `plugin.@PLUGIN@.group` entries.
+
+<a id="infoMessage">
+`plugin.@PLUGIN@.infoMessage`
+:	HTML formatted message that should be displayed in the service user
+	creation dialog.
+
+<a id="onSuccessMessage">
+`plugin.@PLUGIN@.onSuccessMessage`
+:	HTML formatted message that should be displayed after a service
+	user was successfully created.
diff --git a/src/main/resources/Documentation/rest-api-config.md b/src/main/resources/Documentation/rest-api-config.md
index f6bfaf9..b3642bc 100644
--- a/src/main/resources/Documentation/rest-api-config.md
+++ b/src/main/resources/Documentation/rest-api-config.md
@@ -31,7 +31,6 @@
   {
     "ssh_key": "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw=="
   }
-----
 ```
 
 As response a detailed [AccountInfo](../../../Documentation/rest-api-accounts.html#account-info)
@@ -52,10 +51,48 @@
   }
 ```
 
+### <a id="get-messages"> Get Messages
+_GET /config/server/@PLUGIN@~messages_
+
+Gets help messages to be displayed for the service user creation in the
+Web UI.
+
+#### Request
+
+```
+  GET /config/server/@PLUGIN@~messages HTTP/1.0
+```
+
+As response a [MessagesInfo](#messages-info) entity is returned that
+contains the messages.
+
+#### Response
+
+```
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "on_success": "Don't forget to assign \u003ca href\u003d\"Documentation/access-control.html\"\u003eaccess rights\u003c/a\u003e to the service user."
+  }
+```
+
 
 <a id="json-entities">JSON Entities
 -----------------------------------
 
+### <a id="messages-info"></a>MessagesInfo
+
+The `MessagesInfo` entity contains help messages that should be
+displayed for the service user creation in the Web UI.
+
+* _info_: HTML formatted message that should be displayed in the
+  service user creation dialog.
+* _on\_success_: HTML formatted message that should be displayed after
+  a service user was successfully created.
+
 ### <a id="service-user-input"></a>ServiceUserInput
 
 The `ServiceUserInput` entity contains options for creating a service