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

markt-asf pushed a commit to branch 11.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit 8e496e888a954106507a6db297affbc43654f495
Author: Mark Thomas <[email protected]>
AuthorDate: Mon Nov 2 17:12:13 2026 +0000

    Fix access to Bean like properties on a Record
---
 java/jakarta/el/RecordELResolver.java     | 93 +++++++++++++++++--------------
 test/jakarta/el/TestRecordELResolver.java | 17 ++++++
 test/jakarta/el/TesterBeanC.java          | 22 ++++++++
 test/jakarta/el/TesterBeanCImpl.java      | 25 +++++++++
 webapps/docs/changelog.xml                |  5 ++
 5 files changed, 120 insertions(+), 42 deletions(-)

diff --git a/java/jakarta/el/RecordELResolver.java 
b/java/jakarta/el/RecordELResolver.java
index 01b4dc6792..862f9e3b02 100644
--- a/java/jakarta/el/RecordELResolver.java
+++ b/java/jakarta/el/RecordELResolver.java
@@ -43,11 +43,17 @@ public class RecordELResolver extends ELResolver {
     }
 
     /**
-     * If the base object is an instance of {@link Record}, returns the value 
of the given field of this {@link Record}.
+     * If the base object is an instance of {@link Record} and a no-arg method 
exists with the name of the property,
+     * returns the value of the given property of this {@link Record}.
      * <p>
-     * If the base object is an instance of {@link Record}, the {@code 
propertyResolved} property of the provided
-     * {@link ELContext} must be set to {@code true} by this resolver before 
returning. If this property is not {@code
-     * true} after this method is called, the caller should ignore the return 
value.
+     * If the base object is an instance of {@link Record} and no-arg method 
exists with the name of the property, the
+     * {@code propertyResolved} property of the provided {@link ELContext} 
must be set to {@code true} by this resolver
+     * before returning. If this property is not {@code true} after this 
method is called, the caller should ignore the
+     * return value.
+     * <p>
+     * The {@link Record} may include properties using JavaBean naming 
conventions for which no-arg methods do not
+     * exist. Such properties will not be discovered by this resolver. In 
typical usage, such properties would be
+     * resolved by the next resolver in the chain, the {@code BeanELResolver}.
      *
      * @param context  The context of this evaluation.
      * @param base     The {@link Record} on which to get the property.
@@ -57,8 +63,6 @@ public class RecordELResolver extends ELResolver {
      *             the value of the given property. Otherwise, undefined.
      *
      * @throws NullPointerException      if the provided {@link ELContext} is 
{@code null}.
-     * @throws PropertyNotFoundException if the {@code base} is an instance of 
{@link Record} and the specified property
-     *                                       does not exist.
      * @throws ELException               if an exception was throws while 
performing the property resolution. The thrown
      *                                       exception must be included as the 
cause of this exception, if available.
      */
