This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/main by this push:
new 1b2785c63c Fix access to Bean like properties on a Record
1b2785c63c is described below
commit 1b2785c63ca0e0f9a242745b16f26924b5488ddd
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 94fda8a5ca..09fea371bc 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -819,6 +819,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]