-
Notifications
You must be signed in to change notification settings - Fork 6
Bridje EL
Java Unified Expression Language (JUEL) is an implementation of the Unified Expression Language (EL), specified as part of the JSP 2.1 standard (JSR-245), which has been introduced in JEE5. Additionally, JUEL 2.2 implements the JSP 2.2 maintenance release specification for full JEE6 compliance.
Once, the EL started as part of JSTL. Then, the EL made its way into the JSP 2.0 standard. Now, though part of JSP 2.1, the EL API has been separated into package javax.el and all dependencies to the core JSP classes have been removed. In other words: the EL is ready for use in non-JSP applications!
#Features
JUEL provides a lightweight and efficient implementation of the Unified Expression Language.
- High Performance – Parsing expressions is certainly the expected performance bottleneck. JUEL uses a hand-coded parser which is up to 10 times faster than the previously used (javacc) generated parser! Once built, expression trees are evaluated at highest speed.
- Pluggable Cache – Even if JUELs parser is fast, parsing expressions is relative expensive. Therefore, it's best to parse an expression string only once. JUEL provides a default caching mechanism which should be sufficient in most cases. However, JUEL allows to plug in your own cache easily.
- Small Footprint – JUEL has been carefully designed to minimize memory usage as well as code size.
- Method Invocations – JUEL supports method invocations as in ${foo.matches('[0-9]+')} . Methods are resolved and invoked using the EL's resolver mechanism. As of JUEL 2.2, method invocations are enabled by default.
- VarArg Calls – JUEL supports Java 5 VarArgs in function and method invocations. E.g., binding String.format(String, String...) to function format allows for ${format('Hey %s','Joe')} . As of JUEL 2.2, VarArgs are enabled by default.
- Pluggable – JUEL can be configured to be transparently detected as EL implementation by a Java runtime environment or JEE application server. Using JUEL does not require an application to explicitly reference any of the JUEL specific implementation classes.
#Basic Classes
1-Expression Factory.
To start using the EL, you need an instance of javax.el.ExpressionFactory . The expression factory is used to create expressions of various types.
JUEL's expression factory implementation is de.odysseus.el.ExpressionFactoryImpl . The easiest way to obtain an expression factory instance is:
javax.el.ExpressionFactory factory = new de.odysseus.el.ExpressionFactoryImpl();
An expression factory is thread-safe and can create an unlimited number of expressions. The expression factory provides operations to:
- perform type coercions,
- create tree value expressions,
- create object value expressions,
- create tree method expressions.
1.1-Expression Cache.
Each factory instance uses its own expression cache. Caching expressions can be an important issue, because parsing is relative expensive. An expression cache maps expression strings to their parsed representations (trees). JUEL provides a caching interface which allows applications to use their own caching mechanism. However, in most scenarios, JUEL's default implementation (based on java.util.concurrent.ConcurrentHashMap and java.util.concurrent.ConcurrentLinkedQueue ) should be fine.
1.2-Type Conversions.
Type conversions are performed at several points while evaluating expressions. • Operands are coerced when performing arithmetic or logical operations • Value expression results are coerced to the expected type specified at creation time • For literal method expressions the text is coerced to the type specified at creation time • For non-literal method expressions the last property is coerced to a method name • Composite expression coerce their sub-expressions to strings before concatenating them All these coercions are done following the same rules. The specification describes these coercion rules in detail. It supports converting between string, character, boolean, enumeration and number types. Additionally, the conversion of strings to other types is supported by the use of (Java Beans) property editors. The EL makes the coercion rules available to client applications via the expression factory method
ExpressionFactoryImpl.coerceToType(Object, Class<?>)
whose return type is Object.
1.3-Factory Configuration.
The factory may be configured via property files. The mechanism described here is used when an expression factory is created without specifying properties. The lookup procedure for properties is as follows:
- JAVA_HOME/lib/el.properties - If this file contains property javax.el.ExpressionFactory whose value is de.odysseus.el.ExpressionFactoryImpl , its properties are loaded and taken as default properties.
- System.getProperties() - if the previous rule (1) did not match and system property javax.el.ExpressionFactory is set to de.odysseus.el.ExpressionFactoryImpl , the system properties are taken as default properties.
- el.properties anywhere on your classpath - These properties may override the default properties from (1) or (2).
2-Value Expressions.
Value expressions are expressions that are evaluated in the "classical sense". There are two kinds of value expressions: those created by parsing an expression string and those simply wrapping an object. A javax.el.ValueExpression is evaluated by calling its getValue(ELContext) method. Value expressions can also be writable and provide methods isReadOnly(ELContext) , getType(ELContext) and setValue(ELContext, Object) . A value expression is called an lvalue expression if its expression string is an eval expression ( #{...} or ${...} ) consisting of a single identifier or a nonliteral prefix (function, identifier or nested expression), followed by a sequence of property operators ( . or [] ). All other value expressions are called non-lvalue expressions.
2.1-Tree Value Expressions.
Creating a tree value expression involves
- parsing an expression string and building an abstract syntax tree,
- binding functions and variables using the mappers provided by the context. Once created, a tree value expression can be evaluated using the getValue(ELContext) method. The result is automatically coerced to the expected type given at creation time.
2.2-Object Value Expressions.
An object value expression simply wraps an object giving it an "expression facade". At the first place, object expressions are used to define variables. Once created, an object value expression can be evaluated using the getValue(ELContext) method, which simply returns the wrapped object, coerced to the expected type provided at creation time.
3-Method Expressions.
Method expressions can be "invoked". A javax.el.MethodExpression is invoked by calling its invoke(ELContext, Object<?>[]) method. The specification also allows to treat literal text as a method expression. A method expression is called a literal method expression if its underlying expression is literal text (that is, isLiteralText() returns true ). All other method expressions are called non-literal method expressions. Non-literal method expressions share the same syntax as lvalue expressions.
3.1-Tree Method Expressions.
Class de.odysseus.el.TreeMethodExpression is a subclass of javax.el.MethodExpression , which is used by JUEL to represent method expressions.
#Utility Classes.
When creating and evaluating expressions, some other important classes come into play: a javax.el.ELContext is required at creation time and evaluation time. It contains methods to access a function mapper ( javax.el.FunctionMapper ), a variable mapper ( javax.el.VariableMapper ) and a resolver ( javax.el.ELResolver ).
1-Simple Context.
Class de.odysseus.el.util.SimpleContext is a simple context implementation. It can be used at creation time as well as evaluation time.
2-Simple Resolver.
JUEL provides the de.odysseus.el.util.SimpleResolver class for use as a simple resolver, suitable to resolve top-level identifiers and to delegate to another resolver provided at construction time.
#Bridje-EL.
Now that we were seeing basic concepts of the JUEL API , we will focus our attention in Bridje-EL API. Bridje-EL API makes use of the JUEL API, we utilized her at work with EL. The same can be used with maven from central repository:
<dependencies>
....
<dependency>
<groupId>org.bridje</groupId>
<artifactId>bridje-el</artifactId>
<version>0.3.0</version>
</dependency>
....
</dependencies>
#Working with Bridje-EL.
ElService elServ = Ioc.context().find(ElService.class); //Acceding to ELService class.
ElEnvironment elEnv = elServ.createElEnvironment(Ioc.context()); //Creates a new expression language environment. The new created EL environment will take the values from the components of this context.
#Working with specified variable.
ElService elServ = Ioc.context().find(ElService.class); //Acceding to ELService class.
ElEnvironment elEnv = elServ.createElEnvironment(Ioc.context()); //Creates a new expression language environment. The new created EL environment will take the values from the components of this context.
elEnv.setVar("myVar", "Hello"); //Sets the value of the specified variable, myVar is a String variable.
String result = elEnv.get("${myVar}", String.class); //Evaluates the given expression and cast the result of it to the given result class.
//the variable result is equal to "Hello".
elEnv.setVar("myList", new ArrayList<>()); //Sets the value of the specified variable, myList is a empty list.
List lst = elEnv.get("${myList}", List.class); //lst is a empty list.
#Evaluating components in expressions.
Let's suppose that we have the following component:
@Component
@ElModel("myModel") //This annotation defines a model that has visibility in the expression language context.
public class MyModel
{
public String getName()
{
return "Some Name";
}
public List getList()
{
List arr = new ArrayList();
arr.add(new Object());
return arr;
}
}
Definition of the ELModel annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ElModel
{
/**
* The name for this model.
*
* @return The name for this model.
*/
String value();
}
Now we will create a expression language with MyModel component.
ElService elServ = Ioc.context().find(ElService.class);
ElEnvironment elEnv = elServ.createElEnvironment(Ioc.context());
String result = elEnv.get("${MyModel.name}", String.class); //In this moment is called resolveAllModels method, after is called getName method.
/**We needed to have implemented ModelResolver interface, this interface contain resolveAllModels method,
*this method is called when the ElEnviroment need to resolve the names of the components
*that will participate in the expressions.
*/
List lst = elEnv.get("${myModel.list}", List.class); //is called getList method.
/**
* the result variable is equal to "Some Name".
* the lst variable is List Object.
*/
Implementation of ModelResolver interface:
@Component
public class ModelResolverImpl implements ModelResolver
{
@Override
public void resolveAllModels(IocContext<?> ctx, Map<String, Class<?>> result)
{
ctx.getClassRepository()
.forEachClass(ElModel.class, (c, a) -> result.put(a.value(), c) );
}
}