el-next@uel.java.net

Re: Proposal: ELProcessor

From: Pete Muir <pmuir_at_redhat.com>
Date: Fri, 9 Apr 2010 14:16:14 +0100

Sorry to come to this discussion so late, but I am now back from vacation.

This seems like a great start. A few things I noted whilst working through the discussion:

* My suggestion around listeners was to continue the trend, and allow easy customization of the EL environment by frameworks. I'm certainly not wedded to the idea. However CDI *does* need a way to "intercept" EL expression evaluation so that contexts can be set up and torn down around the evaluation. If we want to introduce this as a first class concept, then I'm quite happy to abandon the "extreme customization" of the EL environment through listeners. I'll start another topic for this.

* I agree with Christoph (I think ;-) that we should separate out the common concerns of a user of EL (which I'll call the API) from the common concerns of a someone incorporating EL into their "container (which I call the SPI). IMO the API should support the items you have discussed. To put these in one place, my list would be:

   -- evaluate a value expression (directly from a string)
   -- invoke a method expression (directly from a string)
   -- invoke a static method (directly from a string, however we decide to support this)
   -- add variables
   -- add function aliases (?)

And we leave to the SPI functionality such as:

   -- adjusting the ELParseContext, the ELEvaluationContext
   -- adding ELResolvers
   -- allowing expression evaluation to be intercepted

This should also make it much clearer what we intend people to use when.

* Kin-man raises the point of whether to use an interface or an abstract class. I would suggest we have two interfaces, one which contains the API contracts I outlined above, one which contains the SPI contracts. The built in utility class could implement both these interfaces.

* To support my view, Kin-man does point out that we have different views of what ELProcessor is. I, like Christoph, think it's name lends itself well to being the simple API that casual users work with. I would name the class which controls the EL environment (which is what Kin-man's proposal essentially does right? It is a single point at which the entire EL environment is "configured") something more like ELEnvironment. IOW

interface ELProcessor {}

interface ELEnvironment {}

class EL implements ELProcessor, ELEnvironment {}

For example, in a CDI environment, the user could then do:

@Inject ELProcessor elProcessor;

and wouldn't even see anything to do with setting ELResolvers etc. (which in a bean container 95% of people won't need to do).

Outside a bean container you would still be able to do:

public ELProcessor configureEL() {
  EL el = new EL();
  el.setELResolver(...);
  // and so on
  return el;
}

ELProcessor el = configureEL();
el.getValue();

WDYT?

* This could also be used to address the point that sometimes users want the full powered API, rather than the EL-lite API (e.g. deferred expressions), and could still access it via the ELEnvironment interface.

* BeanLookup seems like a great SPI class - it will make writing ELResolvers considerably simpler. For example, in CDI and Seam we have never needed to resolve anything other than the base, so if we could just impl BeanLookup, that would be great. But, I wonder if rather than making this a first class concept it would be neater to add this as a built in ELResolver (which I think you decided below anyway). In code:

addResolver(new ContainerManagedBeanResolver(new BeanLookup() {...}));

where ContainerManagedBeanResolver is the new built in resolver, and BeanLookup is as Kin-man proposed.

* Off topic, but something else to discuss is adding the ability to namespace the base inside EL rather than as an extension inside a bean container

HTH, and sorry for moving out of line!

Pete


On 19 Mar 2010, at 00:41, Kin-man Chung wrote:

>
>
> On 03/17/10 18:40, Christoph Beck wrote:
>> 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?
>
> It's just a place to get a default implementation of ELProcessor, if
> ELProcessor is an interface. I'm inclined to just make ELProcessor
> an utility class, to keep things simple.
>
>>
>>>>> 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);
>
> OK. We should continue our discussion in the other thread, about a more
> direct way of calling static functions.
>
>>
>>> 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);
>>
> You meant ELPRocessor, not ELProcessorBuilder?
>
>>>
>>>>> 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:
>>
> Yes. But top-level identifiers are the import one, and is what most
> users care, and it's also the ones that don't have a default ELResolver
> to resolve them.
>
>> 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?
>>
>
> I think setting a new BeanLookup can be implemented as
>
> processor.addELResolver(new BeanNameELResolver(blu));
>
> so 1. your BeanLookup will be gone,
> 2. by calling setBeanLookup again.
>
> Maybe we should rename it to addBeanLookup?
>
>>>
>>>> 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)
>
> I like IdentifierResolver. JSP used to have a VariableResolver with
> same functionality, but variable is already used in EL for something
> else.
>
>>>
>>>>> 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
>>
> OK.
>
>>> 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?
>>
> But your are assuming that the user wouldn't or shouldn't add variables
> or function mapper in between EL parsings/evaluations. I think setting
> variables to EL expression happens all the time. Also in JSP, a
> function mapper is created just before an expression is parsed, so that
> should be allowed also.
>
> I don't really see the need to have two classes (ELProcessBuild and
> ELPRocess) here.
>
>>>> ELProcessor buildProcessor();
>>>> }
>>>
>>> Now who builds ELProcessorBuilder?
>>>
>> I think this is a detail. It could also be a (utility) class.
>
> If there is only one implementation of it, it should just be a 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).
>
> But there is no javax.el.PropertyResolver! JSF has it though!
> How is this different from setELResolver()?
>
>>>
>>>> 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.
>>
> My main objection is that there are too many classes that the user has
> to use, and that variables, functions, ELResolvers cannot be added after
> ELContext is built. Let's discuss this more.
>
> Thanks
>
> -Kin-man
>
>> -- 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
>>>
>>
>>
>> ---------------------------------------------------------------------
>> 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
>