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 2009/02/05 02:40:30 UTC

svn commit: r740971 [1/2] - in /tapestry/tapestry5/trunk: src/site/apt/ tapestry-core/src/main/java/org/apache/tapestry5/ tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/ tapestry-core/src/main/java/org/apache/tapestry5/corelib/components...

Author: hlship
Date: Thu Feb  5 01:40:29 2009
New Revision: 740971

URL: http://svn.apache.org/viewvc?rev=740971&view=rev
Log:
TAP5-499: Cleanup and simplfy PageTester to remove ComponentInvocation, InvocationTarget, etc.

Added:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/dom/Visitor.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ArrayEventContext.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/test/CaptureRenderedDocument.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/EndOfRequestCleanupFilter.java
Removed:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentEventTarget.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/ComponentInvocationMap.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/NoOpComponentInvocationMap.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/OpaqueConstantTarget.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageRenderTarget.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/ComponentEventInvoker.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/ComponentInvoker.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageRenderInvoker.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageTesterComponentInvocationMap.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableMarkupWriterFactory.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableMarkupWriterFactoryImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentInvocationImplTest.java
Modified:
    tapestry/tapestry5/trunk/src/site/apt/index.apt
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/ComponentResourcesCommon.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractLink.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Form.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/dom/Document.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/dom/Element.java
    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/services/InternalModule.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/LinkSourceImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageTesterModule.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableRequest.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableRequestImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableResponse.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableResponseImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentEventRequestParameters.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/PageRenderRequestParameters.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/test/PageTester.java
    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/dom/DOMTest.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/pagelevel/LocaleTest.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/pagelevel/SubmitTest.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/LinkSourceImplTest.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/test/InternalBaseTestCase.java

Modified: tapestry/tapestry5/trunk/src/site/apt/index.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/src/site/apt/index.apt?rev=740971&r1=740970&r2=740971&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/src/site/apt/index.apt (original)
+++ tapestry/tapestry5/trunk/src/site/apt/index.apt Thu Feb  5 01:40:29 2009
@@ -36,7 +36,7 @@
 
   Now that that 5.0 release is <finally> out and available, work is starting on a 5.1 release.
 
-  The goal is to produce such releases on a regular schedule, ever 4 - 6 months.
+  The goal is to produce such releases on a regular schedule, every 4 - 6 months.
 
   High priorities for 5.1 include Spring Web Flow integration, and support for developing Tapestry applications as Portlets.
 
@@ -79,7 +79,14 @@
 
 New And Of Note
 
-  * Tapestry now {{{guide/compress.html}compresses}} responses for clients that support it.  Context and classpath assets
+  * Tapestry IoC services can now be easily <{{{tapestry-ioc/advice.html}advised}}> as well as
+   <{{{tapestry-ioc/decorator.html}decorated}}> (both of these refer to Aspect Oriented Techniques applied to
+    Tapestry IoC services).
+
+  * Tapestry Services can now be injected into Spring Beans, when using the Tapestry/Spring integration library.
+
+  * Tapestry now {{{guide/compress.html}compresses}} responses for clients that support compression.
+    Context and classpath assets
     are now handled uniformly: versioned URLs, far-future expiration headers, and GZIP compression where applicable.
 
   * Ordered and mapped configurations can now have overrides.
@@ -89,80 +96,7 @@
   * IoC Service contributions may now be made in terms of classes (that are automatically instantiated) as well as
     instances.
 
-  * At long last, an official {{{tapestry-core/ref/org/apache/tapestry5/corelib/components/LinkSubmit.html}LinkSubmit}} component.
-
-  * A {{{tapestry-ioc/injection.html}detailed guide to Injection}} has been added.
-
-  * You can now configure Tapestry to move \<script\> links to the top of the page.
-
-  * Event handler methods for Ajax requests may now return a page name, page class or page instance to force
-    the browser to redirect to the page.
-
-  * The Inject and InjectService annotations may now be used on fields of service implementations or other
-    objects constructed by the IoC container. In the past, injections only occured through method or
-    constructor parameters.
-
-  * Tapestry now bundles Prototype version 1.6.0.2.
-
-  * New methods have been added to
-    {{{apidocs/org/apache/tapestry5/dom/Node.html}Node}} to allow nodes to be moved about or otherwise
-    manipulated.
-
-  * Application State Objects are now automatically saved back to the session at the end of the request,
-    which ensures that ASO data is properly replicated across at cluster.
-
-  * The new {{{apidocs/org/apache/tapestry5/ioc/annotations/Local.html}@Local}}
-    annotation makes it easier to reference services within the same module when injecting.  
-
-  * Most general documentation has been moved from the tapestry-core module up to the project level.
-  
-  * Work has started on a {{{cookbook}Tapestry Cookbook}}, showing how to tackle common scenarios. 
-
-  * Component methods may be marked with the @Log annotation, to enable debug logging of
-    method entry (with parameters) and exit (with return value, or thrown exception).
-
-  * It is now possible to provide method invocation advice to component methods. This opens up
-    a very powerful Aspect Oriented Programming approach to Tapestry components.
-
-  * The Exception Report page now identifies the version of the Tapestry framework, and lists
-    out System properties (including the Java classpath).
-
-  * The {{{tapestry-core/ref/org/apache/tapestry5/corelib/components/Grid.html}Grid}} component can now update itself in place,
-    using Ajax, when paging or sorting links are clicked.
-
-  * Added a zone parameter to the  {{{tapestry-core/ref/org/apache/tapestry5/corelib/components/BeanEditForm.html}BeanEditForm}}
-    component, to support Ajax updates.
-
-  * The @Cached annotation has been added to allowing the caching of method results.
-
-  []
-
-
-What's changed since Tapestry 4?
-
-  Tapestry 5 is an all new code base, written from the ground up to take Java web
-  application development to new levels of productivity.
-  
-  This new release removes many limitations of Tapestry 4:
-  
-  * Components no longer extend from base classes.
-  
-  * {{{tapestry-core/guide/component-classes.html}Components classes are no longer <abstract>}}.  
-     Components are pure, simple POJOs (<plain old Java objects>).
-  
-  * Tapestry no longer uses XML page and component specification files. Information that used to
-    be supplied in such files is now supplied directly in the Java class, using Java annotations and naming conventions.
-    
-  * {{{tapestry-core/guide/reload.html}Changes to Tapestry component templates <and classes> are now picked up <immediately>}}, 
-    without any kind
-    of restart. This will even work properly in <production>, not just during development.
-    
-  * <<Blazing Speed>>. The new code base operates considerably faster than Tapestry 4. Critical
-  code paths have been simplified, and the use of reflection has been virtually eliminated.
-  Tapestry 4 was as fast as an equivalent Servlet/JSP application, Tapestry 5 is much faster.
-    
   []
-  
 
 Adaptive API
 
@@ -271,7 +205,7 @@
   directory, including a POM that links to the Apache snapshots repository.
   
   <<Documentation on this site usually refers to the latest snapshot ... that is, it is usually ahead of the last official release. In some cases,
-  it is written as if the snapshot release is stable; if documentation refers to version 5.0.x and that doesn't work, try 5.0.x-SNAPSHOT.>>
+  it is written as if the snapshot release is stable; if documentation refers to version 5.1.x.x and that doesn't work, try 5.1.x.x-SNAPSHOT.>>
     
 Principle 1 -- Static Structure, Dynamic Behavior
 
@@ -347,3 +281,7 @@
   Because of this, Tapestry will be able to change internally to a great degree without it affecting any
   of the application code <you> write. This should finally crack the backwards compatibility nut, allowing you to have
   great assurance that you can upgrade to future releases of Tapestry without breaking your existing applications.
+
+  This is already evident in Tapestry 5.1, where many new features and improvements have occurred, but is still
+  100% backwards compatible to Tapestry 5.0, as long as you've avoided the temptation to make use of
+  internal APIs.
\ No newline at end of file

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/ComponentResourcesCommon.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/ComponentResourcesCommon.java?rev=740971&r1=740970&r2=740971&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/ComponentResourcesCommon.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/ComponentResourcesCommon.java Thu Feb  5 01:40:29 2009
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2009 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.
@@ -23,14 +23,12 @@
  * Operations shared by the public {@link org.apache.tapestry5.ComponentResources} interface and {@link
  * org.apache.tapestry5.internal.structure.ComponentPageElement} interface (on the internal side).
  */
