el-next@uel.java.net

Re: Proposal: ELProcessor

From: Christoph Beck <beck_at_odysseus.de>
Date: Thu, 18 Mar 2010 02:40:36 +0100

Kin-man Chung wrote:
>
>
> On 03/15/10 15:41, Christoph Beck wrote:
>> Kin-man Chung wrote:
>>> From the discussions we;ve had so far, it has been clear that we need a
>>> simpler API for getting an ExpressionFactory and ELContext, outside of
>>> JSP or
>>> JSF. We'll need an object to setup a defualt for ELContext
>>> ELResolvers, etc. I'll call it ELProcessor, because it also can be
>>> used to simplify EL parsing and evaluations.
>>>
>>> Here's my tentative proposal. I'm trying to keep it simple and use
>>> a design that seems to fit in the current EL. Therefore there are no
>>> CDI extensions, or listeners. :-)
>>>
>>> Use example:
>>>
>>> ELProcessor elp = new ELProcessor();
>>> boolean t = elp.getValue("${'2 > 1'}", Boolean.class);
>>>
>>> An alternative is to make ELProcessor an interface instead of a
>>> concrete class, and use a ELFactory to create an instance of
>>> ELProcessor, as suggested by Pete in one of his mails.
>>>
>> So far I'm with you. However, I would try to avoid changing the existing
>> ELFactory API. The processor stuff should be a new layer on top of the
>> existing API without cyclic dependencies between the two layers.
>>
> But I'm not talking about ExpressionFactory, which I agree that we
> should not change for this purpose.
>
OK, I think I missed something... what is ELFactory?

