You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by hl...@apache.org on 2014/04/04 19:59:05 UTC

git commit: TAP5-1744: Returning a StreamPageContent instance from any of a Form's events results in "Form components may not be placed inside other Form components" exception

Repository: tapestry-5
Updated Branches:
  refs/heads/master f6a21aad9 -> 3de7acb9e


TAP5-1744: Returning a StreamPageContent instance from any of a Form's events results in "Form components may not be placed inside other Form components" exception


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/3de7acb9
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/3de7acb9
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/3de7acb9

Branch: refs/heads/master
Commit: 3de7acb9e0c6d979aaaae024f9e4938da9827dd5
Parents: f6a21aa
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Fri Apr 4 10:58:56 2014 -0700
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Fri Apr 4 10:58:56 2014 -0700

----------------------------------------------------------------------
 54_RELEASE_NOTES.md                             | 11 +++
 .../org/apache/tapestry5/TapestryConstants.java | 22 ++++--
 .../AjaxPartialResponseRendererImpl.java        | 49 +++++++------
 .../services/DeferredResponseRenderer.java      | 76 ++++++++++++++++++++
 .../internal/services/EnvironmentImpl.java      | 33 +++------
 ...derCommandComponentEventResultProcessor.java |  4 +-
 .../StreamPageContentResultProcessor.java       | 18 +++--
 .../MultiZoneUpdateEventResultProcessor.java    |  4 +-
 .../tapestry5/modules/TapestryModule.java       | 15 +++-
 .../services/ComponentEventResultProcessor.java | 17 +++--
 .../apache/tapestry5/services/Environment.java  | 13 ++--
 .../internal/services/EnvironmentImplTest.java  | 16 +----
 12 files changed, 189 insertions(+), 89 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/3de7acb9/54_RELEASE_NOTES.md
----------------------------------------------------------------------
diff --git a/54_RELEASE_NOTES.md b/54_RELEASE_NOTES.md
index 282c34d..172e421 100644
--- a/54_RELEASE_NOTES.md
+++ b/54_RELEASE_NOTES.md
@@ -158,6 +158,17 @@ The interface org.apache.tapestry5.services.assets.ResourceTransformer has had a
 getTransformedContentType(). This makes it possible to determine which file extensions map to which content types
 (for example, a ResourceTransformer for CoffeeScript files, with extension "coffee", would map to "text/javascript").
 
+## Rendering Changes
+
+There have been some subtle changes to how rendering of page content (in both traditional and Ajax requests) occurs;
+such rendering now occurs deferred to a later stage of request processing, rather than immediately after
+a component event handler method returns a value.
+
+## Environment.cloak() and decloak() deprecated
+
+Related to the rendering changes, the `cloak()` and `decloak()` methods of the Environment service are deprecated
+and should not be invoked: they now throw an UnsupportedOperationException.
+
 ## Zone component change
 
 Older versions of Tapestry included client-side support for an element with the CSS class "t-zone-update" as the actual

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/3de7acb9/tapestry-core/src/main/java/org/apache/tapestry5/TapestryConstants.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/TapestryConstants.java b/tapestry-core/src/main/java/org/apache/tapestry5/TapestryConstants.java
index 83a6606..c136fb2 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/TapestryConstants.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/TapestryConstants.java
@@ -1,4 +1,4 @@
-// Copyright 2010, 2013 The Apache Software Foundation
+// Copyright 2010-2014 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.
@@ -36,9 +36,7 @@ public class TapestryConstants
      * page). This mostly includes the redirects sent after a component event request. Page render
      * requests
      * that do <em>not</em> have the LOOPBACK query parameter will trigger a {@linkplain PageResetListener reset
-     * notification} after the initialization event; the
-     * LOOPBACK
-     * prevents this reset notification.
+     * notification} after the initialization event; the LOOPBACK prevents this reset notification.
      *
      * @see ComponentEventLinkEncoder#createPageRenderLink(org.apache.tapestry5.services.PageRenderRequestParameters)
      * @see ComponentEventLinkEncoder#decodePageRenderRequest(org.apache.tapestry5.services.Request)
