This is an automated email from the ASF dual-hosted git repository.

benw pushed a commit to branch TAP5-2763
in repository https://gitbox.apache.org/repos/asf/tapestry-5.git

commit c55d039af792c7b2052473d3219f3acd71888c1a
Author: Ben Weidig <b...@netzgut.net>
AuthorDate: Sun Sep 24 11:36:39 2023 +0200

    TAP5-2763: improve @RequestParameter blank handling
    
    @RequestParamter now also has "boolean treatBlankAsNull() default false" to 
better handl blank values.
    
    A value might be blank, even if it's supposed to be a null coming from the 
client, as any parameter in a request set to null will end up as blank on the 
server side.
    This might lead to coercion problems if no sensible coercer from blank to 
the target type exists.
    To keep the handling as close to the original, an additional annotation 
parameter made more sense than changing the overall handling.
---
 .../tapestry5/annotations/RequestParameter.java    | 22 ++++++++---
 .../internal/transform/OnEventWorker.java          | 44 +++++++++++++++-------
 .../src/test/app1/RequestParameterDemo.tml         |  6 +++
 .../integration/app1/RequestParameterTests.java    | 19 ++++++++++
 .../app1/pages/RequestParameterDemo.java           | 34 +++++++++++++++--
 5 files changed, 103 insertions(+), 22 deletions(-)

diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/annotations/RequestParameter.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/annotations/RequestParameter.java
index be48db3fc..4dd571dc7 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/annotations/RequestParameter.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/annotations/RequestParameter.java
@@ -12,17 +12,19 @@
 
 package org.apache.tapestry5.annotations;
 
-import org.apache.tapestry5.http.services.Request;
-import org.apache.tapestry5.internal.transform.OnEventWorker;
-import org.apache.tapestry5.ioc.annotations.UseWith;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static 
org.apache.tapestry5.ioc.annotations.AnnotationUseContext.COMPONENT;
+import static org.apache.tapestry5.ioc.annotations.AnnotationUseContext.MIXIN;
+import static org.apache.tapestry5.ioc.annotations.AnnotationUseContext.PAGE;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 
-import static java.lang.annotation.ElementType.PARAMETER;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import static org.apache.tapestry5.ioc.annotations.AnnotationUseContext.*;
+import org.apache.tapestry5.http.services.Request;
+import org.apache.tapestry5.internal.transform.OnEventWorker;
+import org.apache.tapestry5.ioc.annotations.UseWith;
 
 /**
  * Annotation that may be placed on parameters of event handler methods.
@@ -54,4 +56,12 @@ public @interface RequestParameter
      * implementation.
      */
     boolean allowBlank() default false;
+
+    /**
+     * Treats a blank value as null before passing it along to the appropriate 
{@link org.apache.tapestry5.ValueEncoder}
+     * implementation. This only affects request parameters with {@link 
#allowBlank()} set to true.
+     * 
+     * @since 5.8.4
+     */
+    boolean treatBlankAsNull() default false;
 }
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/OnEventWorker.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/OnEventWorker.java
index 5468bc487..f35018a62 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/OnEventWorker.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/OnEventWorker.java
@@ -329,7 +329,7 @@ public class OnEventWorker implements 
ComponentClassTransformWorker2
                     String parameterName = parameterAnnotation.value();
 
                     providers.add(createQueryParameterProvider(method, i, 
parameterName, type,
-                            parameterAnnotation.allowBlank()));
+                            parameterAnnotation.allowBlank(), 
parameterAnnotation.treatBlankAsNull()));
                     continue;
                 }
 
@@ -615,7 +615,7 @@ public class OnEventWorker implements 
ComponentClassTransformWorker2
     }
 
     private EventHandlerMethodParameterProvider 