>>> interface ELProcessor {
>>>
>>> ExpressionFactory getExpressionFactory();
>>> void setExpressionFactory(ExpressionFactory ef); // Note 2
>>>
>>> ELParseContext getELParseContext();
>>> void setELParseContext(ELParseContext elpc);
>>>
>>> ELEvaluationContext getELEvaluationContext();
>>> void setELEvaluationContext(ELEvaluationContext elec);
>>>
>>> ELResolver getELResolver();
>>> void setELResolver(ELResolver elr);
>>>
>>> // May also need getter/setters for function mapper and variables
>>>
>>> // Add an user defined ELResolver
>>> void addELResolver(ELResolver elr);
>>>
>>> // See note 4 below for BeanLookup
>>> void setBeanLookup(BeanLookup blu);
>>>
>>> ValueExpression createValueExpression(String expression,
>>> Class expectedType);
>>> Object getValue(String expression);
>>> Object getValue(String expression,
>>> expectedReturnType);
>>> MethodExpression createMethodExpression(String expression,
>>> Class expectedReturnType,
>>> Class[] extectedParamTypes);
>>> Object invoke(String expression, Object[] params, Class returnType);
>>> }
>>>
>> It may be a good idea to identifiy classes from the API that are pretty
>> "useless" to the end user. The ELProcessor should hide these classes
>> rather than expose them. More or less "useless" classes are:
>> - FunctionMapper (the user doesn't want to resolve functions; she wants
>> to add methods)
>> - VariableMapper (the user doesn't want to resolve variables; she might
>> want to add variables, but maybe it's better to have a setVariable(...)
>> method for this)
>> - ELContext (the user doesn't want to resolve properties, etc)
>> - ExpressionFactory (the user evaluates expressions through the
>> ELProcessor API and the ExpressionFactory requires an ELContext at hand)
>> - ValueExpression, MethodExpression (same as above)
>> So, I would suggest that these classes are candidates to _not_ appear in
>> the ELProcessor interface.
>>
> I agree with you in spirit about information hiding, but the ultimate
> question is really this: what is ELProcess? Is it just for
> casual users who would only want to evalute EL expressions, but nothing
> else, or is it something that allows for full EL customization? You
> seems to think it's the former, while I the later.
>
I was thinking of a simplified API which allows intuitive use of the EL,
supporting functions, variables and custom resolvers.

> For instance, if we take away FunctionMapper, then EL functions cannot
> be used in the expression. I want to allow the user to be able to
> supply her own FunctionMapper, so that EL functions can be used.
>
ELProcessorBuilder.addFunction(String name, Method method);

> Also, we should allow for deferred EL expressions, in that EL parsing
> and evaluation do not happen at the same time. Would you disallow this
> also?
>
OK, I omitted deferred evaluation to be able to hide the "useless"
classes and to reduce complexity. I think most users are "casual" users
and do not need it. But if I think of this again, there should be a way
to do this. I would let the user fall back to the core API somehow, e.g.

ELProcessorBuilder.createValueExpression(String expression, Class<T> type);

>
>>> Notes:
>>>
>>> 1. The life time of ELProcessor instances should be controlled by
>>> the application, not EL. The application can use a single instance
>>> of ELProcessor for the entire application, or use an instance of
>>> shorter life. For instance, JSP and JSF applications can use one
>>> for application, and one in each of the servlet listeners.
>>>
>> Yes.
>>
>>> 2. I am not sure we need or even should allow setter for
>>> ExpressionFactory.
>>
>> I'm not sure we need a getter.
>>>
>>> 3. Objects in ELProcessor has the following defaults:
>>>
>>> a. ExpressionFactory: singleton from ExpressionFactory.newInstance().
>>> b. ELParseContext: with null function mapper and variable mapper.
>>> c. ELEvalutaionContext: with the default ELResolver, below
>>> d. ELResolver: a composite ELResolver, consists of:
>>> MapELResolver,
>>> ResourceBundleELResolver,
>>> ListELResolver,
>>> ArrayELResolver,
>>> BeanELResolver,
>>> BeanNameELResolver (proposed, see below)
>>>
>>> 4. Expressions such as #{foo.bar} will need a way to resolve "foo". I
>>> think
>>> bean discovery is the job of the bean containers, and not EL.
>>> Therefore I
>>> propose the following:
>>>
>>> interface BeanLookup {
>>> Object getBean(String name);
>>> }
>>>
>> We already have ELResolver and ELResolver.getValue(context, null,
>> property) does exactly this, so why invent a new thing? Also, the name
>
> Writing your own ELResolver is definitely non-trivial, not that it is
> technically hard, but that it requires a lot of typing and boiler-plate
> filling. This is one of the things people have been complaining
> about. BeanLoopup and BeanNameELResolver are proposed to solve this
> problem.

But this only addresses top-level identifiers, a special case. Maybe we
can find something more general. Btw, I see some problems with BeanLookup:

1. side effects:
...
processor.setBeanLookup(BeanLookup blu);
processor.setELResolver(ELResolver elr);
...
Is my BeanLookup still there or kicked away?

2. how can I have multiple root resolvers?

>
>> BeanLookup could be misleading as this would be used to resolve any
>> top-level identifiers. Not sure if it's worth having this.
>>
> BeanNameLoopup? getTopLevelBeans? Arg, I'm no good with names! :-)

IdentifierResolver, RootPropertyResolver, TopLevelResolver, ...
(anything without "Bean" in it)
>
>>> The method ELProcessor.setBeanLookup() let the application or container
>>> specify how to lookup a bean with the registered name.
>>>
>>> As an example, in the servlet listener, EL can be used to evaluate
>>> expressions involving an request scope object "foo":
>>>
>>> ELProcessor elp;
>>> elp.setBeanLookup(new BeanLookup {
>>> Object getBean(String name) {
>>> return request.getAttribute(name);
>>> }
>>> });
>>> elp.getValue("#{foo.bar}");
>>>
>>> BeanNameELResolver is a (proposed) ELResolver that uses the BeanLookup
>>> instance to resolve beans with names.
>>
>> OK, currently I have no clear idea of how to make resolving
>> identifiers/properties easier. This definitely needs further discussion.
>>>
> OK.
>
>>> 5. Of course we can also have different ELProcessors with slightly
>>> different
>>> defaults. The question is whether we need to specify them in the
>>> EL api, or just left as an implementation for the bean container.
>>> Ideally, the container should generate the appropiate ELProcessor
>>> depending
>>> on life cycle events of the applications, but that'll require tight
>>> coupling between EL and the container.
>>>
>>>
>>> Comments?
>>>
>> Here's what I could think of:
>>
>> interface ELProcessorBuilder {
>> void addVariable(String name, String expression);
>> void addVariable(String name, Object value);
>> void addFunction(String name, Method method);
>
> How are these for? Let me guess. Maybe I can do
>
> elProcessorBuilder.addVariable("x", "#{myBean}");
> elProcessor.getValue("#{x.foo}"); // returns myBean.foo ?

Yes.
>
> but how do I use the other two methods?
>
Sorry. The ELProcessorBuilder configures an EL parser with variables and
functions. It provides an ELParseContext to the ELProcessor it creates.

- ELProcessorBuilder.addVariable(String name, Object value) adds a
constant (delegates to ExpressionFactory.createValueExpression(Object
instance, Class<?> expectedType))
- ELProcessorBuilder.addFunction(String name, Method method) adds a
static method as a function

> Also, why are they not also in ELProcessor?
>
Once it has created an ELProcessor from it, subsequent changes to the
ELProcessorBuilder (e.g. adding funtions) do not affect the ELProcessor.
Btw, should I have called the ELProcessorBuilder ELFactory?

>> ELProcessor buildProcessor();
>> }
>
> Now who builds ELProcessorBuilder?
>
I think this is a detail. It could also be a (utility) class.

>>
>> interface ELProcessor {
>> void setPropertyResolver(PropertyResolver resolver);
> What is this?

What do you mean? This method is used to set the
javax.el.PropertyResolver (often an instance of CompositeELResolver).
>
>> void setTypeConverter(TypeConverter converter);
>>
>> Object getValue(String expression, Class<?> type);
>> void setValue(String expression, Object value);
>
> Yes, I think this can be useful.
>
>> Object invoke(String expression, Object[] params, Class<?> returnType);
>> }
>>
>> class DefaultProcessorBuilder implements ELProcessorBuilder {
>> // convenience implementation
>> }
>>
> This discussion is useful, but we'll need to get into more details. It
> helps to include some examples for using new proposed features, to make
> things clearer.
>
> -Kin-man

Revised proposal...

/*
 * Build parse context with variables and functions. No need to
implement FunctionMapper and VariableResolver.
 * I'm proposing this as an interface, but it could also be an abstract
class or even a utility class. At least there should be a redy-to use
default implementation.
 */
interface ParseContextBuilder {
   void addVariable(String name, String expression); // add a value
expression as a variable, e.g. processor.addVariable("zero", "${0}");
   void addVariable(String name, Object value); // add a constant as a
variable, e.g. processor.addVariable("zero", 0);
   void addFunction(String name, Method method); // add static public
method as a function, e.g. processor.addFunction("math:sqrt",
Math.class.getMethod("sqrt", double.class));

   ELParseContext buildParseContext(); // create a parse context
configured with variables and functions from this builder.
}

It would be great to have something similar for an ELEvaluationContext
(resolvers and converter).

/*
 * Build evaluation context with resolvers and converter.
 * If no converter is set, a default implementation is used
 * If no resolver is set, a default is used
 * I'm proposing this as an interface, but it could also be an abstract
class or even a utility class. At least there should be a redy-to use
default implementation.
 */
interface EvaluationContextBuilder {
   void setPropertyResolver(PropertyResolver resolver); // set the
resolver; TODO: replace by methods to add simplified root/property
resolvers; needs discussion
   void setTypeConverter(TypeConverter converter); // set the type
converter used during evaluation

   ELEvaluationContext buildEvaluationContext(); // create evaluation
context configured with property resolver and type converter from this
builder
}

This does not clash with the ELProcessor but takes some responsibility
from it, i.e getELResolver(), setELResolver(ELResolver elr),
addELResolver(ELResolver elr), setBeanLookup(BeanLookup blu), as well as
methods to set/get FunctionMapper and VariableMapper could be dropped.

-- Christoph
>>
>>> ---------------------------------------------------------------------
>>> To unsubscribe, e-mail: el-next-unsubscribe_at_uel.dev.java.net
>>> For additional commands, e-mail: el-next-help_at_uel.dev.java.net
>>>
>>
>>
>> ---------------------------------------------------------------------
>> To unsubscribe, e-mail: el-next-unsubscribe_at_uel.dev.java.net
>> For additional commands, e-mail: el-next-help_at_uel.dev.java.net
>>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: el-next-unsubscribe_at_uel.dev.java.net
> For additional commands, e-mail: el-next-help_at_uel.dev.java.net
>