blog.christianbauer.name

Java, Unified EL, CDI
September 1, 2013

Java EL in CDI without JSF

In my current project I want to evaluate Unified EL expressions manually. The CDI container and managed beans should be included in expression valuation: When #{myManagedBean.someProperty} is evaluated, the named bean myManagedBean should be looked up in CDI contexts, then its getSomeProperty() method is called. This is what you have probably used before with JSF but it's not that easy to enable without a Java EE container, for example, when you want to run Weld standalone. (Worth noting that although the Solder project provided this functionality, it now doesn't work out of the box with CDI 1.1 and Weld 2 anymore.)

First, the dependencies:

<weld.version>2.0.3.Final</weld.version>
<juel.version>2.2.6</juel.version>

<!-- Weld -->
<dependency>
    <groupId>org.jboss.weld.servlet</groupId>
    <artifactId>weld-servlet-core</artifactId>
    <version>${weld.version}</version>
</dependency>
<dependency>
    <groupId>org.jboss.weld</groupId>
    <artifactId>weld-core-impl</artifactId>
    <version>${weld.version}</version>
</dependency>
<!-- WTF... -->
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>javax.servlet.jsp-api</artifactId>
    <version>2.3.1</version>
</dependency>

<!-- Java Expression Language -->
<dependency>
    <groupId>de.odysseus.juel</groupId>
    <artifactId>juel-api</artifactId>
    <version>${juel.version}</version>
</dependency>
<dependency>
    <groupId>de.odysseus.juel</groupId>
    <artifactId>juel-impl</artifactId>
    <version>${juel.version}</version>
</dependency>

Yes, Weld has a runtime dependency on the JSP API...

Next, due to some hardcoded service provider lookup bug in the ELUtil class of JUEL, you need to set up a service provider in META-INF/services/javax.el.ExpressionFactory. Create this file with the content my.project.Expressions$Factory

Now create the Expressions class with its nested factory:

public class Expressions {

    public static final class Factory extends WeldExpressionFactory {
        public Factory() {
            super(new ExpressionFactoryImpl());
        }
    }

    public final static Pattern PATTERN = Pattern.compile("#\\{(.+?)\\}");
    public final static ExpressionFactory expressionFactory = ExpressionFactory.newInstance();

    final protected ELContext context;

    @Inject
    public Expressions(BeanManagerImpl beanManager) {
        // Chain the resolvers, the Weld resolver first, then
        // read-only "simple" bean/map/list resolvers
        CompositeELResolver compositeELResolver = new CompositeELResolver();
        compositeELResolver.add(beanManager.getELResolver());
        compositeELResolver.add(new SimpleResolver(true));
        context = new de.odysseus.el.util.SimpleContext(compositeELResolver);

        // Let Weld know about the context, so it can handle dependent
        // scope properly if beans are instantiated by EL expressions
        ELContextEvent event = new ELContextEvent(context);
        new WeldELContextListener().contextCreated(event);
    }

    public ExpressionFactory getExpressionFactory() {
        return expressionFactory;
    }

    public ELContext getContext() {
        return context;
    }

    public <T> T evaluateValueExpression(String expression, Class<T> expectedType) {
        Object result = getExpressionFactory()
            .createValueExpression(context, expression, expectedType).getValue(context);
        return result != null ? expectedType.cast(result) : null;
    }

    public <T> T evaluateValueExpression(String expression) {
        Object result = evaluateValueExpression(expression, Object.class);
        return result != null ? Reflections.<T>cast(result) : null;
    }

    public <T> T evaluateMethodExpression(String expression,
                                          Class<T> expectedReturnType,
                                          Object[] params,
                                          Class<?>[] expectedParamTypes) {
        Object result = getExpressionFactory()
        .createMethodExpression(context, expression, expectedReturnType,
                                expectedParamTypes).invoke(context, params);
        return result != null ? expectedReturnType.cast(result) : null;
    }

    public <T> T evaluateMethodExpression(String expression, Class<T> expectedReturnType) {
        return evaluateMethodExpression(
            expression, expectedReturnType, new Object[0], new Class[0]
            );
    }

    public <T> T evaluateMethodExpression(String expression) {
        Object result = evaluateMethodExpression(expression, Object.class);
        return result != null ? Reflections.<T>cast(result) : null;
    }

    public <T> T evaluateMethodExpression(String expression, Object... params) {
        Object result = evaluateMethodExpression(
            expression, Object.class, params, new Class[params.length]
        );
        return result != null ? Reflections.<T>cast(result) : null;
    }

    public String toExpression(String name) {
        return "#{" + name + "}";
    }

    public <T> void addVariableValue(String name, Class<T> type, T value) {
        getContext().getVariableMapper().setVariable(
            name, getExpressionFactory().createValueExpression(value, type)
        );
    }

    public String evaluateAllValueExpressions(String s) {
        StringBuffer sb = new StringBuffer();
        Matcher matcher = PATTERN.matcher(s);
        while (matcher.find()) {
            String expression = toExpression(matcher.group(1));
            Object result = evaluateValueExpression(expression);
            matcher.appendReplacement(sb, result != null ? result.toString() : "");
        }
        matcher.appendTail(sb);
        return sb.toString();
    }
}

This is a managed bean class, with dependent scope. This scoping is important, the ELContext is not thread-safe. You can inject the Expressions utility in other managed beans and evaluate expressions:

@RequestScoped
public class Foo {

    @Inject
    Expressions expressions;

    public void doStuff() {
        Object result = expressions.evaluateValueExpression("#{someBean.someProperty}");
        Object value = expressions.evaluateValueExpression("#{someHashMap['key']}");
        String replacement = expressions.evaluateAllValueExpressions(
            "This evaluates #{all.expressions} in this #{text.render('Hello Text')}..."
        );
    }
}