@@ -46,4 +44,20 @@ public class TapestryConstants
      * @since 5.2.0
      */
     public static final String PAGE_LOOPBACK_PARAMETER_NAME = "t:lb";
+
+    /**
+     * Name of a request attribute that contains an {@link org.apache.tapestry5.ioc.IOOperation}
+     * used to render the response. The operation should return void.
+     * <p/>
+     * Implementations of {@link org.apache.tapestry5.services.ComponentEventResultProcessor}
+     * will store a response rendering operation into the request; the operation, if present,
+     * will be executed as the first filter inside the
+     * {@link org.apache.tapestry5.services.ComponentRequestHandler} pipeline.
+     * <p/>
+     * This approach is recommended for any "complex" rendering that involves components or pages.
+     * It is optional for other types.
+     *
+     * @since 5.4
+     */
+    public static final String RESPONSE_RENDERER = "tapestry.response-renderer";
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/3de7acb9/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AjaxPartialResponseRendererImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AjaxPartialResponseRendererImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AjaxPartialResponseRendererImpl.java
index aa69acf..8def717 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AjaxPartialResponseRendererImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/AjaxPartialResponseRendererImpl.java
@@ -1,4 +1,4 @@
-// Copyright 2007, 2008, 2009, 2010, 2011, 2012 The Apache Software Foundation
+// Copyright 2007-2014 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,9 @@ package org.apache.tapestry5.internal.services;
 import org.apache.tapestry5.ContentType;
 import org.apache.tapestry5.MarkupWriter;
 import org.apache.tapestry5.SymbolConstants;
+import org.apache.tapestry5.TapestryConstants;
 import org.apache.tapestry5.internal.InternalConstants;
+import org.apache.tapestry5.ioc.IOOperation;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.json.JSONObject;
@@ -40,8 +42,6 @@ public class AjaxPartialResponseRendererImpl implements AjaxPartialResponseRende
 
     private final boolean compactJSON;
 
-    private final Environment environment;
-
     public AjaxPartialResponseRendererImpl(MarkupWriterFactory factory,
 
                                            Request request,
@@ -55,7 +55,7 @@ public class AjaxPartialResponseRendererImpl implements AjaxPartialResponseRende
                                            String outputEncoding,
 
                                            @Symbol(SymbolConstants.COMPACT_JSON)
-                                           boolean compactJSON, Environment environment)
+                                           boolean compactJSON)
     {
         this.factory = factory;
         this.request = request;
@@ -63,40 +63,39 @@ public class AjaxPartialResponseRendererImpl implements AjaxPartialResponseRende
         this.partialMarkupRenderer = partialMarkupRenderer;
         this.outputEncoding = outputEncoding;
         this.compactJSON = compactJSON;
-        this.environment = environment;
     }
 
