Allow unique from address generation.

Allow the from email address to be a ParameterizedString that
handles the ${userHash} variable. The value of the variable is
the md5 hash of the user name. This allows unique generation of
email addresses, so GMAIL threads names of users in conversations
correctly. For example, the from pattern for gerrit-review will be
of the form:
"${user} <noreply-gerritcodereview+${userHash}@google.com>"

Change-Id: I1bd649a9912e0a8f042b2130fbd694f2106468e8
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java
index bb50374..86cebdc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.mail;
 
+import com.google.common.base.Charsets;
 import com.google.gerrit.common.data.ParameterizedString;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.GerritPersonIdent;
@@ -24,9 +25,13 @@
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
+import org.apache.commons.codec.binary.Base64;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.PersonIdent;
 
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
 /** Creates a {@link FromAddressGenerator} from the {@link GerritServerConfig} */
 @Singleton
 public class FromAddressGeneratorProvider implements
@@ -121,7 +126,7 @@
   }
 
   static final class PatternGen implements FromAddressGenerator {
-    private final String senderEmail;
+    private final ParameterizedString senderEmailPattern;
     private final Address serverAddress;
     private final AccountCache accountCache;
     private final String anonymousCowardName;
@@ -130,7 +135,7 @@
     PatternGen(final Address serverAddress, final AccountCache accountCache,
         final String anonymousCowardName,
         final ParameterizedString namePattern, final String senderEmail) {
-      this.senderEmail = senderEmail;
+      this.senderEmailPattern = new ParameterizedString(senderEmail);
       this.serverAddress = serverAddress;
       this.accountCache = accountCache;
       this.anonymousCowardName = anonymousCowardName;
@@ -158,7 +163,25 @@
         senderName = serverAddress.name;
       }
 
+      String senderEmail;
+      if (senderEmailPattern.getParameterNames().isEmpty()) {
+        senderEmail = senderEmailPattern.getRawPattern();
+      } else {
+        senderEmail = senderEmailPattern
+            .replace("userHash", hashOf(senderName))
+            .toString();
+      }
       return new Address(senderName, senderEmail);
     }
   }
+
+  private static String hashOf(String data) {
+    try {
+      MessageDigest hash = MessageDigest.getInstance("MD5");
+      byte[] bytes = hash.digest(data.getBytes(Charsets.UTF_8));
+      return Base64.encodeBase64URLSafeString(bytes);
+    } catch (NoSuchAlgorithmException e) {
+      throw new RuntimeException("No MD5 available", e);
+    }
+  }
 }