This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit af5e7f78aa8583a8f61c4c763162ad0cecffcb7d Author: opencode <[email protected]> AuthorDate: Sun Oct 18 14:55:25 2026 +0200 Resolve imported identifiers in AstIdentifier.getType() AstIdentifier evaluated an identifier that no resolver handled by falling back to the ImportHandler: a name matching an imported class yielded an ELClass and a name matching a statically imported field yielded the field value. getType() had no equivalent fallback, so for these expressions getValue() succeeded while getType() reported PropertyNotFoundException. Since ValueExpression.getType() is the API used to determine the result type before evaluating, the gap made import-resolved identifiers unusable for any client that inspects the type first. Mirror the fallback in getType(). A class import reports ELClass rather than Class, since that is the type of the value getValue() returns and ELClass is not itself a Class, which keeps the reported type assignable from the value. A static field import reports the declared type of the field, matching how a bean property of the same type is reported. The check stays behind the resolver so a resolver that handles the name still takes precedence, as it does when getting the value, and a name that resolves nothing continues to report PropertyNotFoundException. Adds tests for both import kinds, for the resolver precedence and for the unchanged error for unresolvable names. The import tests fail against the previous implementation. --- java/org/apache/el/parser/AstIdentifier.java | 23 +++++- test/org/apache/el/parser/TestAstIdentifier.java | 94 ++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 3 deletions(-) diff --git a/java/org/apache/el/parser/AstIdentifier.java b/java/org/apache/el/parser/AstIdentifier.java index 56330d5125..f6fffb9e31 100644 --- a/java/org/apache/el/parser/AstIdentifier.java +++ b/java/org/apache/el/parser/AstIdentifier.java @@ -49,10 +49,27 @@ public final class AstIdentifier extends SimpleNode { } ctx.setPropertyResolved(false); Class<?> result = ctx.getELResolver().getType(ctx, null, this.image); - if (!ctx.isPropertyResolved()) { - throw new PropertyNotFoundException(MessageFactory.get("error.resolver.unhandled.null", this.image)); + if (ctx.isPropertyResolved()) { + return result; } - return result; + + // Import + Object imported = ctx.getImportHandler().resolveClass(this.image); + if (imported != null) { + // getValue() returns an ELClass for an imported class + return ELClass.class; + } + imported = ctx.getImportHandler().resolveStatic(this.image); + if (imported != null) { + // getValue() returns the value of a statically imported field + try { + return ((Class<?>) imported).getField(this.image).getType(); + } catch (IllegalArgumentException | NoSuchFieldException | SecurityException e) { + throw new ELException(e); + } + } + + throw new PropertyNotFoundException(MessageFactory.get("error.resolver.unhandled.null", this.image)); } @Override diff --git a/test/org/apache/el/parser/TestAstIdentifier.java b/test/org/apache/el/parser/TestAstIdentifier.java index e3a3c5f672..46b0c5e5df 100644 --- a/test/org/apache/el/parser/TestAstIdentifier.java +++ b/test/org/apache/el/parser/TestAstIdentifier.java @@ -19,11 +19,13 @@ package org.apache.el.parser; import java.util.ArrayList; import java.util.List; +import jakarta.el.ELClass; import jakarta.el.ELContext; import jakarta.el.ELException; import jakarta.el.ELProcessor; import jakarta.el.ELResolver; import jakarta.el.ExpressionFactory; +import jakarta.el.PropertyNotFoundException; import jakarta.el.ValueExpression; import org.junit.Assert; @@ -101,6 +103,98 @@ public class TestAstIdentifier { } + @Test + public void testImport03() { + // getType() must resolve an imported class, just as getValue() does + ELProcessor processor = new ELProcessor(); + ELContext context = processor.getELManager().getELContext(); + ValueExpression ve = processor.getELManager().getExpressionFactory().createValueExpression(context, + "${Integer}", Object.class); + Assert.assertNotNull(ve.getValue(context)); + Assert.assertEquals(ELClass.class, ve.getType(context)); + } + + + @Test + public void testImport04() { + // getType() must resolve a statically imported field, just as + // getValue() does. The declared type of the field is reported, in the + // same way as for a bean property. + ELProcessor processor = new ELProcessor(); + processor.getELManager().getELContext().getImportHandler().importStatic( + "java.lang.Integer.MAX_VALUE"); + ELContext context = processor.getELManager().getELContext(); + ValueExpression ve = processor.getELManager().getExpressionFactory().createValueExpression(context, + "${MAX_VALUE}", Object.class); + Assert.assertEquals(Integer.valueOf(Integer.MAX_VALUE), ve.getValue(context)); + Assert.assertEquals(Integer.TYPE, ve.getType(context)); + } + + + @Test + public void testImport05() { + // The resolver chain must take precedence over the import fallback in + // getType(), just as it does in getValue(). "Integer" is resolvable + // both ways: by the custom resolver and via the default class import. + ELProcessor processor = new ELProcessor(); + processor.getELManager().addELResolver(new ELResolver() { + @Override + public Object getValue(ELContext context, Object base, Object property) { + if (base == null && "Integer".equals(property)) { + context.setPropertyResolved(null, property); + return "resolved"; + } + return null; + } + + @Override + public Class<?> getType(ELContext context, Object base, Object property) { + if (base == null && "Integer".equals(property)) { + context.setPropertyResolved(null, property); + return String.class; + } + return null; + } + + @Override + public void setValue(ELContext context, Object base, Object property, Object value) { + } + + @Override + public boolean isReadOnly(ELContext context, Object base, Object property) { + return false; + } + + @Override + public Class<?> getCommonPropertyType(ELContext context, Object base) { + return null; + } + }); + ELContext context = processor.getELManager().getELContext(); + ValueExpression ve = processor.getELManager().getExpressionFactory().createValueExpression(context, + "${Integer}", Object.class); + Assert.assertEquals("resolved", ve.getValue(context)); + Assert.assertEquals(String.class, ve.getType(context)); + } + + + @Test + public void testUnresolvedGetType() { + // A name that getValue() cannot resolve must continue to report a + // PropertyNotFoundException from getType() + ELProcessor processor = new ELProcessor(); + ELContext context = processor.getELManager().getELContext(); + ValueExpression ve = processor.getELManager().getExpressionFactory().createValueExpression(context, + "${unresolvedName}", Object.class); + try { + ve.getType(context); + Assert.fail("A PropertyNotFoundException was expected"); + } catch (PropertyNotFoundException e) { + // Expected + } + } + + @Test public void testIdentifierStart() { /* --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
