How to call methods from EL expressions- pre JSP 2.0 trick for JSPs with JSTL

4

I finally got round to describing a trick that I have implemented more than one year ago. It happened to come up during last week’s Spring workshop, so I decided decided to write it down after all. You probably know that EL Expressions in JSTL tags can refer to Objects in any of the scopes: request, session and application. EL Expressions can make use of getter-methods on those objecs or access standard Array, Collection or Map properties. Multi-level ‘navigation’is supported, to follow a path from one object to the next. For example <c:out value="${bean.prop1.prop2OnBean2.prop3OnBean3}"/> etc.

The intent from all of these invocations is to extract values. There is no support for invoking methods, setters or otherwise, passing parameter values to such methods. That means for example that there is no easy way to make use of String manipulation methods. Or to have operations performed, outside the JSP context. Or to extract values from the Model, if the Model is more complex than a simple Map.

It is my ambition here to find a way to allow invocations of any method with any number of parameters from simple EL Expressions. For example, I want to be able to use a simple expression to invoke my special decorateString method for the labels my JSP will render: Label: <c:out value="${bean.decorateString["the string I want to decorate]}"/>.

The essence of the trick lies in the fact that JSTL can extract values from a Map. To do so, it invokes the get(Object key) method on the Map. So if we can load an object into the request scope that implements the Map interface, we can have JSTL call this get() method on it. Typically this method just returns a value. However, we can implement this method anyway we like. So we can have it perform additional actions before returning a value. Or it can invoke methods on other objects and return null.

With the advent in JSP 2.0 of the JSTL FN library, this trick has become somehwat redundant. First of all, the JSTL 1.2 FN library contains 16 functions that are often required, such as:
fn:length(): Get the length of a collection or a string.fn:toUpperCase(),
fn:toLowerCase(): Change the capitalization of a string.fn:substring(),
fn:substringBefore(),
fn:substringAfter(): Get a subset of a string.
fn:trim(): Trim whitespace from a string.
fn:replace…
Also see: Developing JSTL-like Tags with JSP 2.0,
JSP 2.0: The New Deal, Part 1 New JSTL 1.1 Tag Library Identifiers

Second of all, JSTL 1.2/JSP 2.0 technology adds the ability to extend the expression language through writing custom EL functions. This feature enables a developer to quickly and easily make common tasks available to the page author without having to allow unrestricted access to arbitrary method invocations.If for whatever reason you are stuck with JSP 1.2 (JDK 1.3) and you want to call methods on an object, besides plain getter methods, you could use the trick demonstrated

Approach to implementing method calls from EL Expressions

We will build a very simple web application. It consists of a single JSP that refers to an object that has been loaded into the request scope, in this particular case by a Struts Action. The object is an instance of my MyInvokableMap class; this class implements the Map interface, but has its very own implementation of the get(Object key) method:

package nl.amis.hrm.view;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class MyInvokableMap implements Map  {
  public MyInvokableMap() {
  }

  static final int SETTING_PARAMETERS=1;
  static final int INVOKING=2;

  int mode = SETTING_PARAMETERS;

  Map params = new HashMap();

  public int size() {
    return 0;
  }

  public boolean isEmpty() {
    return false;
  }

  public boolean containsKey(Object key) {
    return true;
  }

  public boolean containsValue(Object value) {
    return true;
  }

  public Object get(Object key) {
    // if the init is passed, this object is reinitialized for the next invocation
    if (((String)key).equalsIgnoreCase("init")) {
      mode = SETTING_PARAMETERS;
      params.clear();
      return this;
    }
    // if set is passed, we can expect the next call to get to pass the value of the next attribute
    if (((String)key).equalsIgnoreCase("set")) {
       mode = SETTING_PARAMETERS;
       return this;
    }
    // if invoke is passed, the next call to get will pass the key that optionally identified the method to invoke; we switch now from SETTING_PARAMETERS mode to INVOKING mode
    if (((String)key).equalsIgnoreCase("invoke")) {
       mode = INVOKING;
       return this;
    }
    if (mode == SETTING_PARAMETERS) {
      params.put(new Integer(params.size()),key); // store this parameter with the current number of entries as the key; that means that parameters are stored in params with keys 0..x where x is one less than the number of parameters
      return this;
    }
    if (mode == INVOKING) { // now depending on the value of key, we can decide to invoke the method that is called for
      if (((String)key).equalsIgnoreCase("giveParameterLength")) {
        int length = 0;
        Iterator values = params.values().iterator();
        while (values.hasNext()) {
          length = length + ((String)values.next()).length();
        }
        return new Integer(length);
      }
      else if (((String)key).equalsIgnoreCase("sendEmail")) {
        return new String("&lt;b>I have sent the email, as per instruction&lt;/b>");
      }
      else if (((String)key).equalsIgnoreCase("command")) {
        // Execute command, whatever it is
        return null;
      }
        String parameters="";
        Iterator values = params.values().iterator();
        while (values.hasNext()) {
          parameters = parameters.concat((String)values.next()).concat(",");
        }

      return new String("Could not determine what to do for "+key+"["+parameters+"]");
    }
    return null;
  }

  public Object put(Object key, Object value) {
    return null;
  }

  public Object remove(Object key) {
    return null;
  }

  public void putAll(Map t) {
  }

  public void clear() {
  }

  public Set keySet() {
    return null;
  }

  public Collection values() {
    return null;
  }

  public Set entrySet() {
    return null;
  }

  public boolean equals(Object o) {
    return false;
  }

  public int hashCode() {
    return 0;
  }
}

