This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 9.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 8664b1601eb3d321f7d09f784644234bde6cfc8d 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 | 103 +++++++++++++++++++++++ 2 files changed, 123 insertions(+), 3 deletions(-) diff --git a/java/org/apache/el/parser/AstIdentifier.java b/java/org/apache/el/parser/AstIdentifier.java index 03d442b1fe..2a64dd786b 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)); } diff --git a/test/org/apache/el/parser/TestAstIdentifier.java b/test/org/apache/el/parser/TestAstIdentifier.java index 10d1906d09..550fd30948 100644 --- a/test/org/apache/el/parser/TestAstIdentifier.java +++ b/test/org/apache/el/parser/TestAstIdentifier.java @@ -16,10 +16,16 @@ */ package org.apache.el.parser; +import java.beans.FeatureDescriptor; +import java.util.Iterator; + +import javax.el.ELClass; import javax.el.ELContext; import javax.el.ELException; import javax.el.ELProcessor; +import javax.el.ELResolver; import javax.el.ExpressionFactory; +import javax.el.PropertyNotFoundException; import javax.el.ValueExpression; import org.junit.Assert; @@ -53,6 +59,103 @@ 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; + } + + @Override + public Iterator<FeatureDescriptor> getFeatureDescriptors(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]
