Merge changes I99bb4320,I7d5d74f1,Ic3b4a139

* changes:
  Hide Condition's getValue
  Add handling of DraftPublishedEvent
  Add link formatting to Velocity comments
diff --git a/hooks-its/src/main/java/com/googlesource/gerrit/plugins/hooks/util/PropertyExtractor.java b/hooks-its/src/main/java/com/googlesource/gerrit/plugins/hooks/util/PropertyExtractor.java
index fe69773..77ef563 100644
--- a/hooks-its/src/main/java/com/googlesource/gerrit/plugins/hooks/util/PropertyExtractor.java
+++ b/hooks-its/src/main/java/com/googlesource/gerrit/plugins/hooks/util/PropertyExtractor.java
@@ -26,6 +26,7 @@
 import com.google.gerrit.server.events.ChangeMergedEvent;
 import com.google.gerrit.server.events.ChangeRestoredEvent;
 import com.google.gerrit.server.events.CommentAddedEvent;
+import com.google.gerrit.server.events.DraftPublishedEvent;
 import com.google.gerrit.server.events.PatchSetCreatedEvent;
 import com.google.gerrit.server.events.RefUpdatedEvent;
 import com.google.inject.Inject;
@@ -104,6 +105,18 @@
         event.patchSet.revision, patchSetId);
   }
 
+  private Map<String,Set<String>> extractFrom(DraftPublishedEvent event,
+      Set<Property> common) {
+    common.add(propertyFactory.create("event-type", event.type));
+    common.addAll(propertyAttributeExtractor.extractFrom(event.change));
+    common.addAll(propertyAttributeExtractor.extractFrom(event.patchSet));
+    common.addAll(propertyAttributeExtractor.extractFrom(event.uploader, "uploader"));
+    PatchSet.Id patchSetId = newPatchSetId(event.change.number,
+        event.patchSet.number);
+    return issueExtractor.getIssueIds(event.change.project,
+        event.patchSet.revision, patchSetId);
+  }
+
   private Map<String,Set<String>> extractFrom(RefUpdatedEvent event,
       Set<Property> common) {
     common.add(propertyFactory.create("event-type", event.type));
@@ -199,6 +212,8 @@
       associations = extractFrom((ChangeRestoredEvent) event, common);
     } else if (event instanceof CommentAddedEvent) {
       associations = extractFrom((CommentAddedEvent) event, common);
+    } else if (event instanceof DraftPublishedEvent) {
+      associations = extractFrom((DraftPublishedEvent) event, common);
     } else if (event instanceof PatchSetCreatedEvent) {
       associations = extractFrom((PatchSetCreatedEvent) event, common);
     } else if (event instanceof RefUpdatedEvent) {
diff --git a/hooks-its/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/Condition.java b/hooks-its/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/Condition.java
index 1bb1564..47aae91 100644
--- a/hooks-its/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/Condition.java
+++ b/hooks-its/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/Condition.java
@@ -63,10 +63,6 @@
     return key;
   }
 
-  public Set<String> getValues() {
-    return values;
-  }
-
   /**
    * Checks whether or not the Condition matches the given set of properties
    *
diff --git a/hooks-its/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/action/AddVelocityComment.java b/hooks-its/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/action/AddVelocityComment.java
index 45afb65..e2db316 100644
--- a/hooks-its/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/action/AddVelocityComment.java
+++ b/hooks-its/src/main/java/com/googlesource/gerrit/plugins/hooks/workflow/action/AddVelocityComment.java
@@ -76,6 +76,9 @@
         }
       }
     }
+
+    velocityContext.put("its",  new VelocityAdapterItsFacade(its));
+
     return velocityContext;
   }
 
@@ -114,4 +117,41 @@
       its.addComment(issue, comment);
     }
   }
+
+  /**
+   * Adapter for ItsFacade to be used through Velocity
+   */
+  // Although we'd prefer to keep this class private, Velocity will only pick
+  // it up, if it is public.
+  public class VelocityAdapterItsFacade {
+
+    private final ItsFacade its;
+
+    private VelocityAdapterItsFacade(ItsFacade its) {
+      this.its = its;
+    }
+
+    /**
+     * Format a link to a URL in the used Its' syntax.
+     *
+     * @param url URL to link to
+     * @param caption Text used to represent the link
+     * @return Link to the given URL in the used Its' syntax.
+     */
+    public String formatLink(String url, String caption) {
+      return its.createLinkForWebui(url, caption);
+    }
+
+    /**
+     * Format a link to an URL.
+     * <p>
+     * The provided URL is used as caption for the formatted link.
+     *
+     * @param url URL to link to
+     * @return Link to the given URL in the used Its' syntax.
+     */
+    public String formatLink(String url) {
+      return its.createLinkForWebui(url, url);
+    }
+  }
 }
