Replace sets of Property by a simple map

So far, the information about an event was stored in a collection of
collections of properties. A Property was defined as a wrapper around a
simple pair of key -> value entries. Even if simple as concept, this
data structure made the code unnecessarily complicated. For example, in
order to get a value from the properties, it was needed to iterate over
the collection and compare the desired value with the property key. When
these properties were about to be used,  e.g. when adding a Soy comment,
this collection needed to be converted to a map in order to be used as
the context for the Soy renderer. In the tests, lots of mocking was done
around the Property object which bloated and complicated the test code.

Using a simple map for storing the event information fulfills the same
goal but with some advantages: simpler loops, no need to iterate over
a collection to find and get some values, no need to convert to a map
later when the info is needed and, besides that, simplified tests.

Change-Id: Id429d8cd9e626d88c1f81954e6b37488896f9b90
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/ItsHookModule.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/ItsHookModule.java
index 148874d..530e45d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/base/ItsHookModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/ItsHookModule.java
@@ -35,7 +35,6 @@
 import com.googlesource.gerrit.plugins.its.base.workflow.AddStandardComment;
 import com.googlesource.gerrit.plugins.its.base.workflow.Condition;
 import com.googlesource.gerrit.plugins.its.base.workflow.LogEvent;
-import com.googlesource.gerrit.plugins.its.base.workflow.Property;
 import com.googlesource.gerrit.plugins.its.base.workflow.Rule;
 import java.nio.file.Path;
 
@@ -64,7 +63,6 @@
     DynamicSet.bind(binder(), CommitValidationListener.class).to(ItsValidateComment.class);
     DynamicSet.bind(binder(), EventListener.class).to(ActionController.class);
     factory(ActionRequest.Factory.class);
-    factory(Property.Factory.class);
     factory(Condition.Factory.class);
     factory(Rule.Factory.class);
     factory(AddComment.Factory.class);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/util/PropertyAttributeExtractor.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/util/PropertyAttributeExtractor.java
index 7ce2083..6770a3c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/base/util/PropertyAttributeExtractor.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/util/PropertyAttributeExtractor.java
@@ -14,7 +14,7 @@
 
 package com.googlesource.gerrit.plugins.its.base.util;
 
-import com.google.common.collect.Sets;
+import com.google.common.collect.ImmutableMap;
 import com.google.gerrit.server.data.AccountAttribute;
 import com.google.gerrit.server.data.ApprovalAttribute;
 import com.google.gerrit.server.data.ChangeAttribute;
@@ -22,89 +22,70 @@
 import com.google.gerrit.server.data.RefUpdateAttribute;
 import com.google.inject.Inject;
 import com.googlesource.gerrit.plugins.its.base.its.ItsFacade;
-import com.googlesource.gerrit.plugins.its.base.workflow.Property;
-import java.util.Set;
+import java.util.HashMap;
+import java.util.Map;
 import org.apache.commons.lang.StringEscapeUtils;
 
