You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2008/10/25 20:34:09 UTC

svn commit: r707887 [1/2] - in /tapestry/tapestry5/trunk: tapestry-core/src/main/java/org/apache/tapestry5/internal/ tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ tapestry-core/src/main/java/org/apache/tapestry5/internal/structure...

Author: hlship
Date: Sat Oct 25 11:34:08 2008
New Revision: 707887

URL: http://svn.apache.org/viewvc?rev=707887&view=rev
Log:
TAP5-302: URL encoded strings that contain symbols such as  %2f (encoded "/") are decoded incorrectly in some environments

Added:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentEventTarget.java
      - copied, changed from r704144, tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ActionLinkTarget.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ContextPathEncoderImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageActivationContextCollector.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageActivationContextCollectorImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderTarget.java
      - copied, changed from r704144, tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageLinkTarget.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/URLEncoderImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/ComponentEventInvoker.java
      - copied, changed from r704144, tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/ActionLinkInvoker.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageRenderInvoker.java
      - copied, changed from r704144, tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageLinkInvoker.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ContextPathEncoder.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/URLEncoder.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentInvocationImplTest.java   (contents, props changed)
      - copied, changed from r704144, tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentInvocationTest.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/PageActivationContextCollectorImplTest.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/URLEncoderImplTest.java
Removed:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ActionLinkTarget.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageLinkTarget.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/ActionLinkInvoker.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageLinkInvoker.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentInvocationTest.java
Modified:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/URLEventContext.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClientPersistentFieldStrategy.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentEventDispatcher.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInvocation.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInvocationImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalModule.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InvocationTarget.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LinkFactory.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LinkFactoryImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LinkFactoryListener.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LinkImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderDispatcher.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ServicesMessages.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/PageImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/test/PageTester.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/test/TapestryTestCase.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/internal/services/ServicesStrings.properties
    tapestry/tapestry5/trunk/tapestry-core/src/test/app1/Target.tml
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/corelib/base/AbstractLinkTest.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/AdditionalIntegrationTests.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/IntegrationTests.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/TapestryInternalUtilsTest.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentEventDispatcherTest.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/LinkFactoryImplTest.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/LinkImplTest.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/PageRenderDispatcherTest.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/test/InternalBaseTestCase.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/services/TapestryIOCModule.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/services/TypeCoercerImplTest.java

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java?rev=707887&r1=707886&r2=707887&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java Sat Oct 25 11:34:08 2008
@@ -14,18 +14,13 @@
 
 package org.apache.tapestry5.internal;
 
-import org.apache.commons.codec.EncoderException;
-import org.apache.commons.codec.net.URLCodec;
 import org.apache.tapestry5.OptionModel;
 import org.apache.tapestry5.SelectModel;
 import org.apache.tapestry5.ioc.Messages;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
-import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newList;
 import org.apache.tapestry5.ioc.internal.util.Defense;
-import static org.apache.tapestry5.ioc.internal.util.Defense.notNull;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 
-import java.util.BitSet;
 import java.util.List;
 import java.util.Map;
 import java.util.regex.Pattern;
@@ -35,30 +30,12 @@
  */
 public class TapestryInternalUtils
 {
-    private static final Pattern NON_WORD_PATTERN = Pattern.compile("[^\\w]");
-
-    private static final URLCodec CODEC = new URLCodec()
-    {
+    private static final String SLASH = "/";
 
-        private BitSet contextSafe = (BitSet) WWW_FORM_URL.clone();
+    private static final Pattern SLASH_PATTERN = Pattern.compile(SLASH);
 
-        {
-            // Servlet container does not decode '+' in path to ' ',
-            // so we encode ' ' to %20, not to '+'.
-            contextSafe.clear(' ');
-        }
+    private static final Pattern NON_WORD_PATTERN = Pattern.compile("[^\\w]");
 
-        @Override
-        public byte[] encode(byte[] bytes)
-        {
-            return encodeUrl(contextSafe, bytes);
-        }
-    };
-
-    private TapestryInternalUtils()
-    {
-        // Prevent instantiation.
-    }
 
     /**
      * Capitalizes the string, and inserts a space before each upper case character (or sequence of upper case
@@ -150,7 +127,7 @@
     {
         Defense.notNull(input, "input");
 
-        List<OptionModel> result = newList();
+        List<OptionModel> result = CollectionFactory.newList();
 
         for (String term : input.split(","))
             result.add(toOptionModel(term.trim()));
@@ -179,7 +156,7 @@
      */
     public static OptionModel toOptionModel(Map.Entry input)
     {
-        notNull(input, "input");
+        Defense.notNull(input, "input");
 
         String label = input.getValue() != null ? String.valueOf(input.getValue()) : "";
 
@@ -196,7 +173,7 @@
     {
         Defense.notNull(input, "input");
 
-        List<OptionModel> result = newList();
+        List<OptionModel> result = CollectionFactory.newList();
 
         for (Map.Entry entry : input.entrySet())
             result.add(toOptionModel(entry));
@@ -240,7 +217,7 @@
     {
         Defense.notNull(input, "input");
 
-        List<OptionModel> result = newList();
+        List<OptionModel> result = CollectionFactory.newList();
 
         for (E element : input)
             result.add(toOptionModel(element));
@@ -365,60 +342,6 @@
         return getLabelForEnum(messages, prefix, value);
     }
 
-    /**
-     * Encodes a string for inclusion in a URL.  Slashes and percents are converted to "%25" and "%2F" respectively,
-     * then the entire string is  URL encoded.
-     *
-     * @param input string to include, may not be blank
-     * @return encoded input
-     */
-    public static String encodeContext(String input)
-    {
-        Defense.notBlank(input, "input");
-
-        try
-        {
-            return CODEC.encode(escapePercentAndSlash(input));
-        }
-        catch (EncoderException ex)
-        {
-            throw new RuntimeException(ex);
-        }
-    }
-
-    private static final String PERCENT = "%";
-    private static final Pattern PERCENT_PATTERN = Pattern.compile(PERCENT);
-    private static final String ENCODED_PERCENT = "%25";
-    private static final Pattern ENCODED_PERCENT_PATTERN = Pattern.compile(ENCODED_PERCENT);
-
-    private static final String SLASH = "/";
-    private static final Pattern SLASH_PATTERN = Pattern.compile(SLASH);
-    private static final String ENCODED_SLASH = "%2F";
-    private static final Pattern ENCODED_SLASH_PATTERN = Pattern.compile(ENCODED_SLASH, Pattern.CASE_INSENSITIVE);
-
-    /**
-     * Encodes percent and slash characters in the string for later decoding via {@link
-     * #unescapePercentAndSlash(String)}.
-     *
-     * @param input string to encode
-     * @return modified string
-     */
-    public static String escapePercentAndSlash(String input)
-    {
-        return replace(replace(input, PERCENT_PATTERN, ENCODED_PERCENT), SLASH_PATTERN, ENCODED_SLASH);
-    }
-
-    /**
-     * Used to decode certain escaped characters that are replaced when using {@link #encodeContext(String)}}.
-     *
-     * @param input a previously encoded string
-     * @return the string with slash and percent characters restored
-     */
-    public static String unescapePercentAndSlash(String input)
-    {
-        return replace(replace(input, ENCODED_SLASH_PATTERN, SLASH), ENCODED_PERCENT_PATTERN, PERCENT);
-    }
-
     private static String replace(String input, Pattern pattern, String replacement)
     {
         return pattern.matcher(input).replaceAll(replacement);

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/URLEventContext.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/URLEventContext.java?rev=707887&r1=707886&r2=707887&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/URLEventContext.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/URLEventContext.java Sat Oct 25 11:34:08 2008
@@ -35,7 +35,7 @@
 
     public int getCount()
     {
-        return values.length;
+        return values == null ? 0 : values.length;
     }
 
     public <T> T get(Class<T> desiredType, int index)

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClientPersistentFieldStrategy.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClientPersistentFieldStrategy.java?rev=707887&r1=707886&r2=707887&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClientPersistentFieldStrategy.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClientPersistentFieldStrategy.java Sat Oct 25 11:34:08 2008
@@ -44,7 +44,7 @@
         storage.postChange(pageName, componentId, fieldName, newValue);
     }
 
-    public void createComponentEventLink(Link link)
+    public void createdComponentEventLink(Link link)
     {
         storage.updateLink(link);
     }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentEventDispatcher.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentEventDispatcher.java?rev=707887&r1=707886&r2=707887&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentEventDispatcher.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentEventDispatcher.java Sat Oct 25 11:34:08 2008
@@ -16,10 +16,7 @@
 
 import org.apache.tapestry5.EventConstants;
 import org.apache.tapestry5.EventContext;
-import org.apache.tapestry5.internal.EmptyEventContext;
 import org.apache.tapestry5.internal.InternalConstants;
-import org.apache.tapestry5.internal.TapestryInternalUtils;
-import org.apache.tapestry5.internal.URLEventContext;
 import org.apache.tapestry5.services.*;
 
 import java.io.IOException;
@@ -50,9 +47,7 @@
 
     private final ComponentEventRequestHandler componentEventRequestHandler;
 
-    private final ContextValueEncoder contextValueEncoder;
-
-    private final EventContext emptyContext = new EmptyEventContext();
+    private final ContextPathEncoder contextPathEncoder;
 
     public ComponentEventDispatcher(
             @Traditional
@@ -60,11 +55,11 @@
 
             ComponentClassResolver componentClassResolver,
 
-            ContextValueEncoder contextValueEncoder)
+            ContextPathEncoder contextPathEncoder)
     {
         this.componentEventRequestHandler = componentEventRequestHandler;
         this.componentClassResolver = componentClassResolver;
-        this.contextValueEncoder = contextValueEncoder;
+        this.contextPathEncoder = contextPathEncoder;
     }
 
     // A beast that recognizes all the elements of a path in a single go.
@@ -108,9 +103,10 @@
 
         if (!componentClassResolver.isPageName(activePageName)) return false;
 
-        EventContext eventContext = decodeContext(matcher.group(CONTEXT));
+        EventContext eventContext = contextPathEncoder.decodePath(matcher.group(CONTEXT));
 
-        EventContext activationContext = decodeContext(request.getParameter(InternalConstants.PAGE_CONTEXT_NAME));
+        EventContext activationContext = contextPathEncoder.decodePath(
+                request.getParameter(InternalConstants.PAGE_CONTEXT_NAME));
 
         // The event type is often omitted, and defaults to "action".
 
@@ -132,20 +128,4 @@
 
         return true;
     }
-
-
-    private EventContext decodeContext(String input)
-    {
-        if (input == null) return emptyContext;
-
-        String[] values = TapestryInternalUtils.splitPath(input);
-
-        for (int i = 0; i < values.length; i++)
-        {
-            values[i] = TapestryInternalUtils.unescapePercentAndSlash(values[i]);
-        }
-
-        return new URLEventContext(contextValueEncoder, values);
-    }
-
 }

