Author: hlship
Date: Tue Apr 15 10:55:40 2008
New Revision: 648355

URL: http://svn.apache.org/viewvc?rev=648355&view=rev
Log:
TAPESTRY-2338: Cached values for methods annotated with @Cached do not reset at 
end of Ajax request

Added:
    tapestry/tapestry5/trunk/tapestry-core/src/test/app1/CleanCacheDemo.tml
    
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/CleanCacheDemo.java
Modified:
    
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/annotations/Cached.java
    
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/transform/CachedWorker.java
    
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/IntegrationTests.java
    
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/Start.java

Modified: 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/annotations/Cached.java
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/annotations/Cached.java?rev=648355&r1=648354&r2=648355&view=diff
==============================================================================
--- 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/annotations/Cached.java
 (original)
+++ 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/annotations/Cached.java
 Tue Apr 15 10:55:40 2008
@@ -14,28 +14,25 @@
 
 package org.apache.tapestry.annotations;
 
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+import java.lang.annotation.*;
 
-/** Indicates that a method should only be evaluated once and the result 
cached.
- * All further calls to the method will return the cached result. Note that 
this
- * annotation is inheritence-safe; if a subclass calls a superclass method 
that 
- * has [EMAIL PROTECTED] then the value the subclass method gets is the cached 
value. 
- * <p>
- * The watch parameter can be passed a binding expression
- * which will be evaluated each time the method is called. The method will then
- * only be executed the first time it is called and after that only when the
- * value of the binding changes. This can be used, for instance, to have the
- * method only evaluated once per iteration of a loop by setting watch to
- * the value or index of the loop.
+/**
+ * Indicates that a method should only be evaluated once and the result 
cached. All further calls to the method will
+ * return the cached result. Note that this annotation is inheritence-safe; if 
a subclass calls a superclass method that
+ * has [EMAIL PROTECTED] then the value the subclass method gets is the cached 
value.
+ * <p/>
+ * The watch parameter can be passed a binding expression which will be 
evaluated each time the method is called. The
+ * method will then only be executed the first time it is called and after 
that only when the value of the binding
+ * changes. This can be used, for instance, to have the method only evaluated 
once per iteration of a loop by setting
+ * watch to the value or index of the loop.
  */
 @Target(ElementType.METHOD)
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
-public @interface Cached {
-       /** The optional binding to watch (default binding prefix is "prop") */
-       String watch() default "";
+public @interface Cached
+{
+    /**
+     * The optional binding to watch (default binding prefix is "prop").
+     */
+    String watch() default "";
 }

Modified: 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/transform/CachedWorker.java
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/transform/CachedWorker.java?rev=648355&r1=648354&r2=648355&view=diff
==============================================================================
--- 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/transform/CachedWorker.java
 (original)
+++ 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/transform/CachedWorker.java
 Tue Apr 15 10:55:40 2008
@@ -14,115 +14,127 @@
 
 package org.apache.tapestry.internal.transform;
 
-import static java.lang.reflect.Modifier.PRIVATE;
-
-import java.util.List;
-
 import org.apache.tapestry.Binding;
 import org.apache.tapestry.TapestryConstants;
 import org.apache.tapestry.annotations.Cached;
 import org.apache.tapestry.ioc.util.BodyBuilder;
 import org.apache.tapestry.model.MutableComponentModel;
-import org.apache.tapestry.services.BindingSource;
-import org.apache.tapestry.services.ClassTransformation;
-import org.apache.tapestry.services.ComponentClassTransformWorker;
-import org.apache.tapestry.services.TransformConstants;
-import org.apache.tapestry.services.TransformMethodSignature;
-import org.apache.tapestry.services.TransformUtils;
-
-/** Caches method return values for methods annotated with [EMAIL PROTECTED] 
Cached}. */
-public class CachedWorker implements ComponentClassTransformWorker {
-       private final BindingSource _bindingSource;
-       
-       public CachedWorker(BindingSource bindingSource) {
-               super();
-               this._bindingSource = bindingSource;
-       }
-
-       public void transform(ClassTransformation transformation, 
MutableComponentModel model) {
-               List<TransformMethodSignature> methods = 
transformation.findMethodsWithAnnotation(Cached.class);
-               if (methods.isEmpty())
-                       return;
-               
-               for(TransformMethodSignature method : methods) {
-                       if (method.getReturnType().equals("void"))
-                               throw new 
IllegalArgumentException(TransformMessages.cachedMethodMustHaveReturnValue(method));
-       
-                       if (method.getParameterTypes().length != 0)
-                               throw new 
IllegalArgumentException(TransformMessages.cachedMethodsHaveNoParameters(method));
-                       
-                       String propertyName = method.getMethodName();
-                                               
-                       // add a property to store whether or not the method 
has been called
-                       String fieldName = transformation.addField(PRIVATE, 
method.getReturnType(), propertyName);
-                       String calledField = transformation.addField(PRIVATE, 
"boolean", fieldName + "$called");
-       
-                       Cached once = 
transformation.getMethodAnnotation(method, Cached.class);
-                       String bindingField = null;
-                       String bindingValueField = null;
-                       boolean watching = once.watch().length() > 0;
-                       if (watching) {
-                               // add fields to store the binding and the value
-                               bindingField = transformation.addField(PRIVATE, 
Binding.class.getCanonicalName(), fieldName + "$binding");
-                               bindingValueField = 
transformation.addField(PRIVATE, "java.lang.Object", fieldName + 
"$bindingValue");
-
-                               String bindingSourceField = 
transformation.addInjectedField(BindingSource.class, fieldName + 
"$bindingsource", _bindingSource);
-               
-                               String body = String.format("%s = 
%s.newBinding(\"Watch expression\", %s, \"%s\", \"%s\");", 
-                                               bindingField,
-                                               bindingSourceField,
-                                               
transformation.getResourcesFieldName(),
-                                               
TapestryConstants.PROP_BINDING_PREFIX,
-                                               once.watch());
-                               
transformation.extendMethod(TransformConstants.CONTAINING_PAGE_DID_LOAD_SIGNATURE,
 body);
-                       }
-                       
-                       BodyBuilder b = new BodyBuilder();
-
-                       // on cleanup, reset the field values
-                       b.begin();
-                                               
-                       if (!TransformUtils.isPrimitive(method.getReturnType()))
-                               b.addln("%s = null;", fieldName);
-                       b.addln("%s = false;", calledField);
-                       
-                       if (watching)
-                               b.addln("%s = null;", bindingValueField);
-                       
-                       b.end();
-                       
transformation.extendMethod(TransformConstants.POST_RENDER_CLEANUP_SIGNATURE, 
b.toString());
-                                               
-                       // prefix the existing method to cache the result
-                       b.clear();
-                       b.begin();
-                       
-                       // if it has been called and watch is set and the old 
value is the same as the new value then return
-                       // get the old value and cache it
-                       /* NOTE: evaluates the binding twice when checking the 
new value.
-                        * this is probably not a problem because in most cases 
properties
-                        * that are being watched are not expensive operations. 
plus, we
-                        * never guaranteed that it would be called exactly 
once when
-                        * watching.
-                        */
-                       if (watching) {
-                               b.addln("if (%s && %s == %s.get()) return %s;",
-                                               calledField, bindingValueField, 
bindingField, fieldName);
-                               b.addln("%s = %s.get();", bindingValueField, 
bindingField);
-                       }
-                       else {
-                               b.addln("if (%s) return %s;", calledField, 
fieldName);
-                       }
-                       
-                       b.addln("%s = true;", calledField);                     
-                       b.end();                        
-                       transformation.prefixMethod(method, b.toString());
-                       
-                       // cache the return value
-                       b.clear();
-                       b.begin();
-                       b.addln("%s = $_;", fieldName);
-                       b.end();
-                       transformation.extendExistingMethod(method, 
b.toString());
-               }
-       }
+import org.apache.tapestry.services.*;
+
+import static java.lang.reflect.Modifier.PRIVATE;
+import java.util.List;
+
+/**
+ * Caches method return values for methods annotated with [EMAIL PROTECTED] 
Cached}.
+ */
+public class CachedWorker implements ComponentClassTransformWorker
+{
+    private final BindingSource _bindingSource;
+
+    public CachedWorker(BindingSource bindingSource)
+    {
+        _bindingSource = bindingSource;
+    }
+
+    public void transform(ClassTransformation transformation, 
MutableComponentModel model)
+    {
+        List<TransformMethodSignature> methods = 
transformation.findMethodsWithAnnotation(Cached.class);
+        if (methods.isEmpty())
+            return;
+
+        for (TransformMethodSignature method : methods)
+        {
+            if (method.getReturnType().equals("void"))
+                throw new 
IllegalArgumentException(TransformMessages.cachedMethodMustHaveReturnValue(method));
+
+            if (method.getParameterTypes().length != 0)
+                throw new 
IllegalArgumentException(TransformMessages.cachedMethodsHaveNoParameters(method));
+
+            String propertyName = method.getMethodName();
+
+            // add a property to store whether or not the method has been 
called
+            String fieldName = transformation.addField(PRIVATE, 
method.getReturnType(), propertyName);
+            String calledField = transformation.addField(PRIVATE, "boolean", 
fieldName + "$called");
+
+            Cached once = transformation.getMethodAnnotation(method, 
Cached.class);
+            String bindingField = null;
+            String bindingValueField = null;
+            boolean watching = once.watch().length() > 0;
+
+            if (watching)
+            {
+                // add fields to store the binding and the value
+                bindingField = transformation.addField(PRIVATE, 
Binding.class.getCanonicalName(),
+                                                       fieldName + "$binding");
+                bindingValueField = transformation.addField(PRIVATE, 
"java.lang.Object", fieldName + "$bindingValue");
+
+                String bindingSourceField = 
transformation.addInjectedField(BindingSource.class,
+                                                                            
fieldName + "$bindingsource",
+                                                                            
_bindingSource);
+
+                String body = String.format("%s = %s.newBinding(\"Watch 
expression\", %s, \"%s\", \"%s\");",
+                                            bindingField,
+                                            bindingSourceField,
+                                            
transformation.getResourcesFieldName(),
+                                            
TapestryConstants.PROP_BINDING_PREFIX,
+                                            once.watch());
+
+                
transformation.extendMethod(TransformConstants.CONTAINING_PAGE_DID_LOAD_SIGNATURE,
 body);
+            }
+
+            BodyBuilder b = new BodyBuilder();
+
+            // on cleanup, reset the field values
+            b.begin();
+
+            if (!TransformUtils.isPrimitive(method.getReturnType()))
+                b.addln("%s = null;", fieldName);
+            b.addln("%s = false;", calledField);
+
+            if (watching)
+                b.addln("%s = null;", bindingValueField);
+
+            b.end();
+
+            // TAPESTRY-2338: Cleanup at page detach, not render cleanup.  In 
an Ajax request, the rendering
+            // objects may reference properties of components that don't 
render and so won't execute the
+            // PostCleanupRender phase.
+
+            
transformation.extendMethod(TransformConstants.CONTAINING_PAGE_DID_DETACH_SIGNATURE,
 b.toString());
+
+            // prefix the existing method to cache the result
+            b.clear();
+            b.begin();
+
+            // if it has been called and watch is set and the old value is the 
same as the new value then return
+            // get the old value and cache it
+            /* NOTE: evaluates the binding twice when checking the new value.
+                * this is probably not a problem because in most cases 
properties
+                * that are being watched are not expensive operations. plus, we
+                * never guaranteed that it would be called exactly once when
+                * watching.
+                */
+            if (watching)
+            {
+                b.addln("if (%s && %s == %s.get()) return %s;",
+                        calledField, bindingValueField, bindingField, 
fieldName);
+                b.addln("%s = %s.get();", bindingValueField, bindingField);
+            }
+            else
+            {
+                b.addln("if (%s) return %s;", calledField, fieldName);
+            }
+
+            b.addln("%s = true;", calledField);
+            b.end();
+            transformation.prefixMethod(method, b.toString());
+
+            // cache the return value
+            b.clear();
+            b.begin();
+            b.addln("%s = $_;", fieldName);
+            b.end();
+            transformation.extendExistingMethod(method, b.toString());
+        }
+    }
 }

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/app1/CleanCacheDemo.tml
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/app1/CleanCacheDemo.tml?rev=648355&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/app1/CleanCacheDemo.tml 
(added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/app1/CleanCacheDemo.tml Tue 
Apr 15 10:55:40 2008
@@ -0,0 +1,24 @@
+<html t:type="border" 
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd";>
+
+
+    <p>
+        The following two numbers will be identical, due to @Cache:
+    </p>
+
+    <div t:type="zone" t:id="aZone">
+        <ul>
+            <li id="time1">${timeNanos}</li>
+            <li id="time2">${timeNanos}</li>
+        </ul>
+
+
+    </div>
+
+    <p>
+        Clicking
+        <a t:type="actionlink" t:id="updateZone" href="#" 
t:zone="aZone">update</a>
+        will refresh the numbers.
+    </p>
+
+
+</html>
\ No newline at end of file

Modified: 
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/IntegrationTests.java
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/IntegrationTests.java?rev=648355&r1=648354&r2=648355&view=diff
==============================================================================
--- 
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/IntegrationTests.java
 (original)
+++ 
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/IntegrationTests.java
 Tue Apr 15 10:55:40 2008
@@ -1445,7 +1445,7 @@
     public void action_links_on_custom_url()
     {
         open(BASE_URL + "nested/actiondemo/");
-        
+
         clickAndWait("link=2");
 
         assertTextPresent("Number: 2");
@@ -1897,12 +1897,26 @@
     {
         start("Cached Annotation");
 
-        assertText("//[EMAIL PROTECTED]'value']", "000");
-        assertText("//[EMAIL PROTECTED]'value2size']", "111");
+        assertText("value", "000");
+        assertText("value2size", "111");
 
         assertText("//[EMAIL PROTECTED]'watch'][1]", "0");
         assertText("//[EMAIL PROTECTED]'watch'][2]", "0");
         assertText("//[EMAIL PROTECTED]'watch'][3]", "1");
+
+        clickAndWait("link=Back to index");
+
+        // TAPESTRY-2338: Make sure the data is cleared.
+
+        clickAndWait("link=Cached Annotation");
+
+        assertText("value", "000");
+        assertText("value2size", "111");
+
+        assertText("//[EMAIL PROTECTED]'watch'][1]", "0");
+        assertText("//[EMAIL PROTECTED]'watch'][2]", "0");
+        assertText("//[EMAIL PROTECTED]'watch'][3]", "1");
+
     }
 
     /**
@@ -1912,7 +1926,16 @@
     public void override_method_with_cached()
     {
         start("Cached Annotation2");
-        assertText("//[EMAIL PROTECTED]'value']", "111");
+
+        assertText("value", "111");
+
+        clickAndWait("link=Back to index");
+
+        // TAPESTRY-2338: Make sure the data is cleared.
+
+        clickAndWait("link=Cached Annotation2");
+
+        assertText("value", "111");
     }
 
     private void sleep(long timeout)
@@ -1924,5 +1947,36 @@
         catch (InterruptedException ex)
         {
         }
+    }
+
+    /**
+     * TAPESTRY-2338
+     */
+    @Test
+    public void cached_properties_cleared_at_end_of_request()
+    {
+        start("Clean Cache Demo");
+
+        String time1_1 = getText("time1");
+        String time1_2 = getText("time1");
+
+        // Don't know what they are but they should be the same.
+
+        assertEquals(time1_2, time1_1);
+
+        click("link=update");
+
+        sleep(250);
+
+        String time2_1 = getText("time1");
+        String time2_2 = getText("time1");
+
+        // Check that @Cache is still working
+
+        assertEquals(time2_2, time2_1);
+
+        assertFalse(time2_1.equals(time1_1),
+                    "After update the nanoseconds time did not change, meaning 
@Cache was broken.");
+
     }
 }

Added: 
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/CleanCacheDemo.java
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/CleanCacheDemo.java?rev=648355&view=auto
==============================================================================
--- 
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/CleanCacheDemo.java
 (added)
+++ 
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/CleanCacheDemo.java
 Tue Apr 15 10:55:40 2008
@@ -0,0 +1,36 @@
+// Copyright 2008 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.integration.app1.pages;
+
+import org.apache.tapestry.annotations.Cached;
+import org.apache.tapestry.annotations.Component;
+import org.apache.tapestry.corelib.components.Zone;
+
+public class CleanCacheDemo
+{
+    @Component
+    private Zone aZone;
+
+    Object onActionFromUpdateZone()
+    {
+        return aZone;
+    }
+
+    @Cached
+    public long getTimeNanos()
+    {
+        return System.nanoTime();
+    }
+}

Modified: 
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/Start.java
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/Start.java?rev=648355&r1=648354&r2=648355&view=diff
==============================================================================
--- 
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/Start.java
 (original)
+++ 
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/Start.java
 Tue Apr 15 10:55:40 2008
@@ -63,6 +63,8 @@
     private static final List<Item> ITEMS = CollectionFactory.newList(
             new Item("actionpage", "Action Page", "tests fixture for 
ActionLink component"),
 
+            new Item("cleancachedemo", "Clean Cache Demo", "cache cleared 
properly during Ajax calls"),
+
             new Item("numberbeaneditordemo", "Number BeanEditor Demo",
                      "use of nulls and wrapper types with BeanEditor"),
 
@@ -84,7 +86,8 @@
 
             new Item("nested/AssetDemo", "AssetDemo", "declaring an image 
using Assets"),
 
-            new Item("nested/ActionDemo", "Action With Context Demo", "using 
action links with context on page with activation context"),
+            new Item("nested/ActionDemo", "Action With Context Demo",
+                     "using action links with context on page with activation 
context"),
 
             new Item("blockdemo", "BlockDemo", "use of blocks to control 
rendering"),
 
@@ -246,13 +249,14 @@
             new Item("TrackEditor", "Generic Page Class Demo",
                      "demo use of generics with component classes and, 
particularily, with property types"),
 
-            new Item("IndirectProtectedFields", "Protected Fields Demo", "demo 
exception when component class contains protected fields"),
+            new Item("IndirectProtectedFields", "Protected Fields Demo",
+                     "demo exception when component class contains protected 
fields"),
 
             new Item("injectcomponentdemo", "Inject Component Demo",
                      "inject component defined in template"),
-            
+
             new Item("cachedpage", "Cached Annotation", "Caching method return 
values"),
-            
+
             new Item("cachedpage2", "Cached Annotation2", "Caching method 
return values w/ inheritence")
     );
 
@@ -277,7 +281,7 @@
     {
         _item = item;
     }
-                                                            
+
     @InjectPage
     private SecurePage _securePage;
 


Reply via email to