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 2006/09/26 04:35:16 UTC

svn commit: r449884 - in /tapestry/tapestry5/tapestry-core/trunk/src: main/java/org/apache/tapestry/internal/services/ main/java/org/apache/tapestry/services/ site/resources/ test/java/org/apache/tapestry/internal/services/

Author: hlship
Date: Mon Sep 25 19:35:15 2006
New Revision: 449884

URL: http://svn.apache.org/viewvc?view=rev&rev=449884
Log:
Add the Environment and PageRenderInitializer services.

Added:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/EnvironmentImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/Environment.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/EnvironmentImplTest.java
Modified:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalModule.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageResponseRendererImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java
    tapestry/tapestry5/tapestry-core/trunk/src/site/resources/tap5devwiki.html
    tapestry/tapestry5/tapestry-core/trunk/src/site/resources/tap5devwiki.xml

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/EnvironmentImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/EnvironmentImpl.java?view=auto&rev=449884
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/EnvironmentImpl.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/EnvironmentImpl.java Mon Sep 25 19:35:15 2006
@@ -0,0 +1,66 @@
+package org.apache.tapestry.internal.services;
+
+import static org.apache.tapestry.util.CollectionFactory.newLinkedList;
+
+import java.util.LinkedList;
+import java.util.Map;
+
+import org.apache.tapestry.services.Environment;
+import org.apache.tapestry.util.CollectionFactory;
+
+/**
+ * A non-threadsafe implementation (expects to use the "perthread" service lifecyle.
+ * 
+ * @author Howard M. Lewis Ship
+ */
+public class EnvironmentImpl implements Environment
+{
+    // My generics mojo breaks down when we talk about the key and the value being related
+    // types.
+
+    private final Map<Class, LinkedList> _stacks = CollectionFactory.newMap();
+
+    @SuppressWarnings("unchecked")
+    private <T> LinkedList<T> stackFor(Class<T> type)
+    {
+        LinkedList<T> result = _stacks.get(type);
+
+        if (result == null)
+        {
+            result = newLinkedList();
+            _stacks.put(type, result);
+        }
+
+        return result;
+    }
+
+    public <T> T peek(Class<T> type)
+    {
+        LinkedList<T> stack = stackFor(type);
+
+        return stack.isEmpty() ? null : stack.getFirst();
+    }
+
+    public <T> T pop(Class<T> type)
+    {
+        LinkedList<T> stack = stackFor(type);
+
+        return stack.removeFirst();
+    }
+
+    public <T> T push(Class<T> type, T instance)
+    {
+        LinkedList<T> stack = stackFor(type);
+
+        T result = stack.isEmpty() ? null : stack.getFirst();
+
+        stack.addFirst(instance);
+
+        return result;
+    }
+
+    public void clear()
+    {
+        _stacks.clear();
+    }
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalModule.java?view=diff&rev=449884&r1=449883&r2=449884
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalModule.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalModule.java Mon Sep 25 19:35:15 2006
@@ -210,9 +210,11 @@
 
     public PageResponseRenderer buildPageResponseRenderer(
             @InjectService("tapestry.MarkupWriterFactory")
-            MarkupWriterFactory markupWriterFactory)
+            MarkupWriterFactory markupWriterFactory,
+            @InjectService("tapestry.PageRenderInitializer")
+            Runnable pageRenderInitializer)
     {
-        return new PageResponseRendererImpl(markupWriterFactory);
+        return new PageResponseRendererImpl(markupWriterFactory, pageRenderInitializer);
     }
 
     /**

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageResponseRendererImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageResponseRendererImpl.java?view=diff&rev=449884&r1=449883&r2=449884
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageResponseRendererImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageResponseRendererImpl.java Mon Sep 25 19:35:15 2006
@@ -27,15 +27,21 @@
  */
 public class PageResponseRendererImpl implements PageResponseRenderer
 {
+    private final Runnable _pageRenderInitializer;
+
     private final MarkupWriterFactory _markupWriterFactory;
 
-    public PageResponseRendererImpl(MarkupWriterFactory markupWriterFactory)
+    public PageResponseRendererImpl(MarkupWriterFactory markupWriterFactory,
+            Runnable pageRenderInitializer)
     {
         _markupWriterFactory = markupWriterFactory;
+        _pageRenderInitializer = pageRenderInitializer;
     }
 
     public void renderPageResponse(Page page, WebResponse response) throws IOException
     {
+        _pageRenderInitializer.run();
+
         MarkupWriter writer = _markupWriterFactory.newMarkupWriter();
 
         RenderQueueImpl queue = new RenderQueueImpl(page.getLog());

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/Environment.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/Environment.java?view=auto&rev=449884
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/Environment.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/Environment.java Mon Sep 25 19:35:15 2006
@@ -0,0 +1,51 @@
+package org.apache.tapestry.services;
+
+/**
+ * Provides access to environment services, which are almost always provided to enclosed components
+ * by enclosing components.
+ * <p>
+ * The Environment acts like a collection of stacks. Each stack contains environmental service
+ * providers of a given type.
+ * 
+ * @author Howard M. Lewis Ship
+ */
+public interface Environment
+{
+    /**
+     * @param <T>
+     *            the type of environmental service
+     * @param type
+     *            class used to select a service
+     * @return the current service of that type, or null if no service of that type has been added
+     */
+    <T> T peek(Class<T> type);
+
+    /**
+     * Removes and returns the top environmental service of the selected type.
+     * 
+     * @param <T>
+     * @param type
+     * @return
+     * @throws NoSuchElementException
+     *             if the environmental stack (for the specified type) is empty
+     */
+    <T> T pop(Class<T> type);
+
+    /**
+     * Pushes a new service onto the stack. The old service at the top of the stack is returned (it
+     * may be null).
+     * 
+     * @param <T>
+     * @param type
+     *            the type of service to store
+     * @param instance
+     *            the service instance
+     * @return the previous top service
+     */
+    <T> T push(Class<T> type, T instance);
+
+    /**
+     * Clears all stacks; used when initializing the Environment before a render.
+     */
+    void clear();
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java?view=diff&rev=449884&r1=449883&r2=449884
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java Mon Sep 25 19:35:15 2006
@@ -43,6 +43,7 @@
 import org.apache.tapestry.internal.services.ComponentResourcesInjectionProvider;
 import org.apache.tapestry.internal.services.ComponentWorker;
 import org.apache.tapestry.internal.services.DefaultInjectionProvider;
+import org.apache.tapestry.internal.services.EnvironmentImpl;
 import org.apache.tapestry.internal.services.HTMLDispatcher;
 import org.apache.tapestry.internal.services.InfrastructureImpl;
 import org.apache.tapestry.internal.services.InfrastructureManagerImpl;
@@ -610,5 +611,38 @@
         CoercionTuple<S, T> tuple = new CoercionTuple<S, T>(sourceType, targetType, coercion);
 
         configuration.add(tuple);
+    }
+
+    @Lifecycle("perthread")
+    public Environment buildEnvironment()
+    {
+        return new EnvironmentImpl();
+    }
+
+    /**
+     * A service that acts primarily as a configuration point for render initializers (each
+     * implements Runnable). All of these initializers are invoked at the start of the page
+     * rendering process. The primary use of this is to initialize environmental services inside the
+     * {@link Environment}. The Environment is cleared before any of the contributions are
+     * executed.
+     * 
+     * @param configuration
+     * @param environment
+     * @return
+     */
+    public Runnable buildPageRenderInitializer(final Collection<Runnable> configuration,
+            @InjectService("Environment")
+            final Environment environment)
+    {
+        return new Runnable()
+        {
+            public void run()
+            {
+                environment.clear();
+
+                for (Runnable r : configuration)
+                    r.run();
+            }
+        };
     }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/site/resources/tap5devwiki.html
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/site/resources/tap5devwiki.html?view=diff&rev=449884&r1=449883&r2=449884
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/site/resources/tap5devwiki.html (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/site/resources/tap5devwiki.html Mon Sep 25 19:35:15 2006
@@ -5167,7 +5167,7 @@
 	<div id="contentStash"></div>
 	<div id="storeArea"><div tiddler="DynamicPageState" modifier="HowardLewisShip" modified="200609211635" created="200609211610" tags="">Tapestry 4 has left tracking of dynamic page state as an exercise to the developer.  Mostly, this is done using the ''parameters'' parameter of the ~DirectLink component.\n\nDynamic page state is anything that isn't inside a persistent page property. For the most part, this includes page properties updated by a For component\n\nIt seems likely that this information could be automatically encoded into ~URLs.  \n\nI'm envisioning a service that accumulates a series of //commands//. Each command is used to store a bit of page state. The commands are serializable.  The commands are ultimately serialized into a MIME string and attached as a query parameter to each URL.\n\nWhen such a link is triggered, the commands are de-serialized and each executed in turn. Only when that is finished is any further event processing executed, including calling in
 to to user code.\n\nMy outline for this is to store a series of tuples; each tuple is a component id plus the command to execute.\n\n{{{\npublic interface ComponentCommand&lt;T&gt;\n{\n  void execute(T component);\n}\n}}}\n\nThese commands should be immutable.\n\nSo a component, such as a For loop component, could provide itself and a ComponentCommand instance (probably a static inner class) to some kind of PageStateTracker service.\n\n{{{\npublic interface PageStateTracker\n{\n  void &lt;T&gt; addCommand(T component, ComponentCommand&lt;T&gt; command);\n}\n}}}\n\nThe commands are kept in the order that they are added, except that new commands for the same component //replace// previous commands for that component.\n\nAs with the Tapestry 4 For component, some mechanism will be needed to store object ids inside the URLs (that is, inside the commands serialized into URL query parameters) and translate back to //equivalent// objects when the link is triggered.\n\nDynamic page 
 state outside of a Form will overlap with some of the FormProcessing inside the form.</div>
 <div tiddler="EditTemplate" modifier="HowardLewisShip" modified="200609210649" created="200609210648" tags="">&lt;div class='toolbar' macro='toolbar +saveTiddler -cancelTiddler deleteTiddler'&gt;&lt;/div&gt;\n&lt;div class='title' macro='view title'&gt;&lt;/div&gt;\n&lt;div class='editor' macro='edit title'&gt;&lt;/div&gt;\n&lt;div class='editor' macro='edit text'&gt;&lt;/div&gt;\n&lt;div class='editor' macro='edit tags'&gt;&lt;/div&gt;&lt;div class='editorFooter'&gt;&lt;span macro='message views.editor.tagPrompt'&gt;&lt;/span&gt;&lt;span macro='tagChooser'&gt;&lt;/span&gt;&lt;/div&gt;</div>
-<div tiddler="EnvironmentalServices" modifier="HowardLewisShip" modified="200609251626" created="200609251547" tags="">Frequently, different components need to //cooperate// during the rendering process.\n\nThis is an established pattern from Tapestry 4, which an enclosing component provides services to the components it encloses. By //encloses// we mean, any components that are rendered as part of the Form's body; give the use of the Block/~RenderBlock components, this can not be determined statically, but is instead determined dynamically, as part of the rendering process.\n\nThe canoncial example of this pattern is Form component, and the complex relationship it has with each form element component it encloses.\n\nIn Tapestry 4, this mechanism was based on the ~IRequestCycle which could store named attributes. The service providing component would store itself into the cycle using a well known name, and service consuming components would retrieve the service using the sam
 e well known name.\n\nFor Tapestry 5, this will be formalized. A new service will be used to manage this information:\n\n{{{\npublic interface Enviroment\n\n  &lt;T&gt; T push(Class&lt;T&gt; type, T instance);\n\n  &lt;T&gt; peek(Class&lt;T&gt; type);\n\n  &lt;T&gt; void pop(Class&lt;T&gt; type);\n}\n}}}\n\n</div>
+<div tiddler="EnvironmentalServices" modifier="HowardLewisShip" modified="200609260145" created="200609251547" tags="">Frequently, different components need to //cooperate// during the rendering process.\n\nThis is an established pattern from Tapestry 4, which an enclosing component provides services to the components it encloses. By //encloses// we mean, any components that are rendered as part of the Form's body; give the use of the Block/~RenderBlock components, this can not be determined statically, but is instead determined dynamically, as part of the rendering process.\n\nThe canoncial example of this pattern is Form component, and the complex relationship it has with each form element component it encloses.\n\nIn Tapestry 4, this mechanism was based on the ~IRequestCycle which could store named attributes. The service providing component would store itself into the cycle using a well known name, and service consuming components would retrieve the service using the sam
 e well known name.\n\nFor Tapestry 5, this will be formalized. A new service will be used to manage this information:\n\n{{{\npublic interface Enviroment\n{\n  &lt;T&gt; T push(Class&lt;T&gt; type, T instance);\n\n  &lt;T&gt; peek(Class&lt;T&gt; type);\n\n  &lt;T&gt; T pop(Class&lt;T&gt; type);\n}\n}}}\n\nThe Environment is unique to a request.</div>
 <div tiddler="FormProcessing" modifier="HowardLewisShip" modified="200609211540" created="200609210203" tags="forms">Form processing in Tapestry 4 had certain strengths and limitations.\n\nBasically, any action framework that can do a simple mapping from query parameters to bean property names has advantages in terms of simple forms, and Tapestry 4's approach has huge advantages on more complex forms (with some considerable developer and framework overhead).\n\nWith a direct mapping of query parameter names to bean names, each query parameter becomes self describing. You map query parameters to property of some well known bean. You do simple conversions from strings to other types (typically, ints and dates and the like). You drop query parameters that don't match up. You leave a lot of validation and other plumbing (such as getting those values into your DataTransferObjects) to the developer.\n\nBut you never see a ~StaleLinkException.\n\nYou also have some unwanted loophol
 es in your application in that //any// property can be updated through the URL. This is //one step// towards a security hole.\n\n!Tapestry 4 Approach\n\nEvery form component, as it renders, asks the Form that encloses it to provide a client id.  The terminology is a little messed; client id is the unique (within the form) name for //one rendering// of the component. If the component renders multiple times, because of loops, each rendering gets a unique name.  This becomes the &lt;input&gt;'s name attribute, and ultimately, the query parameter name.\n\nTapestry attempts to make the client id match the (user provided) component id. This is not always possible, especially in a loop, in which case a numeric suffix may be appended to the id to (help) ensure uniqueness.\n\nOn render, a sequence of //component activations// occur, guided by the normal render sequence. The exact sequence of activations guides\nthe production of client ids.\n\nUsing more advanced Tapestry techniques,
  including loops, conditionals and the Block/RenderBlock combo, the exact set of components and\ncomponent activations that will occur for a given rendering of a given form can not be predicted statically. Tapestry must actually render out the form\nto discover all of these.\n\nIn fact, while the Form component is producing this series of client ids, it builds up the list and stores it into the rendered page as a hidden form field. It will need it later, when the client-side form is submitted back to the server.\n\nAn advantage of this approach is the disconnect between the query parameter names (the client ids) and the objects and properties being editted. Often the client ids will be //mneumonic// for the properties, but aren't directly mapped to them. Only the components responsible for each query parameter know how to validate the submitted value, and what property of which object will need to be updated.\n\nWhen a form submission occurs, we want to ensure that each quer
 y parameter value read out of the request is applied to the correct property of the correct object. There's a limit to how much Tapestry can help here (because it has only a casual knowledge of this aspect of the application structure).\n\nDuring this submission process, which endded up with the curious name, //rewind phase//, Tapestry must do two things:\n* Activate each component, such that the component may re-determine its client id, read its parameter, and update its page property\n* Validate that the process has not been comprimised by a change of server side state\n\nThat second element is a tricky one; things can go wonky if a race condition occurs between two users. For example, lets take a simple invoice and line item model. If users A and B both read the same invoice, user A adds a line item, and user B changes a line item ... we can have a problem when user B submits the form. Now that there are three line items (not two) in the form, there will be extra componen
 t activations to process query parameters that don't exist in the request. \n\nThis scenario can occur whenever the processing of the form submission is driven by server-side data that can change between request.\n\nTapestry detects this as a difference in the sequence of client ids allocated, and throws a ~StaleLinkException, which is very frustrating for developers to comprehend and fix.\n\nThere are also other edge cases for different race conditions where data is applied to the wrong server-side objects.\n\nThe Tapestry 3 ~ListEdit component, which evolved into the  Tapestry 4 For component, attempts to address this by serializing a series of //object ids// into the form (as a series of hidden fields). This requires a bit of work on the part of the developer to provide an ~IPrimaryKeyConverter that can help convert objects to ids (when rendering) and ids back to objects (during form submission).\n\nGenerally speaking, the Tapestry 4 approach represents layers of kludge o
 n layers of kludge. It works, it gets the job done, it can handle some very complex situations, but it is less than ideal.\n\n!Tapestry 5\n\nThe goal here is to capture the series of //component activations//, along with any significant page state changes, during the render.\n\nThese activations will be a series of //commands//.  For each component activation there will be two commands:  the first command will be used to inform the component of its client id (this command executes during render and during form submission). The second command will request that the client handle the form submission (this command executes only during form submission).\n\nThe serialized series of commands is stored as a hidden form field.\n\nThere's a lot of API to be figured out, especially the relationship between the form components and the form itself.\n\nFurther, a lot of what the Tapestry 4 For component does, in terms of serializing dynamic page state, will need to fold into this as well.
 \n\nThe end result will be a single hidden field with a big MIME string inside it ... but compared to the Tapestry 4 Form component (which has to write out many hidden fields) the whole will be less than the sum of the parts ... due to the overhead of serialization and gzip compression.\n\n\n\n\n\n\n</div>
 <div tiddler="MainMenu" modifier="HowardLewisShip" modified="200609210701" created="200609210643" tags="">MasterIndex\n[[RSS feed|tap5devwiki.xml]]\n\n[[Tapestry 5 Home|http://tapestry.apache.org/tapestry5/]]\n[[Howard's Blog|http://howardlewisship.com/blog/]]\n\n[[Formatting Help|http://www.blogjones.com/TiddlyWikiTutorial.html#EasyToEdit%20Welcome%20NewFeatures%20WhereToFindHelp]]</div>
 <div tiddler="MasterIndex" modifier="HowardLewisShip" modified="200609251541" created="200609202214" tags="">* PropBinding -- Notes on the workhorse &quot;prop:&quot; binding prefix\n* TypeCoercion -- How Tapestry 5 extensibly addresses type conversion\n* FormProcessing\n* DynamicPageState -- tracking changes to page state during the render\n* EnvironmentalServices -- how components cooperate during page render</div>

Modified: tapestry/tapestry5/tapestry-core/trunk/src/site/resources/tap5devwiki.xml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/site/resources/tap5devwiki.xml?view=diff&rev=449884&r1=449883&r2=449884
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/site/resources/tap5devwiki.xml (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/site/resources/tap5devwiki.xml Mon Sep 25 19:35:15 2006
@@ -6,21 +6,28 @@
 <description>The quick and dirty one-stop shopping of random ideas for Tapestry 5.</description>
 <language>en-us</language>
 <copyright>Copyright 2006 HowardLewisShip</copyright>
-<pubDate>Mon, 25 Sep 2006 16:26:08 GMT</pubDate>
-<lastBuildDate>Mon, 25 Sep 2006 16:26:08 GMT</lastBuildDate>
+<pubDate>Tue, 26 Sep 2006 01:45:14 GMT</pubDate>
+<lastBuildDate>Tue, 26 Sep 2006 01:45:14 GMT</lastBuildDate>
 <docs>http://blogs.law.harvard.edu/tech/rss</docs>
 <generator>TiddlyWiki 2.0.11</generator>
 <item>
 <title>EnvironmentalServices</title>
-<description>Frequently, different components need to //cooperate// during the rendering process.&lt;br /&gt;&lt;br /&gt;This is an established pattern from Tapestry 4, which an enclosing component provides services to the components it encloses. By //encloses// we mean, any components that are rendered as part of the Form's body; give the use of the Block/~RenderBlock components, this can not be determined statically, but is instead determined dynamically, as part of the rendering process.&lt;br /&gt;&lt;br /&gt;The canoncial example of this pattern is Form component, and the complex relationship it has with each form element component it encloses.&lt;br /&gt;&lt;br /&gt;In Tapestry 4, this mechanism was based on the ~IRequestCycle which could store named attributes. The service providing component would store itself into the cycle using a well known name, and service consuming components would retrieve the service using the same well known name.&lt;br /&gt;&lt;br /&gt;For 
 Tapestry 5, this will be formalized. A new service will be used to manage this information:&lt;br /&gt;&lt;br /&gt;{{{&lt;br /&gt;public interface Enviroment&lt;br /&gt;&lt;br /&gt;  &lt;T&gt; T push(Class&lt;T&gt; type, T instance);&lt;br /&gt;&lt;br /&gt;  &lt;T&gt; peek(Class&lt;T&gt; type);&lt;br /&gt;&lt;br /&gt;  &lt;T&gt; void pop(Class&lt;T&gt; type);&lt;br /&gt;}&lt;br /&gt;}}}&lt;br /&gt;&lt;br /&gt;</description>
+<description>Frequently, different components need to //cooperate// during the rendering process.&lt;br /&gt;&lt;br /&gt;This is an established pattern from Tapestry 4, which an enclosing component provides services to the components it encloses. By //encloses// we mean, any components that are rendered as part of the Form's body; give the use of the Block/~RenderBlock components, this can not be determined statically, but is instead determined dynamically, as part of the rendering process.&lt;br /&gt;&lt;br /&gt;The canoncial example of this pattern is Form component, and the complex relationship it has with each form element component it encloses.&lt;br /&gt;&lt;br /&gt;In Tapestry 4, this mechanism was based on the ~IRequestCycle which could store named attributes. The service providing component would store itself into the cycle using a well known name, and service consuming components would retrieve the service using the same well known name.&lt;br /&gt;&lt;br /&gt;For 
 Tapestry 5, this will be formalized. A new service will be used to manage this information:&lt;br /&gt;&lt;br /&gt;{{{&lt;br /&gt;public interface Enviroment&lt;br /&gt;{&lt;br /&gt;  &lt;T&gt; T push(Class&lt;T&gt; type, T instance);&lt;br /&gt;&lt;br /&gt;  &lt;T&gt; peek(Class&lt;T&gt; type);&lt;br /&gt;&lt;br /&gt;  &lt;T&gt; T pop(Class&lt;T&gt; type);&lt;br /&gt;}&lt;br /&gt;}}}&lt;br /&gt;&lt;br /&gt;The Environment is unique to a request.</description>
 <link>http://tapestry.apache.org/tapestry5/tap5devwiki.html#EnvironmentalServices</link>
-<pubDate>Mon, 25 Sep 2006 16:26:08 GMT</pubDate>
+<pubDate>Tue, 26 Sep 2006 01:45:14 GMT</pubDate>
 </item>
 <item>
 <title>MasterIndex</title>
 <description>* PropBinding -- Notes on the workhorse &quot;prop:&quot; binding prefix&lt;br /&gt;* TypeCoercion -- How Tapestry 5 extensibly addresses type conversion&lt;br /&gt;* FormProcessing&lt;br /&gt;* DynamicPageState -- tracking changes to page state during the render&lt;br /&gt;* EnvironmentalServices -- how components cooperate during page render</description>
 <link>http://tapestry.apache.org/tapestry5/tap5devwiki.html#MasterIndex</link>
-<pubDate>Mon, 25 Sep 2006 15:41:20 GMT</pubDate>
+<pubDate>Mon, 25 Sep 2006 15:41:00 GMT</pubDate>
+</item>
+<item>
+<title>PropBinding</title>
+<description>&quot;prop:&quot; the default in a  lot of cases, i.e., in any Java code.&lt;br /&gt;&lt;br /&gt;This binding prefix  supports several common idioms even though they are not, precisely, the names of properties.  In many cases, this will save developers the bother of using a &quot;literal:&quot; prefix.&lt;br /&gt;&lt;br /&gt;The goal of the &quot;prop:&quot; prefix is to be highly efficient and useful in 90%+ of the cases. [[OGNL]], or synthetic properties in the component class, will pick up the remaining cases.&lt;br /&gt;&lt;br /&gt;!Numeric literals&lt;br /&gt;&lt;br /&gt;Simple numeric literals should be parsed into read-only, invariant bindings.&lt;br /&gt;{{{&lt;br /&gt;prop:5&lt;br /&gt;&lt;br /&gt;prop:-22.7&lt;br /&gt;}}}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The resulting objects will be of type Long or type Double. TypeCoercion will ensure that component parameters get values (say, int or float) of the correct type.&lt;br /&gt;&lt;br /&gt;!Boolean liter
 als&lt;br /&gt;&lt;br /&gt;&quot;true&quot; and &quot;false&quot; should also be converted to invariant bindings.&lt;br /&gt;{{{&lt;br /&gt;prop:true&lt;br /&gt;&lt;br /&gt;prop:false&lt;br /&gt;}}}&lt;br /&gt;&lt;br /&gt;!String literals&lt;br /&gt;&lt;br /&gt;//Simple// string literals, enclosed in single quotes.  Example:&lt;br /&gt;{{{&lt;br /&gt;prop:'Hello World'&lt;br /&gt;}}}&lt;br /&gt;&lt;br /&gt;//Remember that the binding expression will always be enclosed in double quotes.//&lt;br /&gt;&lt;br /&gt;!This literal&lt;br /&gt;&lt;br /&gt;In some cases, it is useful to be able to identify the current component:&lt;br /&gt;{{{&lt;br /&gt;prop:this&lt;br /&gt;}}}&lt;br /&gt;&lt;br /&gt;Even though a component is not immutable, the value of //this// does not ever change,&lt;br /&gt;and this binding is also invariant.&lt;br /&gt;&lt;br /&gt;!Null literal&lt;br /&gt;&lt;br /&gt;{{{&lt;br /&gt;prop:null&lt;br /&gt;}}}&lt;br /&gt;&lt;br /&gt;This value is always exactly nul
 l. This can be used to set a parameter who'se default value is non-null to the explicit value null.&lt;br /&gt;&lt;br /&gt;!Property paths&lt;br /&gt;&lt;br /&gt;Multi-step property paths are extremely important.&lt;br /&gt;&lt;br /&gt;{{{&lt;br /&gt;prop:poll.title&lt;br /&gt;&lt;br /&gt;prop:identity.user.name&lt;br /&gt;}}}&lt;br /&gt;&lt;br /&gt;The initial terms need to be readable, they are never updated. Only the final property name must be read/write, and in fact, it is valid to be read-only or write-only.&lt;br /&gt;&lt;br /&gt;The prop: binding factory builds a Java expression to read and update properties. It does not use reflection at runtime. Therefore, the properties of the //declared// type are used. By contrast, [[OGNL]] uses the //actual// type, which is reflection-intensive.&lt;br /&gt;&lt;br /&gt;''Currently, only simple properties, not property paths, are implemented.''&lt;br /&gt;</description>
+<category>bindings</category>
+<link>http://tapestry.apache.org/tapestry5/tap5devwiki.html#PropBinding</link>
+<pubDate>Sat, 23 Sep 2006 22:52:00 GMT</pubDate>
 </item>
 <item>
 <title>TypeCoercion</title>
@@ -31,13 +38,6 @@
 <pubDate>Sat, 23 Sep 2006 22:52:00 GMT</pubDate>
 </item>
 <item>
-<title>PropBinding</title>
-<description>&quot;prop:&quot; the default in a  lot of cases, i.e., in any Java code.&lt;br /&gt;&lt;br /&gt;This binding prefix  supports several common idioms even though they are not, precisely, the names of properties.  In many cases, this will save developers the bother of using a &quot;literal:&quot; prefix.&lt;br /&gt;&lt;br /&gt;The goal of the &quot;prop:&quot; prefix is to be highly efficient and useful in 90%+ of the cases. [[OGNL]], or synthetic properties in the component class, will pick up the remaining cases.&lt;br /&gt;&lt;br /&gt;!Numeric literals&lt;br /&gt;&lt;br /&gt;Simple numeric literals should be parsed into read-only, invariant bindings.&lt;br /&gt;{{{&lt;br /&gt;prop:5&lt;br /&gt;&lt;br /&gt;prop:-22.7&lt;br /&gt;}}}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The resulting objects will be of type Long or type Double. TypeCoercion will ensure that component parameters get values (say, int or float) of the correct type.&lt;br /&gt;&lt;br /&gt;!Boolean liter
 als&lt;br /&gt;&lt;br /&gt;&quot;true&quot; and &quot;false&quot; should also be converted to invariant bindings.&lt;br /&gt;{{{&lt;br /&gt;prop:true&lt;br /&gt;&lt;br /&gt;prop:false&lt;br /&gt;}}}&lt;br /&gt;&lt;br /&gt;!String literals&lt;br /&gt;&lt;br /&gt;//Simple// string literals, enclosed in single quotes.  Example:&lt;br /&gt;{{{&lt;br /&gt;prop:'Hello World'&lt;br /&gt;}}}&lt;br /&gt;&lt;br /&gt;//Remember that the binding expression will always be enclosed in double quotes.//&lt;br /&gt;&lt;br /&gt;!This literal&lt;br /&gt;&lt;br /&gt;In some cases, it is useful to be able to identify the current component:&lt;br /&gt;{{{&lt;br /&gt;prop:this&lt;br /&gt;}}}&lt;br /&gt;&lt;br /&gt;Even though a component is not immutable, the value of //this// does not ever change,&lt;br /&gt;and this binding is also invariant.&lt;br /&gt;&lt;br /&gt;!Null literal&lt;br /&gt;&lt;br /&gt;{{{&lt;br /&gt;prop:null&lt;br /&gt;}}}&lt;br /&gt;&lt;br /&gt;This value is always exactly nul
 l. This can be used to set a parameter who'se default value is non-null to the explicit value null.&lt;br /&gt;&lt;br /&gt;!Property paths&lt;br /&gt;&lt;br /&gt;Multi-step property paths are extremely important.&lt;br /&gt;&lt;br /&gt;{{{&lt;br /&gt;prop:poll.title&lt;br /&gt;&lt;br /&gt;prop:identity.user.name&lt;br /&gt;}}}&lt;br /&gt;&lt;br /&gt;The initial terms need to be readable, they are never updated. Only the final property name must be read/write, and in fact, it is valid to be read-only or write-only.&lt;br /&gt;&lt;br /&gt;The prop: binding factory builds a Java expression to read and update properties. It does not use reflection at runtime. Therefore, the properties of the //declared// type are used. By contrast, [[OGNL]] uses the //actual// type, which is reflection-intensive.&lt;br /&gt;&lt;br /&gt;''Currently, only simple properties, not property paths, are implemented.''&lt;br /&gt;</description>
-<category>bindings</category>
-<link>http://tapestry.apache.org/tapestry5/tap5devwiki.html#PropBinding</link>
-<pubDate>Sat, 23 Sep 2006 22:52:00 GMT</pubDate>
-</item>
-<item>
 <title>DynamicPageState</title>
 <description>Tapestry 4 has left tracking of dynamic page state as an exercise to the developer.  Mostly, this is done using the ''parameters'' parameter of the ~DirectLink component.&lt;br /&gt;&lt;br /&gt;Dynamic page state is anything that isn't inside a persistent page property. For the most part, this includes page properties updated by a For component&lt;br /&gt;&lt;br /&gt;It seems likely that this information could be automatically encoded into ~URLs.  &lt;br /&gt;&lt;br /&gt;I'm envisioning a service that accumulates a series of //commands//. Each command is used to store a bit of page state. The commands are serializable.  The commands are ultimately serialized into a MIME string and attached as a query parameter to each URL.&lt;br /&gt;&lt;br /&gt;When such a link is triggered, the commands are de-serialized and each executed in turn. Only when that is finished is any further event processing executed, including calling into to user code.&lt;br /&gt;&lt;br /&gt;My
  outline for this is to store a series of tuples; each tuple is a component id plus the command to execute.&lt;br /&gt;&lt;br /&gt;{{{&lt;br /&gt;public interface ComponentCommand&lt;T&gt;&lt;br /&gt;{&lt;br /&gt;  void execute(T component);&lt;br /&gt;}&lt;br /&gt;}}}&lt;br /&gt;&lt;br /&gt;These commands should be immutable.&lt;br /&gt;&lt;br /&gt;So a component, such as a For loop component, could provide itself and a ComponentCommand instance (probably a static inner class) to some kind of PageStateTracker service.&lt;br /&gt;&lt;br /&gt;{{{&lt;br /&gt;public interface PageStateTracker&lt;br /&gt;{&lt;br /&gt;  void &lt;T&gt; addCommand(T component, ComponentCommand&lt;T&gt; command);&lt;br /&gt;}&lt;br /&gt;}}}&lt;br /&gt;&lt;br /&gt;The commands are kept in the order that they are added, except that new commands for the same component //replace// previous commands for that component.&lt;br /&gt;&lt;br /&gt;As with the Tapestry 4 For component, some mechanism will be ne
 eded to store object ids inside the URLs (that is, inside the commands serialized into URL query parameters) and translate back to //equivalent// objects when the link is triggered.&lt;br /&gt;&lt;br /&gt;Dynamic page state outside of a Form will overlap with some of the FormProcessing inside the form.</description>
 <link>http://tapestry.apache.org/tapestry5/tap5devwiki.html#DynamicPageState</link>
@@ -87,15 +87,15 @@
 <pubDate>Wed, 20 Sep 2006 22:55:00 GMT</pubDate>
 </item>
 <item>
-<title>SiteTitle</title>
-<description>Tapestry 5 Brain Dump</description>
-<link>http://tapestry.apache.org/tapestry5/tap5devwiki.html#SiteTitle</link>
-<pubDate>Wed, 20 Sep 2006 22:49:00 GMT</pubDate>
-</item>
-<item>
 <title>SiteSubtitle</title>
 <description>&lt;br /&gt;The quick and dirty one-stop shopping of random ideas for Tapestry 5.</description>
 <link>http://tapestry.apache.org/tapestry5/tap5devwiki.html#SiteSubtitle</link>
+<pubDate>Wed, 20 Sep 2006 22:49:00 GMT</pubDate>
+</item>
+<item>
+<title>SiteTitle</title>
+<description>Tapestry 5 Brain Dump</description>
+<link>http://tapestry.apache.org/tapestry5/tap5devwiki.html#SiteTitle</link>
 <pubDate>Wed, 20 Sep 2006 22:49:00 GMT</pubDate>
 </item>
 </channel>

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/EnvironmentImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/EnvironmentImplTest.java?view=auto&rev=449884
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/EnvironmentImplTest.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/EnvironmentImplTest.java Mon Sep 25 19:35:15 2006
@@ -0,0 +1,80 @@
+package org.apache.tapestry.internal.services;
+
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+import org.apache.tapestry.services.Environment;
+import org.apache.tapestry.test.BaseTestCase;
+import org.testng.annotations.Test;
+
+/**
+ * @author Howard M. Lewis Ship
+ */
+public class EnvironmentImplTest extends BaseTestCase
+{
+    @Test
+    public void peek_when_empty_returns_null()
+    {
+        Environment e = new EnvironmentImpl();
+
+        assertNull(e.peek(Runnable.class));
+        assertNull(e.peek(Map.class));
+    }
+
+    @Test
+    public void push_and_pop()
+    {
+        Environment e = new EnvironmentImpl();
+        Runnable r1 = newRunnable();
+        Runnable r2 = newRunnable();
+
+        replay();
+
+        assertNull(e.push(Runnable.class, r1));
+
+        assertSame(r1, e.peek(Runnable.class));
+
+        assertSame(r1, e.push(Runnable.class, r2));
+
+        assertSame(r2, e.peek(Runnable.class));
+
+        assertSame(r2, e.pop(Runnable.class));
+        assertSame(r1, e.pop(Runnable.class));
+
+        verify();
+    }
+
+    @Test
+    public void clear()
+    {
+        Environment e = new EnvironmentImpl();
+        Runnable r1 = newRunnable();
+        Runnable r2 = newRunnable();
+
+        replay();
+
+        e.push(Runnable.class, r1);
+        e.push(Runnable.class, r2);
+
+        e.clear();
+
+        assertNull(e.peek(Runnable.class));
+
+        verify();
+    }
+
+    @Test
+    public void pop_when_empty_is_error()
+    {
+        Environment e = new EnvironmentImpl();
+
+        try
+        {
+            e.pop(Runnable.class);
+            unreachable();
+        }
+        catch (NoSuchElementException ex)
+        {
+        }
+    }
+}