createQueryParameterProvider(PlasticMethod method, final int parameterIndex, 
final String parameterName,
-                                                                             
final String parameterTypeName, final boolean allowBlank)
+                                                                             
final String parameterTypeName, final boolean allowBlank, final boolean 
treatBlankAsNull)
     {
         final String methodIdentifier = method.getMethodIdentifier();
 
@@ -639,23 +639,33 @@ public class OnEventWorker implements 
ComponentClassTransformWorker2
 
                     String parameterValue = 
request.getParameter(parameterName);
 
-                    if (!allowBlank && InternalUtils.isBlank(parameterValue))
-                        throw new RuntimeException(String.format(
-                                "The value for query parameter '%s' was blank, 
but a non-blank value is needed.",
-                                parameterName));
+                    if (InternalUtils.isBlank(parameterValue))
+                    {
+                        if (!allowBlank)
+                        {
+                            throw new RuntimeException(String.format(
+                                    "The value for query parameter '%s' was 
blank, but a non-blank value is needed.",
+                                    parameterName));
+                        }
+
+                        if (treatBlankAsNull)
+                        {
+                            parameterValue = null;
+                        }
+                    }
 
                     Object value;
 
                     if (!isArray)
                     {
-                        value = coerce(parameterName, parameterType, 
parameterValue, valueEncoder, allowBlank);
+                        value = coerce(parameterName, parameterType, 
parameterValue, valueEncoder, allowBlank, treatBlankAsNull);
                     } else
                     {
                         String[] parameterValues = 
request.getParameters(parameterName);
                         Object[] array = (Object[]) 
Array.newInstance(parameterType, parameterValues.length);
                         for (int i = 0; i < parameterValues.length; i++)
                         {
-                            array[i] = coerce(parameterName, parameterType, 
parameterValues[i], valueEncoder, allowBlank);
+                            array[i] = coerce(parameterName, parameterType, 
parameterValues[i], valueEncoder, allowBlank, treatBlankAsNull);
                         }
                         value = array;
                     }
@@ -672,14 +682,22 @@ public class OnEventWorker implements 
ComponentClassTransformWorker2
             }
 
             private Object coerce(final String parameterName, Class 
parameterType,
-                                  String parameterValue, ValueEncoder 
valueEncoder, boolean allowBlank)
+                                  String parameterValue, ValueEncoder 
valueEncoder, boolean allowBlank, boolean treatBlankAsNull)
             {
 
-                if (!allowBlank && InternalUtils.isBlank(parameterValue))
+                if (InternalUtils.isBlank(parameterValue))
                 {
-                    throw new RuntimeException(String.format(
-                            "The value for query parameter '%s' was blank, but 
a non-blank value is needed.",
-                            parameterName));
+                    if (!allowBlank)
+                    {
+                        throw new RuntimeException(String.format(
+                                "The value for query parameter '%s' was blank, 
but a non-blank value is needed.",
+                                parameterName));
+                    }
+
+                    if (treatBlankAsNull)
+                    {
+                        parameterValue = null;
+                    }
                 }
 
                 Object value = valueEncoder.toValue(parameterValue);
diff --git a/tapestry-core/src/test/app1/RequestParameterDemo.tml 
b/tapestry-core/src/test/app1/RequestParameterDemo.tml
index 683b9c4da..9c2fcc4c8 100644
--- a/tapestry-core/src/test/app1/RequestParameterDemo.tml
+++ b/tapestry-core/src/test/app1/RequestParameterDemo.tml
@@ -34,5 +34,11 @@
      <li>
          <a href="${blankAllowedLink}">Blank Allowed Link</a>
      </li>
+     <li>
+         <a href="${blankAllowedLinkNonWrapperThrowsException}">Blank Allowed 
Non-Wrapper Throws Exception Link</a>
+     </li>
+     <li>
+         <a href="${blankAllowedLinkNonWrapper}">Blank Allowed Non-Wrapper 
Treat As Null Link</a>
+     </li>
   </ul>
 </html>
\ No newline at end of file
diff --git 
a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/RequestParameterTests.java
 
b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/RequestParameterTests.java
index f2b845723..fbe2c186d 100644
--- 
a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/RequestParameterTests.java
+++ 
b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/RequestParameterTests.java
@@ -14,6 +14,8 @@ package org.apache.tapestry5.integration.app1;
 
 import org.testng.annotations.Test;
 
+import spock.lang.Issue;
+
 public class RequestParameterTests extends App1TestCase
 {
     @Test
@@ -74,4 +76,21 @@ public class RequestParameterTests extends App1TestCase
         assertText("id=current", "-1");
 
     }
+
+    @Issue("TAP5-2763")
+    @Test
+    public void 
blank_allowed_for_non_wrapper_types_if_enabled_throws_exception() {
+        openLinks("RequestParameter Annotation Demo", "Blank Allowed 
Non-Wrapper Throws Exception Link");
+
+        assertTextPresent("Coercion of to type java.time.LocalDateTime (via 
String --> java.time.LocalDateTime) failed: Text '' could not be parsed at 
index 0");
+    }
+
+    @Issue("TAP5-2763")
+    @Test
+    public void 
blank_allowed_for_non_wrapper_types_if_enabled_and_treat_as_null() {
+        openLinks("RequestParameter Annotation Demo", "Blank Allowed 
Non-Wrapper Treat As Null Link");
+
+        assertText("id=current", "-1");
+    }
+
 }
diff --git 
a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/RequestParameterDemo.java
 
b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/RequestParameterDemo.java
index 1556e86f6..e944ccf22 100644
--- 
a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/RequestParameterDemo.java
+++ 
b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/RequestParameterDemo.java
@@ -12,6 +12,11 @@
 
 package org.apache.tapestry5.integration.app1.pages;
 
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.util.Arrays;
+import java.util.List;
+
 import org.apache.tapestry5.ComponentResources;
 import org.apache.tapestry5.annotations.Persist;
 import org.apache.tapestry5.annotations.Property;
@@ -19,9 +24,6 @@ import org.apache.tapestry5.annotations.RequestParameter;
 import org.apache.tapestry5.http.Link;
 import org.apache.tapestry5.ioc.annotations.Inject;
 
-import java.util.Arrays;
-import java.util.List;
-
 public class RequestParameterDemo
 {
     private static final String PARAMETER_NAME = "gnip";
@@ -72,6 +74,16 @@ public class RequestParameterDemo
         return 
resources.createEventLink("emptyStringAllowed").addParameter(PARAMETER_NAME, 
"");
     }
 
+    public Link getBlankAllowedLinkNonWrapperThrowsException()
+    {
+        return 
resources.createEventLink("emptyStringAllowedNonWrapperThrowsException").addParameter(PARAMETER_NAME,
 "");
+    }
+
+    public Link getBlankAllowedLinkNonWrapper()
+    {
+        return 
resources.createEventLink("emptyStringAllowedNonWrapper").addParameter(PARAMETER_NAME,
 "");
+    }
+
     public Link getNullLink()
     {
         return resources.createEventLink(EVENT_NAME);
@@ -111,4 +123,20 @@ public class RequestParameterDemo
         }
 
     }
+
+    void onEmptyStringAllowedNonWrapperThrowsException(@RequestParameter(value 
= PARAMETER_NAME, allowBlank = true) LocalDateTime value)
+    {
+        this.value = -1;
+    }
+
+    void onEmptyStringAllowedNonWrapper(@RequestParameter(value = 
PARAMETER_NAME, allowBlank = true, treatBlankAsNull = true) LocalDateTime value)
+    {
+        if (value == null)
+        {
+            this.value = -1;
+        } else
+        {
+            this.value = (int) 
value.toInstant(ZoneOffset.UTC).getEpochSecond();
+        }
+    }
 }

Reply via email to