You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by hl...@apache.org on 2007/12/16 01:56:58 UTC

svn commit: r604534 - in /tapestry/tapestry5/trunk/tapestry-core/src: main/java/org/apache/tapestry/ main/java/org/apache/tapestry/internal/services/ main/java/org/apache/tapestry/internal/structure/ test/java/org/apache/tapestry/internal/services/

Author: hlship
Date: Sat Dec 15 16:56:56 2007
New Revision: 604534

URL: http://svn.apache.org/viewvc?rev=604534&view=rev
Log:
TAPESTRY-1949: Component action requests where the action context contains a period are not parsed correctly leading to request failures

Modified:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/ComponentResourcesCommon.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/ActionLinkTarget.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/ComponentActionDispatcher.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/LinkFactory.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/LinkFactoryImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/TemplateParserImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/structure/Page.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/structure/PageImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/ComponentActionDispatcherTest.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/LinkFactoryImplTest.java

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/ComponentResourcesCommon.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/ComponentResourcesCommon.java?rev=604534&r1=604533&r2=604534&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/ComponentResourcesCommon.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/ComponentResourcesCommon.java Sat Dec 15 16:56:56 2007
@@ -39,20 +39,24 @@
     /**
      * Return a string consisting the concatinated ids of all containing components, separated by
      * periods. In addition, nested ids are always all lower case. I.e., "foo.bar.baz". Returns null
-     * for a page.
+     * for the root component of a  page.
      */
     String getNestedId();
 
     /**
-     * Creates a component action request link as a callback for this component.
+     * Creates a component action request link as a callback for this component. The event type
+     * and context (as well as the page name and nested component id) will be encoded into a URL. A request for the
+     * URL will {@linkplain #triggerEvent(String, Object[], ComponentEventHandler)}  trigger} the named event
+     * on the component.
      *
-     * @param action  a name for the action associated with the link
-     * @param forForm if true, the link will be used as the action for an HTML form submission, which
-     *                may affect what information is encoded into the link
-     * @param context additional objects to be encoded into the path portion of the link; each is
-     *                converted to a string an URI encoded
+     * @param eventType the type of event to be triggered.  Event types should be Java identifiers (contain only letters, numbers and the underscore).
+     * @param forForm   if true, the link will be used as the eventType for an HTML form submission, which
+     *                  may affect what information is encoded into the link
+     * @param context   additional objects to be encoded into the path portion of the link; each is
+     *                  converted to a string and URI encoded
+     * @return link object for the callback
      */