diff --git a/hooks-its/src/main/resources/Documentation/config.md b/hooks-its/src/main/resources/Documentation/config.md
index b0093ed..2593171 100644
--- a/hooks-its/src/main/resources/Documentation/config.md
+++ b/hooks-its/src/main/resources/Documentation/config.md
@@ -168,6 +168,7 @@
 * <<event-properties-ChangeMergedEvent,ChangeMergedEvent>>
 * <<event-properties-ChangeRestoredEvent,ChangeRestoredEvent>>
 * <<event-properties-CommentAddedEvent,CommentAddedEvent>>
+* <<event-properties-DraftPublishedEvent,DraftPublishedEvent>>
 * <<event-properties-PatchSetCreatedEvent,PatchSetCreatedEvent>>
 * <<event-properties-RefUpdatedEvent,RefUpdatedEvent>>
 * <<event-properties-change,Common properties for events on a change>>
@@ -270,6 +271,19 @@
 added for, and it's most recent <<event-properties-patch-set,patch
 set>>.
 
+[[event-properties-DraftPublishedEvent]]
+DraftPublishedEvent
+^^^^^^^^^^^^^^^^^^^
+
+'event'::
+  +com.google.gerrit.server.events.DraftPublishedEvent+
+'event-type'::
+  +draft-published+
+
+In addition to the above properties, the event also provides
+properties for the uploaded <<event-properties-patch-set,patch set>>,
+and the <<event-properties-change,change>> it belongs to.
+
 [[event-properties-PatchSetCreatedEvent]]
 PatchSetCreatedEvent
 ^^^^^^^^^^^^^^^^^^^
@@ -444,6 +458,34 @@
 the event's subject property, and +$change-number+ would refer to the
 change's number.
 
+Additionally, the context's 'its' property provides an object that
+allows to format links using the its' syntax:
+
+'formatLink( url )'::
+  Formats a link to a url.
+  +
+  So for example upon adding a comment to a change, the following rule
+  formats a link to the change:
++
+----
+[rule "formatLinkSampleRule"]
+        event-type = comment-added
+        action = add-velocity-comment inline Comment for change $change-number added. See ${its.formatLink($change-url)}
+----
+
+'formatLink( url, caption )'::
+  Formats a link to a url using 'caption' to represent the url.
+  +
+  So for example upon adding a comment to a change, the following rule
+  formats a link to the change using the change number as link
+  capition:
++
+----
+[rule "formatLinkSampleRule"]
+        event-type = comment-added
+        action = add-velocity-comment inline Comment for change ${its.formatLink($change-url, $change-number)} added.
+-----
+
 [[action-log-event]]
 Action: log-event
 ^^^^^^^^^^^^^^^^^
diff --git a/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/util/PropertyExtractorTest.java b/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/util/PropertyExtractorTest.java
index 421038d..dda4173 100644
--- a/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/util/PropertyExtractorTest.java
+++ b/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/util/PropertyExtractorTest.java
@@ -33,6 +33,7 @@
 import com.google.gerrit.server.events.ChangeMergedEvent;
 import com.google.gerrit.server.events.ChangeRestoredEvent;
 import com.google.gerrit.server.events.CommentAddedEvent;
+import com.google.gerrit.server.events.DraftPublishedEvent;
 import com.google.gerrit.server.events.PatchSetCreatedEvent;
 import com.google.gerrit.server.events.RefUpdatedEvent;
 import com.google.inject.Guice;
@@ -276,6 +277,41 @@
     eventHelper(event, "CommentAddedEvent", "comment-added", common, true);
   }
 