-@SuppressWarnings({"JavaDoc"})
+@SuppressWarnings({ "JavaDoc" })
 public interface ComponentResourcesCommon extends Locatable
 {
     /**
      * Returns the simple (or local) id of the component. The id will be unique within the component's immediate
      * container. For a page's root component, the value null is returned.
-     * <p/>
-     * \
      */
     String getId();
 

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractLink.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractLink.java?rev=740971&r1=740970&r2=740971&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractLink.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractLink.java Thu Feb  5 01:40:29 2009
@@ -1,4 +1,4 @@
-// Copyright 2007, 2008 The Apache Software Foundation
+// Copyright 2007, 2008, 2009 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.
@@ -19,7 +19,6 @@
 import org.apache.tapestry5.annotations.SetupRender;
 import org.apache.tapestry5.annotations.SupportsInformalParameters;
 import org.apache.tapestry5.dom.Element;
-import org.apache.tapestry5.internal.services.ComponentInvocationMap;
 import org.apache.tapestry5.ioc.annotations.Inject;
 
 /**
@@ -28,9 +27,6 @@
 @SupportsInformalParameters
 public abstract class AbstractLink implements ClientElement
 {
-    @Inject
-    private ComponentInvocationMap componentInvocationMap;
-
     /**
      * An anchor value to append to the generated URL (the hash separator will be added automatically).
      */
@@ -90,8 +86,6 @@
 
         resources.renderInformalParameters(writer);
 
-        componentInvocationMap.store(element, link);
-
         this.link = link;
     }
 
@@ -140,10 +134,9 @@
     /**
      * Used for testing.
      */
-    final void inject(String anchor, ComponentInvocationMap map, ComponentResources resources)
+    final void inject(String anchor, ComponentResources resources)
     {
         this.anchor = anchor;
-        componentInvocationMap = map;
         this.resources = resources;
     }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Form.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Form.java?rev=740971&r1=740970&r2=740971&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Form.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Form.java Thu Feb  5 01:40:29 2009
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2009 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.
@@ -22,7 +22,6 @@
 import org.apache.tapestry5.corelib.mixins.RenderInformals;
 import org.apache.tapestry5.dom.Element;
 import org.apache.tapestry5.internal.services.ClientBehaviorSupport;
-import org.apache.tapestry5.internal.services.ComponentInvocationMap;
 import org.apache.tapestry5.internal.services.ComponentResultProcessorWrapper;
 import org.apache.tapestry5.internal.services.HeartbeatImpl;
 import org.apache.tapestry5.internal.util.AutofocusValidationDecorator;
@@ -183,9 +182,6 @@
     @Persist(PersistenceConstants.FLASH)
     private ValidationTracker defaultTracker;
 
-    @Inject
-    private ComponentInvocationMap componentInvocationMap;
-
     private InternalFormSupport formSupport;
 
     private Element form;
@@ -276,8 +272,6 @@
                               "method", "post",
                               "action", link);
 
-        componentInvocationMap.store(form, link);
-
         resources.renderInformalParameters(writer);
 
         div = writer.element("div", "class", CSSClassConstants.INVISIBLE);

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/dom/Document.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/dom/Document.java?rev=740971&r1=740970&r2=740971&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/dom/Document.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/dom/Document.java Thu Feb  5 01:40:29 2009
@@ -192,4 +192,15 @@
 
         return rootElement.getNamespaceURIToPrefix();
     }
+
+    /**
+     * Visits the root element of the document.
+     *
+     * @param visitor callback
+     * @since 5.1.0.0
+     */
+    void visit(Visitor visitor)
+    {
+        rootElement.visit(visitor);
+    }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/dom/Element.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/dom/Element.java?rev=740971&r1=740970&r2=740971&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/dom/Element.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/dom/Element.java Thu Feb  5 01:40:29 2009
@@ -18,6 +18,7 @@
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.internal.util.Defense;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
+import org.apache.tapestry5.ioc.util.Stack;
 
 import java.io.PrintWriter;
 import java.util.*;
@@ -691,4 +692,48 @@
 
         return true;
     }
+
+    /**
+     * Depth-first visitor traversal of this Element and its Element children. The traversal order is the same as render
+     * order.
+     *
+     * @param visitor callback
+     * @since 5.1.0.0
+     */
+    public void visit(Visitor visitor)
+    {
+        Stack<Element> queue = CollectionFactory.newStack();
+
+        queue.push(this);
+
+        while (!queue.isEmpty())
+        {
+            Element e = queue.pop();
+
+            visitor.visit(e);
+
+            e.queueChildren(queue);
+        }
+    }
+
+
+    private void queueChildren(Stack<Element> queue)
+    {
+        List<Node> children = getChildren();
+
+        int count = children.size();
+
+        // Push them in reverse order to get the correct
+        // traversal: the lowest index child is traversed first.
+
+        for (int i = count - 1; i >= 0; i--)
+        {
+            Node n = children.get(i);
+
+            if (n instanceof Element)
+            {
+                queue.push((Element) n);
+            }
+        }
+    }
 }

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/dom/Visitor.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/dom/Visitor.java?rev=740971&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/dom/Visitor.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/dom/Visitor.java Thu Feb  5 01:40:29 2009
@@ -0,0 +1,30 @@
+// Copyright 2009 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.dom;
+
+/**
+ * A callback interface used navigate the {@link org.apache.tapestry5.dom.Element}s of a document.
+ *
+ * @since 5.1.0.0
+ */
+public interface Visitor
+{
+    /**
+     * Called for each Element being visited.
+     *
+     * @param element visited
+     */
+    void visit(Element element);
+}

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=740971&r1=740970&r2=740971&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 Thu Feb  5 01:40:29 2009
@@ -17,6 +17,7 @@
 import org.apache.commons.codec.binary.Base64;
 import org.apache.tapestry5.OptionModel;
 import org.apache.tapestry5.SelectModel;
+import org.apache.tapestry5.EventContext;
 import org.apache.tapestry5.ioc.Messages;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.internal.util.Defense;
@@ -437,4 +438,21 @@
 
         return lastx > 0 ? asString.substring(0, lastx) : asString;
     }
+
+    public static boolean isEqual(EventContext left, EventContext right)
+    {
+        if (left == right) return true;
+
+        int count = left.getCount();
+
+        if (count != right.getCount()) return false;
+
+        for (int i = 0; i < count; i++)
+        {
+            if (!left.get(Object.class, i).equals(right.get(Object.class, i)))
+                return false;
+        }
+
+        return true;
+    }
 }

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ArrayEventContext.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ArrayEventContext.java?rev=740971&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ArrayEventContext.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ArrayEventContext.java Thu Feb  5 01:40:29 2009
@@ -0,0 +1,46 @@
+// Copyright 2009 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.ioc.services.TypeCoercer;
+
+/**
+ * Simple implementation of {@link org.apache.tapestry5.EventContext}.
+ *
+ * @since 5.1.0.0
+ */
+public class ArrayEventContext implements EventContext
+{
+    private final TypeCoercer typeCoercer;
+
+    private final Object[] values;
+
+    public ArrayEventContext(TypeCoercer typeCoercer, Object... values)
+    {
+        this.typeCoercer = typeCoercer;
+        this.values = values;
+    }
+
+    public <T> T get(Class<T> desiredType, int index)
+    {
+        return typeCoercer.coerce(values[index], desiredType);
+    }
+
+    public int getCount()
+    {
+        return values.length;
+    }
+}

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=740971&r1=740970&r2=740971&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 Thu Feb  5 01:40:29 2009
@@ -59,7 +59,6 @@
         binder.bind(TemplateParser.class, TemplateParserImpl.class);
         binder.bind(PageResponseRenderer.class, PageResponseRendererImpl.class);
         binder.bind(PageMarkupRenderer.class, PageMarkupRendererImpl.class);