-    Link createActionLink(String action, boolean forForm, Object... context);
+    Link createActionLink(String eventType, boolean forForm, Object... context);
 
     /**
      * Creates a render request link to a specific page.
@@ -66,9 +70,9 @@
     Link createPageLink(String pageName, boolean override, Object... context);
 
     /**
-     * Returns a string consisting of the fully qualified class name of the containing page, and the
+     * Returns a string consisting of the logical name of the containing page, and the
      * {@link #getNestedId() nested id} of this component, separated by a colon. I.e.,
-     * "MyPage:foo.bar.baz". For a page, returns just the page's logical name.
+     * "mypage:foo.bar.baz". For a page, returns just the page's logical name.
      * <p/>
      * This value is often used to obtain an equivalent component instance in a later request.
      *

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/ActionLinkTarget.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/ActionLinkTarget.java?rev=604534&r1=604533&r2=604534&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/ActionLinkTarget.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/ActionLinkTarget.java Sat Dec 15 16:56:56 2007
@@ -28,9 +28,9 @@
 
     private final String _componentNestedId;
 
-    public ActionLinkTarget(String action, String pageName, String componentNestedId)
+    public ActionLinkTarget(String eventType, String pageName, String componentNestedId)
     {
-        _eventType = action;
+        _eventType = eventType;
         _pageName = pageName;
         _componentNestedId = componentNestedId;
 

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/ComponentActionDispatcher.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/ComponentActionDispatcher.java?rev=604534&r1=604533&r2=604534&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/ComponentActionDispatcher.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/ComponentActionDispatcher.java Sat Dec 15 16:56:56 2007
@@ -20,10 +20,32 @@
 import org.apache.tapestry.services.*;
 
 import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Processes component action events sent as requests from the client. Action events include an
  * event type, identify a page and a component, and may provide additional context strings.
+ * <p/>
+ * <p/>
+ * Forms:
+ * <ul>
+ * <li>/context/pagename:eventname -- event on the page, no action context</li>
+ * <li>/context/pagename:eventname/foo/bar -- event on the page with action context "foo", "bar"</li>
+ * <li>/context/pagename.foo.bar -- event on component foo.bar within the page, default event, no action context</li>
+ * <li>/context/pagename.foo.bar/baz.gnu -- event on component foo.bar within the page, default event, with action context "baz", "gnu"</li>
+ * <li>/context/pagename.bar.baz:eventname/foo/gnu -- event on component bar.baz within the page with action context "foo"
+ * , "gnu"</li>
+ * </ul>
+ * <p/>
+ * <p/>
+ * The page name portion may itself consist of a series of folder names, i.e., "admin/user/create".  The context portion isn't the concern
+ * of this code, since {@link org.apache.tapestry.services.Request#getPath()} will already have stripped that off.  We can act as if the context is
+ * always "/" (the path always starts with a slash).
+ * <p/>
+ * <p/>
+ *
+ * @see org.apache.tapestry.internal.services.LinkFactory#createActionLink(org.apache.tapestry.internal.structure.ComponentPageElement, String, boolean, Object[])
  */
 public class ComponentActionDispatcher implements Dispatcher
 {
@@ -40,81 +62,66 @@
         _componentClassResolver = componentClassResolver;
     }
 
