This Ada05 library provides the support for a simple Expression Language close to the Java Unified Expression Language (EL).
The API provided by the EL library is inspired from the Java Unified Expression Language shared by the JSP 2.1 and JSF 1.2 technologies. See Expression Language specification in JSR245 (https://jcp.org/en/jsr/summary?id=245)
The EL expression is intensively used in web development applications built on top of various Java technologies but also on top of Ada Web Application and Ada Server Faces.
- Fix compilation with GCC 13.1
- Fix support for EL expression with functions returning a bean object
- Fix Get_Value to handle the Invalid_Variable exception
alr with elada
Build with the following commands:
./configure
make
The samples can be built using:
gprbuild -Psamples
The unit tests are built using:
gprbuild -Ptests
And unit tests are executed with:
bin/el_harness
or just
make test
The JSP and JSF Unified Expression Language is used to give access to Java bean components within a presentation page (JSP, XHTML). For JSF the expression language creates a bi-directional binding where the value can be obtained when displaying a page but also modified (after a POST). The unified expression language is described in JSR-245 (See also Unified EL).
Example of presentation page:
<b>${user.firstName}</b>
The Ada EL is a library that implements the expression language and provides
an Ada binding to use it. The example below shows a code extract to
bind an Ada record Joe
to the name user
and evaluate the above expression.
Ctx : EL.Contexts.Default.Default_Context;
E : EL.Expressions.Expression;
Result : EL.Objects.Object;
...
E := EL.Expressions.Create_Expression ("${user.firstName}", Ctx);
...
-- Bind the context to 'Joe' and evaluate
Ctx.Set_Variable ("user", Joe);
Result := E.Get_Value (Ctx);
Unlike Java, Ada does not provide a root data type to represent other types (the java.lang.Object
). This makes the adaptation of Ada EL more difficult because
the expression language heavily relies on Java mechanisms (Object
type and introspection).
The EL.Objects
package provides the data type Object
that allows to manage
entities of different types. When an expression is evaluated, the result is
returned in an Object
. The record holds the value itself as well as a basic
type indicator (boolean, integer, float, string, wide wide string, ...).
The Object
is also used to provide variable values to the expression evaluator.
To create an Object
from a basic type, several To_Object
functions are provided.
Val : EL.Objects.Object := EL.Objects.Object.To_Object ("A string");
To get access to the value held by Object
, several To_
type functions are
provided:
S : constant String := EL.Objects.Object.To_String (Val);
The To_
type function will try to convert the value to the target type.
(See el-objects.ads and util-beans-objects.ads)
The EL.Beans
package defines two interfaces that allow to plug an Ada tagged
record to the expression evaluator. The Readonly_Bean
interface defines a unique
Get_Value
function that must be implemented. This function is called by
the expression context resolver to find the value associated with a property.
Basically, the Ada object will be defined as a variable and associated with
a name (for example user
). The Get_Value
function will be called with
the property name and the value must be returned as an Object
(for example firstName
).
For example:
type Person is new EL.Beans.Readonly_Bean with private;
-- Get the value identified by the name.
function Get_Value (From : Person; Name : String) return EL.Objects.Object;
The Bean
interface redefines the Readonly_Bean
to define the Set_Value
procedure.
This interface should be implemented when the expression evaluator has to modify
a value.
(See util-beans-basic.ads)
The expression language uses a context to give access to functions, variables
and resolve access to values. The ELContext
interface represent such context
and it gives access to:
- A function mapper that resolves function that the evaluator can invoke.
- A variable mapper that find the object associated with a name.
- A resolver that will resolve properties on objects.
The function mapper is used only when parsing an expression.
The variable mapper is used to find the variable object knowing its name.
For example it will resolve the name user
and return an instance of the
Readonly_Bean
interface (a Person
).
The resolver will resolve the variable to obtain the value from the property name.
The EL.Contexts
package defines the ELResolver
and ELContext
interfaces.
The EL.Contexts.Default
package provides default implementation of these interfaces.
(See el-contexts.ads)
The EL.Functions
package defines the Function_Mapper
interface that allows to
register functions for the expression parser. The evaluator will invoke the
functions directly (without the need of the Function_Mapper
).
A function can get from one to four arguments (this is pre-defined because Ada does
not support variable argument lists easily). Each argument is recieved as
an Object
. The function must returns an Object
value.
The function below returns the year part of a date. The date is retrieved as
an Ada.Calendar.Time
and the result will be returned as an integer.
function Year (Val : EL.Objects.Object) return EL.Objects.Object is
Date : constant Ada.Calendar.Time := To_Time (Val);
begin
return To_Object (Ada.Calendar.Formatting.Year (Date));
end Format;
The function will be registered as follows:
Fm : constant EL.Functions.Function_Mapper_Access
:= new EL.Functions.Default.Default_Function_Mapper;
...
Fm.Set_Function ("year", Year'Access);
(See el-functions.ads)
The EL.Variables
package defines the VariableMapper
interface and
the EL.Variables.Default
package provides a default implementation.
The VariableMapper
allows to bind a name to an Ada object that implements
the EL.Beans.Readonly_Bean
or EL.Beans.Bean
interfaces (in Java, one would
be able to use any Java object).
Joe : constant Person_Access := Create_Person ("Joe", "Smith", 12);
Ctx.Set_Variable ("user", Joe);
(See el-variables.ads)
The EL.Expressions
package is the main package to parse and evaluate expressions.
An expression string is parsed using the Create_Expression
function which
returns an Expression
record. The expression is parsed only once and it
can be evaluated several times. The expression context is used only to get
access to the function mapper.
E : EL.Expressions.Expression := Create_Expression ("${user.firstName}", Ctx);
The expression is evaluated using the Get_Value
function. The evaluation is made
on the expression context which gives access to the variables and the resolver.
The expression context should be a per-thread object. The expression can be
shared by several threads and evaluated at the same time.
Val : EL.Objects.Object := E.Get_Value (Ctx);
(See el-expressions.ads)