-        binder.bind(ComponentInvocationMap.class, NoOpComponentInvocationMap.class);
         binder.bind(LinkSource.class, LinkSourceImpl.class);
         binder.bind(LocalizationSetter.class, LocalizationSetterImpl.class);
         binder.bind(PageElementFactory.class, PageElementFactoryImpl.class);

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=740971&r1=740970&r2=740971&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 Thu Feb  5 01:40:29 2009
@@ -15,7 +15,8 @@
 package org.apache.tapestry5.internal.services;
 
 import org.apache.tapestry5.Link;
-import org.apache.tapestry5.internal.structure.Page;
+import org.apache.tapestry5.services.ComponentEventRequestParameters;
+import org.apache.tapestry5.services.PageRenderRequestParameters;
 
 /**
  * Used by {@link org.apache.tapestry5.internal.services.LinkSource} service to create {@link org.apache.tapestry5.Link}
@@ -25,12 +26,23 @@
  */
 public interface LinkFactory
 {
+
+    /**
+     * Creates a component event link (possibly for a Form).
+     *
+     * @param parameters defines the data need to create the link
+     * @param forForm    if true, the link is for a form (which handles query parameters differently than normal)
+     * @return the component event link
+     * @see org.apache.tapestry5.services.ComponentEventRequestHandler
+     */
+    Link createComponentEventLink(ComponentEventRequestParameters parameters, boolean forForm);
+
     /**
-     * Creates a new link; the page is used to determine if the request needs to shift between HTTP and HTTPS.
+     * Creates a page render event link.
      *
-     * @param page
-     * @param invocation
-     * @return link that calls the invocation
+     * @param parameters defines the page and page activation context
+     * @return the link
+     * @see org.apache.tapestry5.services.PageRenderRequestHandler
      */
-    Link create(Page page, ComponentInvocation invocation);
+    Link createPageRenderLink(PageRenderRequestParameters parameters);
 }

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=740971&r1=740970&r2=740971&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 Thu Feb  5 01:40:29 2009
@@ -15,10 +15,14 @@
 package org.apache.tapestry5.internal.services;
 
 import org.apache.tapestry5.Link;
+import org.apache.tapestry5.EventConstants;
+import org.apache.tapestry5.EventContext;
+import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.internal.structure.Page;
-import org.apache.tapestry5.services.PersistentLocale;
-import org.apache.tapestry5.services.Request;
-import org.apache.tapestry5.services.Response;
+import org.apache.tapestry5.internal.InternalConstants;
+import org.apache.tapestry5.services.*;
+
+import java.util.Locale;
 
 public class LinkFactoryImpl implements LinkFactory
 {
@@ -30,34 +34,166 @@
 
     private final RequestPathOptimizer optimizer;
 
-    private final ComponentInvocationMap componentInvocationMap;
-
     private final PersistentLocale persistentLocale;
 
+    private final RequestPageCache requestPageCache;
+
+    private final ContextValueEncoder valueEncoder;
+
+    private final URLEncoder urlEncoder;
+
+    private static final int BUFFER_SIZE = 100;
+
+    private static final char SLASH = '/';
+
     public LinkFactoryImpl(Request request, Response response, RequestSecurityManager requestSecurityManager,
-                           RequestPathOptimizer optimizer, ComponentInvocationMap componentInvocationMap,
-                           PersistentLocale persistentLocale)
+                           RequestPathOptimizer optimizer, PersistentLocale persistentLocale,
+                           RequestPageCache requestPageCache,
+                           ContextValueEncoder valueEncoder, URLEncoder urlEncoder)
     {
         this.request = request;
         this.response = response;
         this.requestSecurityManager = requestSecurityManager;
         this.optimizer = optimizer;
-        this.componentInvocationMap = componentInvocationMap;
         this.persistentLocale = persistentLocale;
+        this.requestPageCache = requestPageCache;
+        this.valueEncoder = valueEncoder;
+        this.urlEncoder = urlEncoder;
+    }
+
+    public Link createComponentEventLink(ComponentEventRequestParameters parameters, boolean forForm)
+    {
+        StringBuilder builder = new StringBuilder(BUFFER_SIZE);
+
+        // Build up the absolute URI.
+
+        String activePageName = parameters.getActivePageName();
+        String containingPageName = parameters.getContainingPageName();
+        String eventType = parameters.getEventType();
+
+        Page activePage = requestPageCache.get(activePageName);
+
+        String nestedComponentId = parameters.getNestedComponentId();
+        boolean hasComponentId = InternalUtils.isNonBlank(nestedComponentId);
+
+        String baseURL = requestSecurityManager.getBaseURL(activePage);
+
+        if (baseURL != null)
+            builder.append(baseURL);
+
+        builder.append(request.getContextPath());
+
+        Locale locale = persistentLocale.get();
+
+        if (locale != null)
+        {
+            builder.append(SLASH);
+            builder.append(locale.toString());
+        }
+
+        builder.append(SLASH);
+        builder.append(activePageName.toLowerCase());
+
+        if (hasComponentId)
+        {
+            builder.append('.');
+            builder.append(nestedComponentId);
+        }
+
+        if (!hasComponentId || !eventType.equals(EventConstants.ACTION))
+        {
+            builder.append(":");
+            builder.append(eventType.toLowerCase());
+        }
+
+        appendContext(parameters.getEventContext(), builder);
+
+        Link result = new LinkImpl(builder.toString(), baseURL == null, forForm, response, optimizer);
+
+        EventContext pageActivationContext = parameters.getPageActivationContext();
+
+        if (pageActivationContext.getCount() != 0)
+        {
+            // Reuse the builder            
+            builder.setLength(0);
+            appendContext(pageActivationContext, builder);
+
+            // Omit that first slash
+            result.addParameter(InternalConstants.PAGE_CONTEXT_NAME, builder.substring(1));
+        }
+
+        // TAPESTRY-2044: Sometimes the active page drags in components from another page and we
+        // need to differentiate that.
+
+        if (!containingPageName.equalsIgnoreCase(activePageName))
+            result.addParameter(InternalConstants.CONTAINER_PAGE_NAME, containingPageName.toLowerCase());
+
+        return result;
     }
 
-    public Link create(Page page, ComponentInvocation invocation)
+
+    public Link createPageRenderLink(PageRenderRequestParameters parameters)
     {
-        String baseURL = requestSecurityManager.getBaseURL(page);
+        StringBuilder builder = new StringBuilder(BUFFER_SIZE);
+
+        // Build up the absolute URI.
+
+        String activePageName = parameters.getLogicalPageName();
+
+        Page activePage = requestPageCache.get(activePageName);
+
+
+        String baseURL = requestSecurityManager.getBaseURL(activePage);
+
+        if (baseURL != null)
+            builder.append(baseURL);
+
+        builder.append(request.getContextPath());
 
-        Link link = new LinkImpl(response, optimizer, baseURL, request.getContextPath(), persistentLocale.get(),
-                                 invocation);
+        Locale locale = persistentLocale.get();
 
-        // 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).
+        if (locale != null)
+        {
+            builder.append(SLASH);
+            builder.append(locale.toString());
+        }
+
+        builder.append(SLASH);
+        builder.append(activePageName.toLowerCase());
+
+        appendContext(parameters.getActivationContext(), builder);
+
+        return new LinkImpl(builder.toString(), baseURL == null, false, response, optimizer);
+    }
+
+    public void appendContext(EventContext context, StringBuilder builder)
+    {
+        for (int i = 0; i < context.getCount(); i++)
+        {
+            Object raw = context.get(Object.class, i);
+
+            String valueEncoded = raw == null ? null : valueEncoder.toClient(raw);
+            String urlEncoded = urlEncoder.encode(valueEncoded);
+
+            builder.append(SLASH);
+
+            builder.append(urlEncoded);
+        }
+    }
+
+    private String trimIndex(String pageName)
+    {
+        int lastSlash = pageName.lastIndexOf('/');
 
-        componentInvocationMap.store(link, invocation);
+        String name = lastSlash > 0
+                      ? pageName.substring(lastSlash + 1)
+                      : pageName;
+
+        if (name.equalsIgnoreCase("index"))
+            return lastSlash > 0
+                   ? pageName.substring(0, lastSlash)
+                   : "";
 
-        return link;
+        return pageName;
     }
 }

