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]
