This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 11.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit dddc67941ceed67f2773931053ee9fc2fd9b1d69 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 | 95 ++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 3 deletions(-) diff --git a/java/org/apache/el/parser/AstIdentifier.java b/java/org/apache/el/parser/AstIdentifier.java index ab6944d8f2..35b26b9014 100644 --- a/java/org/apache/el/parser/AstIdentifier.java +++ b/java/org/apache/el/parser/AstIdentifier.java @@ -48,10 +48,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 e2bf237985..ff43028094 100644 --- a/test/org/apache/el/parser/TestAstIdentifier.java +++ b/test/org/apache/el/parser/TestAstIdentifier.java @@ -16,10 +16,13 @@ */ package org.apache.el.parser; +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; @@ -53,6 +56,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]
