How to pass parameters in an EL expression function

When using Facelets or JSF1.2/JSP2.1, there is a standard way to make static java methods in your code callable from EL expressions. Any number of parameters can be passed to the static method, and the value of the EL expression is the return value of the static method. See the JSF1.2 specification for details.

However when using JSF1.1/JSP2.0, there is no direct support for user-defined functions or parameters. There are a couple of ways of doing this, however:

  1. If you are willing to use a MyFaces-specific solution then a custom FunctionMapper can be registered.

  2. A portable (but less flexible) solution is to take advantage of the fact that EL supports Maps: when evaluating "foo.bar" or "foo['bar']", if foo is a Map then foo.get("bar") is invoked. It is therefore possible to write a method that takes a single parameter by implementing that method as the get(key) method of a Map.

It get's even better with EL-2.2 which is part of JSR-245 MR2. In EL-2.2 you can also pass parameters to managed beans, even without the need of having static methods. See HowToEnableEl22 for a quick tutorial on how to make EL-2.2 available in Tomcat6 and Jetty6.

FunctionMapper Solution

While JSP EL expressions allow access to functions, JSF expressions are only supposed to support access to bean properties. The MyFaces code is nicely designed to be extensible, however; it calls a standard el FunctionMapper instance to resolve any functions found in el-expressions, then provides a default implementation that just throws an exception from its resolveFunction method (ie complies with the JSF spec requirements). Implementing real functions within JSF EL expression with MyFaces is therefore simply a matter of writing an alternate implementation that implements resolveFunction appropriately, and installing it as the FunctionMapper to use.

First, create a subclass of ValueBindingImpl in order to modify static variable s_functionMapper. This can only be done from a subclass as it is not a public variable. Of course something needs to force this class to be loaded at app startup.

public class MyValueBindingImpl extends org.apache.myfaces.el.ValueBindingImpl {
    static {
        // Set up our own function mapper to add support for custom
        // functions in JSF expressions, by overriding the member defined in
        // the parent class.
        s_functionMapper = new MyFunctionMapper();
    }
}

Now define the MyFunctionMapper class to extend javax.servlet.jsp.el.FunctionMapper and implement the resolveFunction method (which returns a Method object). The standard EL implementation will then handle mapping parameters from the EL expression into the parameters required by the returned Method object.

Beware, however, of the fact that s_functionMapper is a *static* variable. If your MyFaces library is in a shared directory then this will have a global effect across all webapps that share that same Class instance. It might also cause problems (memory leaks) with webapp unloads. However smarter variants of this idea which solve these issues can probably be created.

Map Solution

Example

As a trivial example, to pass a parameter to the changeToUpperCase() method, the get() method takes the String, testBean.widget here, as a parameter and calls changeToUpperCase() with that parameter. This will result in the page showing "WIDGET".

public class Uppercaser extends DummyMap implements Map {
        public String changeToUpperCase(String stg) {
                return stg.toUpperCase();
        }

        public Object get(Object obj) {
                return changeToUpperCase((String)obj);
        }
}

------------------------------------------------------------------

public class TestBean implements Serializable {
        private static final long serialVersionUID = 1L;
        private Uppercaser uppercaser = new Uppercaser();
        public Uppercaser getUppercaser() {
                return uppercaser;
        }
        public String getWidget() {
                return "widget";
        }
}

------------------------------------------------------------------

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>

<f:view>
        <html>
                <body>
                        <h:form>
                                <h:outputText value="#{testBean.uppercaser[testBean.widget]}" />
                        </h:form>
                </body>
        </html>
</f:view>

The DummyMap class

import java.util.Collection;
import java.util.Map;
import java.util.Set;

// abstract class used by java server faces to pass parameter to a method as map key
public abstract class DummyMap implements Map {
        public Collection values() {return null;}
        public Object put(Object key, Object value) {return null;}
        public Set keySet() {return null;}
        public boolean isEmpty() {return false;}
        public int size() {return 0;}
        public void putAll(Map t) {}
        public void clear() {}
        public boolean containsValue(Object value) {return false;}
        public Object remove(Object key) {return null;  }
        public boolean containsKey(Object key) {return false;}
        public Set entrySet() {return null;}

        // subclasses should override this method call their method with obj as the parameter
        public abstract Object get(Object obj);
}

Parameters_In_EL_Functions (last edited 2010-02-10 09:48:51 by MarkStruberg)