Spring Web Flow 2 and JUEL Integration

Its a tale of romance and tragedy. Okay, not really. But that’s certainly a better starting sentence than, “hey I got Spring Web Flow 2 to work with JUEL instead of JBoss EL.” Anyway, because CAS 4 is utilizing JaValid for its annotation-based validation logic, we’re required to have JUEL on our classpath (JaValid has some compile-time dependencies on JUEL). Unfortunately, the EL specification states that only one provider can be on the path at a time (at the very least, it complains when I have two). Damn them! Can’t we all just get along? Unfortunately, out of the box Spring Web Flow can either work with JBoss EL, or OGNL. OGNL was causing its own set of problems. Since I couldn’t get JaValid to compile against JBoss EL, that meant getting Spring Web Flow 2 to work with JUEL. You think that’d be easy. Not so much.

Its not exceedingly difficult. It just requires writing some custom code. First, we need a ELResolver for JUEL that can process methods (JUEL doesn’t include one). I “borrowed” the one from Camel and modified it a little bit:

/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jasig.cas.server.web.util;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
import java.beans.FeatureDescriptor;

import javax.el.BeanELResolver;
import javax.el.ELContext;
import javax.el.ELResolver;

/**
 * Allows JUEL to resolve methods.
 *
 * @author Scott Battaglia
 * @version $Revision$ $Date$
 * @since 4.0.0
 */
public class MethodELResolver extends ELResolver {

    @Override
    public Object getValue(final ELContext elContext, final Object base, final Object property) {
        Method method = findMethod(elContext, base, property);
        if (method != null) {
            elContext.setPropertyResolved(true);
            return method;
        }

        return null;
    }

    public boolean isReadOnly(final ELContext elContext, final Object o,final  Object o1) {
        return false;
    }

    public Class<?> getType(ELContext elContext, Object o, Object o1) {
        return null;
    }

    public void setValue(ELContext elContext, Object o, Object o1, Object o2) {
        // nothing to do
    }

    public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext elContext, Object o) {
        return null;
    }

    public Class<?> getCommonPropertyType(ELContext elContext, Object o) {
        return null;
    }

    protected Method findMethod(final ELContext elContext, final Object base, final Object property) {
        if (base != null &amp;amp;&amp;amp; property instanceof String) {
            final Method[] methods = base.getClass().getMethods();
            final List<Method> matching = new ArrayList<Method>();
            for (final Method method : methods) {
                if (method.getName().equals(property) &amp;amp;&amp;amp; Modifier.isPublic(method.getModifiers())) {
                    matching.add(method);
                }
            }
            int size = matching.size();
            if (!matching.isEmpty()) {
                if (size > 1) {
                    // TODO there's currently no way for JUEL to tell us how many parameters there are
                    // so lets just pick the first one that has a single param by default
                    for (final Method method : matching) {
                        final Class<?>[] paramTypes = method.getParameterTypes();
                        if (paramTypes.length == 1) {
                            return method;
                        }
                    }
                }
                // lets default to the first one
                return matching.get(0);
            }
        }
        return null;
    }
}

Since JUEL doesn’t have a ELResolver that handles methods you can’t just give the ExpressionFactory to the existing WebFlowELParser as a construction parameter (methods still won’t work then). You need to construct your own parser based off the existing parser:

/**
 * Copyright 2008 JA-SIG, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jasig.cas.server.web.util;

import java.util.ArrayList;
import java.util.List;

import javax.el.ELContext;
import javax.el.ELResolver;
import javax.el.FunctionMapper;
import javax.el.VariableMapper;

import org.springframework.binding.expression.el.DefaultELResolver;
import org.springframework.binding.expression.el.ELContextFactory;
import org.springframework.binding.expression.el.ELExpressionParser;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.expression.el.*;
import de.odysseus.el.ExpressionFactoryImpl;
import de.odysseus.el.tree.impl.Builder;
import de.odysseus.el.tree.impl.Cache;
import de.odysseus.el.tree.TreeStore;

public final class JuelWebFlowELExpressionParser extends ELExpressionParser {

	public JuelWebFlowELExpressionParser() {
		super(new ExpressionFactoryImpl(new TreeStore(new Builder(Builder.Feature.METHOD_INVOCATIONS), new Cache(1000))));
		putContextFactory(RequestContext.class, new RequestContextELContextFactory());
	}

	private static class RequestContextELContextFactory implements ELContextFactory {
		public ELContext getELContext(final Object target) {
			final RequestContext context = (RequestContext) target;
			final List<ELResolver> customResolvers = new ArrayList<ELResolver>();
			customResolvers.add(new RequestContextELResolver(context));
			customResolvers.add(new FlowResourceELResolver(context));
			customResolvers.add(new ImplicitFlowVariableELResolver(context));
			customResolvers.add(new ScopeSearchingELResolver(context));
			customResolvers.add(new SpringBeanWebFlowELResolver(context));
			customResolvers.add(new ActionMethodELResolver());
			customResolvers.add(new MethodELResolver());
			ELResolver resolver = new DefaultELResolver(customResolvers);
			return new WebFlowELContext(resolver);
		}
	}

	private static class WebFlowELContext extends ELContext {

		private final ELResolver resolver;

		public WebFlowELContext(ELResolver resolver) {
			this.resolver = resolver;
		}

		public ELResolver getELResolver() {
			return resolver;
		}

		public FunctionMapper getFunctionMapper() {
			return null;
		}

		public VariableMapper getVariableMapper() {
			return null;
		}
	}
}

Finally, all of this needs to be wired up in your Spring XML configuration file. I’ve included our example here:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:webflow="http://www.springframework.org/schema/webflow-config"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
       http://www.springframework.org/schema/webflow-config http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd">

    <webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices">
        <webflow:flow-location path="/WEB-INF/login/login.xml" id="login"/>
    </webflow:flow-registry>

    <webflow:flow-builder-services expression-parser="expressionParser" id="flowBuilderServices" />

    <bean id="expressionParser" class="org.jasig.cas.server.web.util.JuelWebFlowELExpressionParser" />

    <webflow:flow-executor id="flowExecutor"/>

    <bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter"
          p:flowExecutor-ref="flowExecutor"/>

    <bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping"
          p:flowRegistry-ref="flowRegistry"
          p:order="0"/>
</beans>

Be sure to include JUEL in your Web application and that should be all you need.

2 comments ↓

#1 Christoph Beck on 01.02.09 at 4:41 pm

JUEL 2.1.1-rc1 has been released today with improved method invocations. Instead of passing the method name as property, you’ll get a MethodInvocation object as property. Among other details, this object gives you the number of paramters in the method call.

#2 Scott on 01.05.09 at 6:48 am

Thanks for the information. I’ll check it out and update my code.

Leave a Comment

Spam protection by WP Captcha-Free