+  public void testDraftPublishedEvent() {
+    DraftPublishedEvent event = new DraftPublishedEvent();
+
+    ChangeAttribute changeAttribute = createMock(ChangeAttribute.class);
+    event.change = changeAttribute;
+    Property propertyChange = createMock(Property.class);
+    expect(propertyAttributeExtractor.extractFrom(changeAttribute))
+        .andReturn(Sets.newHashSet(propertyChange));
+
+    AccountAttribute accountAttribute = createMock(AccountAttribute.class);
+    event.uploader = accountAttribute;
+    Property propertySubmitter = createMock(Property.class);
+    expect(propertyAttributeExtractor.extractFrom(accountAttribute,
+        "uploader")).andReturn(Sets.newHashSet(propertySubmitter));
+
+    PatchSetAttribute patchSetAttribute = createMock(PatchSetAttribute.class);
+    event.patchSet = patchSetAttribute;
+    Property propertyPatchSet = createMock(Property.class);
+    expect(propertyAttributeExtractor.extractFrom(patchSetAttribute))
+        .andReturn(Sets.newHashSet(propertyPatchSet));
+
+    changeAttribute.project = "testProject";
+    changeAttribute.number = "176";
+    patchSetAttribute.revision = "testRevision";
+    patchSetAttribute.number = "3";
+
+    Set<Property> common = Sets.newHashSet();
+    common.add(propertyChange);
+    common.add(propertySubmitter);
+    common.add(propertyPatchSet);
+
+    eventHelper(event, "DraftPublishedEvent", "draft-published", common,
+        true);
+  }
+
   public void testPatchSetCreatedEvent() {
     PatchSetCreatedEvent event = new PatchSetCreatedEvent();
 
diff --git a/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ConditionTest.java b/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ConditionTest.java
index d672ab7..857f9e8 100644
--- a/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ConditionTest.java
+++ b/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/ConditionTest.java
@@ -17,10 +17,8 @@
 
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Set;
 
 import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
 import com.google.gerrit.server.config.FactoryModule;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
@@ -29,36 +27,11 @@
 public class ConditionTest  extends LoggingMockingTestCase {
   private Injector injector;
 
-  public void testSimpleValue() {
-    Condition condition = createCondition("testKey", "testValue");
-    assertEquals("key not matching 'testKey'", "testKey", condition.getKey());
-    Set<String> expectedValues = Sets.newHashSet();
-    expectedValues.add("testValue");
-    assertEquals("values do not match", expectedValues, condition.getValues());
-  }
-
   public void testGetKeyNull() {
     Condition condition = new Condition(null, "testValues");
     assertNull("key is not null", condition.getKey());
   }
 
-  public void testGetValuesNull() {
-    Condition condition = createCondition("testKey", null);
-    Set<String> values = condition.getValues();
-    assertNotNull("values is null", values);
-    assertTrue("values is not empty", values.isEmpty());
-  }
-
-  public void testOredValue() {
-    Condition condition = createCondition("testKey", "value1,value2,value3");
-    assertEquals("key not matching 'testKey'", "testKey", condition.getKey());
-    Set<String> expectedValues = Sets.newLinkedHashSet();
-    expectedValues.add("value1");
-    expectedValues.add("value2");
-    expectedValues.add("value3");
-    assertEquals("values do not match", expectedValues, condition.getValues());
-  }
-
   public void testIsMetBySimple() {
     Condition condition = createCondition("testKey", "testValue");
 
@@ -199,16 +172,6 @@
     assertTrue("isMetBy gave false", condition.isMetBy(properties));
   }
 
-  public void testUnmodifiableValue() {
-    Condition condition = createCondition("testKey", "testValue");
-    Set<String> values = condition.getValues();
-    try {
-      values.add("value2");
-      fail("value is not unmodifyable");
-    } catch (UnsupportedOperationException e) {
-    }
-  }
-
   private Condition createCondition(String key, String value) {
     Condition.Factory factory = injector.getInstance(Condition.Factory.class);
     return factory.create(key, value);
diff --git a/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/action/AddVelocityCommentTest.java b/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/action/AddVelocityCommentTest.java
index 5d6b71c..a7de371 100644
--- a/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/action/AddVelocityCommentTest.java
+++ b/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/workflow/action/AddVelocityCommentTest.java
@@ -23,6 +23,7 @@
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.Writer;
+import java.lang.reflect.InvocationTargetException;
 import java.util.HashSet;
 import java.util.Set;
 import java.util.UUID;
@@ -43,6 +44,7 @@
 import com.googlesource.gerrit.plugins.hooks.testutil.LoggingMockingTestCase;
 import com.googlesource.gerrit.plugins.hooks.workflow.ActionRequest;
 import com.googlesource.gerrit.plugins.hooks.workflow.Property;
+import com.googlesource.gerrit.plugins.hooks.workflow.action.AddVelocityComment.VelocityAdapterItsFacade;
 
 public class AddVelocityCommentTest extends LoggingMockingTestCase {
   private Injector injector;
@@ -199,6 +201,79 @@
         context.get("reason"));
   }
 
+  public void testItsWrapperFormatLink1Parameter() throws IOException,
+      SecurityException, NoSuchMethodException, IllegalArgumentException,
+      IllegalAccessException, InvocationTargetException {
+    ActionRequest actionRequest = createMock(ActionRequest.class);
+    expect(actionRequest.getParameter(1)).andReturn("inline");
+    expect(actionRequest.getParameters()).andReturn(
+        new String[] {"inline", "Simple-Text"});
+
+    IAnswer<Boolean> answer = new VelocityWriterFiller("Simple-Text");
+    Capture<VelocityContext> contextCapture = new Capture<VelocityContext>();
+    expect(velocityRuntime.evaluate(capture(contextCapture),
+        (Writer)anyObject(), (String)anyObject(), eq("Simple-Text")))
+        .andAnswer(answer);
+
+    its.addComment("4711", "Simple-Text");
+
+    expect(its.createLinkForWebui("http://www.example.org/",
+        "http://www.example.org/")) .andReturn("Formatted Link");
+
+    replayMocks();
+
+    AddVelocityComment addVelocityComment = createAddVelocityComment();
+    addVelocityComment.execute("4711", actionRequest, new HashSet<Property>());
+
+    VelocityContext context = contextCapture.getValue();
+    Object itsAdapterObj = context.get("its");
+    assertNotNull("its property is null", itsAdapterObj);
+    assertTrue("Its is not a VelocityAdapterItsFacade instance",
+        itsAdapterObj instanceof VelocityAdapterItsFacade);
+    VelocityAdapterItsFacade itsAdapter =
+        (VelocityAdapterItsFacade) itsAdapterObj;
+    String formattedLink = itsAdapter.formatLink("http://www.example.org/");
+    assertEquals("Result of formatLink does not match", "Formatted Link",
+        formattedLink);
+  }
+
+  public void testItsWrapperFormatLink2Parameters() throws IOException,
+      SecurityException, NoSuchMethodException, IllegalArgumentException,
+      IllegalAccessException, InvocationTargetException {
+    ActionRequest actionRequest = createMock(ActionRequest.class);
+    expect(actionRequest.getParameter(1)).andReturn("inline");
+    expect(actionRequest.getParameters()).andReturn(
+        new String[] {"inline", "Simple-Text"});
+
+    IAnswer<Boolean> answer = new VelocityWriterFiller("Simple-Text");
+    Capture<VelocityContext> contextCapture = new Capture<VelocityContext>();
+    expect(velocityRuntime.evaluate(capture(contextCapture),
+        (Writer)anyObject(), (String)anyObject(), eq("Simple-Text")))
+        .andAnswer(answer);
+
+    its.addComment("4711", "Simple-Text");
+
+    expect(its.createLinkForWebui("http://www.example.org/", "Caption"))
+        .andReturn("Formatted Link");
+
+    replayMocks();
+
+    AddVelocityComment addVelocityComment = createAddVelocityComment();
+    addVelocityComment.execute("4711", actionRequest, new HashSet<Property>());
+
+    VelocityContext context = contextCapture.getValue();
+    Object itsAdapterObj = context.get("its");
+    assertNotNull("its property is null", itsAdapterObj);
+    assertTrue("Its is not a VelocityAdapterItsFacade instance",
+        itsAdapterObj instanceof VelocityAdapterItsFacade);
+    VelocityAdapterItsFacade itsAdapter =
+        (VelocityAdapterItsFacade) itsAdapterObj;
+    String formattedLink = itsAdapter.formatLink("http://www.example.org/",
+        "Caption");
+    assertEquals("Result of formatLink does not match", "Formatted Link",
+        formattedLink);
+  }
+
   public void testWarnTemplateNotFound() throws IOException {
     ActionRequest actionRequest = createMock(ActionRequest.class);
     expect(actionRequest.getParameter(1)).andReturn("non-existing-template");