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 2006/09/21 20:40:02 UTC

svn commit: r448627 - in /tapestry/tapestry5/tapestry-core/trunk/src: main/java/org/apache/tapestry/internal/bindings/ main/java/org/apache/tapestry/internal/services/ site/apt/guide/ site/resources/ test/java/org/apache/tapestry/integration/app1/compo...

Author: hlship
Date: Thu Sep 21 11:40:01 2006
New Revision: 448627

URL: http://svn.apache.org/viewvc?view=rev&rev=448627
Log:
Allow the prop: binding factory to process a number of common useful literals true", "false", "this", and "null" as well as simple numeric and string literals.

Added:
    tapestry/tapestry5/tapestry-core/trunk/src/site/resources/tap5devwiki.xml
Modified:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/LiteralBinding.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalModule.java
    tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/component-classes.apt
    tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/parameters.apt
    tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/rendering.apt
    tapestry/tapestry5/tapestry-core/trunk/src/site/resources/tap5devwiki.html
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/components/Loop.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/PropBindingFactoryTest.java

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/LiteralBinding.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/LiteralBinding.java?view=diff&rev=448627&r1=448626&r2=448627
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/LiteralBinding.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/LiteralBinding.java Thu Sep 21 11:40:01 2006
@@ -27,9 +27,9 @@
 {
     private final String _description;
 
-    private final String _value;
+    private final Object _value;
 
-    public LiteralBinding(String description, String value, Location location)
+    public LiteralBinding(String description, Object value, Location location)
     {
         super(location);
         _description = description;

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=448627&r1=448626&r2=448627
==============================================================================
--- 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 Thu Sep 21 11:40:01 2006
@@ -14,10 +14,22 @@
 
 package org.apache.tapestry.internal.services;
 
+import static org.apache.tapestry.util.CollectionFactory.newMap;
+
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 import org.apache.commons.logging.Log;
+import org.apache.tapestry.Binding;
+import org.apache.tapestry.ComponentResources;
+import org.apache.tapestry.Location;
 import org.apache.tapestry.events.InvalidationEvent;
 import org.apache.tapestry.events.InvalidationListener;
 import org.apache.tapestry.internal.InternalConstants;
+import org.apache.tapestry.internal.annotations.SuppressNullCheck;
+import org.apache.tapestry.internal.bindings.LiteralBinding;
 import org.apache.tapestry.internal.bindings.PropBindingFactory;
 import org.apache.tapestry.ioc.LogSource;
 import org.apache.tapestry.ioc.OrderedConfiguration;
@@ -27,6 +39,7 @@
 import org.apache.tapestry.ioc.annotations.Lifecycle;
 import org.apache.tapestry.ioc.annotations.Match;
 import org.apache.tapestry.ioc.annotations.Order;
+import org.apache.tapestry.ioc.services.ChainBuilder;
 import org.apache.tapestry.ioc.services.ClassFactory;
 import org.apache.tapestry.ioc.services.LoggingDecorator;
 import org.apache.tapestry.ioc.services.PropertyAccess;
@@ -57,19 +70,24 @@
 
     private final ComponentClassResolver _componentClassResolver;
 
+    private final ChainBuilder _chainBuilder;
+
     public InternalModule(@InjectService("ComponentInstantiatorSource")
     ComponentInstantiatorSource componentInstantiatorSource, @InjectService("UpdateListenerHub")
     UpdateListenerHub updateListenerHub, @InjectService("tapestry.ioc.ThreadCleanupHub")
     ThreadCleanupHub threadCleanupHub, @InjectService("ComponentTemplateSource")
     ComponentTemplateSource componentTemplateSource,
             @InjectService("tapestry.ComponentClassResolver")
-            ComponentClassResolver componentClassResolver)
+            ComponentClassResolver componentClassResolver,
+            @InjectService("tapestry.ioc.ChainBuilder")
+            ChainBuilder chainBuilder)
     {
         _componentInstantiatorSource = componentInstantiatorSource;
         _updateListenerHub = updateListenerHub;
         _threadCleanupHub = threadCleanupHub;
         _componentTemplateSource = componentTemplateSource;
         _componentClassResolver = componentClassResolver;
+        _chainBuilder = chainBuilder;
     }
 
     public ComponentClassTransformer buildComponentClassTransformer(
@@ -256,15 +274,144 @@
         configuration.add("ClearPropertyAccessCacheOnInvalidation", clearPropertyAccess);
     }
 
-    public BindingFactory buildPropBindingFactory(@InjectService("tapestry.ioc.PropertyAccess")
-    PropertyAccess propertyAccess, @InjectService("tapestry.ComponentClassFactory")
-    ClassFactory classFactory)
+    /**
+     * Builds the PropBindingFactory as a chain of command. The terminator of the chain is
+     * responsible for ordinary property names (and property paths). Contributions to the service
+     * cover additional special cases, such as simple literal values.
+     * 
+     * @param configuration
+     *            contributions of special factories for some constants, each contributed factory
+     *            may return a binding if applicable, or null otherwise
+     * @param propertyAccess
+     * @param classFactory
+     * @return
+     */
+    public BindingFactory buildPropBindingFactory(List<BindingFactory> configuration,
+            @InjectService("tapestry.ioc.PropertyAccess")
+            PropertyAccess propertyAccess, @InjectService("tapestry.ComponentClassFactory")
+            ClassFactory classFactory)
     {
         PropBindingFactory service = new PropBindingFactory(propertyAccess, classFactory);
 
         _componentInstantiatorSource.addInvalidationListener(service);
 
-        return service;
+        configuration.add(service);
+
+        return _chainBuilder.build(BindingFactory.class, configuration);
     }
 
+    public void contributePropBindingFactory(OrderedConfiguration<BindingFactory> configuration)
+    {
+
+        BindingFactory keywordFactory = new BindingFactory()
+        {
+            private final Map<String, Object> _keywords = newMap();
+
+            {
+                _keywords.put("true", Boolean.TRUE);
+                _keywords.put("false", Boolean.FALSE);
+                _keywords.put("null", null);
+            }
+
+            @SuppressNullCheck
+            public Binding newBinding(String description, ComponentResources component,
+                    String expression, Location location)
+            {
+                String key = expression.trim().toLowerCase();
+
+                if (_keywords.containsKey(key))
+                    return new LiteralBinding(description, _keywords.get(key), location);
+
+                return null;
+            }
+        };
+
+        BindingFactory thisFactory = new BindingFactory()
+        {
+            @SuppressNullCheck
+            public Binding newBinding(String description, ComponentResources component,
+                    String expression, Location location)
+            {
+                if ("this".equalsIgnoreCase(expression.trim()))
+                    return new LiteralBinding(description, component.getComponent(), location);
+
+                return null;
+            }
+        };
+
+        BindingFactory longFactory = new BindingFactory()
+        {
+            private final Pattern _pattern = Pattern.compile("^\\s*(-?\\d+)\\s*$");
+
+            @SuppressNullCheck
+            public Binding newBinding(String description, ComponentResources component,
+                    String expression, Location location)
+            {
+                Matcher matcher = _pattern.matcher(expression);
+
+                if (matcher.matches())
+                {
+                    String value = matcher.group(1);
+
+                    return new LiteralBinding(description, new Long(value), location);
+                }
+
+                return null;
+            }
+        };
+
+        BindingFactory doubleFactory = new BindingFactory()
+        {
+            // So, either 1234. or 1234.56 or .78
+            private final Pattern _pattern = Pattern
+                    .compile("^\\s*(\\-?((\\d+\\.)|(\\d*\\.\\d+)))\\s*$");
+
+            @SuppressNullCheck
+            public Binding newBinding(String description, ComponentResources component,
+                    String expression, Location location)
+            {
+                Matcher matcher = _pattern.matcher(expression);
+
+                if (matcher.matches())
+                {
+                    String value = matcher.group(1);
+
+                    return new LiteralBinding(description, new Double(value), location);
+                }
+
+                return null;
+            }
+        };
+
+        BindingFactory stringFactory = new BindingFactory()
+        {
+            // This will match embedded single quotes as-is, no escaping necessary.
+            
+            private final Pattern _pattern = Pattern.compile("^\\s*'(.*)'\\s*$");
+
+            @SuppressNullCheck
+            public Binding newBinding(String description, ComponentResources component,
+                    String expression, Location location)
+            {
+                Matcher matcher = _pattern.matcher(expression);
+
+                if (matcher.matches())
+                {
+                    String value = matcher.group(1);
+
+                    return new LiteralBinding(description, value, location);
+                }
+
+                return null;
+            }
+        };
+
+        // Do be honest, order probably doesn't matter.
+        
+        configuration.add("Keyword", keywordFactory);
+        configuration.add("This", thisFactory);
+        configuration.add("Long", longFactory);
+        configuration.add("Double", doubleFactory);
+        configuration.add("StringLiteral", stringFactory);
+    }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/component-classes.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/component-classes.apt?view=diff&rev=448627&r1=448626&r2=448627
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/component-classes.apt (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/component-classes.apt Thu Sep 21 11:40:01 2006
@@ -147,7 +147,7 @@
 public class Countdown
 {
     @Component(parameters =
-    { "start=literal:5", "end=literal:1", "value=count" })
+    { "start=5", "end=1", "value=count" })
     private Loop _loop;  
   
     private int _count;
@@ -169,8 +169,13 @@
   parameter of the Loop component is bound to the count property of the Countdown component.
   
   Note that inside the component class, the default binding prefix is "prop:", whereas inside a component template,
-  the default binding prefix is "literal:".  To use literal values, you must prefix them, to keep Tapestry from looking
-  for a property named "5" or a property named "1".
+  the default binding prefix is "literal:".  In the example we could write <<<"value=prop:count">>> if we desired to be
+  fully explicit.
+  
+  However, certain literal values, such as the numeric literals in the example,
+  are accepted by the prop: binding prefix even though they are not actually properties (this is largely as a convienience
+  to the application developer).  We could also use the "literal:" prefix, <<<"start=literal:5">>> which accomplishes
+  largely the same thing.
   
   You may specify additional parameters inside the component template, but parameters in the component class
   take precendence.  

Modified: tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/parameters.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/parameters.apt?view=diff&rev=448627&r1=448626&r2=448627
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/parameters.apt (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/parameters.apt Thu Sep 21 11:40:01 2006
@@ -21,7 +21,7 @@
   it will automatically count up or down depending on whether start or end is larger.
   
 +---+
-package org.apache.tapestry.integration.app1.components;
+package org.example.myapp.components;
 
 import org.apache.tapestry.annotations.AfterRender;
 import org.apache.tapestry.annotations.ComponentClass;
@@ -45,16 +45,9 @@
     @SetupRender
     void initializeValue()
     {
-        if (_start < _end)
-        {
-            _value = _start;
-            _increment = true;
-        }
-        else
-        {
-            _value = _start;
-            _increment = false;
-        }
+        _value = _start;
+
+        _increment = _start < _end;
     }
 
     @AfterRender
@@ -134,6 +127,39 @@
 
   <<Note: More prefixes are forthcoming.>>
     
+Property Bindings
+
+  The "prop:" prefix indicates a property binding.
+  
+  Property bindings are normally either simple names of properties ("prop:userName")
+  or paths to properties ("prop:user.address.city").
+  
+  <<Note: Property paths are not yet implemented.>>    
+  
+  In addition, a few special cases are also supported. These are especially useful inside
+  Java annotations, since prop: is the default binding prefix outside the component template.
+  In most cases, these special values save you the trouble of adding a "literal:" prefix to
+  the value.
+  
+  * "true" and "false" will be converted to booleans. 
+  
+  * "null" will be the null value. 
+    
+  * "this" will be the component itself.
+  
+  * Simple numeric values are also accepted. These will be parsed into Long or Double objects.
+    Ex: "prop:3.14".
+    
+  
+  * Literal strings, inside single quotes.  Ex: "prop:'Hello World'"
+  
+  []
+  
+  In all these cases, excess whitespace is ignored.  For the keywords ("true", "false", "this" and
+  "null"), case is ignored.
+  
+  Such values are read only and invariant.
+  
 Parameters Are Bi-Directional
 
   Parameters are not simply variables; each parameter represents a connection, or <binding>, between

Modified: tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/rendering.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/rendering.apt?view=diff&rev=448627&r1=448626&r2=448627
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/rendering.apt (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/rendering.apt Thu Sep 21 11:40:01 2006
@@ -52,7 +52,7 @@
   and stores the current index value in a parameter:
   
 +---+
-package org.example.components;
+package org.example.myapp.components;
 
 import org.apache.tapestry.annotations.ComponentClass;
 import org.apache.tapestry.annotations.Parameter;

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=448627&r1=448626&r2=448627
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/site/resources/tap5devwiki.html (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/site/resources/tap5devwiki.html Thu Sep 21 11:40:01 2006
@@ -5165,12 +5165,19 @@
 	<div id="saveTest"></div>
 	<div id="contentWrapper"></div>
 	<div id="contentStash"></div>
-	<div id="storeArea"><div tiddler="MasterIndex" modifier="HowardLewisShip" modified="200609202220" created="200609202214" tags="">* PropBindingImprovements -- Desired enhancements to the &quot;prop:&quot; binding\n* TypeCoercion -- How Tapestry 5 will, extensibly, address type conversion</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="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="200609211734" created="200609202214" tags="">* PropBindingImprovements -- Desired enhancements to the &quot;prop:&quot; binding\n* TypeCoercion -- How Tapestry 5 will, extensibly, address type conversion\n* FormProcessing\n* DynamicPageState -- tracking changes to page state during the render</div>
 <div tiddler="OGNL" modifier="HowardLewisShip" modified="200609202255" created="200609202254" tags="">The [[Object Graph Navigation Library|http://ognl.org]] was an essential part of Tapestry 4.\n\nOGNL is both exceptionally powerful (especially the higher order things it can do, such as list selections and projections). However, for the highest\nend sites, it is also a performance problem, both because of its heavy use of reflection, and because it uses a lot of code inside synchronized blocks.\n\nIt will be optional in Tapestry 5. I believe it will not be part of the tapestry-core, but may be packaged as tapestry-ognl.\n\nThe &quot;prop:&quot; binding prefix is a partial replacement for OGNL in Tapestry 5.  \n\nAs of this writing (Sep 20 2006), the built in support is limited to just simple property names.  At some point in the near future, the PropBindingImprovements will be implemented.</div>
-<div tiddler="PropBindingImprovements" modifier="HowardLewisShip" modified="200609202258" created="200609202203" tags="bindings">&quot;prop:&quot; is going to be the default in a  lot of cases, i.e., in any Java code.\n\nIt should support a lot of common idioms even if they are not, precisely, the names of properties.  In many cases, this will save developers the bother of using a &quot;literal:&quot; prefix.\n\n!Numeric literals\n\nSimple numeric literals should be parsed into read-only, invariant bindings.\n{{{\nprop:5\n\nprop:-22.7\n}}}\n\n\n!Boolean literals\n\n&quot;true&quot; and &quot;false&quot; should also be converted to invariant bindings.\n{{{\nprop:true\n\nprop:false\n}}}\n\n!String literals\n\n//Simple// string literals, enclosed in single quotes.  Example:\n{{{\nprop:'Hello World'\n}}}\n\n//Remember that the binding expression will always be enclosed in double quotes.//\n\n!This literal\n\nIn some cases, it is useful to be able to identify the current componen
 t:\n{{{\nprop:this\n}}}\n\n!Property paths\n\nSupporting multi-step property paths should be allowed.  \n{{{\nprop:poll.title\n\nprop:identity.user.name\n}}}\n\nThe 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.\n\nThe 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.\n</div>
+<div tiddler="PropBindingImprovements" modifier="HowardLewisShip" modified="200609211739" created="200609202203" tags="bindings">&quot;prop:&quot; is going to be the default in a  lot of cases, i.e., in any Java code.\n\nIt should support a lot of common idioms even if they are not, precisely, the names of properties.  In many cases, this will save developers the bother of using a &quot;literal:&quot; prefix.\n\nThe 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.\n\n!Numeric literals\n\nSimple numeric literals should be parsed into read-only, invariant bindings.\n{{{\nprop:5\n\nprop:-22.7\n}}}\n\n\nThe 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.\n\n!Boolean literals\n\n&quot;true&quot; and &quot;false&quot; should also be con
 verted to invariant bindings.\n{{{\nprop:true\n\nprop:false\n}}}\n\n!String literals\n\n//Simple// string literals, enclosed in single quotes.  Example:\n{{{\nprop:'Hello World'\n}}}\n\n//Remember that the binding expression will always be enclosed in double quotes.//\n\n!This literal\n\nIn some cases, it is useful to be able to identify the current component:\n{{{\nprop:this\n}}}\n\nEven though a component is not immutable, the value of //this// does not ever change,\nand this binding is also invariant.\n\n!Null literal\n\n{{{\nprop:null\n}}}\n\nThis value is always exactly null. This can be used to set a parameter who'se default value is non-null to the explicit value null.\n\n!Property paths\n\nSupporting multi-step property paths should be allowed.  \n{{{\nprop:poll.title\n\nprop:identity.user.name\n}}}\n\nThe 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.
 \n\nThe 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.\n</div>
+<div tiddler="SideBarTabs" modifier="HowardLewisShip" modified="200609210652" created="200609210651" tags="">&lt;&lt;tabs txtMainTab Timeline Timeline TabTimeline All 'All tiddlers' TabAll Tags 'All tags' TabTags More 'More lists' TabMore&gt;&gt;\n</div>
 <div tiddler="SiteSubtitle" modifier="HowardLewisShip" modified="200609202249" created="200609202155" tags="">\nThe quick and dirty one-stop shopping of random ideas for Tapestry 5.</div>
 <div tiddler="SiteTitle" modifier="HowardLewisShip" modified="200609202249" created="200609202155" tags="">Tapestry 5 Brain Dump</div>
-<div tiddler="TypeCoercion" modifier="HowardLewisShip" modified="200609202302" created="200609202217" tags="parameters types">Automatic coercion of types is essential.  This primarily applies to component parameters.\n\nParameters are tied to the [[Binding|http://tapestry.apache.org/tapestry5/apidocs/org/apache/tapestry/Binding.html]] interface.\n\nTapestry component parameters look like simple instance variables, but Tapestry's RuntimeTransformation of component classes means that\nreading the value of a parameter instance variable //may// invoke Binding.get(), and changing the value of a parameter instance variable\nwill invoke Binding.set().\n\n!Reading From Parameters\n\nReading a parameter value involves two steps:\n* Invoking Binding.get()\n* Converting the result to the type of the parameter (where different)\n\nWhen reading parameters, the binding will provide an object of the type of the bound property.  Various kinds of invariant bindings will returned a fixed type
 , typically a String.\n\nThe parameter will be assigned to a variable that has a known type, possibly a primtive type (int, boolean) or an object type (Map, Date).\n\n!Writing To Parameters\n\nWriting to, or updating, a parameter is in two steps:\n* Converting the new value into a type appropriate for the binding\n* Invoking Binding.set()\n\nWe will be adding a getPropertyType() method to the Binding interface, that will identify the property type of the property bound to the parameter.\n\nThe component will be responsible for performing a coercion from the value provided to the proper type, before invoking Binding.set().\n\n!Conversion Tuples\n\nAt the core of this will be a service that performs conversions.\n\nConversions are based on //conversion tuples// that define:\n* A source type\n* A target type\n* An object to perform the conversion from source to target\n* A &quot;cost&quot; for the conversion (possibly, but usually with a standard default value)\n\nAs a special 
 case, the type of null will be treated as type void (i.e., void.class).  Thus we can use the same mechanism to identify how to convert from null to other types, such as Boolean or Integer.\n\nThere should be a large number of these tuples available.  The most common tuples may be conversions between various types and String.\n\n!Conversion Algorithm\n* Determine the source type (treating null as void.class)\n* Determine the target type (converting primitive types to equivalent wrapper types)\n* If the source type is assignable to the target type, then the input value is valid and the process is complete\n* Find a converter that converts between the source type and the target type, pass the source value through the converter to get a target value\n\nThat last part needs a bit of expansion.\n\nFirst off, there will often ''not'' be a tuple for converting directly form the source type to the target type.\n\nIn that scenario, the conversion will involve a search  to find a seque
 nce of tuples that will perform the conversion.  This will take the form a breadth-first search where we look for tuples that convert from the source type to an intermediate type, then search for tuples from the intermediate type to the target type.  This may involve more than two conversions.\n\nMay need to express a &quot;cost&quot; of the conversion from start type to target type; this might be useful if there are multiple paths for the conversion. Cost may factor in both the computing expense, and any loss of detail.\n\nFor example, a conversion from Number to Float may be represented as the tuple:\n(Number, Float, {{{ return new Float(input.floatValue()); }}})\n\nIf the input type is an Integer, then a search for Integer-&gt;Float will find no entries. At that point, it will be necessary to &quot;climb&quot; the inheritance tree and look for conversions from Number (the super class of Integer); this will find the Number-&gt;Float tuple.\n\nAgain, in terms of cost, we mi
 ght also find a pair of tuples:  Object-&gt;String and String-&gt;Float.  This will have a higher cost than the Number-&gt;Float tuple and should be rejected in favor of the lower cost search.\n\nThis search up the inheritance tree has been [[implemented before|http://jakarta.apache.org/hivemind/hivemind-lib/apidocs/org/apache/hivemind/lib/util/StrategyRegistry.html]].\n\nThe algorithm should certainly cache the result of this search (with concurrent access kept in mind).\n\n!Configuring the service\n\nThis looks like an unordered collection of type ~ConversionTuple.</div>
+<div tiddler="SiteUrl" modifier="HowardLewisShip" modified="200609210703" created="200609210641" tags="">http://tapestry.apache.org/tapestry5/tap5devwiki.html</div>
+<div tiddler="TabAll" modifier="HowardLewisShip" modified="200609210650" created="200609210650" tags="">&lt;&lt;list all&gt;&gt;</div>
+<div tiddler="TypeCoercion" modifier="HowardLewisShip" modified="200609211740" created="200609202217" tags="parameters types">Automatic coercion of types is essential.  This primarily applies to component parameters.\n\nParameters are tied to the [[Binding|http://tapestry.apache.org/tapestry5/apidocs/org/apache/tapestry/Binding.html]] interface.\n\nTapestry component parameters look like simple instance variables, but Tapestry's RuntimeTransformation of component classes means that reading the value of a parameter instance variable //may// invoke Binding.get(), and changing the value of a parameter instance variable will invoke Binding.set().\n\n!Reading From Parameters\n\nReading a parameter value involves two steps:\n* Invoking Binding.get()\n* Converting the result to the type of the parameter (where different)\n\nWhen reading parameters, the binding will provide an object of the type of the bound property.  Various kinds of invariant bindings will returned a fixed type, 
 typically a String.\n\nThe parameter will be assigned to a variable that has a known type, possibly a primtive type (int, boolean) or an object type (Map, Date).\n\n!Writing To Parameters\n\nWriting to, or updating, a parameter is in two steps:\n* Converting the new value into a type appropriate for the binding\n* Invoking Binding.set()\n\nWe will be adding a getPropertyType() method to the Binding interface, that will identify the property type of the property bound to the parameter.\n\nThe component will be responsible for performing a coercion from the value provided to the proper type, before invoking Binding.set().\n\n!Conversion Tuples\n\nAt the core of this will be a service that performs conversions.\n\nConversions are based on //conversion tuples// that define:\n* A source type\n* A target type\n* An object to perform the conversion from source to target\n* A &quot;cost&quot; for the conversion (possibly, but usually with a standard default value)\n\nAs a special ca
 se, the type of null will be treated as type void (i.e., void.class).  Thus we can use the same mechanism to identify how to convert from null to other types, such as Boolean or Integer.\n\nThere should be a large number of these tuples available.  The most common tuples may be conversions between various types and String.\n\n!Conversion Algorithm\n* Determine the source type (treating null as void.class)\n* Determine the target type (converting primitive types to equivalent wrapper types)\n* If the source type is assignable to the target type, then the input value is valid and the process is complete\n* Find a converter that converts between the source type and the target type, pass the source value through the converter to get a target value\n\nThat last part needs a bit of expansion.\n\nFirst off, there will often ''not'' be a tuple for converting directly form the source type to the target type.\n\nIn that scenario, the conversion will involve a search  to find a sequenc
 e of tuples that will perform the conversion.  This will take the form a breadth-first search where we look for tuples that convert from the source type to an intermediate type, then search for tuples from the intermediate type to the target type.  This may involve more than two conversions.\n\nMay need to express a &quot;cost&quot; of the conversion from start type to target type; this might be useful if there are multiple paths for the conversion. Cost may factor in both the computing expense, and any loss of detail.\n\nFor example, a conversion from Number to Float may be represented as the tuple:\n(Number, Float, {{{ return new Float(input.floatValue()); }}})\n\n{{{\npublic interface TypeConverter&lt;S,T&gt;\n{\n  T convert(S input);\n}\n}}}\n\nIf the input type is an Integer, then a search for Integer-&gt;Float will find no entries. At that point, it will be necessary to &quot;climb&quot; the inheritance tree and look for conversions from Number (the super class of Inte
 ger); this will find the Number-&gt;Float tuple.\n\nAgain, in terms of cost, we might also find a pair of tuples:  Object-&gt;String and String-&gt;Float.  This will have a higher cost than the Number-&gt;Float tuple and should be rejected in favor of the lower cost search.\n\nThis search up the inheritance tree has been [[implemented before|http://jakarta.apache.org/hivemind/hivemind-lib/apidocs/org/apache/hivemind/lib/util/StrategyRegistry.html]].\n\nThe algorithm should certainly cache the result of this search (with concurrent access kept in mind).\n\n!Configuring the service\n\nThis looks like an unordered collection of type ~ConversionTuple.</div>
 		</div>
 <!--POST-BODY-START-->
 

Added: 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=auto&rev=448627
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/site/resources/tap5devwiki.xml (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/site/resources/tap5devwiki.xml Thu Sep 21 11:40:01 2006
@@ -0,0 +1,96 @@
+<?xml version="1.0"?>
+<rss version="2.0">
+<channel>
+<title>Tapestry 5 Brain Dump</title>
+<link>http://tapestry.apache.org/tapestry5/tap5devwiki.html</link>
+<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>Thu, 21 Sep 2006 17:40:21 GMT</pubDate>
+<lastBuildDate>Thu, 21 Sep 2006 17:40:21 GMT</lastBuildDate>
+<docs>http://blogs.law.harvard.edu/tech/rss</docs>
+<generator>TiddlyWiki 2.0.11</generator>
+<item>
+<title>TypeCoercion</title>
+<description>Automatic coercion of types is essential.  This primarily applies to component parameters.&lt;br /&gt;&lt;br /&gt;Parameters are tied to the [[Binding|http://tapestry.apache.org/tapestry5/apidocs/org/apache/tapestry/Binding.html]] interface.&lt;br /&gt;&lt;br /&gt;Tapestry component parameters look like simple instance variables, but Tapestry's RuntimeTransformation of component classes means that reading the value of a parameter instance variable //may// invoke Binding.get(), and changing the value of a parameter instance variable will invoke Binding.set().&lt;br /&gt;&lt;br /&gt;!Reading From Parameters&lt;br /&gt;&lt;br /&gt;Reading a parameter value involves two steps:&lt;br /&gt;* Invoking Binding.get()&lt;br /&gt;* Converting the result to the type of the parameter (where different)&lt;br /&gt;&lt;br /&gt;When reading parameters, the binding will provide an object of the type of the bound property.  Various kinds of invariant bindings will returned a fixed
  type, typically a String.&lt;br /&gt;&lt;br /&gt;The parameter will be assigned to a variable that has a known type, possibly a primtive type (int, boolean) or an object type (Map, Date).&lt;br /&gt;&lt;br /&gt;!Writing To Parameters&lt;br /&gt;&lt;br /&gt;Writing to, or updating, a parameter is in two steps:&lt;br /&gt;* Converting the new value into a type appropriate for the binding&lt;br /&gt;* Invoking Binding.set()&lt;br /&gt;&lt;br /&gt;We will be adding a getPropertyType() method to the Binding interface, that will identify the property type of the property bound to the parameter.&lt;br /&gt;&lt;br /&gt;The component will be responsible for performing a coercion from the value provided to the proper type, before invoking Binding.set().&lt;br /&gt;&lt;br /&gt;!Conversion Tuples&lt;br /&gt;&lt;br /&gt;At the core of this will be a service that performs conversions.&lt;br /&gt;&lt;br /&gt;Conversions are based on //conversion tuples// that define:&lt;br /&gt;* A source
  type&lt;br /&gt;* A target type&lt;br /&gt;* An object to perform the conversion from source to target&lt;br /&gt;* A &quot;cost&quot; for the conversion (possibly, but usually with a standard default value)&lt;br /&gt;&lt;br /&gt;As a special case, the type of null will be treated as type void (i.e., void.class).  Thus we can use the same mechanism to identify how to convert from null to other types, such as Boolean or Integer.&lt;br /&gt;&lt;br /&gt;There should be a large number of these tuples available.  The most common tuples may be conversions between various types and String.&lt;br /&gt;&lt;br /&gt;!Conversion Algorithm&lt;br /&gt;* Determine the source type (treating null as void.class)&lt;br /&gt;* Determine the target type (converting primitive types to equivalent wrapper types)&lt;br /&gt;* If the source type is assignable to the target type, then the input value is valid and the process is complete&lt;br /&gt;* Find a converter that converts between the source 
 type and the target type, pass the source value through the converter to get a target value&lt;br /&gt;&lt;br /&gt;That last part needs a bit of expansion.&lt;br /&gt;&lt;br /&gt;First off, there will often ''not'' be a tuple for converting directly form the source type to the target type.&lt;br /&gt;&lt;br /&gt;In that scenario, the conversion will involve a search  to find a sequence of tuples that will perform the conversion.  This will take the form a breadth-first search where we look for tuples that convert from the source type to an intermediate type, then search for tuples from the intermediate type to the target type.  This may involve more than two conversions.&lt;br /&gt;&lt;br /&gt;May need to express a &quot;cost&quot; of the conversion from start type to target type; this might be useful if there are multiple paths for the conversion. Cost may factor in both the computing expense, and any loss of detail.&lt;br /&gt;&lt;br /&gt;For example, a conversion from Num
 ber to Float may be represented as the tuple:&lt;br /&gt;(Number, Float, {{{ return new Float(input.floatValue()); }}})&lt;br /&gt;&lt;br /&gt;{{{&lt;br /&gt;public interface TypeConverter&lt;S,T&gt;&lt;br /&gt;{&lt;br /&gt;  T convert(S input);&lt;br /&gt;}&lt;br /&gt;}}}&lt;br /&gt;&lt;br /&gt;If the input type is an Integer, then a search for Integer-&gt;Float will find no entries. At that point, it will be necessary to &quot;climb&quot; the inheritance tree and look for conversions from Number (the super class of Integer); this will find the Number-&gt;Float tuple.&lt;br /&gt;&lt;br /&gt;Again, in terms of cost, we might also find a pair of tuples:  Object-&gt;String and String-&gt;Float.  This will have a higher cost than the Number-&gt;Float tuple and should be rejected in favor of the lower cost search.&lt;br /&gt;&lt;br /&gt;This search up the inheritance tree has been [[implemented before|http://jakarta.apache.org/hivemind/hivemind-lib/apidocs/org/apache/hivemind/li
 b/util/StrategyRegistry.html]].&lt;br /&gt;&lt;br /&gt;The algorithm should certainly cache the result of this search (with concurrent access kept in mind).&lt;br /&gt;&lt;br /&gt;!Configuring the service&lt;br /&gt;&lt;br /&gt;This looks like an unordered collection of type ~ConversionTuple.</description>
+<category>parameters</category>
+<category>types</category>
+<link>http://tapestry.apache.org/tapestry5/tap5devwiki.html#TypeCoercion</link>
+<pubDate>Thu, 21 Sep 2006 17:40:21 GMT</pubDate>
+</item>
+<item>
+<title>PropBindingImprovements</title>
+<description>&quot;prop:&quot; is going to be the default in a  lot of cases, i.e., in any Java code.&lt;br /&gt;&lt;br /&gt;It should support a lot of common idioms even if 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;Supporting multi-step property paths should be allowed.  &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;</description>
+<category>bindings</category>
+<link>http://tapestry.apache.org/tapestry5/tap5devwiki.html#PropBindingImprovements</link>
+<pubDate>Thu, 21 Sep 2006 17:39:34 GMT</pubDate>
+</item>
+<item>
+<title>MasterIndex</title>
+<description>* PropBindingImprovements -- Desired enhancements to the &quot;prop:&quot; binding&lt;br /&gt;* TypeCoercion -- How Tapestry 5 will, extensibly, address type conversion&lt;br /&gt;* FormProcessing&lt;br /&gt;* DynamicPageState -- tracking changes to page state during the render</description>
+<link>http://tapestry.apache.org/tapestry5/tap5devwiki.html#MasterIndex</link>
+<pubDate>Thu, 21 Sep 2006 17:34:20 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>
+<pubDate>Thu, 21 Sep 2006 16:35:00 GMT</pubDate>
+</item>
+<item>
+<title>FormProcessing</title>
+<description>Form processing in Tapestry 4 had certain strengths and limitations.&lt;br /&gt;&lt;br /&gt;Basically, 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).&lt;br /&gt;&lt;br /&gt;With 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.&lt;br /&gt;&lt;br /&gt;But you never see a ~StaleLinkException.&lt;br /&gt;&lt;br /&gt;You also have some unwanted loopholes in your application i
 n that //any// property can be updated through the URL. This is //one step// towards a security hole.&lt;br /&gt;&lt;br /&gt;!Tapestry 4 Approach&lt;br /&gt;&lt;br /&gt;Every 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.&lt;br /&gt;&lt;br /&gt;Tapestry 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.&lt;br /&gt;&lt;br /&gt;On render, a sequence of //component activations// occur, guided by the normal render sequence. The exact sequence of activations guides&lt;br /&gt;the pro
 duction of client ids.&lt;br /&gt;&lt;br /&gt;Using more advanced Tapestry techniques, including loops, conditionals and the Block/RenderBlock combo, the exact set of components and&lt;br /&gt;component activations that will occur for a given rendering of a given form can not be predicted statically. Tapestry must actually render out the form&lt;br /&gt;to discover all of these.&lt;br /&gt;&lt;br /&gt;In 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.&lt;br /&gt;&lt;br /&gt;An 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.&lt;br /&gt;&lt;br /&gt;When a form submission occurs, we want to ensure that each query 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).&lt;br /&gt;&lt;br /&gt;During this submission process, which endded up with the curious name, //rewind phase//, Tapestry must do two things:&lt;br /&gt;* Activate each component, such that the component may re-determine its client id, read its parameter, and update its page property&lt;br /&gt;* Validate that the process has not been comprimised by a change of server side state&lt;br /&gt;&lt;br /&gt;That 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 b
 oth 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 component activations to process query parameters that don't exist in the request. &lt;br /&gt;&lt;br /&gt;This scenario can occur whenever the processing of the form submission is driven by server-side data that can change between request.&lt;br /&gt;&lt;br /&gt;Tapestry 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.&lt;br /&gt;&lt;br /&gt;There are also other edge cases for different race conditions where data is applied to the wrong server-side objects.&lt;br /&gt;&lt;br /&gt;The 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).&lt;br /&gt;&lt;br /&gt;Generally speaking, the Tapestry 4 approach represents layers of kludge on layers of kludge. It works, it gets the job done, it can handle some very complex situations, but it is less than ideal.&lt;br /&gt;&lt;br /&gt;!Tapestry 5&lt;br /&gt;&lt;br /&gt;The goal here is to capture the series of //component activations//, along with any significant page state changes, during the render.&lt;br /&gt;&lt;br /&gt;These 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).&lt;br /&gt;&lt;br /&gt;The serialized series of commands is stored as a hidden form field.&lt;br /&gt;&lt;br /&gt;There's a lot of API to be figured out, especially the relationship between the form components and the form itself.&lt;br /&gt;&lt;br /&gt;Further, 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.&lt;br /&gt;&lt;br /&gt;The 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.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;</description>
+<category>forms</category>
+<link>http://tapestry.apache.org/tapestry5/tap5devwiki.html#FormProcessing</link>
+<pubDate>Thu, 21 Sep 2006 15:40:00 GMT</pubDate>
+</item>
+<item>
+<title>SiteUrl</title>
+<description>http://tapestry.apache.org/tapestry5/tap5devwiki.html</description>
+<link>http://tapestry.apache.org/tapestry5/tap5devwiki.html#SiteUrl</link>
+<pubDate>Thu, 21 Sep 2006 07:03:00 GMT</pubDate>
+</item>
+<item>
+<title>MainMenu</title>
+<description>MasterIndex&lt;br /&gt;[[RSS feed|tap5devwiki.xml]]&lt;br /&gt;&lt;br /&gt;[[Tapestry 5 Home|http://tapestry.apache.org/tapestry5/]]&lt;br /&gt;[[Howard's Blog|http://howardlewisship.com/blog/]]&lt;br /&gt;&lt;br /&gt;[[Formatting Help|http://www.blogjones.com/TiddlyWikiTutorial.html#EasyToEdit%20Welcome%20NewFeatures%20WhereToFindHelp]]</description>
+<link>http://tapestry.apache.org/tapestry5/tap5devwiki.html#MainMenu</link>
+<pubDate>Thu, 21 Sep 2006 07:01:00 GMT</pubDate>
+</item>
+<item>
+<title>SideBarTabs</title>
+<description>&lt;&lt;tabs txtMainTab Timeline Timeline TabTimeline All 'All tiddlers' TabAll Tags 'All tags' TabTags More 'More lists' TabMore&gt;&gt;&lt;br /&gt;</description>
+<link>http://tapestry.apache.org/tapestry5/tap5devwiki.html#SideBarTabs</link>
+<pubDate>Thu, 21 Sep 2006 06:52:00 GMT</pubDate>
+</item>
+<item>
+<title>TabAll</title>
+<description>&lt;&lt;list all&gt;&gt;</description>
+<link>http://tapestry.apache.org/tapestry5/tap5devwiki.html#TabAll</link>
+<pubDate>Thu, 21 Sep 2006 06:50:00 GMT</pubDate>
+</item>
+<item>
+<title>EditTemplate</title>
+<description>&lt;div class='toolbar' macro='toolbar +saveTiddler -cancelTiddler deleteTiddler'&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class='title' macro='view title'&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class='editor' macro='edit title'&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class='editor' macro='edit text'&gt;&lt;/div&gt;&lt;br /&gt;&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;</description>
+<link>http://tapestry.apache.org/tapestry5/tap5devwiki.html#EditTemplate</link>
+<pubDate>Thu, 21 Sep 2006 06:49:00 GMT</pubDate>
+</item>
+<item>
+<title>OGNL</title>
+<description>The [[Object Graph Navigation Library|http://ognl.org]] was an essential part of Tapestry 4.&lt;br /&gt;&lt;br /&gt;OGNL is both exceptionally powerful (especially the higher order things it can do, such as list selections and projections). However, for the highest&lt;br /&gt;end sites, it is also a performance problem, both because of its heavy use of reflection, and because it uses a lot of code inside synchronized blocks.&lt;br /&gt;&lt;br /&gt;It will be optional in Tapestry 5. I believe it will not be part of the tapestry-core, but may be packaged as tapestry-ognl.&lt;br /&gt;&lt;br /&gt;The &quot;prop:&quot; binding prefix is a partial replacement for OGNL in Tapestry 5.  &lt;br /&gt;&lt;br /&gt;As of this writing (Sep 20 2006), the built in support is limited to just simple property names.  At some point in the near future, the PropBindingImprovements will be implemented.</description>
+<link>http://tapestry.apache.org/tapestry5/tap5devwiki.html#OGNL</link>
+<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>
+</channel>
+</rss>
\ No newline at end of file

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/components/Loop.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/components/Loop.java?view=diff&rev=448627&r1=448626&r2=448627
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/components/Loop.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/components/Loop.java Thu Sep 21 11:40:01 2006
@@ -36,16 +36,9 @@
     @SetupRender
     void initializeValue()
     {
-        if (_start < _end)
-        {
-            _value = _start;
-            _increment = true;
-        }
-        else
-        {
-            _value = _start;
-            _increment = false;
-        }
+        _value = _start;
+
+        _increment = _start < _end;
     }
 
     @AfterRender

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/PropBindingFactoryTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/PropBindingFactoryTest.java?view=diff&rev=448627&r1=448626&r2=448627
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/PropBindingFactoryTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/PropBindingFactoryTest.java Thu Sep 21 11:40:01 2006
@@ -18,11 +18,19 @@
 import org.apache.tapestry.ComponentResources;
 import org.apache.tapestry.Location;
 import org.apache.tapestry.events.InvalidationEvent;
+import org.apache.tapestry.internal.annotations.SuppressNullCheck;
 import org.apache.tapestry.internal.ioc.IOCUtilities;
 import org.apache.tapestry.internal.ioc.services.ClassFactoryImpl;
 import org.apache.tapestry.internal.ioc.services.PropertyAccessImpl;
 import org.apache.tapestry.internal.test.InternalBaseTestCase;
+import org.apache.tapestry.ioc.Registry;
+import org.apache.tapestry.ioc.RegistryBuilder;
 import org.apache.tapestry.runtime.ComponentLifecycle;
+import org.apache.tapestry.services.BindingFactory;
+import org.apache.tapestry.services.TapestryModule;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
 /**
@@ -233,5 +241,93 @@
         }
 
         verify();
+    }
+
+    private Registry _registry;
+
+    private BindingFactory _propFactory;
+
+    @BeforeClass
+    public void setupRegistry()
+    {
+        RegistryBuilder builder = new RegistryBuilder();
+        builder.add(TapestryModule.class);
+
+        _registry = builder.build();
+
+        _propFactory = _registry.getService(
+                "tapestry.internal.PropBindingFactory",
+                BindingFactory.class);
+    }
+
+    @AfterClass
+    public void shutdownRegistry()
+    {
+        _registry.shutdown();
+
+        _registry = null;
+        _propFactory = null;
+    }
+
+    @Test
+    public void special_prop_binding_value_null()
+    {
+        Location l = newLocation();
+        String description = "my description";
+        ComponentResources resources = newComponentResources();
+        ComponentLifecycle component = newComponentLifecycle();
+
+        train_getComponent(resources, component);
+
+        replay();
+
+        Binding binding = _propFactory.newBinding(description, resources, "this", l);
+
+        assertSame(binding.get(), component);
+
+        verify();
+    }
+
+    @Test(dataProvider = "values")
+    @SuppressNullCheck
+    public void special_prop_binding_values(String expression, Object expected)
+    {
+        Location l = newLocation();
+        String description = "my description";
+        ComponentResources resources = newComponentResources();
+
+        replay();
+
+        Binding binding = _propFactory.newBinding(description, resources, expression, l);
+
+        assertEquals(binding.get(), expected);
+
+        verify();
+    }
+
+    @DataProvider(name = "values")
+    public Object[][] values()
+    {
+        return new Object[][]
+        {
+        { "true", true, },
+        { "True", true, },
+        { " true ", true, },
+        { "false", false },
+        { "null", null },
+        { "3", 3l },
+        { " 37 ", 37l },
+        { " -227", -227l },
+        { " 5.", 5d },
+        { " -100.", -100d },
+        { " -0.0 ", -0d },
+        { "0.", 0d },
+        { " 227.75", 227.75d },
+        { " -10123.67", -10123.67d },
+        { "'Hello World'", "Hello World" },
+        { " 'Whitespace Ignored' ", "Whitespace Ignored" },
+        { " ' Inside ' ", " Inside " },
+        { " 'Nested ' Quotes ' Inside'", "Nested ' Quotes ' Inside" },
+        { "'''", "'" } };
     }
 }