1 Reply Latest reply on Feb 11, 2010 6:00 PM by Aslak Knutsen

    Evaluating EL expressions in a test case

    Dan Allen Master

      The Unified EL is the bastard child of the Java EE programming model. It's still considered part of the web tier, and is stuck inside of the JSP specification, even though it continues to take a more central and vital role in the model. I've let my frustrations about this situation with the EL be known in my blog entry Why you didn't know the EL is being updated.

       

      Most recently, the EL became an integral part of Java EE 6 via CDI. Beans can be assigned a name so that they can be referenced by name instead of by type and qualifier. The idea is to provide a bridge between the XML-based UI template and the business tier. But, if you think about it, being able to reference a bean by name is generally useful for a wide range of integrations, not just limited to the UI...especially not just to JSP or JSF.

       

      While the EL originated in JSP, its scope is by no means limited to JSP or even JSF. The EL Javadoc even states this intention explicitly:

      It is intended for general use outside of the JSP and JSF specifications as well.

       

      Yet every framework that wants to use it is responsible for initializing it, rather than it being managed as a container resource. The situation is really ridiculous.

       

      So what's this rant got to do with Arquillian?

       

      In short, there is no way to resolve EL expressions in your test (unless you bootstrap it yourself). You have access to the EL in a JSP or JSF application. But the EL is being bootstrapped directly by those frameworks; generally speaking in the web tier. Outside of the web tier, you are left high and dry. Arquillian test cases are outside of the web tier. And now we have a problem.

       

      The long term solution is to get the Unified EL to be a container resource. But we need to offer an interim solution in Arquillian. Thus, I propose that we initialize the EL as part of the Arquillian lifecycle and give the developer a simple API for resolving expressions.

       

      I'll propose here one possible solution that works as a CDI extension to be deployed with the test. The point is to give you an idea of what initialization steps must be done, and how the abstraction API might look. We need to take this idea and assimilate it into Arquillian.

       

      Part of the problem with the Unified EL is that just trying to figure out what you need to actually resolve an expression is a nightmare. It breaks down into three parts:

       

      • ELContext - Simply put, "To evaluate an Expression, an ELContext must be provided." A real bitch.
      • ExpressionFactory - Needed to create a ValueExpression or MethodExpression from a string
      • ELResolver - The thing that actually parses the expression and deferences its parts; each job is handled by a different resolver (one being to find CDI beans by name) and those resolvers are wrapped in a resolver which is a chain of resolver

       

      What isn't provided is a simple API to just take an expression and get a result from it. So I want to start with the proposed API, Expressions:

       

      import javax.el.ELContext;
      import javax.el.ExpressionFactory;
      import javax.enterprise.inject.Instance;
      import javax.inject.Inject;
      
      public class Expressions {
         @Inject private Instance<ELContext> elContext;
         @Inject private ExpressionFactory expressionFactory;
      
         public ELContext getELContext() {
            return elContext.get();
         }
      
         public <T> T evaluateValueExpression(String expression, Class<T> expectedType) {
            ELContext ctx = elContext.get();
            return (T) expressionFactory.createValueExpression(ctx, expression, expectedType)
               .getValue(ctx);
         }
      
         public Object evaluateValueExpression(String expression) {
            return evaluateValueExpression(expression, Object.class);
         }
      
         public <T> T invokeMethodExpression(String expression, Class<T> expectedReturnType,
               Object[] args, Class<?>[] argTypes) {
            ELContext ctx = elContext.get();
            return (T) expressionFactory.createMethodExpression(ctx, expression, expectedReturnType,
               argTypes).invoke(ctx, args);
         }
      
         public <T> T invokeMethodExpression(String expression, Class<T> expectedReturnType) {
            return invokeMethodExpression(expression, expectedReturnType, new Object[0], new Class[0]);
         }
      
         public Object invokeMethodExpression(String expression) {
            return invokeMethodExpression(expression, Object.class, new Object[0], new Class[0]);
         }
      
         public Object invokeMethodExpression(String expression, Object... args) {
            return invokeMethodExpression(expression, Object.class, args, new Class[args.length]);
         }
      
         public <T> T resolveName(String name, Class<T> expectedType) {
            return evaluateValueExpression(toExpression(name), expectedType);
         }
      
         public Object resolveName(String name) {
            return resolveName(name, Object.class);
         }
      
         private String toExpression(String name) {
            return "#{" + name + "}";
         }
      }
      

       

      Let's assume we have the following named bean that produces the current date:

       

      public class CurrentDateProducer {
         public
         @Produces
         @Named("currentDate"
         @CurrentDate
         Date getCurrentTimestamp() {
            return new Date();
         }
      }
      

       

      We now want to write a test that uses an instance of our Expressions class to verify that we can produce a Date:

       

      public class CurrentDateProducerTest extends Arquillian {
         ...
         @Inject Expressions expressions;
      
         @Test
         public void testProducesNamedCurrentDate() {
            Date d = expressions.resolveName("currentDate", Date.class);
            Assert.assertNotNull(d, "Expecting date instance when resolving name: currentDate");
            Assert.assertTrue(d instanceof Date);
         }
      }
      

       

      What's left is to provide the producers for ExpressionFactory:

       

      import javax.el.ExpressionFactory;
      import javax.enterprise.context.ApplicationScoped;
      import javax.enterprise.inject.Produces;
      
      public class ExpressionFactoryProducer {
         public
         @Produces
         @ApplicationScoped
         ExpressionFactory createExpressionFactory() {
            return ExpressionFactory.newInstance();
         }
      }
      

       

      and ELResolver:

       

      import com.sun.el.lang.FunctionMapperImpl;
      import com.sun.el.lang.VariableMapperImpl;
      import javax.el.*;
      import javax.enterprise.inject.Produces;
      import javax.enterprise.inject.spi.BeanManager;
      
      public class ELContextProducer {
      
         public
         @Produces
         ELContext createELContext(BeanManager beanManager) {
            return createELContext(createELResolver(beanManager),
               new FunctionMapperImpl(), new VariableMapperImpl());
         }
      
         private ELResolver createELResolver(BeanManager beanManager) {
            CompositeELResolver resolver = new CompositeELResolver();
            resolver.add(beanManager.getELResolver());
            resolver.add(new MapELResolver());
            resolver.add(new ListELResolver());
            resolver.add(new ArrayELResolver());
            resolver.add(new ResourceBundleELResolver());
            resolver.add(new BeanELResolver());
            return resolver;
         }
      
         private ELContext createELContext(final ELResolver resolver,
            final FunctionMapper functionMapper, final VariableMapper variableMapper) {
            return new ELContext() {
               @Override
               public ELResolver getELResolver() {
                  return resolver;
               }
      
               @Override
               public FunctionMapper getFunctionMapper() {
                  return functionMapper;
               }
      
               @Override
               public VariableMapper getVariableMapper() {
                  return variableMapper;
               }
            };
         }
      }
      

       

      The way that JBoss Test Harness handles this situation is that the porting package must provide an implementation of EL (the Unified EL bootstrap) and the Configuration object offers access to a convenience API similar to Expressions. We need to decide how to bake this into Arquillian.