-    public void renderPartialPageMarkup(JSONObject reply) throws IOException
+    public void renderPartialPageMarkup(final JSONObject reply) throws IOException
     {
         assert reply != null;
 
-        environment.cloak();
-
-        try
+        request.setAttribute(TapestryConstants.RESPONSE_RENDERER, new IOOperation<Void>()
         {
-            // This is a complex area as we are trying to keep public and private services properly
-            // separated, and trying to keep stateless and stateful (i.e., perthread scope) services
-            // separated. So we inform the stateful queue service what it needs to do here ...
+            public Void perform() throws IOException
+            {
+                // This is a complex area as we are trying to keep public and private services properly
+                // separated, and trying to keep stateless and stateful (i.e., perthread scope) services
+                // separated. So we inform the stateful queue service what it needs to do here ...
 
-            ContentType contentType = new ContentType(InternalConstants.JSON_MIME_TYPE, outputEncoding);
-            
-            String pageName = (String) request.getAttribute(InternalConstants.PAGE_NAME_ATTRIBUTE_NAME);
+                ContentType contentType = new ContentType(InternalConstants.JSON_MIME_TYPE, outputEncoding);
 
-            MarkupWriter writer = factory.newPartialMarkupWriter(pageName);
+                String pageName = (String) request.getAttribute(InternalConstants.PAGE_NAME_ATTRIBUTE_NAME);
 
-            // ... and here, the pipeline eventually reaches the PRQ to let it render the root render command.
+                MarkupWriter writer = factory.newPartialMarkupWriter(pageName);
 
-            partialMarkupRenderer.renderMarkup(writer, reply);
+                // ... and here, the pipeline eventually reaches the PRQ to let it render the root render command.
 
-            PrintWriter pw = response.getPrintWriter(contentType.toString());
+                partialMarkupRenderer.renderMarkup(writer, reply);
 
-            reply.print(pw, compactJSON);
+                PrintWriter pw = response.getPrintWriter(contentType.toString());
 
-            pw.close();
-        } finally
-        {
-            environment.decloak();
-        }
+                reply.print(pw, compactJSON);
+
+                pw.close();
+
+                return null;
+            }
+        });
     }
 
     public void renderPartialPageMarkup() throws IOException

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/3de7acb9/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DeferredResponseRenderer.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DeferredResponseRenderer.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DeferredResponseRenderer.java
new file mode 100644
index 0000000..72d2b88
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DeferredResponseRenderer.java
@@ -0,0 +1,76 @@
+// Copyright 2014 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.TapestryConstants;
+import org.apache.tapestry5.ioc.IOOperation;
+import org.apache.tapestry5.ioc.OperationTracker;
+import org.apache.tapestry5.services.*;
+
+import java.io.IOException;
+
+/**
+ * After processing the component event request (including Ajax requests), or the page render request,
+ * checks for the {@link org.apache.tapestry5.TapestryConstants#RESPONSE_RENDERER} request attribute,
+ * and invokes it to render the deferred response.
+ *
+ * @since 5.4
+ */
+public class DeferredResponseRenderer implements ComponentRequestFilter
+{
+    private final Request request;
+
+    private final OperationTracker tracker;
+
+    public DeferredResponseRenderer(Request request, OperationTracker tracker)
+    {
+        this.request = request;
+        this.tracker = tracker;
+    }
+
+    public void handleComponentEvent(ComponentEventRequestParameters parameters, ComponentRequestHandler handler) throws IOException
+    {
+        handler.handleComponentEvent(parameters);
+
+        invokeQueuedRenderer();
+    }
+
+    public void handlePageRender(PageRenderRequestParameters parameters, ComponentRequestHandler handler) throws IOException
+    {
+        handler.handlePageRender(parameters);
+
+        invokeQueuedRenderer();
+    }
+
+    private void invokeQueuedRenderer() throws IOException
+    {
+        while (true)
+        {
+
+            IOOperation responseRenderer = (IOOperation) request.getAttribute(TapestryConstants.RESPONSE_RENDERER);
+
+            if (responseRenderer == null)
+            {
+                break;
+            }
+
+            // There's a particular case where an operation puts a different operation into the attribute;
+            // we'll handle that on the next pass.
+            request.setAttribute(TapestryConstants.RESPONSE_RENDERER, null);
+
+            tracker.perform("Executing deferred response renderer.", responseRenderer);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/3de7acb9/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/EnvironmentImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/EnvironmentImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/EnvironmentImpl.java
index 599d4c4..5af8575 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/EnvironmentImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/EnvironmentImpl.java
@@ -1,4 +1,4 @@
-// Copyright 2006-2013 The Apache Software Foundation
+// Copyright 2006-2014 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,9 +19,7 @@ import org.apache.tapestry5.func.Mapper;
 import org.apache.tapestry5.func.Predicate;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.internal.util.OneShotLock;
-import org.apache.tapestry5.ioc.services.ThreadCleanupListener;
 import org.apache.tapestry5.ioc.util.AvailableValues;
-import org.apache.tapestry5.ioc.util.Stack;
 import org.apache.tapestry5.ioc.util.UnknownValueException;
 import org.apache.tapestry5.services.Environment;
 
@@ -33,7 +31,7 @@ import java.util.Map.Entry;
 /**
  * A non-threadsafe implementation (expects to use the "perthread" service lifecyle).
  */
-public class EnvironmentImpl implements Environment, ThreadCleanupListener
+public class EnvironmentImpl implements Environment
 {
 
     // My generics mojo breaks down when we talk about the key and the value being related
@@ -41,12 +39,6 @@ public class EnvironmentImpl implements Environment, ThreadCleanupListener
 
     private Map<Class, LinkedList> typeToStack = CollectionFactory.newMap();
 
-    // Support for cloak/decloak.  Cloaking pushes the current typeToStack map onto the stack
-    // and creates a new, empyt, Map to replace it. Decloaking discards the current map
-    // and replaces it with the top map on the stack.
-
-    private final Stack<Map<Class, LinkedList>> cloakStack = CollectionFactory.newStack();
-
     private final OneShotLock lock = new OneShotLock();
 
     @SuppressWarnings("unchecked")
@@ -89,10 +81,12 @@ public class EnvironmentImpl implements Environment, ThreadCleanupListener
 
             throw new UnknownValueException(String.format("No object of type %s is available from the Environment.", type.getName()),
                     new AvailableValues("Environmentals",
-                            F.flow(typeToStack.entrySet()).remove(new Predicate<Entry<Class, LinkedList>>() {
-                              public boolean accept(Entry<Class, LinkedList> element) {
-                                return element.getValue().isEmpty();
-                              }
+                            F.flow(typeToStack.entrySet()).remove(new Predicate<Entry<Class, LinkedList>>()
+                            {
+                                public boolean accept(Entry<Class, LinkedList> element)
+                                {
+                                    return element.getValue().isEmpty();
+                                }
                             }).map(new Mapper<Entry<Class, LinkedList>, String>()
                             {
                                 public String map(Entry<Class, LinkedList> element)
@@ -123,11 +117,6 @@ public class EnvironmentImpl implements Environment, ThreadCleanupListener
         return result;
     }
 
-    public void clear()
-    {
-        throw new IllegalStateException("Environment.clear() is no longer supported.");
-    }
-
     public void threadDidCleanup()
     {
         lock.lock();
@@ -135,13 +124,11 @@ public class EnvironmentImpl implements Environment, ThreadCleanupListener
 
     public void cloak()
     {
-        cloakStack.push(typeToStack);
-
-        typeToStack = CollectionFactory.newMap();
+        throw new UnsupportedOperationException("cloak() is no longer available in Tapestry 5.4.");
     }
 
     public void decloak()
     {
-        typeToStack = cloakStack.pop();
+        throw new UnsupportedOperationException("decloak() is no longer available in Tapestry 5.4.");
     }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/3de7acb9/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RenderCommandComponentEventResultProcessor.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RenderCommandComponentEventResultProcessor.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RenderCommandComponentEventResultProcessor.java
index 8e387ab..136a5d3 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RenderCommandComponentEventResultProcessor.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RenderCommandComponentEventResultProcessor.java
@@ -1,4 +1,4 @@
-// Copyright 2007, 2008, 2010, 2011 The Apache Software Foundation
+// Copyright 2007-2014 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.
@@ -53,7 +53,7 @@ public class RenderCommandComponentEventResultProcessor implements ComponentEven
         pageRenderQueue.addPartialMarkupRendererFilter(this);
         pageRenderQueue.addPartialRenderer(value);
 
-        // And render the content right now.
+        // And setup to render the content deferred.
 
         partialRenderer.renderPartialPageMarkup();
     }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/3de7acb9/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/StreamPageContentResultProcessor.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/StreamPageContentResultProcessor.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/StreamPageContentResultProcessor.java
index 264b6d5..962b811 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/StreamPageContentResultProcessor.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/StreamPageContentResultProcessor.java
@@ -1,4 +1,4 @@
-// Copyright 2010, 2011 The Apache Software Foundation
+// Copyright 2010-2014 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,8 +15,10 @@
 package org.apache.tapestry5.internal.services;
 
 import org.apache.tapestry5.EventContext;
+import org.apache.tapestry5.TapestryConstants;
 import org.apache.tapestry5.internal.EmptyEventContext;
 import org.apache.tapestry5.internal.InternalConstants;
+import org.apache.tapestry5.ioc.IOOperation;
 import org.apache.tapestry5.ioc.services.TypeCoercer;
 import org.apache.tapestry5.services.*;
 
@@ -54,11 +56,11 @@ public class StreamPageContentResultProcessor implements ComponentEventResultPro
         Class<?> pageClass = value.getPageClass();
         Object[] activationContext = value.getPageActivationContext();
 
-        String pageName = pageClass == null
+        final String pageName = pageClass == null
                 ? requestGlobals.getActivePageName()
                 : resolver.resolvePageClassNameToPageName(pageClass.getName());
 
-        EventContext context = activationContext == null
+        final EventContext context = activationContext == null
                 ? new EmptyEventContext()
                 : new ArrayEventContext(typeCoercer, activationContext);
 
@@ -67,6 +69,14 @@ public class StreamPageContentResultProcessor implements ComponentEventResultPro
             request.setAttribute(InternalConstants.BYPASS_ACTIVATION, true);
         }
 
-        handler.handle(new PageRenderRequestParameters(pageName, context, false));
+        request.setAttribute(TapestryConstants.RESPONSE_RENDERER, new IOOperation<Void>()
+        {
+            public Void perform() throws IOException
+            {
+                handler.handle(new PageRenderRequestParameters(pageName, context, false));
+
+                return null;
+            }
+        });
     }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/3de7acb9/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/MultiZoneUpdateEventResultProcessor.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/MultiZoneUpdateEventResultProcessor.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/MultiZoneUpdateEventResultProcessor.java
index 07f3270..69e1998 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/MultiZoneUpdateEventResultProcessor.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/MultiZoneUpdateEventResultProcessor.java
@@ -1,4 +1,4 @@
-// Copyright 2009-2013 The Apache Software Foundation
+// Copyright 2009-2014 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.
@@ -70,6 +70,8 @@ public class MultiZoneUpdateEventResultProcessor implements ComponentEventResult
             ajaxResponseRenderer.addRender(zoneId, zoneRenderCommand);
         }
 
+        // This is actually executed deferred:
+
         partialRenderer.renderPartialPageMarkup();
     }
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/3de7acb9/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
index df9d3d5..2f9ba45 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
@@ -2299,9 +2299,15 @@ public final class TapestryModule
     @Scope(ScopeConstants.PERTHREAD)
     public Environment buildEnvironment(PerthreadManager perthreadManager)
     {
-        EnvironmentImpl service = new EnvironmentImpl();
+        final EnvironmentImpl service = new EnvironmentImpl();
 
-        perthreadManager.addThreadCleanupListener(service);
+        perthreadManager.addThreadCleanupCallback(new Runnable()
+        {
+            public void run()
+            {
+                service.threadDidCleanup();
+            }
+        });
 
         return service;
     }
@@ -2421,9 +2427,11 @@ public final class TapestryModule
      * Contributes:
      * <dl>
      * <dt>OperationTracker</dt>
-     * <dd>Tracks general inforamtion about the request using {@link OperationTracker}</dd>
+     * <dd>Tracks general information about the request using {@link OperationTracker}</dd>
      * <dt>InitializeActivePageName
      * <dd>{@link InitializeActivePageName}
+     * <dt>DeferredResponseRenderer</dt>
+     * <dd>{@link DeferredResponseRenderer}</dd>
      * </dl>
      *
      * @since 5.2.0
@@ -2432,6 +2440,7 @@ public final class TapestryModule
     {
         configuration.addInstance("OperationTracker", RequestOperationTracker.class);
         configuration.addInstance("InitializeActivePageName", InitializeActivePageName.class);
+        configuration.addInstance("DeferredResponseRenderer", DeferredResponseRenderer.class);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/3de7acb9/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentEventResultProcessor.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentEventResultProcessor.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentEventResultProcessor.java
index 06475f5..c43b2d9 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentEventResultProcessor.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentEventResultProcessor.java
@@ -1,4 +1,4 @@
-// Copyright 2007, 2008 The Apache Software Foundation
+// Copyright 200-2014 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,10 +23,11 @@ import java.io.IOException;
  * <p/>
  * There are two services built into Tapestry that implement this interface: ComponentEventResultProcessor (used for
  * ordinary page-oriented requests, and distinguished by the @{@link org.apache.tapestry5.services.Traditional}
- * and/or @{@link org.apache.tapestry5.ioc.annotations.Primary} marker annotations) and 
+ * and/or @{@link org.apache.tapestry5.ioc.annotations.Primary} marker annotations) and
  * AjaxComponentEventResultProcessor, used
  * for Ajax requests (which typically return a partially rendered page), distinguished by the @{@link
  * org.apache.tapestry5.services.Ajax} marker annotation.
+ *
  * @param <T>
  */
 @UsesMappedConfiguration(key = Class.class, value = ComponentEventResultProcessor.class)
@@ -34,9 +35,17 @@ public interface ComponentEventResultProcessor<T>
 {
     /**
      * For a given, non-null return value from a component event method, construct and send a response.
+     * <p/>
+     * Starting in release 5.4, it is recommended that for any response that involves Tapestry pages or components,
+     * the implementation should create an {@link org.apache.tapestry5.ioc.IOOperation} to do the rendering, and
+     * add the operation to the {@link org.apache.tapestry5.services.Request} as attribute
+     * {@link org.apache.tapestry5.TapestryConstants#RESPONSE_RENDERER}.
+     * This avoids a number of issues related to the {@link org.apache.tapestry5.services.Environment}.
      *
-     * @param value the value returned from a method
-     * @throws RuntimeException if the value can not handled
+     * @param value
+     *         the value returned from a method
+     * @throws RuntimeException
+     *         if the value can not handled
      */
     void processResultValue(T value) throws IOException;
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/3de7acb9/tapestry-core/src/main/java/org/apache/tapestry5/services/Environment.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/Environment.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/Environment.java
index 2ab4fb9..be00050 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/Environment.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/Environment.java
@@ -1,4 +1,4 @@
-// Copyright 2006, 2008, 2011 The Apache Software Foundation
+// Copyright 2006-2014 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.
@@ -69,17 +69,12 @@ public interface Environment
     <T> T push(Class<T> type, T instance);
 
     /**
-     * Clears all stacks; no longer used by Tapestry.
-     *
-     * @deprecated Deprecated in 5.3 with no replacement.
-     */
-    void clear();
-
-    /**
      * Hides all current environment values, making the Environment object appear empty, until
      * a call to {@link #decloak()}} restores the original state.
      *
      * @since 5.3
+     * @deprecated Deprecated in 5.4 with no replacement; not longer used by Tapestry.
+     * @see org.apache.tapestry5.TapestryConstants#RESPONSE_RENDERER
      */
     void cloak();
 
@@ -87,6 +82,8 @@ public interface Environment
      * Restores state previously hidden by {@link #cloak()}}.
      *
      * @since 5.3
+     * @deprecated Deprecated in 5.4 with no replacement; not longer used by Tapestry.
+     * @see org.apache.tapestry5.TapestryConstants#RESPONSE_RENDERER
      */
     void decloak();
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/3de7acb9/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/EnvironmentImplTest.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/EnvironmentImplTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/EnvironmentImplTest.java
index a2396c9..7e1ac95 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/EnvironmentImplTest.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/EnvironmentImplTest.java
@@ -1,4 +1,4 @@
-// Copyright 2006-2013 The Apache Software Foundation
+// Copyright 2006-2014 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.
@@ -59,20 +59,6 @@ public class EnvironmentImplTest extends TapestryTestCase
         verify();
     }
 
-    @Test
-    public void clear()
-    {
-        Environment e = new EnvironmentImpl();
-
-        try
-        {
-            e.clear();
-            unreachable();
-        } catch (IllegalStateException ex)
-        {
-            assertMessageContains(ex, "no longer supported");
-        }
-    }
 
     @Test
     public void pop_when_empty_is_error()