Added: 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=740971&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LinkImpl.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LinkImpl.java Thu Feb  5 01:40:29 2009
@@ -0,0 +1,154 @@
+// Copyright 2009 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.Link;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.ioc.internal.util.Defense;
+import org.apache.tapestry5.ioc.internal.util.InternalUtils;
+import org.apache.tapestry5.services.Response;
+
+import java.util.List;
+import java.util.Map;
+
+public class LinkImpl implements Link
+{
+    private Map<String, String> parameters;
+
+    private final String absoluteURI;
+
+    private final boolean optimizable;
+
+    private final boolean forForm;
+
+    private final Response response;
+
+    private final RequestPathOptimizer optimizer;
+
+    private String anchor;
+
+    public LinkImpl(String absoluteURI, boolean optimizable, boolean forForm, Response response,
+                     RequestPathOptimizer optimizer)
+    {
+        this.absoluteURI = absoluteURI;
+        this.optimizable = optimizable;
+        this.forForm = forForm;
+        this.response = response;
+        this.optimizer = optimizer;
+    }
+
+    public void addParameter(String parameterName, String value)
+    {
+        Defense.notBlank(parameterName, "parameterName");
+        Defense.notBlank(value, "value");
+
+        if (parameters == null)
+            parameters = CollectionFactory.newMap();
+
+        parameters.put(parameterName, value);
+    }
+
+    public String getAnchor()
+    {
+        return anchor;
+    }
+
+    public List<String> getParameterNames()
+    {
+        return InternalUtils.sortedKeys(parameters);
+    }
+
+    public String getParameterValue(String name)
+    {
+        return InternalUtils.get(parameters, name);
+    }
+
+    public void setAnchor(String anchor)
+    {
+        this.anchor = anchor;
+    }
+
+    public String toAbsoluteURI()
+    {
+        return appendAnchor(response.encodeURL(buildURI()));
+    }
+
+    public String toRedirectURI()
+    {
+        return appendAnchor(response.encodeRedirectURL(buildURI()));
+    }
+
+    public String toURI()
+    {
+        String path = buildURI();
+
+        if (optimizable)
+            path = optimizer.optimizePath(path);
+
+        return appendAnchor(response.encodeURL(path));
+    }
+
+    private String appendAnchor(String path)
+    {
+        return InternalUtils.isBlank(anchor)
+               ? path
+               : path + "#" + anchor;
+    }
+
+    /**
+     * Returns the value from {@link #toURI()}
+     */
+    @Override
+    public String toString()
+    {
+        return toURI();
+    }
+
+
+    /**
+     * Extends the absolute path with any query parameters. Query parameters are never added to a forForm link.
+     *
+     * @return absoluteURI appended with query parameters
+     */
+    private String buildURI()
+    {
+        if (forForm || parameters == null)
+            return absoluteURI;
+
+        StringBuilder builder = new StringBuilder(absoluteURI.length() * 2);
+
+        builder.append(absoluteURI);
+
+        String sep = "?";
+
+        for (String name : getParameterNames())
+        {
+            String value = parameters.get(name);
+
+            builder.append(sep);
+
+            // We assume that the name is URL safe and that the value will already have been URL
+            // encoded if it is not known to be URL safe.
+
+            builder.append(name);
+            builder.append("=");
+            builder.append(value);
+
+            sep = "&";
+        }
+
+        return builder.toString();
+    }
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LinkSourceImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LinkSourceImpl.java?rev=740971&r1=740970&r2=740971&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LinkSourceImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/LinkSourceImpl.java Thu Feb  5 01:40:29 2009
@@ -15,13 +15,11 @@
 package org.apache.tapestry5.internal.services;
 
 import org.apache.tapestry5.Link;
-import org.apache.tapestry5.internal.InternalConstants;
 import org.apache.tapestry5.internal.structure.Page;
 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.LinkCreationHub;
-import org.apache.tapestry5.services.LinkCreationListener;
+import org.apache.tapestry5.ioc.services.TypeCoercer;
+import org.apache.tapestry5.services.*;
 
 import java.util.List;
 
@@ -31,26 +29,25 @@
 
     private final PageRenderQueue pageRenderQueue;
 
-    private final ContextPathEncoder contextPathEncoder;
-
     private final PageActivationContextCollector contextCollector;
 
     private final LinkFactory linkFactory;
 
     private final List<LinkCreationListener> listeners = CollectionFactory.newThreadSafeList();
 
+    private final TypeCoercer typeCoercer;
+
     public LinkSourceImpl(
             RequestPageCache pageCache,
             PageRenderQueue pageRenderQueue,
-            ContextPathEncoder contextPathEncoder,
             PageActivationContextCollector contextCollector,
-            LinkFactory linkFactory)
+            LinkFactory linkFactory, TypeCoercer typeCoercer)
     {
         this.pageCache = pageCache;
         this.pageRenderQueue = pageRenderQueue;
-        this.contextPathEncoder = contextPathEncoder;
         this.contextCollector = contextCollector;
         this.linkFactory = linkFactory;
+        this.typeCoercer = typeCoercer;
     }
 
     public Link createComponentEventLink(Page page, String nestedId, String eventType, boolean forForm,
@@ -65,20 +62,19 @@
         if (activePage == null)
             activePage = page;
 
-        ComponentEventTarget target = new ComponentEventTarget(eventType, activePage.getLogicalName(), nestedId);
-
         Object[] pageActivationContext = contextCollector.collectPageActivationContext(activePage);
 
-        ComponentInvocation invocation = new ComponentInvocationImpl(contextPathEncoder, target, eventContext,
-                                                                     pageActivationContext, forForm);
-
-        Link link = linkFactory.create(activePage, invocation);
+        ComponentEventRequestParameters parameters
+                = new ComponentEventRequestParameters(
+                activePage.getLogicalName(),
+                page.getLogicalName(),
+                toBlank(nestedId),
+                eventType,
+                new ArrayEventContext(typeCoercer, pageActivationContext),
+                new ArrayEventContext(typeCoercer, eventContext));
 
-        // TAPESTRY-2044: Sometimes the active page drags in components from another page and we
-        // need to differentiate that.
 
-        if (activePage != page)
-            link.addParameter(InternalConstants.CONTAINER_PAGE_NAME, page.getLogicalName().toLowerCase());
+        Link link = linkFactory.createComponentEventLink(parameters, forForm);
 
         for (LinkCreationListener listener : listeners)
             listener.createdComponentEventLink(link);
@@ -86,6 +82,10 @@
         return link;
     }
 
