Fix bean property regression with RecordELResolver Enables JavaBean properties exposed on a Record to be accessed via EL
diff --git a/api/src/main/java/jakarta/el/RecordELResolver.java b/api/src/main/java/jakarta/el/RecordELResolver.java index 8c2e8e5..2f19259 100644 --- a/api/src/main/java/jakarta/el/RecordELResolver.java +++ b/api/src/main/java/jakarta/el/RecordELResolver.java
@@ -32,17 +32,23 @@ * <p> * {@code ELResolver}s are combined together using {@link CompositeELResolver}s to define rich semantics for evaluating * an expression. See the javadocs for {@link ELResolver} for details. - * + * * @since Jakarta Expression Language 6.0 */ 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. @@ -52,8 +58,6 @@ * 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 thrown while performing the property resolution. The thrown * exception must be included as the cause of this exception, if available. */ @@ -62,18 +66,17 @@ 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( - getExceptionMessageString(context, "propertyNotReadable", new Object[] { base.getClass().getName(), property.toString() })); + return null; } + context.setPropertyResolved(base, property); + try { return method.invoke(base); } catch (ELException ex) { @@ -89,12 +92,17 @@ /** - * 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. @@ -103,43 +111,43 @@ * @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( - getExceptionMessageString(context, "propertyNotReadable", new Object[] { base.getClass().getName(), property.toString() })); + 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. */ @@ -147,17 +155,16 @@ 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( - getExceptionMessageString(context, "propertyNotFound", new Object[] { base.getClass().getName(), property.toString() })); + return; } + context.setPropertyResolved(base, property); + throw new PropertyNotWritableException( getExceptionMessageString(context, "propertyNotWritable", new Object[] { base.getClass().getName(), property.toString() })); } @@ -165,35 +172,37 @@ /** - * 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( - getExceptionMessageString(context, "propertyNotFound", new Object[] { base.getClass().getName(), property.toString() })); + return false; } + context.setPropertyResolved(base, property); return true; } return false;