-/** Extractor to translate the various {@code *Attribute}s to {@link Property Properties}. */
+/** Extractor to translate the various {@code *Attribute}s to properties. */
 class PropertyAttributeExtractor {
-  private Property.Factory propertyFactory;
   private ItsFacade its;
 
   @Inject
-  PropertyAttributeExtractor(ItsFacade its, Property.Factory propertyFactory) {
+  PropertyAttributeExtractor(ItsFacade its) {
     this.its = its;
-    this.propertyFactory = propertyFactory;
   }
 
-  Set<Property> extractFrom(AccountAttribute accountAttribute, String prefix) {
-    Set<Property> properties = Sets.newHashSet();
+  Map<String, String> extractFrom(AccountAttribute accountAttribute, String prefix) {
+    Map<String, String> properties = new HashMap<>();
     if (accountAttribute != null) {
-      properties.add(propertyFactory.create(prefix + "Email", accountAttribute.email));
-      properties.add(propertyFactory.create(prefix + "Username", accountAttribute.username));
-      properties.add(propertyFactory.create(prefix + "Name", accountAttribute.name));
+      properties.put(prefix + "Email", accountAttribute.email);
+      properties.put(prefix + "Username", accountAttribute.username);
+      properties.put(prefix + "Name", accountAttribute.name);
     }
     return properties;
   }
 
-  Set<Property> extractFrom(ChangeAttribute changeAttribute) {
-    Set<Property> properties = Sets.newHashSet();
-    properties.add(propertyFactory.create("branch", changeAttribute.branch));
-    properties.add(propertyFactory.create("topic", changeAttribute.topic));
-    properties.add(propertyFactory.create("subject", changeAttribute.subject));
-    properties.add(
-        propertyFactory.create(
-            "escapedSubject", StringEscapeUtils.escapeJava(changeAttribute.subject)));
-
-    properties.add(propertyFactory.create("commitMessage", changeAttribute.commitMessage));
-    properties.add(propertyFactory.create("changeId", changeAttribute.id));
-    properties.add(propertyFactory.create("changeNumber", String.valueOf(changeAttribute.number)));
-    properties.add(propertyFactory.create("changeUrl", changeAttribute.url));
-
-    properties.add(
-        propertyFactory.create(
-            "formatChangeUrl", its.createLinkForWebui(changeAttribute.url, changeAttribute.url)));
-
-    String status = null;
-    if (changeAttribute.status != null) {
-      status = changeAttribute.status.toString();
-    }
-    properties.add(propertyFactory.create("status", status));
-    properties.addAll(extractFrom(changeAttribute.owner, "owner"));
-    return properties;
+  Map<String, String> extractFrom(ChangeAttribute changeAttribute) {
+    return ImmutableMap.<String, String>builder()
+        .put("branch", changeAttribute.branch)
+        .put("topic", changeAttribute.topic != null ? changeAttribute.topic : "")
+        .put("subject", changeAttribute.subject)
+        .put("escapedSubject", StringEscapeUtils.escapeJava(changeAttribute.subject))
+        .put("commitMessage", changeAttribute.commitMessage)
+        .put("changeId", changeAttribute.id)
+        .put("changeNumber", String.valueOf(changeAttribute.number))
+        .put("changeUrl", changeAttribute.url)
+        .put("formatChangeUrl", its.createLinkForWebui(changeAttribute.url, changeAttribute.url))
+        .put("status", changeAttribute.status != null ? changeAttribute.status.toString() : "")
+        .putAll(extractFrom(changeAttribute.owner, "owner"))
+        .build();
   }
 
-  Set<Property> extractFrom(PatchSetAttribute patchSetAttribute) {
-    Set<Property> properties = Sets.newHashSet();
-    properties.add(propertyFactory.create("revision", patchSetAttribute.revision));
-    properties.add(
-        propertyFactory.create("patchSetNumber", String.valueOf(patchSetAttribute.number)));
-    properties.add(propertyFactory.create("ref", patchSetAttribute.ref));
-    properties.add(propertyFactory.create("createdOn", patchSetAttribute.createdOn.toString()));
-
-    properties.add(propertyFactory.create("parents", patchSetAttribute.parents.toString()));
-    properties.add(
-        propertyFactory.create("deletions", Integer.toString(patchSetAttribute.sizeDeletions)));
-    properties.add(
-        propertyFactory.create("insertions", Integer.toString(patchSetAttribute.sizeInsertions)));
-    properties.addAll(extractFrom(patchSetAttribute.uploader, "uploader"));
-    properties.addAll(extractFrom(patchSetAttribute.author, "author"));
-    return properties;
+  Map<String, String> extractFrom(PatchSetAttribute patchSetAttribute) {
+    return ImmutableMap.<String, String>builder()
+        .put("revision", patchSetAttribute.revision)
+        .put("patchSetNumber", String.valueOf(patchSetAttribute.number))
+        .put("ref", patchSetAttribute.ref)
+        .put("createdOn", patchSetAttribute.createdOn.toString())
+        .put("parents", patchSetAttribute.parents.toString())
+        .put("deletions", Integer.toString(patchSetAttribute.sizeDeletions))
+        .put("insertions", Integer.toString(patchSetAttribute.sizeInsertions))
+        .putAll(extractFrom(patchSetAttribute.uploader, "uploader"))
+        .putAll(extractFrom(patchSetAttribute.author, "author"))
+        .build();
   }
 
-  Set<Property> extractFrom(RefUpdateAttribute refUpdateAttribute) {
-    Set<Property> properties = Sets.newHashSet();
-    properties.add(propertyFactory.create("revision", refUpdateAttribute.newRev));
-    properties.add(propertyFactory.create("revisionOld", refUpdateAttribute.oldRev));
-    properties.add(propertyFactory.create("ref", refUpdateAttribute.refName));
-    return properties;
+  Map<String, String> extractFrom(RefUpdateAttribute refUpdateAttribute) {
+    return ImmutableMap.<String, String>builder()
+        .put("revision", refUpdateAttribute.newRev)
+        .put("revisionOld", refUpdateAttribute.oldRev)
+        .put("ref", refUpdateAttribute.refName)
+        .build();
   }
 
-  Set<Property> extractFrom(ApprovalAttribute approvalAttribute) {
-    Set<Property> properties = Sets.newHashSet();
-    properties.add(
-        propertyFactory.create(
-            "approval" + approvalAttribute.type.replace("-", ""), approvalAttribute.value));
-    return properties;
+  public Map<String, String> extractFrom(ApprovalAttribute approvalAttribute) {
+    return ImmutableMap.<String, String>builder()
+        .put("approval" + approvalAttribute.type.replace("-", ""), approvalAttribute.value)
+        .build();
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/util/PropertyExtractor.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/util/PropertyExtractor.java
index 85055e3..5fde1ab 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/base/util/PropertyExtractor.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/util/PropertyExtractor.java
@@ -14,7 +14,6 @@
 
 package com.googlesource.gerrit.plugins.its.base.util;
 
-import com.google.common.collect.Sets;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
@@ -32,27 +31,26 @@
 import com.google.gerrit.server.events.RefEvent;
 import com.google.gerrit.server.events.RefUpdatedEvent;
 import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.its.base.workflow.Property;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import org.eclipse.jgit.lib.ObjectId;
 
-/** Extractor to translate an {@link ChangeEvent} to {@link Property Properties}. */
+/** Extractor to translate an {@link ChangeEvent} to a map of properties}. */
 public class PropertyExtractor {
   private IssueExtractor issueExtractor;
-  private Property.Factory propertyFactory;
   private PropertyAttributeExtractor propertyAttributeExtractor;
   private final String pluginName;
 
   @Inject
   PropertyExtractor(
       IssueExtractor issueExtractor,
-      Property.Factory propertyFactory,
       PropertyAttributeExtractor propertyAttributeExtractor,
       @PluginName String pluginName) {
     this.issueExtractor = issueExtractor;
-    this.propertyFactory = propertyFactory;
     this.propertyAttributeExtractor = propertyAttributeExtractor;
     this.pluginName = pluginName;
   }
@@ -73,40 +71,38 @@
     }
   }
 
-  private Map<String, Set<String>> extractFrom(PatchSetEvent event, Set<Property> common) {
-    common.add(propertyFactory.create("event-type", event.type));
+  private Map<String, Set<String>> extractMapFrom(PatchSetEvent event, Map<String, String> common) {
     ChangeAttribute change = event.change.get();
     PatchSetAttribute patchSet = event.patchSet.get();
-    common.addAll(propertyAttributeExtractor.extractFrom(change));
-    common.addAll(propertyAttributeExtractor.extractFrom(patchSet));
+    common.putAll(propertyAttributeExtractor.extractFrom(change));
+    common.putAll(propertyAttributeExtractor.extractFrom(patchSet));
     PatchSet.Id patchSetId =
         newPatchSetId(Integer.toString(change.number), Integer.toString(patchSet.number));
     return issueExtractor.getIssueIds(change.project, patchSet.revision, patchSetId);
   }
 
-  private Map<String, Set<String>> extractFrom(ChangeAbandonedEvent event, Set<Property> common) {
-    common.addAll(propertyAttributeExtractor.extractFrom(event.abandoner.get(), "abandoner"));
-    common.add(propertyFactory.create("reason", event.reason));
-    return extractFrom((PatchSetEvent) event, common);
+  private Map<String, Set<String>> extractFrom(
+      ChangeAbandonedEvent event, Map<String, String> common) {
+    common.putAll(propertyAttributeExtractor.extractFrom(event.abandoner.get(), "abandoner"));
+    common.put("reason", event.reason);
+    return extractMapFrom(event, common);
   }
 
-  private Map<String, Set<String>> extractFrom(ChangeMergedEvent event, Set<Property> common) {
-    common.addAll(propertyAttributeExtractor.extractFrom(event.submitter.get(), "submitter"));
-    return extractFrom((PatchSetEvent) event, common);
+  private Map<String, Set<String>> extractFrom(
+      ChangeMergedEvent event, Map<String, String> common) {
+    common.putAll(propertyAttributeExtractor.extractFrom(event.submitter.get(), "submitter"));
+    return extractMapFrom(event, common);
   }
 
-  private Map<String, Set<String>> extractFrom(ChangeRestoredEvent event, Set<Property> common) {
-    common.addAll(propertyAttributeExtractor.extractFrom(event.restorer.get(), "restorer"));
-    common.add(propertyFactory.create("reason", event.reason));
-    return extractFrom((PatchSetEvent) event, common);
+  private Map<String, Set<String>> extractFrom(
+      ChangeRestoredEvent event, Map<String, String> common) {
+    common.putAll(propertyAttributeExtractor.extractFrom(event.restorer.get(), "restorer"));
+    return extractMapFrom(event, common);
   }
 
-  private Map<String, Set<String>> extractFrom(RefUpdatedEvent event, Set<Property> common) {
-    common.add(propertyFactory.create("event-type", event.type));
-    if (event.submitter != null) {
-      common.addAll(propertyAttributeExtractor.extractFrom(event.submitter.get(), "submitter"));
-    }
-    common.addAll(propertyAttributeExtractor.extractFrom(event.refUpdate.get()));
+  private Map<String, Set<String>> extractFrom(RefUpdatedEvent event, Map<String, String> common) {
+    common.putAll(propertyAttributeExtractor.extractFrom(event.submitter.get(), "submitter"));
+    common.putAll(propertyAttributeExtractor.extractFrom(event.refUpdate.get()));
     RefUpdateAttribute refUpdated = event.refUpdate.get();
     if (ObjectId.zeroId().name().equals(refUpdated.newRev)) {
       return Collections.emptyMap();
@@ -114,29 +110,31 @@
     return issueExtractor.getIssueIds(event.getProjectNameKey().get(), refUpdated.newRev);
   }
 
-  private Map<String, Set<String>> extractFrom(PatchSetCreatedEvent event, Set<Property> common) {
-    common.addAll(propertyAttributeExtractor.extractFrom(event.uploader.get(), "uploader"));
-    return extractFrom((PatchSetEvent) event, common);
+  private Map<String, Set<String>> extractFrom(
+      PatchSetCreatedEvent event, Map<String, String> common) {
+    common.putAll(propertyAttributeExtractor.extractFrom(event.uploader.get(), "uploader"));
+    return extractMapFrom(event, common);
   }
 
-  private Map<String, Set<String>> extractFrom(CommentAddedEvent event, Set<Property> common) {
-    common.addAll(propertyAttributeExtractor.extractFrom(event.author.get(), "commenter"));
+  private Map<String, Set<String>> extractFrom(
+      CommentAddedEvent event, Map<String, String> common) {
+    common.putAll(propertyAttributeExtractor.extractFrom(event.author.get(), "commenter"));
+    common.put("comment", event.comment);
     if (event.approvals != null) {
       for (ApprovalAttribute approvalAttribute : event.approvals.get()) {
-        common.addAll(propertyAttributeExtractor.extractFrom(approvalAttribute));
+        common.putAll(propertyAttributeExtractor.extractFrom(approvalAttribute));
       }
     }
-    common.add(propertyFactory.create("comment", event.comment));
-    return extractFrom((PatchSetEvent) event, common);
+    return extractMapFrom(event, common);
   }
 
   /**
-   * A set of property sets extracted from an event.
+   * A set of properties extracted from an event.
    *
-   * <p>As events may relate to more that a single issue, and properties sets are should be tied to
-   * a single issue, returning {@code Set<Property>} is not sufficient, and we need to return {@code
-   * Set<Set<Property>>}. Using this approach, a PatchSetCreatedEvent for a patch set with commit
-   * message:
+   * <p>As events may relate to more that a single issue and a group of properties should be tied to
+   * a single issue, we need to return {@code Set<Map>} of properties. As properties we understand a
+   * map of event attributes. Using this approach, a PatchSetCreatedEvent for a patch set with
+   * commit message:
    *
    * <pre>
    *   (bug 4711) Fix treatment of special characters in title
@@ -166,16 +164,17 @@
    * same event. So in the above example, a comment "mentioned in change 123" may be added for issue
    * 42, and a comment "fixed by change 123” may be added for issue 4711.
    *
-   * @param event The event to extract property sets from.
-   * @return sets of property sets extracted from the event.
+   * @param event The event to extract property maps from.
+   * @return set of property maps extracted from the event.
    */
-  public Set<Set<Property>> extractFrom(RefEvent event) {
+  public Set<Map<String, String>> extractFrom(RefEvent event) {
     Map<String, Set<String>> associations = null;
-    Set<Set<Property>> ret = Sets.newHashSet();
-
-    Set<Property> common = Sets.newHashSet();
-    common.add(propertyFactory.create("event", event.getClass().getName()));
-    common.add(propertyFactory.create("project", event.getProjectNameKey().get()));
+    Map<String, String> common = new HashMap<>();
+    common.put("event", event.getClass().getName());
+    common.put("event-type", event.type);
+    common.put("project", event.getProjectNameKey().get());
+    common.put("ref", event.getRefName());
+    common.put("itsName", pluginName);
 
     if (event instanceof ChangeAbandonedEvent) {
       associations = extractFrom((ChangeAbandonedEvent) event, common);
@@ -191,18 +190,13 @@
       associations = extractFrom((RefUpdatedEvent) event, common);
     }
 
+    Set<Map<String, String>> ret = new HashSet<>();
     if (associations != null) {
-      for (String issue : associations.keySet()) {
-        Set<Property> properties = Sets.newHashSet();
-        Property property = propertyFactory.create("issue", issue);
-        properties.add(property);
-        property = propertyFactory.create("its-name", pluginName);
-        properties.add(property);
-        for (String occurrence : associations.get(issue)) {
-          property = propertyFactory.create("association", occurrence);
-          properties.add(property);
-        }
-        properties.addAll(common);
+      for (Entry<String, Set<String>> assoc : associations.entrySet()) {
+        Map<String, String> properties = new HashMap<>();
+        properties.put("issue", assoc.getKey());
+        properties.put("association", String.join(" ", assoc.getValue()));
+        properties.putAll(common);
         ret.add(properties);
       }
     }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/Action.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/Action.java
index 7f73195..10f362a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/Action.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/Action.java
@@ -15,7 +15,7 @@
 package com.googlesource.gerrit.plugins.its.base.workflow;
 
 import java.io.IOException;
-import java.util.Set;
+import java.util.Map;
 
 /** Interface for actions on an issue tracking system */
 interface Action {
@@ -27,6 +27,6 @@
    * @param actionRequest The request to execute.
    * @param properties The properties for the execution.
    */
-  void execute(String issue, ActionRequest actionRequest, Set<Property> properties)
+  void execute(String issue, ActionRequest actionRequest, Map<String, String> properties)
       throws IOException;
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionController.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionController.java
index 844ac91..3329610 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionController.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionController.java
@@ -21,6 +21,7 @@
 import com.googlesource.gerrit.plugins.its.base.its.ItsConfig;
 import com.googlesource.gerrit.plugins.its.base.util.PropertyExtractor;
 import java.util.Collection;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -58,16 +59,11 @@
   }
 
   private void handleEvent(RefEvent refEvent) {
-    Set<Set<Property>> propertiesCollections = propertyExtractor.extractFrom(refEvent);
-    for (Set<Property> properties : propertiesCollections) {
-      Collection<ActionRequest> actions = ruleBase.actionRequestsFor(properties);
+    Set<Map<String, String>> properties = propertyExtractor.extractFrom(refEvent);
+    for (Map<String, String> propertiesMap : properties) {
+      Collection<ActionRequest> actions = ruleBase.actionRequestsFor(propertiesMap);
       if (!actions.isEmpty()) {
-        for (Property property : properties) {
-          if ("issue".equals(property.getKey())) {
-            String issue = property.getValue();
-            actionExecutor.execute(issue, actions, properties);
-          }
-        }
+        actionExecutor.execute(actions, propertiesMap);
       }
     }
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionExecutor.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionExecutor.java
index 3cc9afe..726efac 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionExecutor.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionExecutor.java
@@ -17,7 +17,7 @@
 import com.google.inject.Inject;
 import com.googlesource.gerrit.plugins.its.base.its.ItsFacade;
 import java.io.IOException;
-import java.util.Set;
+import java.util.Map;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -60,7 +60,7 @@
     }
   }
 
-  public void execute(String issue, ActionRequest actionRequest, Set<Property> properties) {
+  private void execute(String issue, ActionRequest actionRequest, Map<String, String> properties) {
     try {
       Action action = getAction(actionRequest.getName());
       if (action == null) {
@@ -73,9 +73,9 @@
     }
   }
 
-  public void execute(String issue, Iterable<ActionRequest> actions, Set<Property> properties) {
+  public void execute(Iterable<ActionRequest> actions, Map<String, String> properties) {
     for (ActionRequest actionRequest : actions) {
-      execute(issue, actionRequest, properties);
+      execute(properties.get("issue"), actionRequest, properties);
     }
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/AddComment.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/AddComment.java
index 6a64db2..3f376f2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/AddComment.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/AddComment.java
@@ -18,7 +18,7 @@
 import com.google.inject.Inject;
 import com.googlesource.gerrit.plugins.its.base.its.ItsFacade;
 import java.io.IOException;
-import java.util.Set;
+import java.util.Map;
 
 /**
  * Adds a fixed comment to an issue.
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public void execute(String issue, ActionRequest actionRequest, Set<Property> properties)
+  public void execute(String issue, ActionRequest actionRequest, Map<String, String> properties)
       throws IOException {
     String comment = String.join(" ", actionRequest.getParameters());
     if (!Strings.isNullOrEmpty(comment)) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/AddSoyComment.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/AddSoyComment.java
index 1521092..f1c1962 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/AddSoyComment.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/AddSoyComment.java
@@ -19,6 +19,7 @@
 import com.google.inject.Inject;
 import com.google.inject.ProvisionException;
 import com.google.template.soy.SoyFileSet;
+import com.google.template.soy.SoyFileSet.Builder;
 import com.google.template.soy.data.SanitizedContent;
 import com.google.template.soy.tofu.SoyTofu;
 import com.googlesource.gerrit.plugins.its.base.ItsPath;
@@ -29,7 +30,7 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.HashMap;
-import java.util.Set;
+import java.util.Map;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -55,26 +56,11 @@
     this.its = its;
   }
 
-  private HashMap<String, Object> getSoyContext(Set<Property> properties) {
-    HashMap<String, Object> soyContext = new HashMap<>();
-    for (Property property : properties) {
-      String key = property.getKey();
-      if (!Strings.isNullOrEmpty(key)) {
-        String value = property.getValue();
-        if (!Strings.isNullOrEmpty(value)) {
-          soyContext.put(key, value);
-        }
-      }
-    }
-
-    return soyContext;
-  }
-
   private String soyTemplate(
       SoyFileSet.Builder builder,
       String template,
       SanitizedContent.ContentKind kind,
-      Set<Property> properties) {
+      Map<String, String> properties) {
     Path templatePath = templateDir.resolve(template + ".soy");
     String content;
 
@@ -86,26 +72,22 @@
     }
 
     builder.add(content, templatePath.toAbsolutePath().toString());
-
-    HashMap<String, Object> context = getSoyContext(properties);
-
     SoyTofu.Renderer renderer =
         builder
             .build()
             .compileToTofu()
             .newRenderer("etc.its.templates." + template)
             .setContentKind(kind)
-            .setData(context);
+            .setData(properties);
     return renderer.render();
   }
 
-  protected String soyTextTemplate(
-      SoyFileSet.Builder builder, String template, Set<Property> properties) {
+  private String soyTextTemplate(Builder builder, String template, Map<String, String> properties) {
     return soyTemplate(builder, template, SanitizedContent.ContentKind.TEXT, properties);
   }
 
   @Override
-  public void execute(String issue, ActionRequest actionRequest, Set<Property> properties)
+  public void execute(String issue, ActionRequest actionRequest, Map<String, String> properties)
       throws IOException {
     String comment = buildComment(actionRequest, properties);
     if (!Strings.isNullOrEmpty(comment)) {
@@ -113,7 +95,7 @@
     }
   }
 
-  private String buildComment(ActionRequest actionRequest, Set<Property> properties) {
+  private String buildComment(ActionRequest actionRequest, Map<String, String> properties) {
     String template = actionRequest.getParameter(1);
     if (!template.isEmpty()) {
       return soyTextTemplate(SoyFileSet.builder(), template, properties);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/AddStandardComment.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/AddStandardComment.java
index 52c1369..95b9c5e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/AddStandardComment.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/AddStandardComment.java
@@ -15,12 +15,10 @@
 package com.googlesource.gerrit.plugins.its.base.workflow;
 
 import com.google.common.base.Strings;
-import com.google.common.collect.Maps;
 import com.google.inject.Inject;
 import com.googlesource.gerrit.plugins.its.base.its.ItsFacade;
 import java.io.IOException;
 import java.util.Map;
-import java.util.Set;
 
 /**
  * Adds a short predefined comments to an issue.
@@ -76,7 +74,7 @@
   }
 
   @Override
-  public void execute(String issue, ActionRequest actionRequest, Set<Property> properties)
+  public void execute(String issue, ActionRequest actionRequest, Map<String, String> properties)
       throws IOException {
     String comment = buildComment(properties);
     if (!Strings.isNullOrEmpty(comment)) {
@@ -84,28 +82,16 @@
     }
   }
 
-  private String buildComment(Set<Property> properties) {
-    Map<String, String> map = Maps.newHashMap();
-    for (Property property : properties) {
-      String current = property.getValue();
-      if (!Strings.isNullOrEmpty(current)) {
-        String key = property.getKey();
-        String old = Strings.nullToEmpty(map.get(key));
-        if (!old.isEmpty()) {
-          old += ", ";
-        }
-        map.put(key, old + current);
-      }
-    }
-    switch (map.get("event-type")) {
+  private String buildComment(Map<String, String> properties) {
+    switch (properties.get("event-type")) {
       case "change-abandoned":
-        return getCommentChangeEvent("abandoned", "abandoner", map);
+        return getCommentChangeEvent("abandoned", "abandoner", properties);
       case "change-merged":
-        return getCommentChangeEvent("merged", "submitter", map);
+        return getCommentChangeEvent("merged", "submitter", properties);
       case "change-restored":
-        return getCommentChangeEvent("restored", "restorer", map);
+        return getCommentChangeEvent("restored", "restorer", properties);
       case "patchset-created":
-        return getCommentChangeEvent("had a related patch set uploaded", "uploader", map);
+        return getCommentChangeEvent("had a related patch set uploaded", "uploader", properties);
       default:
         return "";
     }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/Condition.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/Condition.java
index 7c06bb4..e527652 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/Condition.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/Condition.java
@@ -22,6 +22,7 @@
 import com.google.inject.assistedinject.Assisted;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -86,13 +87,12 @@
    *     conditions, true iff properties does not contain any property that matches the rules key
    *     and whose value matches at least one of the rule's value.
    */
-  public boolean isMetBy(Iterable<Property> properties) {
-    for (Property property : properties) {
-      String propertyKey = property.getKey();
-      if ((key == null && propertyKey == null) || (key != null && key.equals(propertyKey))) {
-        if (values.contains(property.getValue())) {
-          return !negated;
-        }
+  public boolean isMetBy(Map<String, String> properties) {
+    String property = properties.get(key);
+    String[] propertyValues = property != null ? property.split(" ") : new String[] {};
+    for (String p : propertyValues) {
+      if (values.contains(p.trim())) {
+        return !negated;
       }
     }
     return negated;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/LogEvent.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/LogEvent.java
index baf64b2..5df562e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/LogEvent.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/LogEvent.java
@@ -16,7 +16,8 @@
 
 import com.google.inject.Inject;
 import java.io.IOException;
-import java.util.Set;
+import java.util.Map;
+import java.util.Map.Entry;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -53,8 +54,8 @@
   @Inject
   public LogEvent() {}
 
-  private void logProperty(Level level, Property property) {
-    String message = property.toString();
+  private void logProperty(Level level, Entry<String, String> property) {
+    String message = String.format("[%s = %s]", property.getKey(), property.getValue());
     switch (level) {
       case ERROR:
         log.error(message);
@@ -74,10 +75,10 @@
   }
 
   @Override
-  public void execute(String issue, ActionRequest actionRequest, Set<Property> properties)
+  public void execute(String issue, ActionRequest actionRequest, Map<String, String> properties)
       throws IOException {
     Level level = Level.fromString(actionRequest.getParameter(1));
-    for (Property property : properties) {
+    for (Entry<String, String> property : properties.entrySet()) {
       logProperty(level, property);
     }
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/Property.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/Property.java
deleted file mode 100644
index eb1f087..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/Property.java
+++ /dev/null
@@ -1,79 +0,0 @@
-// 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.its.base.workflow;
-
-import com.google.gerrit.common.Nullable;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-/**
- * A property to match against {@code Condition}s.
- *
- * <p>A property is a simple key value pair.
- */
-public class Property {
-  public interface Factory {
-    Property create(@Assisted("key") String key, @Assisted("value") String value);
-  }
-
-  private final String key;
-  private final String value;
-
-  @Inject
-  public Property(@Assisted("key") String key, @Nullable @Assisted("value") String value) {
-    this.key = key;
-    this.value = value;
-  }
-
-  public String getKey() {
-    return key;
-  }
-
-  public String getValue() {
-    return value;
-  }
-
-  @Override
-  public boolean equals(Object other) {
-    boolean ret = false;
-    if (other != null && other instanceof Property) {
-      Property otherProperty = (Property) other;
-      ret = true;
-
-      if (key == null) {
-        ret &= otherProperty.getKey() == null;
-      } else {
-        ret &= key.equals(otherProperty.getKey());
-      }
-
-      if (value == null) {
-        ret &= otherProperty.getValue() == null;
-      } else {
-        ret &= value.equals(otherProperty.getValue());
-      }
-    }
-    return ret;
-  }
-
-  @Override
-  public int hashCode() {
-    return (key == null ? 0 : key.hashCode()) * 31 + (value == null ? 0 : value.hashCode());
-  }
-
-  @Override
-  public String toString() {
-    return "[" + key + " = " + value + "]";
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/Rule.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/Rule.java
index 3efa269..b83b969 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/Rule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/Rule.java
@@ -21,6 +21,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /** A single rule that associates {@code Action}s to {@code Condition}s. */
@@ -71,7 +72,7 @@
    * @param properties The properties to check against the rule's conditions.
    * @return The actions that should get fired.
    */
-  public Collection<ActionRequest> actionRequestsFor(Iterable<Property> properties) {
+  public Collection<ActionRequest> actionRequestsFor(Map<String, String> properties) {
     for (Condition condition : conditions) {
       if (!condition.isMetBy(properties)) {
         return Collections.emptyList();
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/RuleBase.java b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/RuleBase.java
index a6afa08..fc705ad 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/RuleBase.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/base/workflow/RuleBase.java
@@ -23,6 +23,7 @@
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.Collection;
+import java.util.Map;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.storage.file.FileBasedConfig;
 import org.eclipse.jgit.util.FS;
@@ -139,7 +140,7 @@
    * @param properties The properties to search actions for.
    * @return Requests for the actions that should be fired.
    */
-  public Collection<ActionRequest> actionRequestsFor(Iterable<Property> properties) {
+  public Collection<ActionRequest> actionRequestsFor(Map<String, String> properties) {
     Collection<ActionRequest> ret = Lists.newLinkedList();
     for (Rule rule : rules) {
       ret.addAll(rule.actionRequestsFor(properties));
diff --git a/src/test/java/com/googlesource/gerrit/plugins/its/base/util/PropertyAttributeExtractorTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/util/PropertyAttributeExtractorTest.java
index b7e157a..5339d60 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/its/base/util/PropertyAttributeExtractorTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/util/PropertyAttributeExtractorTest.java
@@ -15,9 +15,10 @@
 
 import static org.easymock.EasyMock.expect;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
 import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Change.Status;
 import com.google.gerrit.server.data.AccountAttribute;
 import com.google.gerrit.server.data.ApprovalAttribute;
@@ -28,23 +29,22 @@
 import com.google.inject.Injector;
 import com.googlesource.gerrit.plugins.its.base.its.ItsFacade;
 import com.googlesource.gerrit.plugins.its.base.testutil.LoggingMockingTestCase;
-import com.googlesource.gerrit.plugins.its.base.workflow.Property;
-import java.util.Set;
+import java.util.HashMap;
+import java.util.Map;
 
 public class PropertyAttributeExtractorTest extends LoggingMockingTestCase {
   private Injector injector;
 
   private ItsFacade facade;
-  private Property.Factory propertyFactory;
 
   public void testAccountAttributeNull() {
     replayMocks();
 
     PropertyAttributeExtractor extractor = injector.getInstance(PropertyAttributeExtractor.class);
 
-    Set<Property> actual = extractor.extractFrom(null, "prefix");
+    Map<String, String> actual = extractor.extractFrom(null, "prefix");
 
-    Set<Property> expected = Sets.newHashSet();
+    Map<String, String> expected = new HashMap<>();
 
     assertEquals("Properties do not match", expected, actual);
   }
@@ -55,25 +55,18 @@
     accountAttribute.name = "testName";
     accountAttribute.username = "testUsername";
 
-    Property propertyEmail = createMock(Property.class);
-    expect(propertyFactory.create("prefixEmail", "testEmail")).andReturn(propertyEmail);
-
-    Property propertyName = createMock(Property.class);
-    expect(propertyFactory.create("prefixName", "testName")).andReturn(propertyName);
-
-    Property propertyUsername = createMock(Property.class);
-    expect(propertyFactory.create("prefixUsername", "testUsername")).andReturn(propertyUsername);
-
     replayMocks();
 
     PropertyAttributeExtractor extractor = injector.getInstance(PropertyAttributeExtractor.class);
 
-    Set<Property> actual = extractor.extractFrom(accountAttribute, "prefix");
+    Map<String, String> actual = extractor.extractFrom(accountAttribute, "prefix");
 
-    Set<Property> expected = Sets.newHashSet();
-    expected.add(propertyEmail);
-    expected.add(propertyName);
-    expected.add(propertyUsername);
+    ImmutableMap<String, String> expected =
+        new ImmutableMap.Builder<String, String>()
+            .put("prefixEmail", "testEmail")
+            .put("prefixName", "testName")
+            .put("prefixUsername", "testUsername")
+            .build();
     assertEquals("Properties do not match", expected, actual);
   }
 
@@ -92,49 +85,7 @@
     changeAttribute.url = "http://www.example.org/test";
     changeAttribute.owner = owner;
     changeAttribute.commitMessage = "Commit Message";
-
-    Property propertyBranch = createMock(Property.class);
-    expect(propertyFactory.create("branch", "testBranch")).andReturn(propertyBranch);
-
-    Property propertyTopic = createMock(Property.class);
-    expect(propertyFactory.create("topic", "testTopic")).andReturn(propertyTopic);
-
-    Property propertySubject = createMock(Property.class);
-    expect(propertyFactory.create("subject", "testSubject")).andReturn(propertySubject);
-
-    Property propertyEscapedSubject = createMock(Property.class);
-    expect(propertyFactory.create("escapedSubject", "testSubject"))
-        .andReturn(propertyEscapedSubject);
-
-    Property propertyId = createMock(Property.class);
-    expect(propertyFactory.create("changeId", "testId")).andReturn(propertyId);
-
-    Property propertyNumber = createMock(Property.class);
-    expect(propertyFactory.create("changeNumber", "4711")).andReturn(propertyNumber);
-
-    Property propertyUrl = createMock(Property.class);
-    expect(propertyFactory.create("changeUrl", "http://www.example.org/test"))
-        .andReturn(propertyUrl);
-
-    Property propertyStatus = createMock(Property.class);
-    expect(propertyFactory.create("status", null)).andReturn(propertyStatus);
-
-    Property propertyEmail = createMock(Property.class);
-    expect(propertyFactory.create("ownerEmail", "testEmail")).andReturn(propertyEmail);
-
-    Property propertyName = createMock(Property.class);
-    expect(propertyFactory.create("ownerName", "testName")).andReturn(propertyName);
-
-    Property propertyUsername = createMock(Property.class);
-    expect(propertyFactory.create("ownerUsername", "testUsername")).andReturn(propertyUsername);
-
-    Property propertyCommitMessage = createMock(Property.class);
-    expect(propertyFactory.create("commitMessage", "Commit Message"))
-        .andReturn(propertyCommitMessage);
-
-    Property propertyFormatChangeUrl = createMock(Property.class);
-    expect(propertyFactory.create("formatChangeUrl", "http://www.example.org/test"))
-        .andReturn(propertyFormatChangeUrl);
+    changeAttribute.status = Change.Status.NEW;
 
     expect(facade.createLinkForWebui("http://www.example.org/test", "http://www.example.org/test"))
         .andReturn("http://www.example.org/test");
@@ -143,22 +94,24 @@
 
     PropertyAttributeExtractor extractor = injector.getInstance(PropertyAttributeExtractor.class);
 
-    Set<Property> actual = extractor.extractFrom(changeAttribute);
+    Map<String, String> actual = extractor.extractFrom(changeAttribute);
 
-    Set<Property> expected = Sets.newHashSet();
-    expected.add(propertyBranch);
-    expected.add(propertyTopic);
-    expected.add(propertySubject);
-    expected.add(propertyEscapedSubject);
-    expected.add(propertyId);
-    expected.add(propertyNumber);
-    expected.add(propertyUrl);
-    expected.add(propertyStatus);
-    expected.add(propertyEmail);
-    expected.add(propertyName);
-    expected.add(propertyUsername);
-    expected.add(propertyCommitMessage);
-    expected.add(propertyFormatChangeUrl);
+    ImmutableMap<String, String> expected =
+        new ImmutableMap.Builder<String, String>()
+            .put("branch", "testBranch")
+            .put("topic", "testTopic")
+            .put("subject", "testSubject")
+            .put("escapedSubject", "testSubject")
+            .put("changeId", "testId")
+            .put("changeNumber", "4711")
+            .put("changeUrl", "http://www.example.org/test")
+            .put("status", Change.Status.NEW.name())
+            .put("ownerEmail", "testEmail")
+            .put("ownerName", "testName")
+            .put("ownerUsername", "testUsername")
+            .put("commitMessage", "Commit Message")
+            .put("formatChangeUrl", "http://www.example.org/test")
+            .build();
     assertEquals("Properties do not match", expected, actual);
   }
 
@@ -179,49 +132,6 @@
     changeAttribute.owner = owner;
     changeAttribute.commitMessage = "Commit Message";
 
-    Property propertyBranch = createMock(Property.class);
-    expect(propertyFactory.create("branch", "testBranch")).andReturn(propertyBranch);
-
-    Property propertyTopic = createMock(Property.class);
-    expect(propertyFactory.create("topic", "testTopic")).andReturn(propertyTopic);
-
-    Property propertySubject = createMock(Property.class);
-    expect(propertyFactory.create("subject", "testSubject")).andReturn(propertySubject);
-
-    Property propertyEscapedSubject = createMock(Property.class);
-    expect(propertyFactory.create("escapedSubject", "testSubject"))
-        .andReturn(propertyEscapedSubject);
-
-    Property propertyId = createMock(Property.class);
-    expect(propertyFactory.create("changeId", "testId")).andReturn(propertyId);
-
-    Property propertyNumber = createMock(Property.class);
-    expect(propertyFactory.create("changeNumber", "4711")).andReturn(propertyNumber);
-
-    Property propertyUrl = createMock(Property.class);
-    expect(propertyFactory.create("changeUrl", "http://www.example.org/test"))
-        .andReturn(propertyUrl);
-
-    Property propertyStatus = createMock(Property.class);
-    expect(propertyFactory.create("status", "ABANDONED")).andReturn(propertyStatus);
-
-    Property propertyEmail = createMock(Property.class);
-    expect(propertyFactory.create("ownerEmail", "testEmail")).andReturn(propertyEmail);
-
-    Property propertyName = createMock(Property.class);
-    expect(propertyFactory.create("ownerName", "testName")).andReturn(propertyName);
-
-    Property propertyUsername = createMock(Property.class);
-    expect(propertyFactory.create("ownerUsername", "testUsername")).andReturn(propertyUsername);
-
-    Property propertyCommitMessage = createMock(Property.class);
-    expect(propertyFactory.create("commitMessage", "Commit Message"))
-        .andReturn(propertyCommitMessage);
-
-    Property propertyFormatChangeUrl = createMock(Property.class);
-    expect(propertyFactory.create("formatChangeUrl", "http://www.example.org/test"))
-        .andReturn(propertyFormatChangeUrl);
-
     expect(facade.createLinkForWebui("http://www.example.org/test", "http://www.example.org/test"))
         .andReturn("http://www.example.org/test");
 
@@ -229,22 +139,24 @@
 
     PropertyAttributeExtractor extractor = injector.getInstance(PropertyAttributeExtractor.class);
 
-    Set<Property> actual = extractor.extractFrom(changeAttribute);
+    Map<String, String> actual = extractor.extractFrom(changeAttribute);
 
-    Set<Property> expected = Sets.newHashSet();
-    expected.add(propertyBranch);
-    expected.add(propertyTopic);
-    expected.add(propertySubject);
-    expected.add(propertyEscapedSubject);
-    expected.add(propertyId);
-    expected.add(propertyNumber);
-    expected.add(propertyUrl);
-    expected.add(propertyStatus);
-    expected.add(propertyEmail);
-    expected.add(propertyName);
-    expected.add(propertyUsername);
-    expected.add(propertyCommitMessage);
-    expected.add(propertyFormatChangeUrl);
+    ImmutableMap<String, String> expected =
+        new ImmutableMap.Builder<String, String>()
+            .put("branch", "testBranch")
+            .put("topic", "testTopic")
+            .put("subject", "testSubject")
+            .put("escapedSubject", "testSubject")
+            .put("changeId", "testId")
+            .put("changeNumber", "4711")
+            .put("changeUrl", "http://www.example.org/test")
+            .put("status", Change.Status.ABANDONED.name())
+            .put("ownerEmail", "testEmail")
+            .put("ownerName", "testName")
+            .put("ownerUsername", "testUsername")
+            .put("commitMessage", "Commit Message")
+            .put("formatChangeUrl", "http://www.example.org/test")
+            .build();
     assertEquals("Properties do not match", expected, actual);
   }
 
@@ -270,68 +182,28 @@
     patchSetAttribute.uploader = uploader;
     patchSetAttribute.author = author;
 
-    Property propertyRevision = createMock(Property.class);
-    expect(propertyFactory.create("revision", "1234567891123456789212345678931234567894"))
-        .andReturn(propertyRevision);
-
-    Property propertyNumber = createMock(Property.class);
-    expect(propertyFactory.create("patchSetNumber", "42")).andReturn(propertyNumber);
-
-    Property propertyRef = createMock(Property.class);
-    expect(propertyFactory.create("ref", "testRef")).andReturn(propertyRef);
-
-    Property propertyCreatedOn = createMock(Property.class);
-    expect(propertyFactory.create("createdOn", "1234567890")).andReturn(propertyCreatedOn);
-
-    Property propertyParents = createMock(Property.class);
-    expect(propertyFactory.create("parents", "[parent1, parent2]")).andReturn(propertyParents);
-
-    Property propertyDeletions = createMock(Property.class);
-    expect(propertyFactory.create("deletions", "7")).andReturn(propertyDeletions);
-
-    Property propertyInsertions = createMock(Property.class);
-    expect(propertyFactory.create("insertions", "12")).andReturn(propertyInsertions);
-
-    Property propertyUploaderEmail = createMock(Property.class);
-    expect(propertyFactory.create("uploaderEmail", "testEmail1")).andReturn(propertyUploaderEmail);
-
-    Property propertyUploaderName = createMock(Property.class);
-    expect(propertyFactory.create("uploaderName", "testName1")).andReturn(propertyUploaderName);
-
-    Property propertyUploaderUsername = createMock(Property.class);
-    expect(propertyFactory.create("uploaderUsername", "testUsername1"))
-        .andReturn(propertyUploaderUsername);
-
-    Property propertyAuthorEmail = createMock(Property.class);
-    expect(propertyFactory.create("authorEmail", "testEmail2")).andReturn(propertyAuthorEmail);
-
-    Property propertyAuthorName = createMock(Property.class);
-    expect(propertyFactory.create("authorName", "testName2")).andReturn(propertyAuthorName);
-
-    Property propertyAuthorUsername = createMock(Property.class);
-    expect(propertyFactory.create("authorUsername", "testUsername2"))
-        .andReturn(propertyAuthorUsername);
-
     replayMocks();
 
     PropertyAttributeExtractor extractor = injector.getInstance(PropertyAttributeExtractor.class);
 
-    Set<Property> actual = extractor.extractFrom(patchSetAttribute);
+    Map<String, String> actual = extractor.extractFrom(patchSetAttribute);
 
-    Set<Property> expected = Sets.newHashSet();
-    expected.add(propertyRevision);
-    expected.add(propertyNumber);
-    expected.add(propertyRef);
-    expected.add(propertyCreatedOn);
-    expected.add(propertyParents);
-    expected.add(propertyDeletions);
-    expected.add(propertyInsertions);
-    expected.add(propertyUploaderEmail);
-    expected.add(propertyUploaderName);
-    expected.add(propertyUploaderUsername);
-    expected.add(propertyAuthorEmail);
-    expected.add(propertyAuthorName);
-    expected.add(propertyAuthorUsername);
+    ImmutableMap<String, String> expected =
+        new ImmutableMap.Builder<String, String>()
+            .put("revision", "1234567891123456789212345678931234567894")
+            .put("patchSetNumber", "42")
+            .put("ref", "testRef")
+            .put("createdOn", "1234567890")
+            .put("parents", "[parent1, parent2]")
+            .put("deletions", "7")
+            .put("insertions", "12")
+            .put("uploaderEmail", "testEmail1")
+            .put("uploaderName", "testName1")
+            .put("uploaderUsername", "testUsername1")
+            .put("authorEmail", "testEmail2")
+            .put("authorName", "testName2")
+            .put("authorUsername", "testUsername2")
+            .build();
     assertEquals("Properties do not match", expected, actual);
   }
 
@@ -341,27 +213,18 @@
     refUpdateAttribute.oldRev = "9876543211987654321298765432139876543214";
     refUpdateAttribute.refName = "testRef";
 
-    Property propertyRevision = createMock(Property.class);
-    expect(propertyFactory.create("revision", "1234567891123456789212345678931234567894"))
-        .andReturn(propertyRevision);
-
-    Property propertyRevisionOld = createMock(Property.class);
-    expect(propertyFactory.create("revisionOld", "9876543211987654321298765432139876543214"))
-        .andReturn(propertyRevisionOld);
-
-    Property propertyRef = createMock(Property.class);
-    expect(propertyFactory.create("ref", "testRef")).andReturn(propertyRef);
-
     replayMocks();
 
     PropertyAttributeExtractor extractor = injector.getInstance(PropertyAttributeExtractor.class);
 
-    Set<Property> actual = extractor.extractFrom(refUpdateAttribute);
+    Map<String, String> actual = extractor.extractFrom(refUpdateAttribute);
 
-    Set<Property> expected = Sets.newHashSet();
-    expected.add(propertyRevision);
-    expected.add(propertyRevisionOld);
-    expected.add(propertyRef);
+    ImmutableMap<String, String> expected =
+        new ImmutableMap.Builder<String, String>()
+            .put("revision", "1234567891123456789212345678931234567894")
+            .put("revisionOld", "9876543211987654321298765432139876543214")
+            .put("ref", "testRef")
+            .build();
     assertEquals("Properties do not match", expected, actual);
   }
 
@@ -370,17 +233,13 @@
     approvalAttribute.type = "TestType";
     approvalAttribute.value = "TestValue";
 
-    Property propertyApproval = createMock(Property.class);
-    expect(propertyFactory.create("approvalTestType", "TestValue")).andReturn(propertyApproval);
-
     replayMocks();
 
     PropertyAttributeExtractor extractor = injector.getInstance(PropertyAttributeExtractor.class);
 
-    Set<Property> actual = extractor.extractFrom(approvalAttribute);
+    Map<String, String> actual = extractor.extractFrom(approvalAttribute);
 
-    Set<Property> expected = Sets.newHashSet();
-    expected.add(propertyApproval);
+    Map<String, String> expected = ImmutableMap.of("approvalTestType", "TestValue");
     assertEquals("Properties do not match", expected, actual);
   }
 
@@ -389,17 +248,13 @@
     approvalAttribute.type = "Test-Type";
     approvalAttribute.value = "TestValue";
 
-    Property propertyApproval = createMock(Property.class);
-    expect(propertyFactory.create("approvalTestType", "TestValue")).andReturn(propertyApproval);
-
     replayMocks();
 
     PropertyAttributeExtractor extractor = injector.getInstance(PropertyAttributeExtractor.class);
 
-    Set<Property> actual = extractor.extractFrom(approvalAttribute);
+    Map<String, String> actual = extractor.extractFrom(approvalAttribute);
 
-    Set<Property> expected = Sets.newHashSet();
-    expected.add(propertyApproval);
+    Map<String, String> expected = ImmutableMap.of("approvalTestType", "TestValue");
     assertEquals("Properties do not match", expected, actual);
   }
 
@@ -414,8 +269,6 @@
     protected void configure() {
       facade = createMock(ItsFacade.class);
       bind(ItsFacade.class).toInstance(facade);
-      propertyFactory = createMock(Property.Factory.class);
-      bind(Property.Factory.class).toInstance(propertyFactory);
     }
   }
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/its/base/util/PropertyExtractorTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/util/PropertyExtractorTest.java
index 8ea6d7b..249abcd 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/its/base/util/PropertyExtractorTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/util/PropertyExtractorTest.java
@@ -16,6 +16,7 @@
 import static org.easymock.EasyMock.expect;
 
 import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import com.google.gerrit.extensions.annotations.PluginName;
@@ -39,35 +40,25 @@
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.googlesource.gerrit.plugins.its.base.testutil.LoggingMockingTestCase;
-import com.googlesource.gerrit.plugins.its.base.workflow.Property;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
 
 public class PropertyExtractorTest extends LoggingMockingTestCase {
   private Injector injector;
 
   private IssueExtractor issueExtractor;
-  private Property.Factory propertyFactory;
   private PropertyAttributeExtractor propertyAttributeExtractor;
 
   public void testDummyChangeEvent() {
     PropertyExtractor propertyExtractor = injector.getInstance(PropertyExtractor.class);
 
-    Property property1 = createMock(Property.class);
-    expect(
-            propertyFactory.create(
-                "event",
-                "com.googlesource.gerrit.plugins."
-                    + "its.base.util.PropertyExtractorTest$DummyEvent"))
-        .andReturn(property1);
-    Property propertyProject = createMock(Property.class);
-    expect(propertyFactory.create("project", "testProject")).andReturn(propertyProject);
-
     replayMocks();
 
-    Set<Set<Property>> actual = propertyExtractor.extractFrom(new DummyEvent());
+    Set<Map<String, String>> actual = propertyExtractor.extractFrom(new DummyEvent());
 
-    Set<Set<Property>> expected = Sets.newHashSet();
+    Set<Map<String, String>> expected = new HashSet<>();
     assertEquals("Properties do not match", expected, actual);
   }
 
@@ -76,41 +67,36 @@
 
     ChangeAttribute changeAttribute = createMock(ChangeAttribute.class);
     event.change = Suppliers.ofInstance(changeAttribute);
-    Property propertyChange = createMock(Property.class);
-    expect(propertyAttributeExtractor.extractFrom(changeAttribute))
-        .andReturn(Sets.newHashSet(propertyChange));
+    Map<String, String> changeProperties =
+        ImmutableMap.of("project", "testProject", "changeNumber", "176");
+    expect(propertyAttributeExtractor.extractFrom(changeAttribute)).andReturn(changeProperties);
 
     AccountAttribute accountAttribute = createMock(AccountAttribute.class);
     event.abandoner = Suppliers.ofInstance(accountAttribute);
-    Property propertySubmitter = createMock(Property.class);
+    Map<String, String> accountProperties = ImmutableMap.of("abandonerName", "testName");
     expect(propertyAttributeExtractor.extractFrom(accountAttribute, "abandoner"))
-        .andReturn(Sets.newHashSet(propertySubmitter));
+        .andReturn(accountProperties);
 
     PatchSetAttribute patchSetAttribute = createMock(PatchSetAttribute.class);
     event.patchSet = Suppliers.ofInstance(patchSetAttribute);
-    Property propertyPatchSet = createMock(Property.class);
-    expect(propertyAttributeExtractor.extractFrom(patchSetAttribute))
-        .andReturn(Sets.newHashSet(propertyPatchSet));
+    Map<String, String> patchSetProperties =
+        ImmutableMap.of("revision", "testRevision", "patchSetNumber", "3");
+    expect(propertyAttributeExtractor.extractFrom(patchSetAttribute)).andReturn(patchSetProperties);
 
     event.reason = "testReason";
-    Property propertyReason = createMock(Property.class);
-    expect(propertyFactory.create("reason", "testReason")).andReturn(propertyReason);
-
-    event.project = new Project.NameKey("testProject");
-    Property propertyProject = createMock(Property.class);
-    expect(propertyFactory.create("project", "testProject")).andReturn(propertyProject);
-
     changeAttribute.project = "testProject";
     changeAttribute.number = 176;
     patchSetAttribute.revision = "testRevision";
     patchSetAttribute.number = 3;
 
-    Set<Property> common = Sets.newHashSet();
-    common.add(propertyProject);
-    common.add(propertyChange);
-    common.add(propertySubmitter);
-    common.add(propertyPatchSet);
-    common.add(propertyReason);
+    Map<String, String> common =
+        ImmutableMap.<String, String>builder()
+            .putAll(changeProperties)
+            .putAll(accountProperties)
+            .putAll(patchSetProperties)
+            .put("reason", "testReason")
+            .put("ref", "refs/heads/testBranch")
+            .build();
 
     eventHelper(event, "ChangeAbandonedEvent", "change-abandoned", common, true);
   }
@@ -120,36 +106,34 @@
 
     ChangeAttribute changeAttribute = createMock(ChangeAttribute.class);
     event.change = Suppliers.ofInstance(changeAttribute);
-    Property propertyChange = createMock(Property.class);
-    expect(propertyAttributeExtractor.extractFrom(changeAttribute))
-        .andReturn(Sets.newHashSet(propertyChange));
+    Map<String, String> changeProperties =
+        ImmutableMap.of("project", "testProject", "changeNumber", "176");
+    expect(propertyAttributeExtractor.extractFrom(changeAttribute)).andReturn(changeProperties);
 
     AccountAttribute accountAttribute = createMock(AccountAttribute.class);
     event.submitter = Suppliers.ofInstance(accountAttribute);
-    Property propertySubmitter = createMock(Property.class);
+    Map<String, String> accountProperties = ImmutableMap.of("submitterName", "testName");
     expect(propertyAttributeExtractor.extractFrom(accountAttribute, "submitter"))
-        .andReturn(Sets.newHashSet(propertySubmitter));
+        .andReturn(accountProperties);
 
     PatchSetAttribute patchSetAttribute = createMock(PatchSetAttribute.class);
     event.patchSet = Suppliers.ofInstance(patchSetAttribute);
-    Property propertyPatchSet = createMock(Property.class);
-    expect(propertyAttributeExtractor.extractFrom(patchSetAttribute))
-        .andReturn(Sets.newHashSet(propertyPatchSet));
-
-    event.project = new Project.NameKey("testProject");
-    Property propertyProject = createMock(Property.class);
-    expect(propertyFactory.create("project", "testProject")).andReturn(propertyProject);
+    Map<String, String> patchSetProperties =
+        ImmutableMap.of("revision", "testRevision", "patchSetNumber", "3");
+    expect(propertyAttributeExtractor.extractFrom(patchSetAttribute)).andReturn(patchSetProperties);
 
     changeAttribute.project = "testProject";
     changeAttribute.number = 176;
     patchSetAttribute.revision = "testRevision";
     patchSetAttribute.number = 3;
 
-    Set<Property> common = Sets.newHashSet();
-    common.add(propertyProject);
-    common.add(propertyChange);
-    common.add(propertySubmitter);
-    common.add(propertyPatchSet);
+    Map<String, String> common =
+        ImmutableMap.<String, String>builder()
+            .putAll(changeProperties)
+            .putAll(accountProperties)
+            .putAll(patchSetProperties)
+            .put("ref", "refs/heads/testBranch")
+            .build();
 
     eventHelper(event, "ChangeMergedEvent", "change-merged", common, true);
   }
@@ -159,41 +143,35 @@
 
     ChangeAttribute changeAttribute = createMock(ChangeAttribute.class);
     event.change = Suppliers.ofInstance(changeAttribute);
-    Property propertyChange = createMock(Property.class);
-    expect(propertyAttributeExtractor.extractFrom(changeAttribute))
-        .andReturn(Sets.newHashSet(propertyChange));
+    Map<String, String> changeProperties =
+        ImmutableMap.of("project", "testProject", "changeNumber", "176");
+    expect(propertyAttributeExtractor.extractFrom(changeAttribute)).andReturn(changeProperties);
 
     AccountAttribute accountAttribute = createMock(AccountAttribute.class);
     event.restorer = Suppliers.ofInstance(accountAttribute);
-    Property propertySubmitter = createMock(Property.class);
+    Map<String, String> accountProperties = ImmutableMap.of("restorerName", "testName");
     expect(propertyAttributeExtractor.extractFrom(accountAttribute, "restorer"))
-        .andReturn(Sets.newHashSet(propertySubmitter));
+        .andReturn(accountProperties);
 
     PatchSetAttribute patchSetAttribute = createMock(PatchSetAttribute.class);
     event.patchSet = Suppliers.ofInstance(patchSetAttribute);
-    Property propertyPatchSet = createMock(Property.class);
-    expect(propertyAttributeExtractor.extractFrom(patchSetAttribute))
-        .andReturn(Sets.newHashSet(propertyPatchSet));
+    Map<String, String> patchSetProperties =
+        ImmutableMap.of("revision", "testRevision", "patchSetNumber", "3");
+    expect(propertyAttributeExtractor.extractFrom(patchSetAttribute)).andReturn(patchSetProperties);
 
     event.reason = "testReason";
-    Property propertyReason = createMock(Property.class);
-    expect(propertyFactory.create("reason", "testReason")).andReturn(propertyReason);
-
-    event.project = new Project.NameKey("testProject");
-    Property propertyProject = createMock(Property.class);
-    expect(propertyFactory.create("project", "testProject")).andReturn(propertyProject);
-
     changeAttribute.project = "testProject";
     changeAttribute.number = 176;
     patchSetAttribute.revision = "testRevision";
     patchSetAttribute.number = 3;
 
-    Set<Property> common = Sets.newHashSet();
-    common.add(propertyProject);
-    common.add(propertyChange);
-    common.add(propertySubmitter);
-    common.add(propertyPatchSet);
-    common.add(propertyReason);
+    Map<String, String> common =
+        ImmutableMap.<String, String>builder()
+            .putAll(changeProperties)
+            .putAll(accountProperties)
+            .putAll(patchSetProperties)
+            .put("ref", "refs/heads/testBranch")
+            .build();
 
     eventHelper(event, "ChangeRestoredEvent", "change-restored", common, true);
   }
@@ -203,41 +181,36 @@
 
     ChangeAttribute changeAttribute = createMock(ChangeAttribute.class);
     event.change = Suppliers.ofInstance(changeAttribute);
-    Property propertyChange = createMock(Property.class);
-    expect(propertyAttributeExtractor.extractFrom(changeAttribute))
-        .andReturn(Sets.newHashSet(propertyChange));
+    Map<String, String> changeProperties =
+        ImmutableMap.of("project", "testProject", "changeNumber", "176");
+    expect(propertyAttributeExtractor.extractFrom(changeAttribute)).andReturn(changeProperties);
 
     AccountAttribute accountAttribute = createMock(AccountAttribute.class);
     event.author = Suppliers.ofInstance(accountAttribute);
-    Property propertySubmitter = createMock(Property.class);
+    Map<String, String> accountProperties = ImmutableMap.of("commenterName", "testName");
     expect(propertyAttributeExtractor.extractFrom(accountAttribute, "commenter"))
-        .andReturn(Sets.newHashSet(propertySubmitter));
+        .andReturn(accountProperties);
 
     PatchSetAttribute patchSetAttribute = createMock(PatchSetAttribute.class);
     event.patchSet = Suppliers.ofInstance(patchSetAttribute);
-    Property propertyPatchSet = createMock(Property.class);
-    expect(propertyAttributeExtractor.extractFrom(patchSetAttribute))
-        .andReturn(Sets.newHashSet(propertyPatchSet));
+    Map<String, String> patchSetProperties =
+        ImmutableMap.of("revision", "testRevision", "patchSetNumber", "3");
+    expect(propertyAttributeExtractor.extractFrom(patchSetAttribute)).andReturn(patchSetProperties);
 
     event.comment = "testComment";
-    Property propertyComment = createMock(Property.class);
-    expect(propertyFactory.create("comment", "testComment")).andReturn(propertyComment);
-
-    event.project = new Project.NameKey("testProject");
-    Property propertyProject = createMock(Property.class);
-    expect(propertyFactory.create("project", "testProject")).andReturn(propertyProject);
-
     changeAttribute.project = "testProject";
     changeAttribute.number = 176;
     patchSetAttribute.revision = "testRevision";
     patchSetAttribute.number = 3;
 
-    Set<Property> common = Sets.newHashSet();
-    common.add(propertyProject);
-    common.add(propertyChange);
-    common.add(propertySubmitter);
-    common.add(propertyPatchSet);
-    common.add(propertyComment);
+    Map<String, String> common =
+        ImmutableMap.<String, String>builder()
+            .putAll(changeProperties)
+            .putAll(accountProperties)
+            .putAll(patchSetProperties)
+            .put("ref", "refs/heads/testBranch")
+            .put("comment", "testComment")
+            .build();
 
     eventHelper(event, "CommentAddedEvent", "comment-added", common, true);
   }
@@ -247,54 +220,49 @@
 
     ChangeAttribute changeAttribute = createMock(ChangeAttribute.class);
     event.change = Suppliers.ofInstance(changeAttribute);
-    Property propertyChange = createMock(Property.class);
-    expect(propertyAttributeExtractor.extractFrom(changeAttribute))
-        .andReturn(Sets.newHashSet(propertyChange));
+    Map<String, String> changeProperties =
+        ImmutableMap.of("project", "testProject", "changeNumber", "176");
+    expect(propertyAttributeExtractor.extractFrom(changeAttribute)).andReturn(changeProperties);
 
     AccountAttribute accountAttribute = createMock(AccountAttribute.class);
     event.author = Suppliers.ofInstance(accountAttribute);
-    Property propertySubmitter = createMock(Property.class);
+    Map<String, String> accountProperties = ImmutableMap.of("commenterName", "testName");
     expect(propertyAttributeExtractor.extractFrom(accountAttribute, "commenter"))
-        .andReturn(Sets.newHashSet(propertySubmitter));
+        .andReturn(accountProperties);
 
     PatchSetAttribute patchSetAttribute = createMock(PatchSetAttribute.class);
     event.patchSet = Suppliers.ofInstance(patchSetAttribute);
-    Property propertyPatchSet = createMock(Property.class);
-    expect(propertyAttributeExtractor.extractFrom(patchSetAttribute))
-        .andReturn(Sets.newHashSet(propertyPatchSet));
+    Map<String, String> patchSetProperties =
+        ImmutableMap.of("revision", "testRevision", "patchSetNumber", "3");
+    expect(propertyAttributeExtractor.extractFrom(patchSetAttribute)).andReturn(patchSetProperties);
 
     ApprovalAttribute approvalAttribute1 = createMock(ApprovalAttribute.class);
-    Property propertyApproval1 = createMock(Property.class);
+    Map<String, String> approuvalProperties1 = ImmutableMap.of("approvalCodeReview", "0");
     expect(propertyAttributeExtractor.extractFrom(approvalAttribute1))
-        .andReturn(Sets.newHashSet(propertyApproval1));
+        .andReturn(approuvalProperties1);
     ApprovalAttribute approvalAttribute2 = createMock(ApprovalAttribute.class);
-    Property propertyApproval2 = createMock(Property.class);
+    Map<String, String> approuvalProperties2 = ImmutableMap.of("approvalVerified", "0");
     expect(propertyAttributeExtractor.extractFrom(approvalAttribute2))
-        .andReturn(Sets.newHashSet(propertyApproval2));
+        .andReturn(approuvalProperties2);
     ApprovalAttribute[] approvalAttributes = {approvalAttribute1, approvalAttribute2};
     event.approvals = Suppliers.ofInstance(approvalAttributes);
 
     event.comment = "testComment";
-    Property propertyComment = createMock(Property.class);
-    expect(propertyFactory.create("comment", "testComment")).andReturn(propertyComment);
-
-    event.project = new Project.NameKey("testProject");
-    Property propertyProject = createMock(Property.class);
-    expect(propertyFactory.create("project", "testProject")).andReturn(propertyProject);
-
     changeAttribute.project = "testProject";
     changeAttribute.number = 176;
     patchSetAttribute.revision = "testRevision";
     patchSetAttribute.number = 3;
 
-    Set<Property> common = Sets.newHashSet();
-    common.add(propertyProject);
-    common.add(propertyChange);
-    common.add(propertySubmitter);
-    common.add(propertyPatchSet);
-    common.add(propertyComment);
-    common.add(propertyApproval1);
-    common.add(propertyApproval2);
+    Map<String, String> common =
+        ImmutableMap.<String, String>builder()
+            .putAll(changeProperties)
+            .putAll(accountProperties)
+            .putAll(patchSetProperties)
+            .putAll(approuvalProperties1)
+            .putAll(approuvalProperties2)
+            .put("ref", "refs/heads/testBranch")
+            .put("comment", "testComment")
+            .build();
 
     eventHelper(event, "CommentAddedEvent", "comment-added", common, true);
   }
@@ -304,36 +272,34 @@
 
     ChangeAttribute changeAttribute = createMock(ChangeAttribute.class);
     event.change = Suppliers.ofInstance(changeAttribute);
-    Property propertyChange = createMock(Property.class);
-    expect(propertyAttributeExtractor.extractFrom(changeAttribute))
-        .andReturn(Sets.newHashSet(propertyChange));
+    Map<String, String> changeProperties =
+        ImmutableMap.of("project", "testProject", "changeNumber", "176");
+    expect(propertyAttributeExtractor.extractFrom(changeAttribute)).andReturn(changeProperties);
 
     AccountAttribute accountAttribute = createMock(AccountAttribute.class);
     event.uploader = Suppliers.ofInstance(accountAttribute);
-    Property propertySubmitter = createMock(Property.class);
+    Map<String, String> accountProperties = ImmutableMap.of("uploaderName", "testName");
     expect(propertyAttributeExtractor.extractFrom(accountAttribute, "uploader"))
-        .andReturn(Sets.newHashSet(propertySubmitter));
+        .andReturn(accountProperties);
 
     PatchSetAttribute patchSetAttribute = createMock(PatchSetAttribute.class);
     event.patchSet = Suppliers.ofInstance(patchSetAttribute);
-    Property propertyPatchSet = createMock(Property.class);
-    expect(propertyAttributeExtractor.extractFrom(patchSetAttribute))
-        .andReturn(Sets.newHashSet(propertyPatchSet));
-
-    event.project = new Project.NameKey("testProject");
-    Property propertyProject = createMock(Property.class);
-    expect(propertyFactory.create("project", "testProject")).andReturn(propertyProject);
+    Map<String, String> patchSetProperties =
+        ImmutableMap.of("revision", "testRevision", "patchSetNumber", "3");
+    expect(propertyAttributeExtractor.extractFrom(patchSetAttribute)).andReturn(patchSetProperties);
 
     changeAttribute.project = "testProject";
     changeAttribute.number = 176;
     patchSetAttribute.revision = "testRevision";
     patchSetAttribute.number = 3;
 
-    Set<Property> common = Sets.newHashSet();
-    common.add(propertyProject);
-    common.add(propertyChange);
-    common.add(propertySubmitter);
-    common.add(propertyPatchSet);
+    Map<String, String> common =
+        ImmutableMap.<String, String>builder()
+            .putAll(changeProperties)
+            .putAll(accountProperties)
+            .putAll(patchSetProperties)
+            .put("ref", "refs/heads/testBranch")
+            .build();
 
     eventHelper(event, "PatchSetCreatedEvent", "patchset-created", common, true);
   }
@@ -343,61 +309,41 @@
 
     AccountAttribute accountAttribute = createMock(AccountAttribute.class);
     event.submitter = Suppliers.ofInstance(accountAttribute);
-    Property propertySubmitter = createMock(Property.class);
+    Map<String, String> accountProperties = ImmutableMap.of("submitterName", "testName");
     expect(propertyAttributeExtractor.extractFrom(accountAttribute, "submitter"))
-        .andReturn(Sets.newHashSet(propertySubmitter));
+        .andReturn(accountProperties);
 
     RefUpdateAttribute refUpdateAttribute = createMock(RefUpdateAttribute.class);
     event.refUpdate = Suppliers.ofInstance(refUpdateAttribute);
-    Property propertyRefUpdated = createMock(Property.class);
+    Map<String, String> refUpdatedProperties =
+        ImmutableMap.of("revision", "testRevision", "revisionOld", "oldRevision");
     expect(propertyAttributeExtractor.extractFrom(refUpdateAttribute))
-        .andReturn(Sets.newHashSet(propertyRefUpdated));
-
-    Property propertyProject = createMock(Property.class);
-    expect(propertyFactory.create("project", "testProject")).andReturn(propertyProject).anyTimes();
+        .andReturn(refUpdatedProperties);
 
     refUpdateAttribute.project = "testProject";
     refUpdateAttribute.newRev = "testRevision";
+    refUpdateAttribute.oldRev = "oldRevision";
+    refUpdateAttribute.refName = "testBranch";
 
-    Set<Property> common = Sets.newHashSet();
-    common.add(propertyProject);
-    common.add(propertySubmitter);
-    common.add(propertyRefUpdated);
+    Map<String, String> common =
+        ImmutableMap.<String, String>builder()
+            .putAll(accountProperties)
+            .putAll(refUpdatedProperties)
+            .put("ref", "testBranch")
+            .put("project", "testProject")
+            .build();
 
     eventHelper(event, "RefUpdatedEvent", "ref-updated", common, false);
   }
 
   private void eventHelper(
-      RefEvent event, String className, String type, Set<Property> common, boolean withRevision) {
+      RefEvent event,
+      String className,
+      String type,
+      Map<String, String> common,
+      boolean withRevision) {
     PropertyExtractor propertyExtractor = injector.getInstance(PropertyExtractor.class);
 
-    Property propertyItsName = createMock(Property.class);
-    expect(propertyFactory.create("its-name", "ItsTestName")).andReturn(propertyItsName).anyTimes();
-
-    Property propertyEvent = createMock(Property.class);
-    expect(propertyFactory.create("event", "com.google.gerrit.server.events." + className))
-        .andReturn(propertyEvent);
-
-    Property propertyEventType = createMock(Property.class);
-    expect(propertyFactory.create("event-type", type)).andReturn(propertyEventType);
-
-    Property propertyAssociationFooter = createMock(Property.class);
-    expect(propertyFactory.create("association", "footer")).andReturn(propertyAssociationFooter);
-
-    Property propertyAssociationAnywhere = createMock(Property.class);
-    expect(propertyFactory.create("association", "anywhere"))
-        .andReturn(propertyAssociationAnywhere)
-        .times(2);
-
-    Property propertyAssociationBody = createMock(Property.class);
-    expect(propertyFactory.create("association", "body")).andReturn(propertyAssociationBody);
-
-    Property propertyIssue42 = createMock(Property.class);
-    expect(propertyFactory.create("issue", "42")).andReturn(propertyIssue42);
-
-    Property propertyIssue4711 = createMock(Property.class);
-    expect(propertyFactory.create("issue", "4711")).andReturn(propertyIssue4711);
-
     HashMap<String, Set<String>> issueMap = Maps.newHashMap();
     issueMap.put("4711", Sets.newHashSet("body", "anywhere"));
     issueMap.put("42", Sets.newHashSet("footer", "anywhere"));
@@ -411,28 +357,30 @@
 
     replayMocks();
 
-    Set<Set<Property>> actual = propertyExtractor.extractFrom(event);
+    Set<Map<String, String>> actual = propertyExtractor.extractFrom(event);
 
-    Set<Set<Property>> expected = Sets.newHashSet();
-    Set<Property> properties = Sets.newHashSet();
-    properties.add(propertyItsName);
-    properties.add(propertyEvent);
-    properties.add(propertyEventType);
-    properties.add(propertyAssociationAnywhere);
-    properties.add(propertyAssociationFooter);
-    properties.add(propertyIssue42);
-    properties.addAll(common);
-    expected.add(properties);
+    Map<String, String> propertiesIssue4711 =
+        ImmutableMap.<String, String>builder()
+            .put("itsName", "ItsTestName")
+            .put("event", "com.google.gerrit.server.events." + className)
+            .put("event-type", type)
+            .put("association", "body anywhere")
+            .put("issue", "4711")
+            .putAll(common)
+            .build();
+    Map<String, String> propertiesIssue42 =
+        ImmutableMap.<String, String>builder()
+            .put("itsName", "ItsTestName")
+            .put("event", "com.google.gerrit.server.events." + className)
+            .put("event-type", type)
+            .put("association", "anywhere footer")
+            .put("issue", "42")
+            .putAll(common)
+            .build();
+    Set<Map<String, String>> expected = new HashSet<>();
+    expected.add(propertiesIssue4711);
+    expected.add(propertiesIssue42);
 
-    properties = Sets.newHashSet();
-    properties.add(propertyItsName);
-    properties.add(propertyEvent);
-    properties.add(propertyEventType);
-    properties.add(propertyAssociationAnywhere);
-    properties.add(propertyAssociationBody);
-    properties.add(propertyIssue4711);
-    properties.addAll(common);
-    expected.add(properties);
     assertEquals("Properties do not match", expected, actual);
   }
 
@@ -452,9 +400,6 @@
 
       propertyAttributeExtractor = createMock(PropertyAttributeExtractor.class);
       bind(PropertyAttributeExtractor.class).toInstance(propertyAttributeExtractor);
-
-      propertyFactory = createMock(Property.Factory.class);
-      bind(Property.Factory.class).toInstance(propertyFactory);
     }
   }
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionControllerTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionControllerTest.java
index 7c85f7f..4f8b4ef 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionControllerTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionControllerTest.java
@@ -16,8 +16,9 @@
 import static org.easymock.EasyMock.anyObject;
 import static org.easymock.EasyMock.expect;
 
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.gerrit.server.events.ChangeEvent;
 import com.google.gerrit.server.events.RefEvent;
@@ -28,6 +29,8 @@
 import com.googlesource.gerrit.plugins.its.base.util.PropertyExtractor;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
 
 public class ActionControllerTest extends LoggingMockingTestCase {
@@ -43,7 +46,7 @@
 
     ChangeEvent event = createMock(ChangeEvent.class);
 
-    Set<Set<Property>> propertySets = Collections.emptySet();
+    Set<Map<String, String>> propertySets = new HashSet<>();
     expect(propertyExtractor.extractFrom(event)).andReturn(propertySets).anyTimes();
 
     replayMocks();
@@ -51,117 +54,73 @@
     actionController.onEvent(event);
   }
 
-  public void testNoActions() {
+  public void testNoActionsOrNotIssues() {
     ActionController actionController = createActionController();
 
     ChangeEvent event = createMock(ChangeEvent.class);
 
-    Set<Set<Property>> propertySets = Sets.newHashSet();
-    Set<Property> propertySet = Collections.emptySet();
-    propertySets.add(propertySet);
+    Set<Map<String, String>> propertySets = new HashSet<>();
+    Map<String, String> properties = ImmutableMap.of("fake", "property");
+    propertySets.add(properties);
 
     expect(propertyExtractor.extractFrom(event)).andReturn(propertySets).anyTimes();
 
+    // When no issues are found in the commit message, the list of actions is empty
+    // as there are no matchs with an empty map of properties.
     Collection<ActionRequest> actions = Collections.emptySet();
-    expect(ruleBase.actionRequestsFor(propertySet)).andReturn(actions).once();
+    expect(ruleBase.actionRequestsFor(properties)).andReturn(actions).once();
 
     replayMocks();
 
     actionController.onEvent(event);
   }
 
-  public void testNoIssues() {
+  public void testSinglePropertyMapSingleActionSingleIssue() {
     ActionController actionController = createActionController();
 
     ChangeEvent event = createMock(ChangeEvent.class);
 
-    Set<Set<Property>> propertySets = Sets.newHashSet();
-    Set<Property> propertySet = Collections.emptySet();
-    propertySets.add(propertySet);
+    Map<String, String> properties = ImmutableMap.of("issue", "testIssue");
+
+    Set<Map<String, String>> propertySets = ImmutableSet.of(properties);
 
     expect(propertyExtractor.extractFrom(event)).andReturn(propertySets).anyTimes();
 
-    Collection<ActionRequest> actions = Lists.newArrayListWithCapacity(1);
-    ActionRequest action1 = createMock(ActionRequest.class);
-    actions.add(action1);
-    expect(ruleBase.actionRequestsFor(propertySet)).andReturn(actions).once();
-
-    replayMocks();
-
-    actionController.onEvent(event);
-  }
-
-  public void testSinglePropertySetSingleActionSingleIssue() {
-    ActionController actionController = createActionController();
-
-    ChangeEvent event = createMock(ChangeEvent.class);
-
-    Property propertyIssue1 = createMock(Property.class);
-    expect(propertyIssue1.getKey()).andReturn("issue").anyTimes();
-    expect(propertyIssue1.getValue()).andReturn("testIssue").anyTimes();
-
-    Set<Property> propertySet = Sets.newHashSet();
-    propertySet.add(propertyIssue1);
-
-    Set<Set<Property>> propertySets = Sets.newHashSet();
-    propertySets.add(propertySet);
-
-    expect(propertyExtractor.extractFrom(event)).andReturn(propertySets).anyTimes();
-
-    Collection<ActionRequest> actionRequests = Lists.newArrayListWithCapacity(1);
     ActionRequest actionRequest1 = createMock(ActionRequest.class);
-    actionRequests.add(actionRequest1);
-    expect(ruleBase.actionRequestsFor(propertySet)).andReturn(actionRequests).once();
+    Collection<ActionRequest> actionRequests = ImmutableList.of(actionRequest1);
+    expect(ruleBase.actionRequestsFor(properties)).andReturn(actionRequests).once();
 
-    actionExecutor.execute("testIssue", actionRequests, propertySet);
+    actionExecutor.execute(actionRequests, properties);
 
     replayMocks();
 
     actionController.onEvent(event);
   }
 
-  public void testMultiplePropertySetsMultipleActionMultipleIssue() {
+  public void testMultiplePropertyMapsMultipleActionMultipleIssue() {
     ActionController actionController = createActionController();
 
     ChangeEvent event = createMock(ChangeEvent.class);
 
-    Property propertyIssue1 = createMock(Property.class);
-    expect(propertyIssue1.getKey()).andReturn("issue").anyTimes();
-    expect(propertyIssue1.getValue()).andReturn("testIssue").anyTimes();
+    Map<String, String> properties1 = ImmutableMap.of("issue", "testIssue");
+    Map<String, String> properties2 = ImmutableMap.of("issue", "testIssue2");
 
-    Property propertyIssue2 = createMock(Property.class);
-    expect(propertyIssue2.getKey()).andReturn("issue").anyTimes();
-    expect(propertyIssue2.getValue()).andReturn("testIssue2").anyTimes();
-
-    Set<Property> propertySet1 = Sets.newHashSet();
-    propertySet1.add(propertyIssue1);
-
-    Set<Property> propertySet2 = Sets.newHashSet();
-    propertySet2.add(propertyIssue1);
-    propertySet2.add(propertyIssue2);
-
-    Set<Set<Property>> propertySets = Sets.newHashSet();
-    propertySets.add(propertySet1);
-    propertySets.add(propertySet2);
+    Set<Map<String, String>> propertySets = ImmutableSet.of(properties1, properties2);
 
     expect(propertyExtractor.extractFrom(event)).andReturn(propertySets).anyTimes();
 
-    Collection<ActionRequest> actionRequests1 = Lists.newArrayListWithCapacity(1);
     ActionRequest actionRequest1 = createMock(ActionRequest.class);
-    actionRequests1.add(actionRequest1);
+    Collection<ActionRequest> actionRequests1 = ImmutableList.of(actionRequest1);
 
-    Collection<ActionRequest> actionRequests2 = Lists.newArrayListWithCapacity(2);
     ActionRequest actionRequest2 = createMock(ActionRequest.class);
-    actionRequests2.add(actionRequest2);
     ActionRequest actionRequest3 = createMock(ActionRequest.class);
-    actionRequests2.add(actionRequest3);
+    Collection<ActionRequest> actionRequests2 = ImmutableList.of(actionRequest2, actionRequest3);
 
-    expect(ruleBase.actionRequestsFor(propertySet1)).andReturn(actionRequests1).once();
-    expect(ruleBase.actionRequestsFor(propertySet2)).andReturn(actionRequests2).once();
+    expect(ruleBase.actionRequestsFor(properties1)).andReturn(actionRequests1).once();
+    expect(ruleBase.actionRequestsFor(properties2)).andReturn(actionRequests2).once();
 
-    actionExecutor.execute("testIssue", actionRequests1, propertySet1);
-    actionExecutor.execute("testIssue", actionRequests2, propertySet2);
-    actionExecutor.execute("testIssue2", actionRequests2, propertySet2);
+    actionExecutor.execute(actionRequests1, properties1);
+    actionExecutor.execute(actionRequests2, properties2);
 
     replayMocks();
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionExecutorTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionExecutorTest.java
index 85173ab..be0c33a 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionExecutorTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ActionExecutorTest.java
@@ -16,14 +16,15 @@
 import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.expectLastCall;
 
-import com.google.common.collect.Sets;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.googlesource.gerrit.plugins.its.base.its.ItsFacade;
 import com.googlesource.gerrit.plugins.its.base.testutil.LoggingMockingTestCase;
 import java.io.IOException;
-import java.util.Collections;
+import java.util.Map;
 import java.util.Set;
 
 public class ActionExecutorTest extends LoggingMockingTestCase {
@@ -35,19 +36,21 @@
   private AddSoyComment.Factory addSoyCommentFactory;
   private LogEvent.Factory logEventFactory;
 
+  private Map<String, String> properties = ImmutableMap.of("issue", "4711");
+
   public void testExecuteItem() throws IOException {
     ActionRequest actionRequest = createMock(ActionRequest.class);
     expect(actionRequest.getName()).andReturn("unparsed");
     expect(actionRequest.getUnparsed()).andReturn("unparsed action 1");
 
-    Set<Property> properties = Collections.emptySet();
+    Set<ActionRequest> actionRequests = ImmutableSet.of(actionRequest);
 
     its.performAction("4711", "unparsed action 1");
 
     replayMocks();
 
     ActionExecutor actionExecutor = createActionExecutor();
-    actionExecutor.execute("4711", actionRequest, properties);
+    actionExecutor.execute(actionRequests, properties);
   }
 
   public void testExecuteItemException() throws IOException {
@@ -55,7 +58,7 @@
     expect(actionRequest.getName()).andReturn("unparsed");
     expect(actionRequest.getUnparsed()).andReturn("unparsed action 1");
 
-    Set<Property> properties = Collections.emptySet();
+    Set<ActionRequest> actionRequests = ImmutableSet.of(actionRequest);
 
     its.performAction("4711", "unparsed action 1");
     expectLastCall().andThrow(new IOException("injected exception 1"));
@@ -63,7 +66,7 @@
     replayMocks();
 
     ActionExecutor actionExecutor = createActionExecutor();
-    actionExecutor.execute("4711", actionRequest, properties);
+    actionExecutor.execute(actionRequests, properties);
 
     assertLogThrowableMessageContains("injected exception 1");
   }
@@ -77,7 +80,7 @@
     expect(actionRequest2.getName()).andReturn("unparsed");
     expect(actionRequest2.getUnparsed()).andReturn("unparsed action 2");
 
-    Set<Property> properties = Collections.emptySet();
+    Set<ActionRequest> actionRequests = ImmutableSet.of(actionRequest1, actionRequest2);
 
     its.performAction("4711", "unparsed action 1");
     its.performAction("4711", "unparsed action 2");
@@ -85,7 +88,7 @@
     replayMocks();
 
     ActionExecutor actionExecutor = createActionExecutor();
-    actionExecutor.execute("4711", Sets.newHashSet(actionRequest1, actionRequest2), properties);
+    actionExecutor.execute(actionRequests, properties);
   }
 
   public void testExecuteIterableExceptions() throws IOException {
@@ -101,7 +104,8 @@
     expect(actionRequest3.getName()).andReturn("unparsed");
     expect(actionRequest3.getUnparsed()).andReturn("unparsed action 3");
 
-    Set<Property> properties = Collections.emptySet();
+    Set<ActionRequest> actionRequests =
+        ImmutableSet.of(actionRequest1, actionRequest2, actionRequest3);
 
     its.performAction("4711", "unparsed action 1");
     expectLastCall().andThrow(new IOException("injected exception 1"));
@@ -112,8 +116,7 @@
     replayMocks();
 
     ActionExecutor actionExecutor = createActionExecutor();
-    actionExecutor.execute(
-        "4711", Sets.newHashSet(actionRequest1, actionRequest2, actionRequest3), properties);
+    actionExecutor.execute(actionRequests, properties);
 
     assertLogThrowableMessageContains("injected exception 1");
     assertLogThrowableMessageContains("injected exception 3");
@@ -123,7 +126,7 @@
     ActionRequest actionRequest = createMock(ActionRequest.class);
     expect(actionRequest.getName()).andReturn("add-comment");
 
-    Set<Property> properties = Collections.emptySet();
+    Set<ActionRequest> actionRequests = ImmutableSet.of(actionRequest);
 
     AddComment addComment = createMock(AddComment.class);
     expect(addCommentFactory.create()).andReturn(addComment);
@@ -133,14 +136,14 @@
     replayMocks();
 
     ActionExecutor actionExecutor = createActionExecutor();
-    actionExecutor.execute("4711", actionRequest, properties);
+    actionExecutor.execute(actionRequests, properties);
   }
 
   public void testAddSoyCommentDelegation() throws IOException {
     ActionRequest actionRequest = createMock(ActionRequest.class);
     expect(actionRequest.getName()).andReturn("add-soy-comment");
 
-    Set<Property> properties = Collections.emptySet();
+    Set<ActionRequest> actionRequests = ImmutableSet.of(actionRequest);
 
     AddSoyComment addSoyComment = createMock(AddSoyComment.class);
     expect(addSoyCommentFactory.create()).andReturn(addSoyComment);
@@ -150,14 +153,14 @@
     replayMocks();
 
     ActionExecutor actionExecutor = createActionExecutor();
-    actionExecutor.execute("4711", actionRequest, properties);
+    actionExecutor.execute(actionRequests, properties);
   }
 
   public void testAddStandardCommentDelegation() throws IOException {
     ActionRequest actionRequest = createMock(ActionRequest.class);
     expect(actionRequest.getName()).andReturn("add-standard-comment");
 
-    Set<Property> properties = Collections.emptySet();
+    Set<ActionRequest> actionRequests = ImmutableSet.of(actionRequest);
 
     AddStandardComment addStandardComment = createMock(AddStandardComment.class);
     expect(addStandardCommentFactory.create()).andReturn(addStandardComment);
@@ -167,14 +170,14 @@
     replayMocks();
 
     ActionExecutor actionExecutor = createActionExecutor();
-    actionExecutor.execute("4711", actionRequest, properties);
+    actionExecutor.execute(actionRequests, properties);
   }
 
   public void testLogEventDelegation() throws IOException {
     ActionRequest actionRequest = createMock(ActionRequest.class);
     expect(actionRequest.getName()).andReturn("log-event");
 
-    Set<Property> properties = Collections.emptySet();
+    Set<ActionRequest> actionRequests = ImmutableSet.of(actionRequest);
 
     LogEvent logEvent = createMock(LogEvent.class);
     expect(logEventFactory.create()).andReturn(logEvent);
@@ -184,7 +187,7 @@
     replayMocks();
 
     ActionExecutor actionExecutor = createActionExecutor();
-    actionExecutor.execute("4711", actionRequest, properties);
+    actionExecutor.execute(actionRequests, properties);
   }
 
   private ActionExecutor createActionExecutor() {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/AddCommentTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/AddCommentTest.java
index 1993406..e11f8f2 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/AddCommentTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/AddCommentTest.java
@@ -15,13 +15,13 @@
 
 import static org.easymock.EasyMock.expect;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.googlesource.gerrit.plugins.its.base.its.ItsFacade;
 import com.googlesource.gerrit.plugins.its.base.testutil.LoggingMockingTestCase;
 import java.io.IOException;
-import java.util.HashSet;
 
 public class AddCommentTest extends LoggingMockingTestCase {
   private Injector injector;
@@ -35,7 +35,7 @@
     replayMocks();
 
     AddComment addComment = createAddComment();
-    addComment.execute("4711", actionRequest, new HashSet<>());
+    addComment.execute("4711", actionRequest, ImmutableMap.of());
   }
 
   public void testPlain() throws IOException {
@@ -47,7 +47,7 @@
     replayMocks();
 
     AddComment addComment = createAddComment();
-    addComment.execute("4711", actionRequest, new HashSet<>());
+    addComment.execute("4711", actionRequest, ImmutableMap.of());
   }
 
   private AddComment createAddComment() {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/AddStandardCommentTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/AddStandardCommentTest.java
index c26ea73..8b21c1d 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/AddStandardCommentTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/AddStandardCommentTest.java
@@ -13,16 +13,14 @@
 // limitations under the License.
 package com.googlesource.gerrit.plugins.its.base.workflow;
 
-import static org.easymock.EasyMock.expect;
-
-import com.google.common.collect.Sets;
+import com.google.common.collect.ImmutableMap;
 import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.googlesource.gerrit.plugins.its.base.its.ItsFacade;
 import com.googlesource.gerrit.plugins.its.base.testutil.LoggingMockingTestCase;
 import java.io.IOException;
-import java.util.Set;
+import java.util.Map;
 
 public class AddStandardCommentTest extends LoggingMockingTestCase {
   private Injector injector;
@@ -32,12 +30,7 @@
   public void testChangeMergedPlain() throws IOException {
     ActionRequest actionRequest = createMock(ActionRequest.class);
 
-    Set<Property> properties = Sets.newHashSet();
-
-    Property propertyEventType = createMock(Property.class);
-    expect(propertyEventType.getKey()).andReturn("event-type").anyTimes();
-    expect(propertyEventType.getValue()).andReturn("change-merged").anyTimes();
-    properties.add(propertyEventType);
+    Map<String, String> properties = ImmutableMap.of("event-type", "change-merged");
 
     its.addComment("42", "Change merged");
     replayMocks();
@@ -49,32 +42,14 @@
   public void testChangeMergedFull() throws IOException {
     ActionRequest actionRequest = createMock(ActionRequest.class);
 
-    Set<Property> properties = Sets.newHashSet();
-
-    Property propertyEventType = createMock(Property.class);
-    expect(propertyEventType.getKey()).andReturn("event-type").anyTimes();
-    expect(propertyEventType.getValue()).andReturn("change-merged").anyTimes();
-    properties.add(propertyEventType);
-
-    Property propertySubject = createMock(Property.class);
-    expect(propertySubject.getKey()).andReturn("subject").anyTimes();
-    expect(propertySubject.getValue()).andReturn("Test-Change-Subject").anyTimes();
-    properties.add(propertySubject);
-
-    Property propertyChangeNumber = createMock(Property.class);
-    expect(propertyChangeNumber.getKey()).andReturn("changeNumber").anyTimes();
-    expect(propertyChangeNumber.getValue()).andReturn("4711").anyTimes();
-    properties.add(propertyChangeNumber);
-
-    Property propertySubmitterName = createMock(Property.class);
-    expect(propertySubmitterName.getKey()).andReturn("submitterName").anyTimes();
-    expect(propertySubmitterName.getValue()).andReturn("John Doe").anyTimes();
-    properties.add(propertySubmitterName);
-
-    Property propertyChangeUrl = createMock(Property.class);
-    expect(propertyChangeUrl.getKey()).andReturn("formatChangeUrl").anyTimes();
-    expect(propertyChangeUrl.getValue()).andReturn("HtTp://ExAmPlE.OrG/ChAnGe").anyTimes();
-    properties.add(propertyChangeUrl);
+    Map<String, String> properties =
+        ImmutableMap.<String, String>builder()
+            .put("event-type", "change-merged")
+            .put("subject", "Test-Change-Subject")
+            .put("changeNumber", "4711")
+            .put("submitterName", "John Doe")
+            .put("formatChangeUrl", "HtTp://ExAmPlE.OrG/ChAnGe")
+            .build();
 
     its.addComment(
         "176",
@@ -91,12 +66,7 @@
   public void testChangeAbandonedPlain() throws IOException {
     ActionRequest actionRequest = createMock(ActionRequest.class);
 
-    Set<Property> properties = Sets.newHashSet();
-
-    Property propertyEventType = createMock(Property.class);
-    expect(propertyEventType.getKey()).andReturn("event-type").anyTimes();
-    expect(propertyEventType.getValue()).andReturn("change-abandoned").anyTimes();
-    properties.add(propertyEventType);
+    Map<String, String> properties = ImmutableMap.of("event-type", "change-abandoned");
 
     its.addComment("42", "Change abandoned");
     replayMocks();
@@ -108,37 +78,15 @@
   public void testChangeAbandonedFull() throws IOException {
     ActionRequest actionRequest = createMock(ActionRequest.class);
 
-    Set<Property> properties = Sets.newHashSet();
-
-    Property propertyEventType = createMock(Property.class);
-    expect(propertyEventType.getKey()).andReturn("event-type").anyTimes();
-    expect(propertyEventType.getValue()).andReturn("change-abandoned").anyTimes();
-    properties.add(propertyEventType);
-
-    Property propertyReason = createMock(Property.class);
-    expect(propertyReason.getKey()).andReturn("reason").anyTimes();
-    expect(propertyReason.getValue()).andReturn("Test-Reason").anyTimes();
-    properties.add(propertyReason);
-
-    Property propertySubject = createMock(Property.class);
-    expect(propertySubject.getKey()).andReturn("subject").anyTimes();
-    expect(propertySubject.getValue()).andReturn("Test-Change-Subject").anyTimes();
-    properties.add(propertySubject);
-
-    Property propertyChangeNumber = createMock(Property.class);
-    expect(propertyChangeNumber.getKey()).andReturn("changeNumber").anyTimes();
-    expect(propertyChangeNumber.getValue()).andReturn("4711").anyTimes();
-    properties.add(propertyChangeNumber);
-
-    Property propertySubmitterName = createMock(Property.class);
-    expect(propertySubmitterName.getKey()).andReturn("abandonerName").anyTimes();
-    expect(propertySubmitterName.getValue()).andReturn("John Doe").anyTimes();
-    properties.add(propertySubmitterName);
-
-    Property propertyChangeUrl = createMock(Property.class);
-    expect(propertyChangeUrl.getKey()).andReturn("formatChangeUrl").anyTimes();
-    expect(propertyChangeUrl.getValue()).andReturn("HtTp://ExAmPlE.OrG/ChAnGe").anyTimes();
-    properties.add(propertyChangeUrl);
+    Map<String, String> properties =
+        ImmutableMap.<String, String>builder()
+            .put("event-type", "change-abandoned")
+            .put("reason", "Test-Reason")
+            .put("subject", "Test-Change-Subject")
+            .put("changeNumber", "4711")
+            .put("abandonerName", "John Doe")
+            .put("formatChangeUrl", "HtTp://ExAmPlE.OrG/ChAnGe")
+            .build();
 
     its.addComment(
         "176",
@@ -158,12 +106,7 @@
   public void testChangeRestoredPlain() throws IOException {
     ActionRequest actionRequest = createMock(ActionRequest.class);
 
-    Set<Property> properties = Sets.newHashSet();
-
-    Property propertyEventType = createMock(Property.class);
-    expect(propertyEventType.getKey()).andReturn("event-type").anyTimes();
-    expect(propertyEventType.getValue()).andReturn("change-restored").anyTimes();
-    properties.add(propertyEventType);
+    Map<String, String> properties = ImmutableMap.of("event-type", "change-restored");
 
     its.addComment("42", "Change restored");
     replayMocks();
@@ -175,37 +118,15 @@
   public void testChangeRestoredFull() throws IOException {
     ActionRequest actionRequest = createMock(ActionRequest.class);
 
-    Set<Property> properties = Sets.newHashSet();
-
-    Property propertyEventType = createMock(Property.class);
-    expect(propertyEventType.getKey()).andReturn("event-type").anyTimes();
-    expect(propertyEventType.getValue()).andReturn("change-restored").anyTimes();
-    properties.add(propertyEventType);
-
-    Property propertyReason = createMock(Property.class);
-    expect(propertyReason.getKey()).andReturn("reason").anyTimes();
-    expect(propertyReason.getValue()).andReturn("Test-Reason").anyTimes();
-    properties.add(propertyReason);
-
-    Property propertySubject = createMock(Property.class);
-    expect(propertySubject.getKey()).andReturn("subject").anyTimes();
-    expect(propertySubject.getValue()).andReturn("Test-Change-Subject").anyTimes();
-    properties.add(propertySubject);
-
-    Property propertyChangeNumber = createMock(Property.class);
-    expect(propertyChangeNumber.getKey()).andReturn("changeNumber").anyTimes();
-    expect(propertyChangeNumber.getValue()).andReturn("4711").anyTimes();
-    properties.add(propertyChangeNumber);
-
-    Property propertySubmitterName = createMock(Property.class);
-    expect(propertySubmitterName.getKey()).andReturn("restorerName").anyTimes();
-    expect(propertySubmitterName.getValue()).andReturn("John Doe").anyTimes();
-    properties.add(propertySubmitterName);
-
-    Property propertyChangeUrl = createMock(Property.class);
-    expect(propertyChangeUrl.getKey()).andReturn("formatChangeUrl").anyTimes();
-    expect(propertyChangeUrl.getValue()).andReturn("HtTp://ExAmPlE.OrG/ChAnGe").anyTimes();
-    properties.add(propertyChangeUrl);
+    Map<String, String> properties =
+        ImmutableMap.<String, String>builder()
+            .put("event-type", "change-restored")
+            .put("reason", "Test-Reason")
+            .put("subject", "Test-Change-Subject")
+            .put("changeNumber", "4711")
+            .put("restorerName", "John Doe")
+            .put("formatChangeUrl", "HtTp://ExAmPlE.OrG/ChAnGe")
+            .build();
 
     its.addComment(
         "176",
@@ -225,12 +146,7 @@
   public void testPatchSetCreatedPlain() throws IOException {
     ActionRequest actionRequest = createMock(ActionRequest.class);
 
-    Set<Property> properties = Sets.newHashSet();
-
-    Property propertyEventType = createMock(Property.class);
-    expect(propertyEventType.getKey()).andReturn("event-type").anyTimes();
-    expect(propertyEventType.getValue()).andReturn("patchset-created").anyTimes();
-    properties.add(propertyEventType);
+    Map<String, String> properties = ImmutableMap.of("event-type", "patchset-created");
 
     its.addComment("42", "Change had a related patch set uploaded");
     replayMocks();
@@ -242,32 +158,14 @@
   public void testPatchSetCreatedFull() throws IOException {
     ActionRequest actionRequest = createMock(ActionRequest.class);
 
-    Set<Property> properties = Sets.newHashSet();
-
-    Property propertyEventType = createMock(Property.class);
-    expect(propertyEventType.getKey()).andReturn("event-type").anyTimes();
-    expect(propertyEventType.getValue()).andReturn("patchset-created").anyTimes();
-    properties.add(propertyEventType);
-
-    Property propertySubject = createMock(Property.class);
-    expect(propertySubject.getKey()).andReturn("subject").anyTimes();
-    expect(propertySubject.getValue()).andReturn("Test-Change-Subject").anyTimes();
-    properties.add(propertySubject);
-
-    Property propertyChangeNumber = createMock(Property.class);
-    expect(propertyChangeNumber.getKey()).andReturn("changeNumber").anyTimes();
-    expect(propertyChangeNumber.getValue()).andReturn("4711").anyTimes();
-    properties.add(propertyChangeNumber);
-
-    Property propertySubmitterName = createMock(Property.class);
-    expect(propertySubmitterName.getKey()).andReturn("uploaderName").anyTimes();
-    expect(propertySubmitterName.getValue()).andReturn("John Doe").anyTimes();
-    properties.add(propertySubmitterName);
-
-    Property propertyChangeUrl = createMock(Property.class);
-    expect(propertyChangeUrl.getKey()).andReturn("formatChangeUrl").anyTimes();
-    expect(propertyChangeUrl.getValue()).andReturn("HtTp://ExAmPlE.OrG/ChAnGe").anyTimes();
-    properties.add(propertyChangeUrl);
+    Map<String, String> properties =
+        ImmutableMap.<String, String>builder()
+            .put("event-type", "patchset-created")
+            .put("subject", "Test-Change-Subject")
+            .put("changeNumber", "4711")
+            .put("uploaderName", "John Doe")
+            .put("formatChangeUrl", "HtTp://ExAmPlE.OrG/ChAnGe")
+            .build();
 
     its.addComment(
         "176",
diff --git a/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ConditionTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ConditionTest.java
index c1a6a8d..3df38cc 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ConditionTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/ConditionTest.java
@@ -13,15 +13,12 @@
 // limitations under the License.
 package com.googlesource.gerrit.plugins.its.base.workflow;
 
-import static org.easymock.EasyMock.expect;
-
-import com.google.common.collect.Lists;
+import com.google.common.collect.ImmutableMap;
 import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.googlesource.gerrit.plugins.its.base.testutil.LoggingMockingTestCase;
-import java.util.Collection;
-import java.util.Collections;
+import java.util.Map;
 
 public class ConditionTest extends LoggingMockingTestCase {
   private Injector injector;
@@ -34,12 +31,7 @@
   public void testIsMetBySimple() {
     Condition condition = createCondition("testKey", "testValue");
 
-    Property property1 = createMock(Property.class);
-    expect(property1.getKey()).andReturn("testKey").anyTimes();
-    expect(property1.getValue()).andReturn("testValue").anyTimes();
-
-    Collection<Property> properties = Lists.newArrayListWithCapacity(1);
-    properties.add(property1);
+    Map<String, String> properties = ImmutableMap.of("testKey", "testValue");
 
     replayMocks();
 
@@ -49,7 +41,7 @@
   public void testIsMetBySimpleEmpty() {
     Condition condition = createCondition("testKey", "testValue");
 
-    Collection<Property> properties = Collections.emptySet();
+    Map<String, String> properties = ImmutableMap.of();
 
     replayMocks();
 
@@ -59,12 +51,7 @@
   public void testIsMetByMismatchedKey() {
     Condition condition = createCondition("testKey", "testValue");
 
-    Property property1 = createMock(Property.class);
-    expect(property1.getKey()).andReturn("otherKey").anyTimes();
-    expect(property1.getValue()).andReturn("testValue").anyTimes();
-
-    Collection<Property> properties = Lists.newArrayListWithCapacity(1);
-    properties.add(property1);
+    Map<String, String> properties = ImmutableMap.of("otherKey", "testValue");
 
     replayMocks();
 
@@ -74,12 +61,7 @@
   public void testIsMetByMismatchedValue() {
     Condition condition = createCondition("testKey", "testValue");
 
-    Property property1 = createMock(Property.class);
-    expect(property1.getKey()).andReturn("testKey").anyTimes();
-    expect(property1.getValue()).andReturn("otherValue").anyTimes();
-
-    Collection<Property> properties = Lists.newArrayListWithCapacity(1);
-    properties.add(property1);
+    Map<String, String> properties = ImmutableMap.of("testKey", "otherValue");
 
     replayMocks();
 
@@ -89,12 +71,7 @@
   public void testIsMetByOredSingle() {
     Condition condition = createCondition("testKey", "value1,value2,value3");
 
-    Property property1 = createMock(Property.class);
-    expect(property1.getKey()).andReturn("testKey").anyTimes();
-    expect(property1.getValue()).andReturn("value2").anyTimes();
-
-    Collection<Property> properties = Lists.newArrayListWithCapacity(1);
-    properties.add(property1);
+    Map<String, String> properties = ImmutableMap.of("testKey", "value2");
 
     replayMocks();
 
@@ -104,17 +81,7 @@
   public void testIsMetByOredMultiple() {
     Condition condition = createCondition("testKey", "value1,value2,value3");
 
-    Property property1 = createMock(Property.class);
-    expect(property1.getKey()).andReturn("testKey").anyTimes();
-    expect(property1.getValue()).andReturn("value1").anyTimes();
-
-    Property property2 = createMock(Property.class);
-    expect(property2.getKey()).andReturn("testKey").anyTimes();
-    expect(property2.getValue()).andReturn("value3").anyTimes();
-
-    Collection<Property> properties = Lists.newArrayListWithCapacity(2);
-    properties.add(property1);
-    properties.add(property2);
+    Map<String, String> properties = ImmutableMap.of("testKey", "value1 value3");
 
     replayMocks();
 
@@ -122,19 +89,9 @@
   }
 
   public void testIsMetByOredMultipleWithSpaces() {
-    Condition condition = createCondition("testKey", "value1, value2, value3");
+    Condition condition = createCondition("testKey", "value1, value2 value3");
 
-    Property property1 = createMock(Property.class);
-    expect(property1.getKey()).andReturn("testKey").anyTimes();
-    expect(property1.getValue()).andReturn("value1").anyTimes();
-
-    Property property2 = createMock(Property.class);
-    expect(property2.getKey()).andReturn("testKey").anyTimes();
-    expect(property2.getValue()).andReturn("value3").anyTimes();
-
-    Collection<Property> properties = Lists.newArrayListWithCapacity(2);
-    properties.add(property1);
-    properties.add(property2);
+    Map<String, String> properties = ImmutableMap.of("testKey", "value1 value3");
 
     replayMocks();
 
@@ -144,22 +101,7 @@
   public void testIsMetByOredAll() {
     Condition condition = createCondition("testKey", "value1,value2,value3");
 
-    Property property1 = createMock(Property.class);
-    expect(property1.getKey()).andReturn("testKey").anyTimes();
-    expect(property1.getValue()).andReturn("value1").anyTimes();
-
-    Property property2 = createMock(Property.class);
-    expect(property2.getKey()).andReturn("testKey").anyTimes();
-    expect(property2.getValue()).andReturn("value2").anyTimes();
-
-    Property property3 = createMock(Property.class);
-    expect(property3.getKey()).andReturn("testKey").anyTimes();
-    expect(property3.getValue()).andReturn("value3").anyTimes();
-
-    Collection<Property> properties = Lists.newArrayListWithCapacity(1);
-    properties.add(property1);
-    properties.add(property2);
-    properties.add(property3);
+    Map<String, String> properties = ImmutableMap.of("testKey", "value1 value2 value3");
 
     replayMocks();
 
@@ -169,22 +111,7 @@
   public void testIsMetByOredOvershoot() {
     Condition condition = createCondition("testKey", "value1,value2,value3");
 
-    Property property1 = createMock(Property.class);
-    expect(property1.getKey()).andReturn("testKey").anyTimes();
-    expect(property1.getValue()).andReturn("otherValue1").anyTimes();
-
-    Property property2 = createMock(Property.class);
-    expect(property2.getKey()).andReturn("testKey").anyTimes();
-    expect(property2.getValue()).andReturn("value2").anyTimes();
-
-    Property property3 = createMock(Property.class);
-    expect(property3.getKey()).andReturn("testKey").anyTimes();
-    expect(property3.getValue()).andReturn("otherValue3").anyTimes();
-
-    Collection<Property> properties = Lists.newArrayListWithCapacity(3);
-    properties.add(property1);
-    properties.add(property2);
-    properties.add(property3);
+    Map<String, String> properties = ImmutableMap.of("testKey", "otherValue1 value2 otherValue3");
 
     replayMocks();
 
@@ -194,7 +121,7 @@
   public void testNegatedIsMetByEmpty() {
     Condition condition = createCondition("testKey", "!,testValue");
 
-    Collection<Property> properties = Collections.emptySet();
+    Map<String, String> properties = ImmutableMap.of();
 
     replayMocks();
 
@@ -204,12 +131,7 @@
   public void testNegatedIsMetByMismatchedKey() {
     Condition condition = createCondition("testKey", "!,testValue");
 
-    Property property1 = createMock(Property.class);
-    expect(property1.getKey()).andReturn("otherKey").anyTimes();
-    expect(property1.getValue()).andReturn("testValue").anyTimes();
-
-    Collection<Property> properties = Lists.newArrayListWithCapacity(1);
-    properties.add(property1);
+    Map<String, String> properties = ImmutableMap.of("otherKey", "testValue");
 
     replayMocks();
 
@@ -219,12 +141,7 @@
   public void testNegatedIsMetByMaMismatchedValue() {
     Condition condition = createCondition("testKey", "!,testValue");
 
-    Property property1 = createMock(Property.class);
-    expect(property1.getKey()).andReturn("testKey").anyTimes();
-    expect(property1.getValue()).andReturn("otherValue").anyTimes();
-
-    Collection<Property> properties = Lists.newArrayListWithCapacity(1);
-    properties.add(property1);
+    Map<String, String> properties = ImmutableMap.of("testKey", "otherValue");
 
     replayMocks();
 
@@ -234,12 +151,7 @@
   public void testNegatedIsMetByOredNoMatch() {
     Condition condition = createCondition("testKey", "!,value1,value2,value3");
 
-    Property property1 = createMock(Property.class);
-    expect(property1.getKey()).andReturn("testKey").anyTimes();
-    expect(property1.getValue()).andReturn("otherValue").anyTimes();
-
-    Collection<Property> properties = Lists.newArrayListWithCapacity(1);
-    properties.add(property1);
+    Map<String, String> properties = ImmutableMap.of("testKey", "otherValue");
 
     replayMocks();
 
@@ -249,12 +161,7 @@
   public void testNegatedIsMetByOredSingleMatch() {
     Condition condition = createCondition("testKey", "!,value1,value2,value3");
 
-    Property property1 = createMock(Property.class);
-    expect(property1.getKey()).andReturn("testKey").anyTimes();
-    expect(property1.getValue()).andReturn("value1").anyTimes();
-
-    Collection<Property> properties = Lists.newArrayListWithCapacity(1);
-    properties.add(property1);
+    Map<String, String> properties = ImmutableMap.of("testKey", "value1");
 
     replayMocks();
 
@@ -264,17 +171,7 @@
   public void testNegatedIsMetByOredMultiple() {
     Condition condition = createCondition("testKey", "!,value1,value2,value3");
 
-    Property property1 = createMock(Property.class);
-    expect(property1.getKey()).andReturn("testKey").anyTimes();
-    expect(property1.getValue()).andReturn("value1").anyTimes();
-
-    Property property2 = createMock(Property.class);
-    expect(property2.getKey()).andReturn("testKey").anyTimes();
-    expect(property2.getValue()).andReturn("value3").anyTimes();
-
-    Collection<Property> properties = Lists.newArrayListWithCapacity(2);
-    properties.add(property1);
-    properties.add(property2);
+    Map<String, String> properties = ImmutableMap.of("testKey", "value1 value3");
 
     replayMocks();
 
@@ -284,22 +181,7 @@
   public void testNegatedIsMetByOredAll() {
     Condition condition = createCondition("testKey", "!,value1,value2,value3");
 
-    Property property1 = createMock(Property.class);
-    expect(property1.getKey()).andReturn("testKey").anyTimes();
-    expect(property1.getValue()).andReturn("value1").anyTimes();
-
-    Property property2 = createMock(Property.class);
-    expect(property2.getKey()).andReturn("testKey").anyTimes();
-    expect(property2.getValue()).andReturn("value2").anyTimes();
-
-    Property property3 = createMock(Property.class);
-    expect(property3.getKey()).andReturn("testKey").anyTimes();
-    expect(property3.getValue()).andReturn("value3").anyTimes();
-
-    Collection<Property> properties = Lists.newArrayListWithCapacity(1);
-    properties.add(property1);
-    properties.add(property2);
-    properties.add(property3);
+    Map<String, String> properties = ImmutableMap.of("testKey", "value1 value2 value3");
 
     replayMocks();
 
@@ -309,23 +191,7 @@
   public void testNegatedIsMetByOredOvershoot() {
     Condition condition = createCondition("testKey", "!,value1,value2,value3");
 
-    Property property1 = createMock(Property.class);
-    expect(property1.getKey()).andReturn("testKey").anyTimes();
-    expect(property1.getValue()).andReturn("otherValue1").anyTimes();
-
-    Property property2 = createMock(Property.class);
-    expect(property2.getKey()).andReturn("testKey").anyTimes();
-    expect(property2.getValue()).andReturn("value2").anyTimes();
-
-    Property property3 = createMock(Property.class);
-    expect(property3.getKey()).andReturn("testKey").anyTimes();
-    expect(property3.getValue()).andReturn("otherValue3").anyTimes();
-
-    Collection<Property> properties = Lists.newArrayListWithCapacity(3);
-    properties.add(property1);
-    properties.add(property2);
-    properties.add(property3);
-
+    Map<String, String> properties = ImmutableMap.of("testKey", "otherValue1 value2 otherValue3");
     replayMocks();
 
     assertFalse("isMetBy gave true", condition.isMetBy(properties));
diff --git a/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/LogEventTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/LogEventTest.java
index e9a0872..c97c592 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/LogEventTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/LogEventTest.java
@@ -15,14 +15,13 @@
 
 import static org.easymock.EasyMock.expect;
 
-import com.google.common.collect.Sets;
+import com.google.common.collect.ImmutableMap;
 import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.googlesource.gerrit.plugins.its.base.testutil.LoggingMockingTestCase;
 import java.io.IOException;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.Map;
 import org.apache.log4j.Level;
 
 public class LogEventTest extends LoggingMockingTestCase {
@@ -35,7 +34,7 @@
     replayMocks();
 
     LogEvent logEvent = createLogEvent();
-    logEvent.execute("4711", actionRequest, new HashSet<>());
+    logEvent.execute("4711", actionRequest, ImmutableMap.of());
   }
 
   public void testEmpty() throws IOException {
@@ -45,95 +44,92 @@
     replayMocks();
 
     LogEvent logEvent = createLogEvent();
-    logEvent.execute("4711", actionRequest, new HashSet<>());
+    logEvent.execute("4711", actionRequest, ImmutableMap.of());
   }
 
   public void testLevelDefault() throws IOException {
     ActionRequest actionRequest = createMock(ActionRequest.class);
     expect(actionRequest.getParameter(1)).andReturn("");
 
-    Set<Property> properties = Sets.newHashSet();
-    properties.add(new PropertyMock("KeyA", "ValueA", "PropertyA"));
+    Map<String, String> properties = ImmutableMap.of("KeyA", "ValueA");
     replayMocks();
 
     LogEvent logEvent = createLogEvent();
     logEvent.execute("4711", actionRequest, properties);
 
-    assertLogMessageContains("PropertyA", Level.INFO);
+    assertLogMessageContains("KeyA = ValueA", Level.INFO);
   }
 
   public void testLevelError() throws IOException {
     ActionRequest actionRequest = createMock(ActionRequest.class);
     expect(actionRequest.getParameter(1)).andReturn("error");
 
-    Set<Property> properties = Sets.newHashSet();
-    properties.add(new PropertyMock("KeyA", "ValueA", "PropertyA"));
+    Map<String, String> properties = ImmutableMap.of("KeyA", "ValueA");
     replayMocks();
 
     LogEvent logEvent = createLogEvent();
     logEvent.execute("4711", actionRequest, properties);
 
-    assertLogMessageContains("PropertyA", Level.ERROR);
+    assertLogMessageContains("KeyA = ValueA", Level.ERROR);
   }
 
   public void testLevelWarn() throws IOException {
     ActionRequest actionRequest = createMock(ActionRequest.class);
     expect(actionRequest.getParameter(1)).andReturn("warn");
 
-    Set<Property> properties = Sets.newHashSet();
-    properties.add(new PropertyMock("KeyA", "ValueA", "PropertyA"));
+    Map<String, String> properties = ImmutableMap.of("KeyA", "ValueA");
     replayMocks();
 
     LogEvent logEvent = createLogEvent();
     logEvent.execute("4711", actionRequest, properties);
 
-    assertLogMessageContains("PropertyA", Level.WARN);
+    assertLogMessageContains("KeyA = ValueA", Level.WARN);
   }
 
   public void testLevelInfo() throws IOException {
     ActionRequest actionRequest = createMock(ActionRequest.class);
     expect(actionRequest.getParameter(1)).andReturn("info");
 
-    Set<Property> properties = Sets.newHashSet();
-    properties.add(new PropertyMock("KeyA", "ValueA", "PropertyA"));
+    Map<String, String> properties = ImmutableMap.of("KeyA", "ValueA");
     replayMocks();
 
     LogEvent logEvent = createLogEvent();
     logEvent.execute("4711", actionRequest, properties);
 
-    assertLogMessageContains("PropertyA", Level.INFO);
+    assertLogMessageContains("KeyA = ValueA", Level.INFO);
   }
 
   public void testLevelDebug() throws IOException {
     ActionRequest actionRequest = createMock(ActionRequest.class);
     expect(actionRequest.getParameter(1)).andReturn("debug");
 
-    Set<Property> properties = Sets.newHashSet();
-    properties.add(new PropertyMock("KeyA", "ValueA", "PropertyA"));
+    Map<String, String> properties = ImmutableMap.of("KeyA", "ValueA");
     replayMocks();
 
     LogEvent logEvent = createLogEvent();
     logEvent.execute("4711", actionRequest, properties);
 
-    assertLogMessageContains("PropertyA", Level.DEBUG);
+    assertLogMessageContains("KeyA = ValueA", Level.DEBUG);
   }
 
   public void testMultipleProperties() throws IOException {
     ActionRequest actionRequest = createMock(ActionRequest.class);
     expect(actionRequest.getParameter(1)).andReturn("info");
 
-    Set<Property> properties = Sets.newHashSet();
-    properties.add(new PropertyMock("KeyA", "ValueA", "PropertyA"));
-    properties.add(new PropertyMock("KeyB", "ValueB", "PropertyB"));
-    properties.add(new PropertyMock("KeyC", "ValueC", "PropertyC"));
+    Map<String, String> properties =
+        ImmutableMap.<String, String>builder()
+            .put("KeyA", "ValueA")
+            .put("KeyB", "ValueB")
+            .put("KeyC", "ValueC")
+            .build();
     replayMocks();
 
     LogEvent logEvent = createLogEvent();
     logEvent.execute("4711", actionRequest, properties);
 
-    assertLogMessageContains("PropertyA", Level.INFO);
-    assertLogMessageContains("PropertyB", Level.INFO);
-    assertLogMessageContains("PropertyC", Level.INFO);
+    assertLogMessageContains("KeyA = ValueA", Level.INFO);
+    assertLogMessageContains("KeyB = ValueB", Level.INFO);
+    assertLogMessageContains("KeyC = ValueC", Level.INFO);
   }
 
   private LogEvent createLogEvent() {
@@ -150,18 +146,4 @@
     @Override
     protected void configure() {}
   }
-
-  private class PropertyMock extends Property {
-    private final String toString;
-
-    public PropertyMock(String key, String value, String toString) {
-      super(key, value);
-      this.toString = toString;
-    }
-
-    @Override
-    public String toString() {
-      return toString;
-    }
-  }
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/PropertyTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/PropertyTest.java
deleted file mode 100644
index 06ac41a..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/PropertyTest.java
+++ /dev/null
@@ -1,118 +0,0 @@
-// 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.its.base.workflow;
-
-import com.google.gerrit.extensions.config.FactoryModule;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.googlesource.gerrit.plugins.its.base.testutil.LoggingMockingTestCase;
-
-public class PropertyTest extends LoggingMockingTestCase {
-  private Injector injector;
-
-  public void testGetKeyNull() {
-    Property property = new Property(null, "testValue");
-    assertNull("Key is not null", property.getKey());
-  }
-
-  public void testGetKeyNonNull() {
-    Property property = createProperty("testKey", "testValue");
-    assertEquals("Key does not match", "testKey", property.getKey());
-  }
-
-  public void testGetValueNull() {
-    Property property = createProperty("testKey", null);
-    assertNull("Value is not null", property.getValue());
-  }
-
-  public void testGetValueNonNull() {
-    Property property = createProperty("testKey", "testValue");
-    assertEquals("Value does not match", "testValue", property.getValue());
-  }
-
-  public void testEqualsSimilar() {
-    Property propertyA = createProperty("testKey", "testValue");
-    Property propertyB = createProperty("testKey", "testValue");
-    assertTrue("Property is equal to similar", propertyA.equals(propertyB));
-  }
-
-  public void testEqualsNull() {
-    Property property = createProperty("testKey", "testValue");
-    assertFalse("Property is equal to null", property.equals(null));
-  }
-
-  public void testEqualsNull2() {
-    Property property = new Property(null, null);
-    assertFalse("Property is equal to null", property.equals(null));
-  }
-
-  public void testEqualsNulledKey() {
-    Property propertyA = new Property(null, "testValue");
-    Property propertyB = createProperty("testKey", "testValue");
-    assertFalse("Single nulled key does match", propertyA.equals(propertyB));
-  }
-
-  public void testEqualsNulledKey2() {
-    Property propertyA = createProperty("testKey", "testValue");
-    Property propertyB = new Property(null, "testValue");
-    assertFalse("Single nulled key does match", propertyA.equals(propertyB));
-  }
-
-  public void testEqualsNulledValue() {
-    Property propertyA = createProperty("testKey", "testValue");
-    Property propertyB = createProperty("testKey", null);
-    assertFalse("Single nulled value does match", propertyA.equals(propertyB));
-  }
-
-  public void testEqualsNulledValue2() {
-    Property propertyA = createProperty("testKey", null);
-    Property propertyB = createProperty("testKey", "testValue");
-    assertFalse("Single nulled value does match", propertyA.equals(propertyB));
-  }
-
-  public void testHashCodeEquals() {
-    Property propertyA = createProperty("testKey", "testValue");
-    Property propertyB = createProperty("testKey", "testValue");
-    assertEquals("Hash codes do not match", propertyA.hashCode(), propertyB.hashCode());
-  }
-
-  public void testHashCodeNullKey() {
-    Property property = new Property(null, "testValue");
-    property.hashCode();
-  }
-
-  public void testHashCodeNullValue() {
-    Property property = createProperty("testKey", null);
-    property.hashCode();
-  }
-
-  private Property createProperty(String key, String value) {
-    Property.Factory factory = injector.getInstance(Property.Factory.class);
-    return factory.create(key, value);
-  }
-
-  @Override
-  public void setUp() throws Exception {
-    super.setUp();
-
-    injector = Guice.createInjector(new TestModule());
-  }
-
-  private class TestModule extends FactoryModule {
-    @Override
-    protected void configure() {
-      factory(Property.Factory.class);
-    }
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/RuleBaseTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/RuleBaseTest.java
index d7c66b2..1002a92 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/RuleBaseTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/RuleBaseTest.java
@@ -15,6 +15,7 @@
 
 import static org.easymock.EasyMock.expect;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.config.FactoryModule;
@@ -29,8 +30,8 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 import org.eclipse.jgit.util.FileUtils;
 
@@ -155,7 +156,7 @@
     expect(actionRequestFactory.create("action1")).andReturn(actionRequest1);
     rule1.addActionRequest(actionRequest1);
 
-    Collection<Property> properties = Collections.emptySet();
+    Map<String, String> properties = ImmutableMap.of();
 
     List<ActionRequest> rule1Match = Lists.newArrayListWithCapacity(1);
     rule1Match.add(actionRequest1);
@@ -199,9 +200,7 @@
     expect(actionRequestFactory.create("action3")).andReturn(actionRequest3);
     rule2.addActionRequest(actionRequest3);
 
-    Collection<Property> properties = Lists.newArrayListWithCapacity(1);
-    Property property1 = createMock(Property.class);
-    properties.add(property1);
+    Map<String, String> properties = ImmutableMap.of("sample", "property");
 
     List<ActionRequest> rule1Match = Lists.newArrayListWithCapacity(2);
     rule1Match.add(actionRequest1);
@@ -250,7 +249,7 @@
 
     injectRuleBase("[rule \"rule3\"]\n" + "\taction = action3", RuleBaseKind.ITS);
 
-    Collection<Property> properties = Collections.emptySet();
+    Map<String, String> properties = ImmutableMap.of("sample", "property");
 
     Rule rule2 = createMock(Rule.class);
     expect(ruleFactory.create("rule2")).andReturn(rule2);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/RuleTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/RuleTest.java
index 6d4d234..c106a81 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/RuleTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/base/workflow/RuleTest.java
@@ -15,6 +15,7 @@
 
 import static org.easymock.EasyMock.expect;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.inject.Guice;
@@ -23,6 +24,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 public class RuleTest extends LoggingMockingTestCase {
   private Injector injector;
@@ -33,7 +35,7 @@
   }
 
   public void testActionsForUnconditionalRule() {
-    Collection<Property> properties = Collections.emptySet();
+    Map<String, String> properties = ImmutableMap.of();
 
     Rule rule = createRule("testRule");
 
@@ -50,7 +52,7 @@
   }
 
   public void testActionRequestsForConditionalRuleEmptyProperties() {
-    Collection<Property> properties = Collections.emptySet();
+    Map<String, String> properties = ImmutableMap.of();
 
     Rule rule = createRule("testRule");
 
@@ -70,7 +72,7 @@
   }
 
   public void testActionRequestsForConditionalRules() {
-    Collection<Property> properties = Collections.emptySet();
+    Map<String, String> properties = ImmutableMap.of();
 
     Rule rule = createRule("testRule");
 
@@ -94,7 +96,7 @@
   }
 
   public void testActionRequestsForMultipleActionRequests() {
-    Collection<Property> properties = Collections.emptySet();
+    Map<String, String> properties = ImmutableMap.of();
 
     Rule rule = createRule("testRule");