Copied: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentEventTarget.java (from r704144, tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ActionLinkTarget.java)
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentEventTarget.java?p2=tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentEventTarget.java&p1=tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ActionLinkTarget.java&r1=704144&r2=707887&rev=707887&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ActionLinkTarget.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentEventTarget.java Sat Oct 25 11:34:08 2008
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007 The Apache Software Foundation
+// Copyright 2006, 2007, 2008 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -18,9 +18,9 @@
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 
 /**
- * It represents an invocation target for an action link.
+ * It represents an invocation target for any kind of component event.
  */
-public class ActionLinkTarget implements InvocationTarget
+public class ComponentEventTarget implements InvocationTarget
 {
     private final String eventType;
 
@@ -28,12 +28,11 @@
 
     private final String componentNestedId;
 
-    public ActionLinkTarget(String eventType, String pageName, String componentNestedId)
+    public ComponentEventTarget(String eventType, String pageName, String componentNestedId)
     {
         this.eventType = eventType;
         this.pageName = pageName;
         this.componentNestedId = componentNestedId;
-
     }
 
     public String getPath()
@@ -78,5 +77,4 @@
     {
         return pageName;
     }
-
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInvocation.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInvocation.java?rev=707887&r1=707886&r2=707887&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInvocation.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInvocation.java Sat Oct 25 11:34:08 2008
@@ -1,4 +1,4 @@
-// Copyright 2007 The Apache Software Foundation
+// Copyright 2007, 2008 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -14,26 +14,55 @@
 
 package org.apache.tapestry5.internal.services;
 
+import org.apache.tapestry5.EventContext;
+
 import java.util.List;
 
+/**
+ * Represents an invocation of a page (to render) or a component (to handle an event). This is the core of the {@link
+ * org.apache.tapestry5.Link} implementation, and is seperated out to faciliate the {@link
+ * org.apache.tapestry5.test.PageTester}.
+ */
 public interface ComponentInvocation
 {
     /**
-     * @return A path taking the format <em>target-path</em>/e1/e2?&q1=v1&q2=v2. where the <em>target-path</em> is the
-     *         path provided by the invocation target; e1 and e2 are elements of the context; q1 and q2 are the
-     *         parameters.
+     * Constructs the URI for the component invocation. This may include the event context or page activation context.
+     * If the invocation was constructed for a form, then parameters will be omitted (such that they can be rendered as
+     * individual hidden fields within the form) ... otherwise, the URI will include query parameters.
      */
-    String buildURI(boolean isForm);
+    String buildURI();
 
-    String[] getContext();
+    /**
+     * Returns the event context associated with the component event.  This will be an empty event context for a page
+     * render request.
+     */
+    EventContext getEventContext();
 
-    String[] getActivationContext();
+    /**
+     * Returns the page activation context for the page referenced in a page render or component event request.
+     */
+    EventContext getPageActivationContext();
 
+    /**
+     * Adds an additional parameter to be encoded into the URL.
+     *
+     * @param parameterName name of parameter
+     * @param value         parameter value (should be URL safe)
+     */
     void addParameter(String parameterName, String value);
 
+    /**
+     * Returns sorted list of parameter names.
+     */
     List<String> getParameterNames();
 
+    /**
+     * Returns value for a parameter.
+     */
     String getParameterValue(String name);
 
+    /**
+     * Returns the target of the invocation (this is used by {@link org.apache.tapestry5.test.PageTester}).
+     */
     InvocationTarget getTarget();
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInvocationImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInvocationImpl.java?rev=707887&r1=707886&r2=707887&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInvocationImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInvocationImpl.java Sat Oct 25 11:34:08 2008
@@ -14,51 +14,63 @@
 
 package org.apache.tapestry5.internal.services;
 
-import org.apache.tapestry5.internal.TapestryInternalUtils;
-import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newMap;
+import org.apache.tapestry5.EventContext;
+import org.apache.tapestry5.internal.InternalConstants;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import static org.apache.tapestry5.ioc.internal.util.Defense.notBlank;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
-import org.apache.tapestry5.test.PageTester;
+import org.apache.tapestry5.services.ContextPathEncoder;
 
 import java.util.List;
 import java.util.Map;
 
 /**
- * Represents an invocation for a page or a component in the current application. This information is extracted from
- * incoming URLs for a running application (or created by the {@link PageTester}. Each invocation may provide a context
- * (Object[]) and parameters to the invocation target.
+ * Represents an invocation for a page render or a component event, in the current application.
  */
 public class ComponentInvocationImpl implements ComponentInvocation
 {
-    private final String[] context;
+    private final ContextPathEncoder encoder;
+
+    private final String eventContextPath;
 
     private final InvocationTarget target;
 
-    private final String[] activationContext;
+    private final String pageActivationContextPath;
+
+    private final boolean forForm;
 
     private Map<String, String> parameters;
 
     /**
-     * @param target            identifies the target of the event: a component with a page
-     * @param context           context information supplied by the component to be provided back when the event on the
-     *                          component is triggered, or contains the activation context when the invocation is for a
-     *                          page render request
-     * @param activationContext page activation context for the page containing the component, supplied via a passivate
-     *                          event to the page's root component (used when an action component invocation is for a
-     *                          page with an activation context)
+     * @param encoder
+     * @param target                identifies the target of the event: a component with a page
+     * @param eventContext          event activation context (or null for a page render invocation)
+     * @param pageActivationContext page activation context (may be null)
+     * @param forForm               if true, the URL is rendered for a form
      */
-    public ComponentInvocationImpl(InvocationTarget target, String[] context, String[] activationContext)
+    public ComponentInvocationImpl(ContextPathEncoder encoder, InvocationTarget target, Object[] eventContext,
+                                   Object[] pageActivationContext,
+                                   boolean forForm)
     {
+        this.encoder = encoder;
         this.target = target;
-        this.context = context;
-        this.activationContext = activationContext;
+        this.forForm = forForm;
+        this.eventContextPath = eventContext == null ? null : encoder.encodeIntoPath(eventContext);
+        this.pageActivationContextPath = encoder.encodeIntoPath(pageActivationContext);
+
+        // For component events, the page activation context (if it exists) is a query parameter
+        // not path info.
+
+        if (eventContext != null && !InternalUtils.isBlank(this.pageActivationContextPath))
+            addParameter(InternalConstants.PAGE_CONTEXT_NAME, this.pageActivationContextPath);
     }
 
 
-    public String buildURI(boolean isForm)
+    public String buildURI()
     {
         String path = getPath();
-        if (isForm || parameters == null) return path;
+
+        if (forForm || parameters == null) return path;
 
         StringBuilder builder = new StringBuilder();
 
@@ -86,31 +98,34 @@
     }
 
     /**
-     * @return Just like the return value of {@link #buildURI(boolean)} except that parameters are not included.
+     * Return the path which identifies the page (and perhaps component) plus the event or page activation context. This
+     * is the {@linkplain InvocationTarget#getPath() target path} plus any extra path info.
      */
     private String getPath()
     {
-        StringBuilder builder = new StringBuilder();
-        builder.append(target.getPath());
+        // For component event requests, the extra path info the the event context.  For page render requests,
+        // the extra path info is the page activation context.
 
-        for (String id : context)
-        {
-            if (builder.length() > 0) builder.append("/");
+        String extraPath =
+                eventContextPath != null ? eventContextPath : pageActivationContextPath;
 
-            builder.append(TapestryInternalUtils.encodeContext(id));
-        }
+        String targetPath = target.getPath();
 
-        return builder.toString();
+        if (InternalUtils.isBlank(extraPath)) return targetPath;
+
+        if (targetPath.length() == 0) return extraPath;
+
+        return targetPath + "/" + extraPath;
     }
 
-    public String[] getContext()
+    public EventContext getEventContext()
     {
-        return context;
+        return encoder.decodePath(eventContextPath);
     }
 
-    public String[] getActivationContext()
+    public EventContext getPageActivationContext()
     {
-        return activationContext;
+        return encoder.decodePath(pageActivationContextPath);
     }
 
     public void addParameter(String parameterName, String value)
@@ -118,7 +133,7 @@
         notBlank(parameterName, "parameterName");
         notBlank(value, "value");
 
-        if (parameters == null) parameters = newMap();
+        if (parameters == null) parameters = CollectionFactory.newMap();
 
         if (parameters.containsKey(parameterName)) throw new IllegalArgumentException(
                 ServicesMessages.parameterNameMustBeUnique(parameterName, parameters.get(parameterName)));

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ContextPathEncoderImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ContextPathEncoderImpl.java?rev=707887&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ContextPathEncoderImpl.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ContextPathEncoderImpl.java Sat Oct 25 11:34:08 2008
@@ -0,0 +1,75 @@
+//  Copyright 2008 The Apache Software Foundation
+//
+// 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 org.apache.tapestry5.internal.services;
+
+import org.apache.tapestry5.EventContext;
+import org.apache.tapestry5.internal.EmptyEventContext;
+import org.apache.tapestry5.internal.TapestryInternalUtils;
+import org.apache.tapestry5.internal.URLEventContext;
+import org.apache.tapestry5.ioc.internal.util.InternalUtils;
+import org.apache.tapestry5.services.ContextPathEncoder;
+import org.apache.tapestry5.services.ContextValueEncoder;
+import org.apache.tapestry5.services.URLEncoder;
+
+public class ContextPathEncoderImpl implements ContextPathEncoder
+{
+    private static final int BUFFER_SIZE = 100;
+
+    private final ContextValueEncoder valueEncoder;
+
+    private final URLEncoder urlEncoder;
+
+    private final EventContext EMPTY = new EmptyEventContext();
+
+    public ContextPathEncoderImpl(ContextValueEncoder valueEncoder, URLEncoder urlEncoder)
+    {
+        this.valueEncoder = valueEncoder;
+        this.urlEncoder = urlEncoder;
+    }
+
+    public String encodeIntoPath(Object[] context)
+    {
+        if (context == null || context.length == 0) return "";
+
+        StringBuilder output = new StringBuilder(BUFFER_SIZE);
+
+        for (int i = 0; i < context.length; i++)
+        {
+            Object raw = context[i];
+            String valueEncoded = raw == null ? null : valueEncoder.toClient(raw);
+            String urlEncoded = urlEncoder.encode(valueEncoded);
+
+            if (i > 0) output.append("/");
+
+            output.append(urlEncoded);
+        }
+
+        return output.toString();
+    }
+
+    public EventContext decodePath(String path)
+    {
+        if (InternalUtils.isBlank(path)) return EMPTY;
+
+        String[] split = TapestryInternalUtils.splitPath(path);
+
+        for (int i = 0; i < split.length; i++)
+        {
+            split[i] = urlEncoder.decode(split[i]);
+        }
+
+        return new URLEventContext(valueEncoder, split);
+    }
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalModule.java?rev=707887&r1=707886&r2=707887&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalModule.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalModule.java Sat Oct 25 11:34:08 2008
@@ -84,6 +84,7 @@
         binder.bind(RequestSecurityManager.class, RequestSecurityManagerImpl.class);
         binder.bind(InternalRequestGlobals.class, InternalRequestGlobalsImpl.class);
         binder.bind(EndOfRequestListenerHub.class);
+        binder.bind(PageActivationContextCollector.class);
     }
 
     /**

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InvocationTarget.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InvocationTarget.java?rev=707887&r1=707886&r2=707887&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InvocationTarget.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InvocationTarget.java Sat Oct 25 11:34:08 2008
@@ -1,4 +1,4 @@
-// Copyright 2006 The Apache Software Foundation
+// Copyright 2006, 2008 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -17,8 +17,14 @@
 /**
  * It represents target for a {@link org.apache.tapestry5.internal.services.ComponentInvocation}. For example, it may be
  * a page or an action for a component within a page.
+ *
+ * @see org.apache.tapestry5.services.Dispatcher
  */
 public interface InvocationTarget
 {
+    /**
+     * Represents the invocation as a path, part of a larger URI. The path will come after the context path and before
+     * extra path info (converted from event or page activation context values).
+     */
     String getPath();
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LinkFactory.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LinkFactory.java?rev=707887&r1=707886&r2=707887&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LinkFactory.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LinkFactory.java Sat Oct 25 11:34:08 2008
@@ -64,7 +64,7 @@
      * @param context         activation context for the page
      * @return
      */
-    Link createPageLink(String logicalPageName, boolean override, Object... context);
+    Link createPageRenderLink(String logicalPageName, boolean override, Object... context);
 
     /**
      * Adds a listener, to be notified any time an action or render link is created; this allows the listener to modify

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LinkFactoryImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LinkFactoryImpl.java?rev=707887&r1=707886&r2=707887&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LinkFactoryImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LinkFactoryImpl.java Sat Oct 25 11:34:08 2008
@@ -14,26 +14,16 @@
 
 package org.apache.tapestry5.internal.services;
 
-import org.apache.tapestry5.ComponentEventCallback;
-import org.apache.tapestry5.EventConstants;
 import org.apache.tapestry5.Link;
 import org.apache.tapestry5.internal.InternalConstants;
-import org.apache.tapestry5.internal.TapestryInternalUtils;
-import org.apache.tapestry5.internal.structure.ComponentPageElement;
 import org.apache.tapestry5.internal.structure.Page;
-import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.*;
-import static org.apache.tapestry5.ioc.internal.util.Defense.notBlank;
-import static org.apache.tapestry5.ioc.internal.util.Defense.notNull;
-import org.apache.tapestry5.ioc.internal.util.InternalUtils;
-import org.apache.tapestry5.ioc.util.StrategyRegistry;
-import org.apache.tapestry5.services.ContextValueEncoder;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.ioc.internal.util.Defense;
+import org.apache.tapestry5.services.ContextPathEncoder;
 import org.apache.tapestry5.services.Request;
 import org.apache.tapestry5.services.Response;
 
-import java.util.Arrays;
-import java.util.Collection;
 import java.util.List;
-import java.util.Map;
 
 public class LinkFactoryImpl implements LinkFactory
 {
@@ -45,23 +35,18 @@
 
     private final RequestPageCache pageCache;
 
-    private final ContextValueEncoder contextValueEncoder;
-
     private final RequestPathOptimizer optimizer;
 
     private final PageRenderQueue pageRenderQueue;
 
     private final RequestSecurityManager requestSecurityManager;
 
-    private final List<LinkFactoryListener> listeners = newThreadSafeList();
+    private final ContextPathEncoder contextPathEncoder;
 
-    private final StrategyRegistry<PassivateContextHandler> registry;
+    private final PageActivationContextCollector contextCollector;
 
+    private final List<LinkFactoryListener> listeners = CollectionFactory.newThreadSafeList();
 
-    private interface PassivateContextHandler<T>
-    {
-        void handle(T result, List context);
-    }
 
     public LinkFactoryImpl(Request request,
                            Response response,
@@ -69,8 +54,9 @@
                            RequestPageCache pageCache,
                            RequestPathOptimizer optimizer,
                            PageRenderQueue pageRenderQueue,
-                           ContextValueEncoder contextValueEncoder,
-                           RequestSecurityManager requestSecurityManager)
+                           RequestSecurityManager requestSecurityManager,
+                           ContextPathEncoder contextPathEncoder,
+                           PageActivationContextCollector contextCollector)
     {
         this.request = request;
         this.response = response;
@@ -78,40 +64,9 @@
         this.pageCache = pageCache;
         this.optimizer = optimizer;
         this.pageRenderQueue = pageRenderQueue;
-        this.contextValueEncoder = contextValueEncoder;
         this.requestSecurityManager = requestSecurityManager;
-
-        Map<Class, PassivateContextHandler> registrations = newMap();
-
-        registrations.put(Object.class, new PassivateContextHandler()
-        {
-            @SuppressWarnings("unchecked")
-            public void handle(Object result, List context)
-            {
-                context.add(result);
-            }
-        });
-
-        registrations.put(Object[].class, new PassivateContextHandler<Object[]>()
-        {
-
-            @SuppressWarnings("unchecked")
-            public void handle(Object[] result, List context)
-            {
-                context.addAll(Arrays.asList(result));
-            }
-        });
-
-        registrations.put(Collection.class, new PassivateContextHandler<Collection>()
-        {
-            @SuppressWarnings("unchecked")
-            public void handle(Collection result, List context)
-            {
-                context.addAll(result);
-            }
-        });
-
-        registry = StrategyRegistry.newInstance(PassivateContextHandler.class, registrations);
+        this.contextPathEncoder = contextPathEncoder;
+        this.contextCollector = contextCollector;
     }
 
     public void addListener(LinkFactoryListener listener)
@@ -120,27 +75,26 @@
     }
 
     public Link createComponentEventLink(Page page, String nestedId, String eventType, boolean forForm,
-                                         Object... context)
+                                         Object... eventContext)
     {
-        notNull(page, "page");
-        notBlank(eventType, "action");
+        Defense.notNull(page, "page");
+        Defense.notBlank(eventType, "action");
 
         Page activePage = pageRenderQueue.getRenderingPage();
 
         // See TAPESTRY-2184
         if (activePage == null) activePage = page;
 
-        ActionLinkTarget target = new ActionLinkTarget(eventType, activePage.getLogicalName(), nestedId);
-
-        String[] contextStrings = toContextStrings(context);
+        ComponentEventTarget target = new ComponentEventTarget(eventType, activePage.getLogicalName(), nestedId);
 
-        String[] activationContext = collectActivationContextForPage(activePage);
+        Object[] pageActivationContext = contextCollector.collectPageActivationContext(activePage);
 
-        ComponentInvocation invocation = new ComponentInvocationImpl(target, contextStrings, activationContext);
+        ComponentInvocation invocation = new ComponentInvocationImpl(contextPathEncoder, target, eventContext,
+                                                                     pageActivationContext, forForm);
 
         String baseURL = requestSecurityManager.getBaseURL(activePage);
 
-        Link link = new LinkImpl(response, optimizer, baseURL, request.getContextPath(), invocation, forForm);
+        Link link = new LinkImpl(response, optimizer, baseURL, request.getContextPath(), invocation);
 
         // TAPESTRY-2044: Sometimes the active page drags in components from another page and we
         // need to differentiate that.
@@ -148,46 +102,29 @@
         if (activePage != page)
             link.addParameter(InternalConstants.CONTAINER_PAGE_NAME, page.getLogicalName().toLowerCase());
 
-        // Now see if the page has an activation context.
-
-        addActivationContextToLink(link, activationContext, forForm);
+        // This is a hook used for testing; we can relate the link to an invocation so that we can simulate
+        // the clicking of the link (or submitting of the form).
 
         componentInvocationMap.store(link, invocation);
 
         for (LinkFactoryListener listener : listeners)
-            listener.createComponentEventLink(link);
+            listener.createdComponentEventLink(link);
 
         return link;
     }
 
-    private void addActivationContextToLink(Link link, String[] activationContext, boolean forForm)
-    {
-        if (activationContext.length == 0) return;
-
-        StringBuilder builder = new StringBuilder();
-
-        for (int i = 0; i < activationContext.length; i++)
-        {
-            if (i > 0) builder.append("/");
-
-            builder.append(forForm
-                           ? TapestryInternalUtils.escapePercentAndSlash(activationContext[i])
-                           : TapestryInternalUtils.encodeContext(activationContext[i]));
-        }
-
-        link.addParameter(InternalConstants.PAGE_CONTEXT_NAME, builder.toString());
-    }
 
-    public Link createPageRenderLink(Page page, boolean override, Object... activationContext)
+    public Link createPageRenderLink(Page page, boolean override, Object... pageActivationContext)
     {
-        notNull(page, "page");
+        Defense.notNull(page, "page");
 
         String logicalPageName = page.getLogicalName();
 
         // When override is true, we use the activation context even if empty.
 
-        String[] context = (override || activationContext.length != 0) ? toContextStrings(
-                activationContext) : collectActivationContextForPage(page);
+        Object[] context = (override || pageActivationContext.length != 0)
+                           ? pageActivationContext
+                           : contextCollector.collectPageActivationContext(page);
 
         // Strip a trailing "/index" from the path.
 
@@ -202,13 +139,13 @@
             logicalPageName = lastSlashx < 0 ? "" : logicalPageName.substring(0, lastSlashx);
         }
 
-        PageLinkTarget target = new PageLinkTarget(logicalPageName);
+        PageRenderTarget target = new PageRenderTarget(logicalPageName);
 
-        ComponentInvocation invocation = new ComponentInvocationImpl(target, context, null);
+        ComponentInvocation invocation = new ComponentInvocationImpl(contextPathEncoder, target, null, context, false);
 
         String baseURL = requestSecurityManager.getBaseURL(page);
 
-        Link link = new LinkImpl(response, optimizer, baseURL, request.getContextPath(), invocation, false);
+        Link link = new LinkImpl(response, optimizer, baseURL, request.getContextPath(), invocation);
 
         componentInvocationMap.store(link, invocation);
 
@@ -218,57 +155,7 @@
         return link;
     }
 
-    /**
-     * Returns a list of objects acquired by invoking triggering the passivate event on the page's root element. May
-     * return an empty list.
-     */
-    private String[] collectActivationContextForPage(final Page page)
-    {
-        final List context = newList();
-
-        ComponentEventCallback callback = new ComponentEventCallback()
-        {
-            @SuppressWarnings("unchecked")
-            public boolean handleResult(Object result)
-            {
-                PassivateContextHandler contextHandler = registry.getByInstance(result);
-
-                contextHandler.handle(result, context);
-
-                return true;
-            }
-        };
-
-        ComponentPageElement rootElement = page.getRootElement();
-
-        rootElement.triggerEvent(EventConstants.PASSIVATE, null, callback);
-
-        return toContextStrings(context.toArray());
-    }
-
-    private String[] toContextStrings(Object[] context)
-    {
-        if (context == null) return new String[0];
-
-        String[] result = new String[context.length];
-
-        for (int i = 0; i < context.length; i++)
-        {
-
-            Object value = context[i];
-
-            String encoded = value == null ? null : contextValueEncoder.toClient(value);
-
-            if (InternalUtils.isBlank(encoded))
-                throw new RuntimeException(ServicesMessages.contextValueMayNotBeNull());
-
-            result[i] = encoded;
-        }
-
-        return result;
-    }
-
-    public Link createPageLink(String logicalPageName, boolean override, Object... context)
+    public Link createPageRenderLink(String logicalPageName, boolean override, Object... context)
     {
         // This verifies that the page name is valid.
         Page page = pageCache.get(logicalPageName);

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LinkFactoryListener.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LinkFactoryListener.java?rev=707887&r1=707886&r2=707887&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LinkFactoryListener.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LinkFactoryListener.java Sat Oct 25 11:34:08 2008
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007 The Apache Software Foundation
+// Copyright 2006, 2007, 2008 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -35,5 +35,5 @@
      *
      * @param link the newly created link
      */
-    void createComponentEventLink(Link link);
+    void createdComponentEventLink(Link link);
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LinkImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LinkImpl.java?rev=707887&r1=707886&r2=707887&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LinkImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LinkImpl.java Sat Oct 25 11:34:08 2008
@@ -37,21 +37,8 @@
 
     private final ComponentInvocation invocation;
 
-    private final boolean forForm;
-
     private String anchor;
 
-    LinkImpl(Response response, RequestPathOptimizer optimizer, String contextPath, String targetPath)
-    {
-        this(response, optimizer, contextPath, targetPath, false);
-    }
-
-    LinkImpl(Response response, RequestPathOptimizer optimizer, String contextPath, String targetPath, boolean forForm)
-    {
-        this(response, optimizer, null, contextPath,
-             new ComponentInvocationImpl(new OpaqueConstantTarget(targetPath), new String[0], null), forForm);
-    }
-
     /**
      * Creates a new Link.  Links may be full or optimized; optimization involves creating a relative URI from the
      * request's URI to the Link's URI.
@@ -61,17 +48,15 @@
      * @param baseURL     base URL prefix (before the context path), used when switching between secure and non-secure
      * @param contextPath path for the context {@link org.apache.tapestry5.services.Request#getContextPath()}
      * @param invocation  abstraction around the type of link (needed by {@link org.apache.tapestry5.test.PageTester})
-     * @param forForm     if true, then a Form has requested the Link, in which case, the link should not generated
      */
     public LinkImpl(Response response, RequestPathOptimizer optimizer, String baseURL, String contextPath,
-                    ComponentInvocation invocation, boolean forForm)
+                    ComponentInvocation invocation)
     {
         this.response = response;
         this.optimizer = optimizer;
         this.baseURL = baseURL;
         this.contextPath = contextPath;
         this.invocation = invocation;
-        this.forForm = forForm;
     }
 
     public void addParameter(String parameterName, String value)
@@ -108,8 +93,15 @@
         if (baseURL != null) builder.append(baseURL);
 
         builder.append(contextPath);
-        builder.append("/");
-        builder.append(invocation.buildURI(forForm));
+
+        String invocationURI = invocation.buildURI();
+
+        if (invocationURI.length() > 0)
+        {
+            builder.append("/");
+
+            builder.append(invocationURI);
+        }
 
         if (InternalUtils.isNonBlank(anchor))
         {

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageActivationContextCollector.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageActivationContextCollector.java?rev=707887&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageActivationContextCollector.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageActivationContextCollector.java Sat Oct 25 11:34:08 2008
@@ -0,0 +1,33 @@
+//  Copyright 2008 The Apache Software Foundation
+//
+// 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 org.apache.tapestry5.internal.services;
+
+import org.apache.tapestry5.internal.structure.Page;
+
+/**
+ * Fires the {@link org.apache.tapestry5.EventConstants#PASSIVATE} event on a page, and collects the result, converting
+ * it to an array of objects.
+ */
+public interface PageActivationContextCollector
+{
+    /**
+     * Fires the passivate event and collects the response, which is coerced to an object array. A page that does not
+     * have an event handler for the passivate event will return an empty array.
+     *
+     * @param page to collect context from
+     * @return the activation context, or an empty array of the page does not provide a context
+     */
+    Object[] collectPageActivationContext(Page page);
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageActivationContextCollectorImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageActivationContextCollectorImpl.java?rev=707887&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageActivationContextCollectorImpl.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageActivationContextCollectorImpl.java Sat Oct 25 11:34:08 2008
@@ -0,0 +1,59 @@
+//  Copyright 2008 The Apache Software Foundation
+//
+// 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 org.apache.tapestry5.internal.services;
+
+import org.apache.tapestry5.ComponentEventCallback;
+import org.apache.tapestry5.EventConstants;
+import org.apache.tapestry5.internal.structure.ComponentPageElement;
+import org.apache.tapestry5.internal.structure.Page;
+import org.apache.tapestry5.internal.util.Holder;
+import org.apache.tapestry5.ioc.services.TypeCoercer;
+
+public class PageActivationContextCollectorImpl implements PageActivationContextCollector
+{
+    private final Object[] EMPTY = new Object[0];
+
+    private final TypeCoercer typeCoercer;
+
+    public PageActivationContextCollectorImpl(TypeCoercer typeCoercer)
+    {
+        this.typeCoercer = typeCoercer;
+    }
+
+    public Object[] collectPageActivationContext(Page page)
+    {
+        ComponentPageElement element = page.getRootElement();
+
+        final Holder<Object[]> holder = Holder.create();
+
+        ComponentEventCallback callback = new ComponentEventCallback()
+        {
+            public boolean handleResult(Object result)
+            {
+                holder.put(typeCoercer.coerce(result, Object[].class));
+
+                // We've got the value, stop the event.
+
+                return true;
+            }
+        };
+
+        element.triggerEvent(EventConstants.PASSIVATE, null, callback);
+
+        if (!holder.hasValue()) return EMPTY;
+
+        return holder.get();
+    }
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderDispatcher.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderDispatcher.java?rev=707887&r1=707886&r2=707887&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderDispatcher.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderDispatcher.java Sat Oct 25 11:34:08 2008
@@ -15,8 +15,6 @@
 package org.apache.tapestry5.internal.services;
 
 import org.apache.tapestry5.EventContext;
-import org.apache.tapestry5.internal.TapestryInternalUtils;
-import org.apache.tapestry5.internal.URLEventContext;
 import org.apache.tapestry5.services.*;
 
 import java.io.IOException;
@@ -32,14 +30,14 @@
 
     private final PageRenderRequestHandler handler;
 
-    private final ContextValueEncoder contextValueEncoder;
+    private final ContextPathEncoder contextPathEncoder;
 
     public PageRenderDispatcher(ComponentClassResolver componentClassResolver, PageRenderRequestHandler handler,
-                                ContextValueEncoder contextValueEncoder)
+                                ContextPathEncoder contextPathEncoder)
     {
         this.componentClassResolver = componentClassResolver;
         this.handler = handler;
-        this.contextValueEncoder = contextValueEncoder;
+        this.contextPathEncoder = contextPathEncoder;
     }
 
     public boolean dispatch(Request request, final Response response) throws IOException
@@ -87,10 +85,7 @@
     {
         if (!componentClassResolver.isPageName(pageName)) return false;
 
-        String[] values = convertActivationContext(pageActivationContext);
-
-        EventContext activationContext
-                = new URLEventContext(contextValueEncoder, values);
+        EventContext activationContext = contextPathEncoder.decodePath(pageActivationContext);
 
         PageRenderRequestParameters parameters = new PageRenderRequestParameters(pageName, activationContext);
 
@@ -98,23 +93,4 @@
 
         return true;
     }
-
-    /**
-     * Converts the "extra path", the portion after the page name (and after the slash seperating the page name from the
-     * activation context) into an array of strings. LinkFactory and friends URL encode each value, so we URL decode the
-     * value (we assume that page names are "URL safe").
-     */
-    private String[] convertActivationContext(String extraPath)
-    {
-        if (extraPath.length() == 0) return new String[0];
-
-        String[] context = TapestryInternalUtils.splitPath(extraPath);
-
-        for (int i = 0; i < context.length; i++)
-        {
-            context[i] = TapestryInternalUtils.unescapePercentAndSlash(context[i]);
-        }
-
-        return context;
-    }
 }

Copied: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderTarget.java (from r704144, tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageLinkTarget.java)
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderTarget.java?p2=tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderTarget.java&p1=tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageLinkTarget.java&r1=704144&r2=707887&rev=707887&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageLinkTarget.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderTarget.java Sat Oct 25 11:34:08 2008
@@ -20,14 +20,13 @@
  * and the real Tapestry code {@link org.apache.tapestry5.internal.services.PageRenderDispatcher} in order to invoke a
  * page link.
  */
-public class PageLinkTarget implements InvocationTarget
+public class PageRenderTarget implements InvocationTarget
 {
     private final String pageName;
 
-    public PageLinkTarget(String pageName)
+    public PageRenderTarget(String pageName)
     {
         this.pageName = pageName;
-
     }
 
     public String getPath()
@@ -39,5 +38,4 @@
     {
         return pageName;
     }
-
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ServicesMessages.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ServicesMessages.java?rev=707887&r1=707886&r2=707887&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ServicesMessages.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ServicesMessages.java Sat Oct 25 11:34:08 2008
@@ -405,11 +405,6 @@
         return MESSAGES.format("no-such-method", ClassFabUtils.toJavaClassName(clazz), methodName);
     }
 
-    static String contextValueMayNotBeNull()
-    {
-        return MESSAGES.get("context-value-may-not-be-null");
-    }
-
     static String forbidInstantiateComponentClass(String className)
     {
         return MESSAGES.format("forbid-instantiate-component-class", className);

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/URLEncoderImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/URLEncoderImpl.java?rev=707887&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/URLEncoderImpl.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/URLEncoderImpl.java Sat Oct 25 11:34:08 2008
@@ -0,0 +1,153 @@
+//  Copyright 2008 The Apache Software Foundation
+//
+// 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 org.apache.tapestry5.internal.services;
+
+import org.apache.tapestry5.ioc.internal.util.Defense;
+import org.apache.tapestry5.services.URLEncoder;
+
+import java.util.BitSet;
+
+public class URLEncoderImpl implements URLEncoder
+{
+    static final String ENCODED_NULL = "$N";
+    static final String ENCODED_BLANK = "$B";
+
+    /**
+     * Bit set indicating which character are safe to pass through (when encoding or decoding) as-is.  All other
+     * characters are encoded as a kind of unicode escape.
+     */
+    private final BitSet safe = new BitSet(128);
+
+    {
+        markSafe("abcdefghijklmnopqrstuvwxyz");
+        markSafe("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+        markSafe("01234567890-_.:");
+    }
+
+    private void markSafe(String s)
+    {
+        for (char ch : s.toCharArray())
+        {
+            safe.set((int) ch);
+        }
+    }
+
+
+    public String encode(String input)
+    {
+        if (input == null) return ENCODED_NULL;
+
+        if (input.equals("")) return ENCODED_BLANK;
+
+        boolean dirty = false;
+
+        int length = input.length();
+
+        StringBuilder output = new StringBuilder(length * 2);
+
+        for (int i = 0; i < length; i++)
+        {
+            char ch = input.charAt(i);
+
+            if (ch == '$')
+            {
+                output.append("$$");
+                dirty = true;
+                continue;
+            }
+
+            int chAsInt = (int) ch;
+
+            if (safe.get(chAsInt))
+            {
+                output.append(ch);
+                continue;
+            }
+
+            output.append(String.format("$%04x", chAsInt));
+            dirty = true;
+        }
+
+        return dirty ? output.toString() : input;
+    }
+
+    public String decode(String input)
+    {
+        Defense.notNull(input, "input");
+
+        if (input.equals(ENCODED_NULL)) return null;
+
+        if (input.equals(ENCODED_BLANK)) return "";
+
+        boolean dirty = false;
+
+        int length = input.length();
+
+        StringBuilder output = new StringBuilder(length * 2);
+
+        for (int i = 0; i < length; i++)
+        {
+            char ch = input.charAt(i);
+
+            if (ch == '$')
+            {
+                dirty = true;
+
+                if (i + 1 < length && input.charAt(i + 1) == '$')
+                {
+                    output.append('$');
+                    i++;
+
+                    dirty = true;
+                    continue;
+                }
+
+                if (i + 4 < length)
+                {
+                    String hex = input.substring(i + 1, i + 5);
+
+                    try
+                    {
+                        int unicode = Integer.parseInt(hex, 16);
+
+                        output.append((char) unicode);
+                        i += 4;
+                        dirty = true;
+                        continue;
+                    }
+                    catch (NumberFormatException ex)
+                    {
+                        // Ignore.
+                    }
+                }
+
+                throw new IllegalArgumentException(String.format(
+                        "Input string '%s' is not valid; the '$' character at position %d should be followed by another '$' or a four digit hex number (a unicode value).",
+                        input, i + 1));
+            }
+
+            if (!safe.get((int) ch))
+            {
+                throw new IllegalArgumentException(
+                        String.format("Input string '%s' is not valid; the character '%s' at position %d is not valid.",
+                                      input, ch, i + 1));
+            }
+
+            output.append(ch);
+        }
+
+        return dirty ? output.toString() : input;
+    }
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/PageImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/PageImpl.java?rev=707887&r1=707886&r2=707887&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/PageImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/PageImpl.java Sat Oct 25 11:34:08 2008
@@ -163,7 +163,7 @@
 
     public Link createPageRenderLink(String pageName, boolean override, Object... context)
     {
-        return linkFactory.createPageLink(pageName, override, context);
+        return linkFactory.createPageRenderLink(pageName, override, context);
     }
 
     public Link createPageRenderLink(Class pageClass, boolean override, Object... context)
@@ -172,7 +172,7 @@
 
         String pageName = componentClassResolver.resolvePageClassNameToPageName(pageClass.getName());
 
-        return linkFactory.createPageLink(pageName, override, context);
+        return linkFactory.createPageRenderLink(pageName, override, context);
     }
 
     public void persistFieldChange(ComponentResources resources, String fieldName, Object newValue)

Copied: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/ComponentEventInvoker.java (from r704144, tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/ActionLinkInvoker.java)
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/ComponentEventInvoker.java?p2=tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/ComponentEventInvoker.java&p1=tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/ActionLinkInvoker.java&r1=704144&r2=707887&rev=707887&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/ActionLinkInvoker.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/ComponentEventInvoker.java Sat Oct 25 11:34:08 2008
@@ -16,8 +16,7 @@
 
 import org.apache.tapestry5.Link;
 import org.apache.tapestry5.dom.Document;
-import org.apache.tapestry5.internal.URLEventContext;
-import org.apache.tapestry5.internal.services.ActionLinkTarget;
+import org.apache.tapestry5.internal.services.ComponentEventTarget;
 import org.apache.tapestry5.internal.services.ComponentInvocation;
 import org.apache.tapestry5.internal.services.ComponentInvocationMap;
 import org.apache.tapestry5.internal.services.InvocationTarget;
@@ -25,14 +24,13 @@
 import org.apache.tapestry5.ioc.internal.util.Defense;
 import org.apache.tapestry5.services.ComponentEventRequestHandler;
 import org.apache.tapestry5.services.ComponentEventRequestParameters;
-import org.apache.tapestry5.services.ContextValueEncoder;
 
 import java.io.IOException;
 
 /**
- * Simulates a click on an action link.
+ * Simulates a click on an component event invocation link.
  */
-public class ActionLinkInvoker implements ComponentInvoker
+public class ComponentEventInvoker implements ComponentInvoker
 {
     private final Registry registry;
 
@@ -44,21 +42,17 @@
 
     private final TestableResponse response;
 
-    private final ContextValueEncoder contextValueEncoder;
-
-    public ActionLinkInvoker(Registry registry, ComponentInvoker followupInvoker,
-                             ComponentInvocationMap componentInvocationMap)
+    public ComponentEventInvoker(Registry registry, ComponentInvoker followupInvoker,
+                                 ComponentInvocationMap componentInvocationMap)
     {
         this.registry = registry;
         this.followupInvoker = followupInvoker;
-        componentEventRequestHandler = this.registry.getService("ComponentEventRequestHandler",
-                                                                ComponentEventRequestHandler.class);
-
-        response = this.registry.getObject(TestableResponse.class, null);
-
         this.componentInvocationMap = componentInvocationMap;
-        contextValueEncoder = this.registry.getService(ContextValueEncoder.class);
 
+        componentEventRequestHandler = registry.getService("ComponentEventRequestHandler",
+                                                           ComponentEventRequestHandler.class);
+
+        response = registry.getObject(TestableResponse.class, null);
     }
 
     /**
@@ -90,20 +84,20 @@
         {
             InvocationTarget target = invocation.getTarget();
 
-            ActionLinkTarget actionLinkTarget = Defense.cast(target, ActionLinkTarget.class, "target");
+            ComponentEventTarget componentEventTarget = Defense.cast(target, ComponentEventTarget.class, "target");
 
             ComponentEventRequestParameters parameters = new ComponentEventRequestParameters(
-                    actionLinkTarget.getPageName(),
+                    componentEventTarget.getPageName(),
 
-                    actionLinkTarget.getPageName(),
+                    componentEventTarget.getPageName(),
 
-                    actionLinkTarget.getComponentNestedId(),
+                    componentEventTarget.getComponentNestedId(),
 
-                    actionLinkTarget.getEventType(),
+                    componentEventTarget.getEventType(),
 
-                    new URLEventContext(contextValueEncoder, invocation.getActivationContext()),
+                    invocation.getPageActivationContext(),
 
-                    new URLEventContext(contextValueEncoder, invocation.getContext()));
+                    invocation.getEventContext());
 
             componentEventRequestHandler.handle(parameters);
         }

Copied: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageRenderInvoker.java (from r704144, tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageLinkInvoker.java)
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageRenderInvoker.java?p2=tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageRenderInvoker.java&p1=tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageLinkInvoker.java&r1=704144&r2=707887&rev=707887&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageLinkInvoker.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageRenderInvoker.java Sat Oct 25 11:34:08 2008
@@ -14,14 +14,11 @@
 
 package org.apache.tapestry5.internal.test;
 
-import org.apache.tapestry5.EventContext;
 import org.apache.tapestry5.dom.Document;
-import org.apache.tapestry5.internal.URLEventContext;
 import org.apache.tapestry5.internal.services.ComponentInvocation;
 import org.apache.tapestry5.internal.services.InvocationTarget;
-import org.apache.tapestry5.internal.services.PageLinkTarget;
+import org.apache.tapestry5.internal.services.PageRenderTarget;
 import org.apache.tapestry5.ioc.Registry;
-import org.apache.tapestry5.services.ContextValueEncoder;
 import org.apache.tapestry5.services.PageRenderRequestHandler;
 import org.apache.tapestry5.services.PageRenderRequestParameters;
 
@@ -30,7 +27,7 @@
 /**
  * Simulates a click on a page link.
  */
-public class PageLinkInvoker implements ComponentInvoker
+public class PageRenderInvoker implements ComponentInvoker
 {
     private final Registry registry;
 
@@ -40,16 +37,13 @@
 
     private final TestableResponse response;
 
-    private final ContextValueEncoder contextValueEncoder;
-
-    public PageLinkInvoker(Registry registry)
+    public PageRenderInvoker(Registry registry)
     {
         this.registry = registry;
 
         pageRenderRequestHandler = this.registry.getService(PageRenderRequestHandler.class);
         markupWriterFactory = this.registry.getService(TestableMarkupWriterFactory.class);
         response = this.registry.getService(TestableResponse.class);
-        contextValueEncoder = this.registry.getService(ContextValueEncoder.class);
     }
 
     /**
@@ -64,12 +58,10 @@
         {
             InvocationTarget target = invocation.getTarget();
 
-            PageLinkTarget pageLinkTarget = (PageLinkTarget) target;
+            PageRenderTarget pageRenderTarget = (PageRenderTarget) target;
 
-            EventContext activationContext
-                    = new URLEventContext(contextValueEncoder, invocation.getContext());
-            PageRenderRequestParameters parameters = new PageRenderRequestParameters(pageLinkTarget.getPageName(),
-                                                                                     activationContext);
+            PageRenderRequestParameters parameters = new PageRenderRequestParameters(pageRenderTarget.getPageName(),
+                                                                                     invocation.getPageActivationContext());
 
             pageRenderRequestHandler.handle(parameters);
 
@@ -85,7 +77,5 @@
 
             registry.cleanupThread();
         }
-
     }
-
 }

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ContextPathEncoder.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ContextPathEncoder.java?rev=707887&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ContextPathEncoder.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ContextPathEncoder.java Sat Oct 25 11:34:08 2008
@@ -0,0 +1,44 @@
+//  Copyright 2008 The Apache Software Foundation
+//
+// 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 org.apache.tapestry5.services;
+
+import org.apache.tapestry5.EventContext;
+
+/**
+ * A service to provide utilities needed for event context encoding and decoding to and from (partial) URL paths. This
+ * is used for both component event contexts and page activation contexts.
+ */
+public interface ContextPathEncoder
+{
+    /**
+     * Encodes the context values into a path string. Each context value (if non-null) is first value encoded into a
+     * string via the {@link org.apache.tapestry5.services.ContextValueEncoder} service.  Those values are then encoded,
+     * via {@link URLEncoder#encode(String)} into URL-safe strings.  The URL-safe strings are then concatinated
+     * together, seperated with "/" characters.
+     *
+     * @param context an array of objects to encode as the context (may be null)
+     * @return the path-encoded context, or the blank string if the context is empty
+     */
+    String encodeIntoPath(Object[] context);
+
+    /**
+     * Inverse of {@link #encodeIntoPath(Object[])}; the path is split into strings, and the string are decoded and
+     * constructed into an {@link org.apache.tapestry5.EventContext}.
+     *
+     * @param path to decode, possibly empty or null
+     * @return corresponding event context
+     */
+    EventContext decodePath(String path);
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java?rev=707887&r1=707886&r2=707887&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java Sat Oct 25 11:34:08 2008
@@ -195,6 +195,8 @@
         binder.bind(BindingFactory.class, AssetBindingFactory.class).withId("AssetBindingFactory");
         binder.bind(BindingFactory.class, NullFieldStrategyBindingFactory.class).withId(
                 "NullFieldStrategyBindingFactory");
+        binder.bind(URLEncoder.class, URLEncoderImpl.class);
+        binder.bind(ContextPathEncoder.class, ContextPathEncoderImpl.class);
     }
 
     // ========================================================================

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/URLEncoder.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/URLEncoder.java?rev=707887&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/URLEncoder.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/URLEncoder.java Sat Oct 25 11:34:08 2008
@@ -0,0 +1,41 @@
+//  Copyright 2008 The Apache Software Foundation
+//
+// 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 org.apache.tapestry5.services;
+
+/**
+ * Service used to encode or decode strings that are placed into URLs.  This is used as an alternative to UUEncoding.
+ * Alphabetics, numbers and some punctuation ("-", "_", ".", ":") are passed through as is, the "$" character is an
+ * escape, followed by either another "$", or by a four digit hex unicode number.  A null input (not a blank input, but
+ * actual null) has a special encoding, "$N". Likewise, the blank string has the special encoding "$B".
+ */
+public interface URLEncoder
+{
+    /**
+     * Given an input value containing any characters, returns the input string, or an encoded version of the string (as
+     * outlined above).
+     *
+     * @param input string to be encoded, which may be null
+     * @return encoded version of input
+     */
+    String encode(String input);
+
+    /**
+     * Given a previously encoded string, returns the original input.
+     *
+     * @param input encoded string (may not be null)
+     * @return decoded input
+     */
+    String decode(String input);
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/test/PageTester.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/test/PageTester.java?rev=707887&r1=707886&r2=707887&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/test/PageTester.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/test/PageTester.java Sat Oct 25 11:34:08 2008
@@ -29,6 +29,7 @@
 import org.apache.tapestry5.ioc.services.SymbolProvider;
 import org.apache.tapestry5.ioc.util.StrategyRegistry;
 import org.apache.tapestry5.services.ApplicationGlobals;
+import org.apache.tapestry5.services.ContextPathEncoder;
 
 import java.util.Locale;
 import java.util.Map;
@@ -49,10 +50,12 @@
 
     private final StrategyRegistry<ComponentInvoker> invokerRegistry;
 
-    private Locale preferedLanguage;
-
     private final LocalizationSetter localizationSetter;
 
+    private final ContextPathEncoder contextPathEncoder;
+
+    private Locale preferedLanguage;
+
     public static final String DEFAULT_CONTEXT_PATH = "src/main/webapp";
 
     private static final String DEFAULT_SUBMIT_VALUE_ATTRIBUTE = "Submit Query";
@@ -104,10 +107,12 @@
         globals.storeContext(new PageTesterContext(contextPath));
 
         Map<Class, ComponentInvoker> map = newMap();
-        map.put(PageLinkTarget.class, new PageLinkInvoker(registry));
-        map.put(ActionLinkTarget.class, new ActionLinkInvoker(registry, this, invocationMap));
+        map.put(PageRenderTarget.class, new PageRenderInvoker(registry));
+        map.put(ComponentEventTarget.class, new ComponentEventInvoker(registry, this, invocationMap));
 
         invokerRegistry = StrategyRegistry.newInstance(ComponentInvoker.class, map);
+
+        contextPathEncoder = registry.getService(ContextPathEncoder.class);
     }
 
     /**
@@ -156,7 +161,8 @@
      */
     public Document renderPage(String pageName)
     {
-        return invoke(new ComponentInvocationImpl(new PageLinkTarget(pageName), new String[0], null));
+        return invoke(
+                new ComponentInvocationImpl(contextPathEncoder, new PageRenderTarget(pageName), null, null, false));
     }
 
     /**

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/test/TapestryTestCase.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/test/TapestryTestCase.java?rev=707887&r1=707886&r2=707887&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/test/TapestryTestCase.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/test/TapestryTestCase.java Sat Oct 25 11:34:08 2008
@@ -1154,4 +1154,9 @@
     {
         expect(ts.findByType(propertyType)).andReturn(translator);
     }
+
+    protected final void train_toURI(Link link, String URI)
+    {
+        expect(link.toURI()).andReturn(URI);
+    }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/internal/services/ServicesStrings.properties
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/internal/services/ServicesStrings.properties?rev=707887&r1=707886&r2=707887&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/internal/services/ServicesStrings.properties (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/internal/services/ServicesStrings.properties Sat Oct 25 11:34:08 2008
@@ -91,6 +91,5 @@
 no-translator-for-type=No translator is defined for type %s.  Registered types: %s.
 parameter-binding-must-not-be-empty=Parameter '%s' must have a non-empty binding.
 no-such-method=Class %s does not contain a method named '%s()'.
-context-value-may-not-be-null=Context values (which are added to the request URL) may not be null or blank.
 forbid-instantiate-component-class=Component class %s may not be instantiated directly.  You should use an @InjectPage or @InjectComponent annotation instead.
 event-not-handled=Request event '%s' (on component %s) was not handled; you must provide a matching event handler method in the component or in one of its containers.
\ No newline at end of file

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/app1/Target.tml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/app1/Target.tml?rev=707887&r1=707886&r2=707887&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/app1/Target.tml (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/app1/Target.tml Sat Oct 25 11:34:08 2008
@@ -4,7 +4,9 @@
     <t:if test="activationContext">
 
         <ul>
-            <li t:type="loop" source="activationContext" value="object">${object}</li>
+            <li t:type="loop" source="activationContext" value="object">
+                <t:if test="object" else="NULL">${object}</t:if>
+            </li>
         </ul>
         <t:parameter name="else">No activation context.</t:parameter>
     </t:if>
@@ -15,19 +17,19 @@
             <li t:type="loop" source="componentContext" value="object">${object}</li>
         </ul>
 
-        <t:parameter name="else"> No component context. </t:parameter>
+        <t:parameter name="else">No component context.</t:parameter>
     </t:if>
-    <h2>Setup Component Context</h2> [<a t:type="actionlink" context="contextToEncode">go</a>] 
-    
+    <h2>Setup Component Context</h2>
+    [<a t:type="actionlink" context="contextToEncode">go</a>]
+
     <h2>Navigation</h2>
-    
-    <p>[<a t:type="pagelink" page="pagelinkcontext">PageLink Context Demo</a>]</p>
-    
-  <p>
-    [<t:pagelink t:id="nocontext" page="target" context="null">Target base, no context</t:pagelink>]
-  </p>
-    
-    
-    
-    
-    </html>
+
+    <p>[<a t:type="pagelink" page="pagelinkcontext">PageLink Context Demo</a>]
+    </p>
+
+    <p>
+        [<t:pagelink t:id="nocontext" page="target" context="null">Target base, no context</t:pagelink>]
+    </p>
+
+
+</html>