+    // A beast that recognizes all the elements of a path in a single go.
+    // We skip the leading slash, then take the next few terms (until a dot or a colon)
+    // as the page name.  Then there's a sequence that sees a dot
+    // and recognizes the nested component id (which may be missing), which ends
+    // at the colon, or at the slash (or the end of the string).  The colon identifies
+    // the event name (the event name is also optional).  A valid path will always have
+    // a nested component id or an event name (or both) ... when both are missing, then the
+    // path is most likely a page render request.  After the optional event name,
+    // the next piece is the action context, which is the remainder of the path.
+
+    private final Pattern PATH_PATTERN = Pattern.compile(
+
+            "^/" +      // The leading slash is recognized but skipped
+                    "(((\\w+)/)*(\\w+))" + // A series of folder names leading up to the page name, forming the logical page name
+                    "(\\.(\\w+(\\.\\w+)*))?" + // The first dot separates the page name from the nested component id
+                    "(\\:(\\w+))?" + // A colon, then the event type
+                    "(/(.*))?", //  A slash, then the action context
+                                Pattern.COMMENTS);
+
+    // Constants for the match groups in the above pattern.
+    private static final int LOGICAL_PAGE_NAME = 1;
+    private static final int NESTED_ID = 6;
+    private static final int EVENT_NAME = 9;
+    private static final int CONTEXT = 11;
+
+    // Used to split a context into individual chunks.
+
+    private final Pattern SLASH_PATTERN = Pattern.compile("/");
+
     public boolean dispatch(Request request, Response response) throws IOException
     {
         String path = request.getPath();
 
-        String logicalPageName = null;
-        String nestedComponentId = "";
-        String eventType = TapestryConstants.ACTION_EVENT;
-
-        // Will always have a dot or a colon
-
-        int dotx = path.indexOf('.');
-        int colonx = path.indexOf(':');
-
-        int contextStart = -1;
-
-        if (dotx > 0)
-        {
-            logicalPageName = path.substring(1, dotx);
-
-            int slashx = path.indexOf('/', dotx + 1);
-
-            // The nested id ends at the colon (if present) or
-            // the first slash (if present) or the end of the path.
-
-            if (slashx < 0)
-            {
-                slashx = path.length();
-            }
-            else
-            {
-                contextStart = slashx + 1;
-            }
-
-            int nestedIdEnd = slashx;
-
-            if (colonx > 0 && colonx < slashx)
-            {
-                nestedIdEnd = colonx;
-                eventType = path.substring(colonx + 1, slashx);
-            }
-
-            nestedComponentId = path.substring(dotx + 1, nestedIdEnd);
-        }
-        else if (colonx > 0)
-        {
-            // No dot, but a colon. Therefore no nested component id, but an action name and
-            // maybe some event context.
-
-            int slashx = path.indexOf('/', colonx + 1);
-            if (slashx < 0)
-            {
-                slashx = path.length();
-            }
-            else
-            {
-                contextStart = slashx + 1;
-            }
-
-            eventType = path.substring(colonx + 1, slashx);
-            logicalPageName = path.substring(1, colonx);
-        }
+        Matcher matcher = PATH_PATTERN.matcher(request.getPath());
+
+        if (!matcher.matches()) return false;
+
+        String logicalPageName = matcher.group(LOGICAL_PAGE_NAME);
 
-        if (logicalPageName == null) return false;
+        String nestedComponentId = matcher.group(NESTED_ID);
 
-        // We've identified a page name ... does the application contain a page with that name?
+        String eventType = matcher.group(EVENT_NAME);
+
+        if (nestedComponentId == null && eventType == null) return false;
 
         if (!_componentClassResolver.isPageName(logicalPageName)) return false;
 
-        String[] eventContext = contextStart > 0 ? decodeContext(path.substring(contextStart)) : _emptyString;
+        String[] eventContext = decodeContext(matcher.group(CONTEXT));
 
         String activationContextValue = request.getParameter(InternalConstants.PAGE_CONTEXT_NAME);
 
         String[] activationContext = activationContextValue == null ? _emptyString : decodeContext(
                 activationContextValue);
 
+        // The event type is often omitted, and defaults to "action".
+
+        if (eventType == null) eventType = TapestryConstants.ACTION_EVENT;
+
+        if (nestedComponentId == null) nestedComponentId = "";
+
         _componentActionRequestHandler.handle(logicalPageName, nestedComponentId, eventType, eventContext,
                                               activationContext);
 
@@ -123,7 +130,9 @@
 
     private String[] decodeContext(String input)
     {
-        String[] result = input.split("/");
+        if (input == null) return _emptyString;
+
+        String[] result = SLASH_PATTERN.split(input);
 
         for (int i = 0; i < result.length; i++)
         {

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/LinkFactory.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/LinkFactory.java?rev=604534&r1=604533&r2=604534&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/LinkFactory.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/LinkFactory.java Sat Dec 15 16:56:56 2007
@@ -30,14 +30,14 @@
      * encoded by the current request (that is, bound to the current request's session, if any).
      *
      * @param component the component for which an action link is to be generated
-     * @param action    a name associated with the action
+     * @param eventType the type of event to trigger
      * @param forForm   true if the link is for a form, false otherwise
      * @param context   Additional path data, each value will be converted to a string and appended to the
      *                  URI
      * @return a link
+     * @see org.apache.tapestry.ComponentResourcesCommon#createActionLink(String, boolean, Object[])
      */
-    Link createActionLink(ComponentPageElement component, String action, boolean forForm,
-                          Object... context);
+    Link createActionLink(ComponentPageElement component, String eventType, boolean forForm, Object... context);
 
     /**
      * Creates a render link for the page. If an activation context is supplied then that context is
@@ -52,7 +52,8 @@
      * @param page              the page to which a link should be created
      * @param override          if true, then the provided activation context is always used even if empty
      * @param activationContext the activation context for the page
-     * @return
+     * @return a link
+     * @see org.apache.tapestry.ComponentResourcesCommon#createPageLink(String, boolean, Object[])
      */
     Link createPageLink(Page page, boolean override, Object... activationContext);
 
@@ -60,12 +61,12 @@
      * As with {@link #createPageLink(Page, boolean, Object[])}, but the page is specified by
      * logical name, rather than as an instance.
      *
-     * @param page     the logical name of the page to generate a link to
-     * @param override if true, then the provided activation context is always used even if empty
-     * @param context  activation context for the page
+     * @param logicalPageName the logical name of the page to generate a link to
+     * @param override        if true, then the provided activation context is always used even if empty
+     * @param context         activation context for the page
      * @return
      */
-    Link createPageLink(String page, boolean override, Object... context);
+    Link createPageLink(String logicalPageName, boolean override, Object... context);
 
     /**
      * Adds a listener, to be notified any time an action or render link is created; this allows the

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/LinkFactoryImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/LinkFactoryImpl.java?rev=604534&r1=604533&r2=604534&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/LinkFactoryImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/LinkFactoryImpl.java Sat Dec 15 16:56:56 2007
@@ -103,15 +103,15 @@
         _listeners.add(listener);
     }
 
-    public Link createActionLink(ComponentPageElement component, String action, boolean forForm, Object... context)
+    public Link createActionLink(ComponentPageElement component, String eventType, boolean forForm, Object... context)
     {
-        notBlank(action, "action");
+        notBlank(eventType, "action");
 
         Page containingPage = component.getContainingPage();
 
         String logicalPageName = containingPage.getLogicalName();
 
-        ActionLinkTarget target = new ActionLinkTarget(action, logicalPageName, component
+        ActionLinkTarget target = new ActionLinkTarget(eventType, logicalPageName, component
                 .getNestedId());
 
         String[] contextStrings = toContextStrings(context);
@@ -217,10 +217,10 @@
         return result;
     }
 
-    public Link createPageLink(String pageName, boolean override, Object... context)
+    public Link createPageLink(String logicalPageName, boolean override, Object... context)
     {
         // This verifies that the page name is valid.
-        Page page = _pageCache.get(pageName);
+        Page page = _pageCache.get(logicalPageName);
 
         return createPageLink(page, override, context);
     }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/TemplateParserImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/TemplateParserImpl.java?rev=604534&r1=604533&r2=604534&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/TemplateParserImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/TemplateParserImpl.java Sat Dec 15 16:56:56 2007
@@ -52,7 +52,7 @@
 
     public static final String TAPESTRY_SCHEMA_5_0_0 = "http://tapestry.apache.org/schema/tapestry_5_0_0.xsd";
 
-    private static final String ID_REGEXP = "^[a-z]([a-z]|[0-9]|_)*$";
+    private static final String ID_REGEXP = "^[a-z]\\w*$";
 
     private static final Pattern ID_PATTERN = Pattern.compile(ID_REGEXP, Pattern.CASE_INSENSITIVE);
 

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/structure/Page.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/structure/Page.java?rev=604534&r1=604533&r2=604534&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/structure/Page.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/structure/Page.java Sat Dec 15 16:56:56 2007
@@ -117,22 +117,14 @@
     /**
      * Creates a link that will trigger behavior in a component within the page.
      *
-     * @param element
-     * @param action
-     * @param forForm
-     * @param context
-     * @return
+     * @see org.apache.tapestry.ComponentResourcesCommon#createActionLink(String, boolean, Object[])
      */
-    Link createActionLink(ComponentPageElement element, String action, boolean forForm,
-                          Object... context);
+    Link createActionLink(ComponentPageElement element, String eventType, boolean forForm, Object... context);
 
     /**
      * Creates a link to the named page.
      *
-     * @param pageName the logical name of the page
-     * @param override if true, the provided context is used even if empty
-     * @param context  optional activation context for the page (if not provided, it is generated from
-     *                 the page itself)
+     * @see org.apache.tapestry.ComponentResourcesCommon#createPageLink(String, boolean, Object[])
      */
     Link createPageLink(String pageName, boolean override, Object... context);
 

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/structure/PageImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/structure/PageImpl.java?rev=604534&r1=604533&r2=604534&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/structure/PageImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/structure/PageImpl.java Sat Dec 15 16:56:56 2007
@@ -151,10 +151,9 @@
         return _rootElement.getLogger();
     }
 
-    public Link createActionLink(ComponentPageElement element, String action, boolean forForm,
-                                 Object... context)
+    public Link createActionLink(ComponentPageElement element, String eventType, boolean forForm, Object... context)
     {
-        return _linkFactory.createActionLink(element, action, forForm, context);
+        return _linkFactory.createActionLink(element, eventType, forForm, context);
     }
 
     public Link createPageLink(String pageName, boolean override, Object... context)
@@ -169,8 +168,7 @@
 
     public Object getFieldChange(ComponentPageElement element, String fieldName)
     {
-        if (_fieldBundle == null)
-            _fieldBundle = _persistentFieldManager.gatherChanges(_logicalPageName);
+        if (_fieldBundle == null) _fieldBundle = _persistentFieldManager.gatherChanges(_logicalPageName);
 
         return _fieldBundle.getValue(element.getNestedId(), fieldName);
     }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/ComponentActionDispatcherTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/ComponentActionDispatcherTest.java?rev=604534&r1=604533&r2=604534&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/ComponentActionDispatcherTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/ComponentActionDispatcherTest.java Sat Dec 15 16:56:56 2007
@@ -55,6 +55,24 @@
         test("/foo/MyPage:anevent", "foo/MyPage", "", "anevent");
     }
 
+    /**
+     * @see https://issues.apache.org/jira/browse/TAPESTRY-1949
+     */
+    @Test
+    public void event_on_page_with_name_and_dotted_parameters() throws Exception
+    {
+        test("/foo/MyPage:myevent/1.2.3/4.5.6", "foo/MyPage", "", "myevent", "1.2.3", "4.5.6");
+    }
+
+    /**
+     * @see https://issues.apache.org/jira/browse/TAPESTRY-1949
+     */
+    @Test
+    public void event_on_page_dotted_parameters() throws Exception
+    {
+        test("/foo/MyPage:action/1.2.3/4.5.6", "foo/MyPage", "", TapestryConstants.ACTION_EVENT, "1.2.3", "4.5.6");
+    }
+
     @Test
     public void event_on_component_within_page() throws Exception
     {

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/LinkFactoryImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/LinkFactoryImplTest.java?rev=604534&r1=604533&r2=604534&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/LinkFactoryImplTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/LinkFactoryImplTest.java Sat Dec 15 16:56:56 2007
@@ -324,6 +324,49 @@
         };
     }
 
+    @Test
+    public void action_with_context_that_contains_periods()
+    {
+        Request request = mockRequest();
+        Response response = mockResponse();
+        ComponentPageElement element = mockComponentPageElement();
+        Page page = mockPage();
+        ComponentPageElement rootElement = mockComponentPageElement();
+        LinkFactoryListener listener = mockLinkFactoryListener();
+        ComponentInvocationMap map = mockComponentInvocationMap();
+        RequestPageCache cache = mockRequestPageCache();
+
+        final Holder<Link> holder = new Holder<Link>();
+
+        train_getContainingPage(element, page);
+        train_getLogicalName(page, "mypage");
+        train_getContextPath(request, "");
+        train_getNestedId(element, null);
+
+        train_getRootElement(page, rootElement);
+        train_triggerPassivateEventForActionLink(rootElement, listener, holder);
+
+        // This needs to be refactored a bit to be more testable.
+
+        map.store(isA(Link.class), isA(ComponentInvocation.class));
+
+        train_encodeURL(response, "/mypage:myaction/1.2.3/4.5.6?t:ac=foo/bar", ENCODED);
+
+        replay();
+
+        LinkFactory factory = new LinkFactoryImpl(request, response, map, cache, _typeCoercer);
+        factory.addListener(listener);
+
+        Link link = factory.createActionLink(element, "myaction", false, "1.2.3", "4.5.6");
+
+        assertEquals(link.toURI(), ENCODED);
+        assertSame(link, holder.get());
+
+        verify();
+
+    }
+
+
     @SuppressWarnings("unchecked")
     private void testActionLink(String contextPath, String logicalPageName, String nestedId, String eventName,
                                 String expectedURI, Object... context)