@@ -67,18 +71,17 @@ public class RecordELResolver extends ELResolver {
         Objects.requireNonNull(context);
 
         if (base instanceof Record && property != null) {
-            context.setPropertyResolved(base, property);
-
             String propertyName = property.toString();
 
             Method method;
             try {
                 method = base.getClass().getMethod(propertyName);
             } catch (NoSuchMethodException nsme) {
-                throw new PropertyNotFoundException(
-                        Util.message(context, "propertyNotFound", 
base.getClass().getName(), propertyName), nsme);
+                return null;
             }
 
+            context.setPropertyResolved(base, property);
+
             try {
                 return method.invoke(base);
             } catch (ReflectiveOperationException e) {
@@ -91,12 +94,17 @@ public class RecordELResolver extends ELResolver {
 
 
     /**
-     * If the base object is an instance of {@link Record}, always returns 
{@code null} since {@link Record}s are always
-     * read-only.
+     * If the base object is an instance of {@link Record} and a no-arg method 
exists with the name of the property,
+     * returns {@code null} since {@link Record}s are always read-only.
      * <p>
-     * If the base object is an instance of {@link Record}, the {@code 
propertyResolved} property of the provided
-     * {@link ELContext} must be set to {@code true} by this resolver before 
returning. If this property is not {@code
-     * true} after this method is called, the caller should ignore the return 
value.
+     * If the base object is an instance of {@link Record} and no-arg method 
exists with the name of the property, the
+     * {@code propertyResolved} property of the provided {@link ELContext} 
must be set to {@code true} by this resolver
+     * before returning. If this property is not {@code true} after this 
method is called, the caller should ignore the
+     * return value.
+     * <p>
+     * The {@link Record} may include properties using JavaBean naming 
conventions for which no-arg methods do not
+     * exist. Such properties will not be discovered by this resolver. In 
typical usage, such properties would be
+     * resolved by the next resolver in the chain, the {@code BeanELResolver}.
      *
      * @param context  The context of this evaluation.
      * @param base     The {@link Record} to analyze.
@@ -105,43 +113,43 @@ public class RecordELResolver extends ELResolver {
      * @return Always {@code null}
      *
      * @throws NullPointerException      if the provided {@link ELContext} is 
{@code null}.
-     * @throws PropertyNotFoundException if the {@code base} is an instance of 
{@link Record} and the specified property
-     *                                       does not exist.
      */
     @Override
     public Class<?> getType(ELContext context, Object base, Object property) {
         Objects.requireNonNull(context);
         if (base instanceof Record && property != null) {
-            context.setPropertyResolved(base, property);
-
             String propertyName = property.toString();
 
             try {
                 base.getClass().getMethod(propertyName);
             } catch (NoSuchMethodException nsme) {
-                throw new PropertyNotFoundException(
-                        Util.message(context, "propertyNotFound", 
base.getClass().getName(), propertyName), nsme);
+                return null;
             }
+
+            context.setPropertyResolved(base, property);
         }
         return null;
     }
 
 
     /**
-     * If the base object is an instance of {@link Record}, always throws an 
exception since {@link Record}s are
-     * read-only.
+     * If the base object is an instance of {@link Record} and a no-arg method 
exists with the name of the property,
+     * throws an exception since {@link Record}s are read-only.
      * <p>
-     * If the base object is an instance of {@link Record}, the {@code 
propertyResolved} property of the provided
-     * {@link ELContext} must be set to {@code true} by this resolver before 
returning. If this property is not {@code
-     * true} after this method is called, the caller should ignore the return 
value.
+     * If the base object is an instance of {@link Record} and no-arg method 
exists with the name of the property, the
+     * {@code propertyResolved} property of the provided {@link ELContext} 
must be set to {@code true} by this resolver
+     * before returning. If this property is not {@code true} after this 
method is called, the caller should ignore the
+     * return value.
+     * <p>
+     * The {@link Record} may include properties using JavaBean naming 
conventions for which no-arg methods do not
+     * exist. Such properties will not be discovered by this resolver. In 
typical usage, such properties would be
+     * resolved by the next resolver in the chain, the {@code BeanELResolver}.
      *
      * @param context  The context of this evaluation.
      * @param base     The {@link Record} on which to set the property.
      * @param property The name of the property to set. Will be coerced to a 
String.
      *
      * @throws NullPointerException         if the provided {@link ELContext} 
is {@code null}.
-     * @throws PropertyNotFoundException    if the {@code base} is an instance 
of {@link Record} and the specified
-     *                                          property does not exist.
      * @throws PropertyNotWritableException if the {@code base} is an instance 
of {@link Record} and the specified
      *                                          property exists.
      */
@@ -149,17 +157,16 @@ public class RecordELResolver extends ELResolver {
     public void setValue(ELContext context, Object base, Object property, 
Object value) {
         Objects.requireNonNull(context);
         if (base instanceof Record && property != null) {
-            context.setPropertyResolved(base, property);
-
             String propertyName = property.toString();
 
             try {
                 base.getClass().getMethod(propertyName);
             } catch (NoSuchMethodException nsme) {
-                throw new PropertyNotFoundException(
-                        Util.message(context, "propertyNotFound", 
base.getClass().getName(), propertyName), nsme);
+                return;
             }
 
+            context.setPropertyResolved(base, property);
+
             throw new PropertyNotWritableException(
                     Util.message(context, "resolverNotWritable", 
base.getClass().getName()));
         }
@@ -167,35 +174,37 @@ public class RecordELResolver extends ELResolver {
 
 
     /**
-     * If the base object is an instance of {@link Record}, always returns 
{@code true}.
+     * If the base object is an instance of {@link Record} and a no-arg method 
exists with the name of the property,
+     * returns {@code true}.
      * <p>
-     * If the base object is an instance of {@link Record}, the {@code 
propertyResolved} property of the provided
-     * {@link ELContext} must be set to {@code true} by this resolver before 
returning. If this property is not {@code
-     * true} after this method is called, the caller should ignore the return 
value.
+     * If the base object is an instance of {@link Record} and no-arg method 
exists with the name of the property, the
+     * {@code propertyResolved} property of the provided {@link ELContext} 
must be set to {@code true} by this resolver
+     * before returning. If this property is not {@code true} after this 
method is called, the caller should ignore the
+     * return value.
+     * <p>
+     * The {@link Record} may include properties using JavaBean naming 
conventions for which no-arg methods do not
+     * exist. Such properties will not be discovered by this resolver. In 
typical usage, such properties would be
+     * resolved by the next resolver in the chain, the {@code BeanELResolver}.
      *
      * @param context  The context of this evaluation.
      * @param base     The {@link Record} to analyze.
      * @param property The name of the property to analyze. Will be coerced to 
a String.
      *
      * @throws NullPointerException      if the provided {@link ELContext} is 
{@code null}.
-     * @throws PropertyNotFoundException if the {@code base} is an instance of 
{@link Record} and the specified property
-     *                                       does not exist.
      */
     @Override
     public boolean isReadOnly(ELContext context, Object base, Object property) 
{
         Objects.requireNonNull(context);
         if (base instanceof Record && property != null) {
-            context.setPropertyResolved(base, property);
-
             String propertyName = property.toString();
 
             try {
                 base.getClass().getMethod(propertyName);
             } catch (NoSuchMethodException nsme) {
-                throw new PropertyNotFoundException(
-                        Util.message(context, "propertyNotFound", 
base.getClass().getName(), propertyName), nsme);
+                return false;
             }
 
+            context.setPropertyResolved(base, property);
             return true;
         }
         return false;
diff --git a/test/jakarta/el/TestRecordELResolver.java 
b/test/jakarta/el/TestRecordELResolver.java
index 22151eb8b5..d5c65dbeae 100644
--- a/test/jakarta/el/TestRecordELResolver.java
+++ b/test/jakarta/el/TestRecordELResolver.java
@@ -72,4 +72,21 @@ public class TestRecordELResolver {
 
         Assert.assertEquals(LONG_DATA, result.longValue());
     }
+
+
+    @Test
+    public void testRecordFallBackToBean() {
+        ExpressionFactory factory = ExpressionFactory.newInstance();
+        StandardELContext context = new StandardELContext(factory);
+
+        TesterBeanCImpl beanC = new TesterBeanCImpl();
+
+        ValueExpression varBeanC = factory.createValueExpression(beanC, 
TesterBeanCImpl.class);
+        context.getVariableMapper().setVariable("beanC", varBeanC);
+
+        ValueExpression ve = factory.createValueExpression(context, 
"${beanC.valid}", Boolean.class);
+        Boolean result = ve.getValue(context);
+
+        Assert.assertEquals(Boolean.TRUE, result);
+    }
 }
diff --git a/test/jakarta/el/TesterBeanC.java b/test/jakarta/el/TesterBeanC.java
new file mode 100644
index 0000000000..4ba7675e82
--- /dev/null
+++ b/test/jakarta/el/TesterBeanC.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 jakarta.el;
+
+public interface TesterBeanC {
+
+    boolean isValid();
+}
diff --git a/test/jakarta/el/TesterBeanCImpl.java 
b/test/jakarta/el/TesterBeanCImpl.java
new file mode 100644
index 0000000000..d5c41793fe
--- /dev/null
+++ b/test/jakarta/el/TesterBeanCImpl.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 jakarta.el;
+
+public record TesterBeanCImpl() implements TesterBeanC {
+
+    @Override
+    public boolean isValid() {
+        return true;
+    }
+}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 7f10041dd8..99559d184d 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -596,6 +596,11 @@
         keeps the parameters of each tag to itself and never touches page
         scope attributes. (remm)
       </fix>
+      <fix>
+        <code>RecordELResolver</code> no longer prevents
+        <code>BeanELResolver</code> from resolving bean-style properties (such
+        as <code>isValid()</code>) on a <code>Record</code>. (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Cluster">


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to