Add link formatting to Velocity comments

For example the rule

  [rule "formatLinkSampleRule"]
          event-type = comment-added
          action = add-velocity-comment inline Comment for change $change-number added. See ${its.formatLink($change-url)}

would format a link to the event's change-url in the its' syntax.

Change-Id: Ic3b4a13913309719c34ed1e66a9acdf246096522
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..2745d9c 100644
--- a/hooks-its/src/main/resources/Documentation/config.md
+++ b/hooks-its/src/main/resources/Documentation/config.md
@@ -444,6 +444,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/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");