+    private String toBlank(String input)
+    {
+        return input == null ? "" : input;
+    }
 
     public Link createPageRenderLink(Page page, boolean override, Object... pageActivationContext)
     {
@@ -99,11 +99,11 @@
                            ? pageActivationContext
                            : contextCollector.collectPageActivationContext(page);
 
-        PageRenderTarget target = new PageRenderTarget(logicalPageName);
-
-        ComponentInvocation invocation = new ComponentInvocationImpl(contextPathEncoder, target, null, context, false);
+        PageRenderRequestParameters parameters =
+                new PageRenderRequestParameters(logicalPageName,
+                                                new ArrayEventContext(typeCoercer, context));
 
-        Link link = linkFactory.create(page, invocation);
+        Link link = linkFactory.createPageRenderLink(parameters);
 
         for (LinkCreationListener listener : listeners)
             listener.createdPageRenderLink(link);

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/CaptureRenderedDocument.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/CaptureRenderedDocument.java?rev=740971&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/CaptureRenderedDocument.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/CaptureRenderedDocument.java Thu Feb  5 01:40:29 2009
@@ -0,0 +1,43 @@
+// Copyright 2009 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.test;
+
+import org.apache.tapestry5.services.MarkupRendererFilter;
+import org.apache.tapestry5.services.MarkupRenderer;
+import org.apache.tapestry5.MarkupWriter;
+
+/**
+ * Used to capture the rendered document from a traditional page render. Invokes {@link
+ * org.apache.tapestry5.internal.test.TestableResponse#setRenderedDocument(org.apache.tapestry5.dom.Document)}.
+ *
+ * @since 5.1.0.0
+ */
+public class CaptureRenderedDocument implements MarkupRendererFilter
+{
+    private final TestableResponse testableResponse;
+
+    public CaptureRenderedDocument(TestableResponse testableResponse)
+    {
+        this.testableResponse = testableResponse;
+    }
+
+
+    public void renderMarkup(MarkupWriter writer, MarkupRenderer renderer)
+    {
+        renderer.renderMarkup(writer);
+
+        testableResponse.setRenderedDocument(writer.getDocument());
+    }
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/EndOfRequestCleanupFilter.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/EndOfRequestCleanupFilter.java?rev=740971&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/EndOfRequestCleanupFilter.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/EndOfRequestCleanupFilter.java Thu Feb  5 01:40:29 2009
@@ -0,0 +1,49 @@
+// Copyright 2009 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.test;
+
+import org.apache.tapestry5.services.RequestHandler;
+import org.apache.tapestry5.services.Request;
+import org.apache.tapestry5.services.Response;
+import org.apache.tapestry5.services.RequestFilter;
+import org.apache.tapestry5.ioc.services.PerthreadManager;
+
+import java.io.IOException;
+
+/**
+ * Makes sure that {@link org.apache.tapestry5.ioc.services.PerthreadManager#cleanup()} is invoked at the end of each
+ * request (normally handled by {@link org.apache.tapestry5.TapestryFilter}).
+ */
+public class EndOfRequestCleanupFilter implements RequestFilter
+{
+    private final PerthreadManager perThreadManager;
+
+    public EndOfRequestCleanupFilter(PerthreadManager perThreadManager)
+    {
+        this.perThreadManager = perThreadManager;
+    }
+
+    public boolean service(Request request, Response response, RequestHandler handler) throws IOException
+    {
+        try
+        {
+            return handler.service(request, response);
+        }
+        finally
+        {
+            perThreadManager.cleanup();
+        }
+    }
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageTesterModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageTesterModule.java?rev=740971&r1=740970&r2=740971&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageTesterModule.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageTesterModule.java Thu Feb  5 01:40:29 2009
@@ -1,4 +1,4 @@
-// Copyright 2007, 2008 The Apache Software Foundation
+// Copyright 2007, 2008, 2009 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.
@@ -15,17 +15,10 @@
 package org.apache.tapestry5.internal.test;
 
 import org.apache.tapestry5.SymbolConstants;
-import org.apache.tapestry5.internal.services.ComponentInvocationMap;
 import org.apache.tapestry5.internal.services.CookieSink;
 import org.apache.tapestry5.internal.services.CookieSource;
-import org.apache.tapestry5.ioc.Configuration;
-import org.apache.tapestry5.ioc.MappedConfiguration;
-import org.apache.tapestry5.ioc.ObjectLocator;
-import org.apache.tapestry5.ioc.ServiceBinder;
-import org.apache.tapestry5.services.AliasContribution;
-import org.apache.tapestry5.services.MarkupWriterFactory;
-import org.apache.tapestry5.services.Request;
-import org.apache.tapestry5.services.Response;
+import org.apache.tapestry5.ioc.*;
+import org.apache.tapestry5.services.*;
 import org.apache.tapestry5.test.PageTester;
 
 /**
@@ -40,32 +33,28 @@
     {
         binder.bind(TestableRequest.class, TestableRequestImpl.class);
         binder.bind(TestableResponse.class, TestableResponseImpl.class);
-        binder.bind(TestableMarkupWriterFactory.class, TestableMarkupWriterFactoryImpl.class);
     }
 
     public static void contributeAlias(Configuration<AliasContribution> configuration, ObjectLocator locator)
     {
-        add(configuration, ComponentInvocationMap.class, new PageTesterComponentInvocationMap());
-
-        add(configuration, locator, Request.class, "TestableRequest");
-        add(configuration, locator, Response.class, "TestableResponse");
-        add(configuration, locator, MarkupWriterFactory.class, "TestableMarkupWriterFactory");
+        alias(configuration, locator, Request.class, "TestableRequest");
+        alias(configuration, locator, Response.class, "TestableResponse");
 
         TestableCookieSinkSource cookies = new TestableCookieSinkSource();
 
-        add(configuration, CookieSink.class, cookies);
-        add(configuration, CookieSource.class, cookies);
+        alias(configuration, CookieSink.class, cookies);
+        alias(configuration, CookieSource.class, cookies);
     }
 
-    private static <T> void add(Configuration<AliasContribution> configuration, ObjectLocator locator,
-                                Class<T> serviceClass, String serviceId)
+    private static <T> void alias(Configuration<AliasContribution> configuration, ObjectLocator locator,
+                                  Class<T> serviceClass, String serviceId)
     {
         T service = locator.getService(serviceId, serviceClass);
 
-        add(configuration, serviceClass, service);
+        alias(configuration, serviceClass, service);
     }
 
-    private static <T> void add(Configuration<AliasContribution> configuration, Class<T> serviceClass, T service)
+    private static <T> void alias(Configuration<AliasContribution> configuration, Class<T> serviceClass, T service)
     {
         AliasContribution<T> contribution = AliasContribution.create(serviceClass, TEST_MODE, service);
 
@@ -76,4 +65,14 @@
     {
         configuration.add(SymbolConstants.FORCE_ABSOLUTE_URIS, "true");
     }
+
+    public static void contributeRequestHandler(OrderedConfiguration<RequestFilter> configuration)
+    {
+        configuration.addInstance("EndOfRequestCleanup", EndOfRequestCleanupFilter.class, "before:StaticFiles");
+    }
+
+    public static void contributeMarkupRenderer(OrderedConfiguration<MarkupRendererFilter> configuration)
+    {
+        configuration.addInstance("CaptureRenderedDocument", CaptureRenderedDocument.class, "before:DocumentLinker");
+    }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableRequest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableRequest.java?rev=740971&r1=740970&r2=740971&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableRequest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableRequest.java Thu Feb  5 01:40:29 2009
@@ -1,4 +1,4 @@
-// Copyright 2007 The Apache Software Foundation
+// Copyright 2007, 2009 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,7 +17,7 @@
 import org.apache.tapestry5.services.Request;
 import org.apache.tapestry5.test.PageTester;
 
-import java.util.Map;
+import java.util.Locale;
 
 /**
  * An extended version of {@link Request} that allows the {@link PageTester} to control and override behavior,
@@ -27,16 +27,36 @@
 {
     /**
      * Clears the internal parameters map.
+     *
+     * @return the request for further configuration
      */
-    void clear();
+    TestableRequest clear();
 
     /**
-     * Loads new parameter/value pairs into the map.
+     * Sets the path; the path should begin with a "/" character and contain everything from there to the start of query
+     * parameters (if any).
+     *
+     * @param path
+     * @return the request for further configuration
      */
-    void loadParameters(Map<String, String> parameterValues);
+    TestableRequest setPath(String path);
 
     /**
-     * Loads a single parameter/value pair.
+     * Sets the locale requested by "the browser".
+     *
+     * @returns the request for further configuration
      */
-    void loadParameter(String parameterName, String parameterValue);
+    TestableRequest setLocale(Locale locale);
+
+    /**
+     * Loads a single parameter/value pair. This may define a new parameter, or add a value to a list of parameters.
+     *
+     * @return the request for further configuration
+     */
+    TestableRequest loadParameter(String parameterName, String parameterValue);
+
+    /**
+     * Overrides a parameter to the specific value, regardless of how the parameter was previously set.
+     */
+    TestableRequest overrideParameter(String parameterName, String parameterValue);
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableRequestImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableRequestImpl.java?rev=740971&r1=740970&r2=740971&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableRequestImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableRequestImpl.java Thu Feb  5 01:40:29 2009
@@ -1,4 +1,4 @@
-// Copyright 2007, 2008 The Apache Software Foundation
+// Copyright 2007, 2008, 2009 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.
@@ -22,17 +22,22 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.ArrayList;
 
 public class TestableRequestImpl implements TestableRequest
 {
     private final String contextPath;
 
-    private final Map<String, String> parameters = CollectionFactory.newMap();
+    private final Map<String, Object> parameters = CollectionFactory.newMap();
 
     private final Map<String, Object> attributes = CollectionFactory.newMap();
 
     private Session session;
 
+    private String path = "/";
+
+    private Locale locale = Locale.getDefault();
+
     @Inject
     public TestableRequestImpl()
     {
@@ -50,24 +55,65 @@
                 String.format("Request: method %s() not yet implemented by TestableRequestImpl.", methodName));
     }
 
-    public void clear()
+    public TestableRequest clear()
     {
         parameters.clear();
+
+        return this;
     }
 
-    public void loadParameter(String parameterName, String parameterValue)
+    public TestableRequest setPath(String path)
     {
-        parameters.put(parameterName, parameterValue);
+        this.path = path;
+
+        return this;
     }
 
-    public void loadParameters(Map<String, String> parameterValues)
+    public TestableRequest setLocale(Locale locale)
     {
-        parameters.putAll(parameterValues);
+        this.locale = locale;
+
+        return this;
+    }
+
+    public TestableRequest loadParameter(String parameterName, String parameterValue)
+    {
+        Object existing = parameters.get(parameterName);
+
+        if (existing == null)
+        {
+            parameters.put(parameterName, parameterValue);
+            return this;
+        }
+
+        if (existing instanceof List)
+        {
+            ((List) existing).add(parameterValue);
+            return this;
+        }
+
+        // Convert from a single String to a List of Strings.
+
+        List list = new ArrayList();
+        list.add(existing);
+        list.add(parameterValue);
+
+        parameters.put(parameterName, list);
+
+        return this;
+    }
+
+    public TestableRequest overrideParameter(String parameterName, String parameterValue)
+    {
+        parameters.put(parameterName, parameterValue);
+
+        return this;
     }
 
     public long getDateHeader(String name)
     {
         nyi("getDateHeader");
+
         return 0;
     }
 
@@ -83,7 +129,7 @@
 
     public Locale getLocale()
     {
-        return nyi("getLocale");
+        return locale;
     }
 
     public List<String> getParameterNames()
@@ -93,14 +139,21 @@
 
     public String[] getParameters(String name)
     {
-        String value = getParameter(name);
+        Object value = parameters.get(name);
 
-        return value == null ? null : new String[] {value};
+        if (value == null) return null;
+
+        if (value instanceof String)
+            return new String[] { (String) value };
+
+        List list = (List) value;
+
+        return (String[]) list.toArray(new String[list.size()]);
     }
 
     public String getPath()
     {
-        return nyi("getPath");
+        return path;
     }
 
     public String getContextPath()
@@ -110,7 +163,13 @@
 
     public String getParameter(String name)
     {
-        return parameters.get(name);
+        Object value = parameters.get(name);
+
+        if (value == null || value instanceof String) return (String) value;
+
+        List<String> list = (List<String>) value;
+
+        return list.get(0);
     }
 
     public Session getSession(boolean create)

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableResponse.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableResponse.java?rev=740971&r1=740970&r2=740971&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableResponse.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableResponse.java Thu Feb  5 01:40:29 2009
@@ -1,4 +1,4 @@
-// Copyright 2007 The Apache Software Foundation
+// Copyright 2007, 2009 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.
@@ -15,11 +15,22 @@
 package org.apache.tapestry5.internal.test;
 
 import org.apache.tapestry5.Link;
+import org.apache.tapestry5.dom.Document;
 import org.apache.tapestry5.services.Response;
 
 public interface TestableResponse extends Response
 {
     /**
+     * Invoked as part of the rendering pipeline to store the final rendered Document object.
+     */
+    void setRenderedDocument(Document document);
+
+    /**
+     * Allows access to the rendered document.
+     */
+    Document getRenderedDocument();
+
+    /**
      * Returns the link redirected to via {@link org.apache.tapestry5.services.Response#sendRedirect(org.apache.tapestry5.Link)}.
      */
     Link getRedirectLink();

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableResponseImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableResponseImpl.java?rev=740971&r1=740970&r2=740971&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableResponseImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableResponseImpl.java Thu Feb  5 01:40:29 2009
@@ -1,4 +1,4 @@
-// Copyright 2007, 2008 The Apache Software Foundation
+// Copyright 2007, 2008, 2009 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.
@@ -15,6 +15,7 @@
 package org.apache.tapestry5.internal.test;
 
 import org.apache.tapestry5.Link;
+import org.apache.tapestry5.dom.Document;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -27,6 +28,8 @@
 
     private boolean committed;
 
+    private Document renderedDocument;
+
     private void nyi(String methodName)
     {
         throw new RuntimeException(String.format("TestableResponse: Method %s() not yet implemented.", methodName));
@@ -60,22 +63,18 @@
 
     public void setContentLength(int length)
     {
-        nyi("setContentLength");
     }
 
     public void setDateHeader(String name, long date)
     {
-        nyi("setDateHeader");
     }
 
     public void setHeader(String name, String value)
     {
-        nyi("setHeader");
     }
 
     public void setIntHeader(String name, int value)
     {
-        nyi("setIntHeader");
     }
 
     public void sendRedirect(Link link) throws IOException
@@ -113,5 +112,17 @@
     {
         committed = false;
         link = null;
+
+        renderedDocument = null;
+    }
+
+    public Document getRenderedDocument()
+    {
+        return renderedDocument;
+    }
+
+    public void setRenderedDocument(Document document)
+    {
+        renderedDocument = document;
     }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentEventRequestParameters.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentEventRequestParameters.java?rev=740971&r1=740970&r2=740971&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentEventRequestParameters.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentEventRequestParameters.java Thu Feb  5 01:40:29 2009
@@ -15,6 +15,7 @@
 package org.apache.tapestry5.services;
 
 import org.apache.tapestry5.EventContext;
+import org.apache.tapestry5.internal.TapestryInternalUtils;
 import org.apache.tapestry5.ioc.internal.util.Defense;
 
 /**
@@ -67,26 +68,9 @@
         if (!eventType.equals(that.eventType)) return false;
         if (!nestedComponentId.equals(that.nestedComponentId)) return false;
 
-        if (!isEqual(eventContext, that.eventContext)) return false;
+        if (!TapestryInternalUtils.isEqual(eventContext, that.eventContext)) return false;
 
-        return isEqual(pageActivationContext, that.pageActivationContext);
-    }
-
-    private boolean isEqual(EventContext left, EventContext right)
-    {
-        if (left == right) return true;
-
-        int count = left.getCount();
-
-        if (count != right.getCount()) return false;
-
-        for (int i = 0; i < count; i++)
-        {
-            if (!left.get(Object.class, i).equals(right.get(Object.class, i)))
-                return false;
-        }
-
-        return true;
+        return TapestryInternalUtils.isEqual(pageActivationContext, that.pageActivationContext);
     }
 
 

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/PageRenderRequestParameters.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/PageRenderRequestParameters.java?rev=740971&r1=740970&r2=740971&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/PageRenderRequestParameters.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/PageRenderRequestParameters.java Thu Feb  5 01:40:29 2009
@@ -1,4 +1,4 @@
-// Copyright 2008 The Apache Software Foundation
+// Copyright 2008, 2009 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.
@@ -15,6 +15,7 @@
 package org.apache.tapestry5.services;
 
 import org.apache.tapestry5.EventContext;
+import org.apache.tapestry5.internal.TapestryInternalUtils;
 import org.apache.tapestry5.ioc.internal.util.Defense;
 
 /**
@@ -46,4 +47,24 @@
     {
         return activationContext;
     }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj) return true;
+
+        if (obj == null || getClass() != obj.getClass()) return false;
+
+        PageRenderRequestParameters other = (PageRenderRequestParameters) obj;
+
+        return logicalPageName.equals(other.logicalPageName) &&
+                TapestryInternalUtils.isEqual(activationContext, other.activationContext);
+    }
+
+
+    @Override
+    public String toString()
+    {
+        return String.format("PageRenderRequestParameters[%s]", logicalPageName);
+    }
 }

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=740971&r1=740970&r2=740971&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 Thu Feb  5 01:40:29 2009
@@ -14,52 +14,51 @@
 
 package org.apache.tapestry5.test;
 
+import org.apache.tapestry5.Link;
 import org.apache.tapestry5.dom.Document;
 import org.apache.tapestry5.dom.Element;
-import org.apache.tapestry5.dom.Node;
+import org.apache.tapestry5.dom.Visitor;
 import org.apache.tapestry5.internal.InternalConstants;
 import org.apache.tapestry5.internal.SingleKeySymbolProvider;
 import org.apache.tapestry5.internal.TapestryAppInitializer;
-import org.apache.tapestry5.internal.services.*;
-import org.apache.tapestry5.internal.test.*;
+import org.apache.tapestry5.internal.test.PageTesterContext;
+import org.apache.tapestry5.internal.test.PageTesterModule;
+import org.apache.tapestry5.internal.test.TestableRequest;
+import org.apache.tapestry5.internal.test.TestableResponse;
 import org.apache.tapestry5.ioc.Registry;
 import org.apache.tapestry5.ioc.def.ModuleDef;
-import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newMap;
-import static org.apache.tapestry5.ioc.internal.util.Defense.notNull;
+import org.apache.tapestry5.ioc.internal.util.Defense;
+import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.ioc.services.SymbolProvider;
-import org.apache.tapestry5.ioc.services.ThreadLocale;
-import org.apache.tapestry5.ioc.util.StrategyRegistry;
 import org.apache.tapestry5.services.ApplicationGlobals;
-import org.apache.tapestry5.services.ContextPathEncoder;
+import org.apache.tapestry5.services.RequestHandler;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.IOException;
 import java.util.Locale;
 import java.util.Map;
 
 /**
- * This class is used to run a Tapestry app in an in-process testing environment. You can ask it to render a certain
- * page and check the DOM object created. You can also ask it to click on a link element in the DOM object to get the
- * next page. Because no servlet container is required, it is very fast and you can directly debug into your code in
- * your IDE.
+ * This class is used to run a Tapestry app in a single-threaded, in-process testing environment. You can ask it to
+ * render a certain page and check the DOM object created. You can also ask it to click on a link element in the DOM
+ * object to get the next page. Because no servlet container is required, it is very fast and you can directly debug
+ * into your code in your IDE.
  */
-public class PageTester implements ComponentInvoker
+public class PageTester
 {
+    @SuppressWarnings({ "FieldCanBeLocal" })
     private final Logger logger = LoggerFactory.getLogger(PageTester.class);
 
     private final Registry registry;
 
-    private final ComponentInvocationMap invocationMap;
-
     private final TestableRequest request;
 
-    private final StrategyRegistry<ComponentInvoker> invokerRegistry;
-
-    private final ThreadLocale threadLocale;
+    private final TestableResponse response;
 
-    private final ContextPathEncoder contextPathEncoder;
+    // private final StrategyRegistry<ComponentInvoker> invokerRegistry;
 
-    private Locale preferedLanguage;
+    private final RequestHandler requestHandler;
 
     public static final String DEFAULT_CONTEXT_PATH = "src/main/webapp";
 
@@ -89,8 +88,6 @@
      */
     public PageTester(String appPackage, String appName, String contextPath, Class... moduleClasses)
     {
-        preferedLanguage = Locale.ENGLISH;
-
         SymbolProvider provider = new SingleKeySymbolProvider(InternalConstants.TAPESTRY_APP_PACKAGE_PARAM, appPackage);
 
         TapestryAppInitializer initializer = new TapestryAppInitializer(logger, provider, appName,
@@ -102,25 +99,16 @@
 
         registry = initializer.createRegistry();
 
-        request = registry.getObject(TestableRequest.class, null);
-
-        threadLocale = registry.getService(ThreadLocale.class);
-
-        invocationMap = registry.getObject(ComponentInvocationMap.class, null);
+        request = registry.getService(TestableRequest.class);
+        response = registry.getService(TestableResponse.class);
 
         ApplicationGlobals globals = registry.getObject(ApplicationGlobals.class, null);
 
         globals.storeContext(new PageTesterContext(contextPath));
 
-        Map<Class, ComponentInvoker> map = newMap();
-
-        map.put(PageRenderTarget.class, new PageRenderInvoker(registry, this, invocationMap));
-
-        map.put(ComponentEventTarget.class, new ComponentEventInvoker(registry, this, invocationMap));
-
-        invokerRegistry = StrategyRegistry.newInstance(ComponentInvoker.class, map);
+        requestHandler = registry.getService("RequestHandler", RequestHandler.class);
 
-        contextPathEncoder = registry.getService(ContextPathEncoder.class);
+        request.setLocale(Locale.ENGLISH);
     }
 
     /**
@@ -134,7 +122,8 @@
 
 
     /**
-     * You should call it after use
+     * Invoke this method when done using the PageTester; it shuts down the internal {@link
+     * org.apache.tapestry5.ioc.Registry} used by the tester.
      */
     public void shutdown()
     {
@@ -169,50 +158,170 @@
      */
     public Document renderPage(String pageName)
     {
-        return invoke(
-                new ComponentInvocationImpl(contextPathEncoder, new PageRenderTarget(pageName), null, null, false));
+        request.clear().setPath("/" + pageName);
+
+        while (true)
+        {
+            try
+            {
+                response.clear();
+
+                boolean handled = requestHandler.service(request, response);
+
+                if (!handled)
+                {
+                    throw new RuntimeException(
+                            String.format("Request was not handled: '%s' may not be a valid page name.", pageName));
+                }
+
+                Link link = response.getRedirectLink();
+
+                if (link != null)
+                {
+                    setupRequestFromLink(link);
+                    continue;
+                }
+
+                Document result = response.getRenderedDocument();
+
+                if (result == null)
+                    throw new RuntimeException(
+                            String.format("Render of page '%s' did not result in a Document.", pageName));
+
+
+                return result;
+            }
+            catch (IOException ex)
+            {
+                throw new RuntimeException(ex);
+            }
+
+        }
+
     }
 
     /**
      * Simulates a click on a link.
      *
-     * @param link The Link object to be "clicked" on.
+     * @param linkElement The Link object to be "clicked" on.
      * @return The DOM created. Typically you will assert against it.
      */
-    public Document clickLink(Element link)
+
+    public Document clickLink(Element linkElement)
     {
-        notNull(link, "link");
+        Defense.notNull(linkElement, "link");
+
+        validateElementName(linkElement, "a");
+
+        String href = extractNonBlank(linkElement, "href");
 
-        ComponentInvocation invocation = getInvocation(link);
+        setupRequestFromURI(href);
 
-        return invoke(invocation);
+        return runComponentEventRequest();
     }
 
-    private ComponentInvocation getInvocation(Element element)
+    private String extractNonBlank(Element element, String attributeName)
     {
-        ComponentInvocation invocation = invocationMap.get(element);
+        String result = element.getAttribute(attributeName);
 
-        if (invocation == null)
-            throw new IllegalArgumentException("No component invocation object is associated with the Element.");
+        if (InternalUtils.isBlank(result))
+            throw new RuntimeException(
+                    String.format("The %s attribute of the <%s> element was blank or missing.",
+                                  element.getName(), attributeName));
 
-        return invocation;
+        return result;
     }
 
-    public Document invoke(ComponentInvocation invocation)
+    private void validateElementName(Element element, String expectedElementName)
     {
-        // It is critical to clear the map before invoking an invocation (render a page or click a
-        // link).
-        invocationMap.clear();
+        if (!element.getName().equalsIgnoreCase(expectedElementName))
+            throw new RuntimeException(
+                    String.format("The element must be type '%s', not '%s'.", expectedElementName, element.getName()));
+    }
+
+    private Document runComponentEventRequest()
+    {
+        while (true)
+        {
+            response.clear();
+
+            try
+            {
+                boolean handled = requestHandler.service(request, response);
+
+                if (!handled)
+                    throw new RuntimeException(String.format("Request for path '%s' was not handled by Tapestry.",
+                                                             request.getPath()));
+
+                Link link = response.getRedirectLink();
+
+                if (link != null)
+                {
+                    setupRequestFromLink(link);
+                    continue;
+                }
+
+                Document result = response.getRenderedDocument();
+
+                if (result == null)
+                    throw new RuntimeException(
+                            String.format("Render request '%s' did not result in a Document.", request.getPath()));
+
+                return result;
+            }
+            catch (IOException ex)
+            {
+                throw new RuntimeException(ex);
+            }
+
+        }
+
+
+    }
 
-        threadLocale.setLocale(preferedLanguage);
+    private void setupRequestFromLink(Link link)
+    {
+        setupRequestFromURI(link.toRedirectURI());
+    }
+
+    private void setupRequestFromURI(String URI)
+    {
+        String linkPath = stripContextFromPath(URI);
 
-        ComponentInvoker invoker = invokerRegistry.getByInstance(invocation.getTarget());
+        int comma = linkPath.indexOf('?');
 
-        return invoker.invoke(invocation);
+        String path = comma < 0
+                      ? linkPath
+                      : linkPath.substring(0, comma);
+
+        request.clear().setPath(path);
+
+        if (comma > 0)
+            decodeParametersIntoRequest(path.substring(comma + 1));
+    }
+
+    private void decodeParametersIntoRequest(String queryString)
+    {
+        if (InternalUtils.isNonBlank(queryString))
+            throw new RuntimeException("Have not yet implemented this method.");
+    }
+
+    private String stripContextFromPath(String path)
+    {
+        String contextPath = request.getContextPath();
+
+        if (contextPath.equals("")) return path;
+
+        if (!path.startsWith(contextPath))
+            throw new RuntimeException(String.format("Path '%s' does not start with context path '%s'.",
+                                                     path, contextPath));
+
+        return path.substring(contextPath.length());
     }
 
     /**
-     * Simulates a submission of the form specified. The caller can specify values for the form fields.
+     * Simulates a submission of the form specified. The caller can specify values for the form fields, which act as
+     * overrides on the values stored inside the elements.
      *
      * @param form       the form to be submitted.
      * @param parameters the query parameter name/value pairs
@@ -220,17 +329,96 @@
      */
     public Document submitForm(Element form, Map<String, String> parameters)
     {
-        notNull(form, "form");
+        Defense.notNull(form, "form");
+
+        validateElementName(form, "form");
+
+        request.clear().setPath(stripContextFromPath(extractNonBlank(form, "action")));
+
+        pushFieldValuesIntoRequest(form);
+
+        overrideParameters(parameters);
+
+        // addHiddenFormFields(form);
+
+        // ComponentInvocation invocation = getInvocation(form);
+
+        return runComponentEventRequest();
+    }
+
+    private void overrideParameters(Map<String, String> fieldValues)
+    {
+        for (Map.Entry<String, String> e : fieldValues.entrySet())
+        {
+            request.overrideParameter(e.getKey(), e.getValue());
+        }
+    }
+
+    private void pushFieldValuesIntoRequest(Element form)
+    {
+        Visitor visitor = new Visitor()
+        {
+            public void visit(Element element)
+            {
+                if (InternalUtils.isNonBlank(element.getAttribute("disabled")))
+                    return;
+
+                String name = element.getName();
+
+                if (name.equals("input"))
+                {
+                    String type = extractNonBlank(element, "type");
+
+                    if (type.equals("radio") || type.equals("checkbox"))
+                    {
+                        if (InternalUtils.isBlank(element.getAttribute("checked")))
+                            return;
+                    }
 
-        request.clear();
+                    // Assume that, if the element is a button/submit, it wasn't clicked,
+                    // and therefore, is not part of the submission.
 
-        request.loadParameters(parameters);
+                    if (type.equals("button") || type.equals("submit"))
+                        return;
 
-        addHiddenFormFields(form);
+                    // Handle radio, checkbox, text, radio, hidden
+                    String value = element.getAttribute("value");
 
-        ComponentInvocation invocation = getInvocation(form);
+                    if (InternalUtils.isNonBlank(value))
+                        request.loadParameter(extractNonBlank(element, "name"), value);
 
-        return invoke(invocation);
+                    return;
+                }
+
+                if (name.equals("option"))
+                {
+                    String value = element.getAttribute("value");
+
+                    // TODO: If value is blank do we use the content, or is the content only the label?
+
+                    if (InternalUtils.isNonBlank(element.getAttribute("selected")))
+                    {
+                        String selectName = extractNonBlank(findAncestor(element, "select"), "name");
+
+                        request.loadParameter(selectName, value);
+                    }
+
+                    return;
+                }
+
+                if (name.equals("textarea"))
+                {
+                    String content = element.getChildMarkup();
+
+                    if (InternalUtils.isNonBlank(content))
+                        request.loadParameter(extractNonBlank(element, "name"), content);
+
+                    return;
+                }
+            }
+        };
+
+        form.visit(visitor);
     }
 
     /**
@@ -243,18 +431,26 @@
      */
     public Document clickSubmit(Element submitButton, Map<String, String> fieldValues)
     {
-        notNull(submitButton, "submitButton");
+        Defense.notNull(submitButton, "submitButton");
 
         assertIsSubmit(submitButton);
 
         Element form = getFormAncestor(submitButton);
+
+        request.clear().setPath(stripContextFromPath(extractNonBlank(form, "action")));
+
+        pushFieldValuesIntoRequest(form);
+
+        overrideParameters(fieldValues);
+
         String value = submitButton.getAttribute("value");
 
-        if (value == null) value = DEFAULT_SUBMIT_VALUE_ATTRIBUTE;
+        if (value == null)
+            value = DEFAULT_SUBMIT_VALUE_ATTRIBUTE;
 
-        fieldValues.put(submitButton.getAttribute("name"), value);
+        request.overrideParameter(extractNonBlank(submitButton, "name"), value);
 
-        return submitForm(form, fieldValues);
+        return runComponentEventRequest();
     }
 
     private void assertIsSubmit(Element element)
@@ -271,37 +467,34 @@
 
     private Element getFormAncestor(Element element)
     {
-        while (true)
-        {
-            if (element == null) throw new IllegalArgumentException("The given element is not contained by a form.");
-
-            if (element.getName().equalsIgnoreCase("form")) return element;
-
-            element = element.getParent();
-        }
+        return findAncestor(element, "form");
     }
 
-    private void addHiddenFormFields(Element element)
+    private Element findAncestor(Element element, String ancestorName)
     {
-        if (isHiddenFormField(element))
-            request.loadParameter(element.getAttribute("name"), element.getAttribute("value"));
+        Element e = element;
 
-        for (Node child : element.getChildren())
+        while (e != null)
         {
-            if (child instanceof Element)
-            {
-                addHiddenFormFields((Element) child);
-            }
+            if (e.getName().equalsIgnoreCase(ancestorName))
+                return e;
+
+            e = e.getParent();
         }
-    }
 
-    private boolean isHiddenFormField(Element element)
-    {
-        return element.getName().equalsIgnoreCase("input") && "hidden".equalsIgnoreCase(element.getAttribute("type"));
+        throw new RuntimeException(
+                String.format("Could not locate an ancestor element of type '%s'.", ancestorName));
+
     }
 
+    /**
+     * Sets the simulated browser's preferred language, i.e., the value returned from {@link
+     * org.apache.tapestry5.services.Request#getLocale()}.
+     *
+     * @param preferedLanguage preferred language setting
+     */
     public void setPreferedLanguage(Locale preferedLanguage)
     {
-        this.preferedLanguage = preferedLanguage;
+        request.setLocale(preferedLanguage);
     }
 }