The Struts Action that will load our object into the request is very simple:

package nl.amis.hrm.view;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionForm;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoadObjects extends Action  {
  public LoadObjects() {
  }

  public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {

   // load objects to be invoked from JSP
    request.getSession().setAttribute("callable" , new MyInvokableMap());
    request.getSession().setAttribute("someString" , new String("An interesting string to show that parameters do not have to be static values"));

    return mapping.findForward("success");
  }
}

The entire struts-config.xml file for this mini-application is as simple as you would expect it to be:

&lt;?xml version = '1.0' encoding = 'windows-1252'?>
&lt;!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
&lt;struts-config>
  &lt;action-mappings>
    &lt;action path="/callmethod" type="nl.amis.hrm.view.LoadObjects" >
      &lt;forward name="success" path="/WEB-INF/CallMethods.jsp" />
    &lt;/action>
  &lt;/action-mappings>

  &lt;message-resources parameter="nl.amis.hrm.view.ApplicationResources"/>
&lt;/struts-config>

Now with the Struts Action LoadObjects having put an instance of MyInvokableMap, it is time to invoke the JSP to do the work in rendering a response to browser. Our JSP is nothing very special, except for the four JSTL Core tags that use the MyInvokeableMap object to process special tasks:

      &lt;c:out value="${callable.init.set['value1'].invoke['command']}"/>
      &lt;c:out value="${callable.init.set['value1'].set['value2'].invoke['sendEmail']}" escapeXml="false"/>
      &lt;c:out value="${callable.init.set['Short'].set['SomewhatLonger'].set['ExtremelyLongParameterValue'].invoke['giveParameterLength']}"/>
      &lt;c:out value="${callable.init.set[someString].invoke['notYetDefinedInvocation']}"/>

Note in the last expression that someString is without quotation marks. That means that we are referring to a variable called someString; this can be a page level object, or an object in either one of the scopes. In this case, someString was put in the request by the Struts Action LoadObjects.

The EL Expressions start with ${callable. They invoke the callable object in the request scope. This object implements the Map interface; this means that the JSTL processor will continue to invoke the get(Object key) method. It will pass the string “init” to this get() method. In this case the get method will return this, the object itself that still implements the Map. The next step will be another call to the get() method, this time with the value of ‘set’ for the key. Then follows the value of the first parameter, possibly more calls passing the values ‘set’ for the key and the values for more parameters. In the end, the Key value “invoke” is passed to the get method. This turns the object into INVOKING mode; the next key passed in, specifies the method to be invoked or the action to be taken. It is up to the callable object to decide what to do in INVOKING mode. In the end, it may return a result – or just null.

Below you see the webpage resulting from our JSP, in particular the result of the four EL Expression calls to our MyInvokableMap object.

This would be an even cooler trick if we make MyInvokableMap more generic. It could use reflection to get hold of an object and method to dynamically call.

Resources

Download the Oracle JDeveloper 10.1.2 Workspace with all code presented in this article:JstlCallMethod.zip , articles: JSP 2.0 Technology,
JSP 2.0: The New Deal, Part 1, Developing JSTL-like Tags with JSP 2.0

Share.

About Author

Lucas Jellema, active in IT (and with Oracle) since 1994. Oracle ACE Director for Fusion Middleware. Consultant, trainer and instructor on diverse areas including Oracle Database (SQL & PLSQL), Service Oriented Architecture, BPM, ADF, Java in various shapes and forms and many other things. Author of the Oracle Press book: Oracle SOA Suite 11g Handbook. Frequent presenter on conferences such as JavaOne, Oracle OpenWorld, ODTUG Kaleidoscope, Devoxx and OBUG. Presenter for Oracle University Celebrity specials.

4 Comments