Call JavaBean methods from JSP 2.0 pages

Develop custom tags with dynamic attributes

Page 3 of 3
    public void doTag() throws JspException, IOException {
        Object returnValue
            = MethodCaller.call(object, methodName, paramValues);
        JspContext context = getJspContext();
        if (returnVar != null) {
            if (returnValue != null)
                context.setAttribute(returnVar, returnValue, returnScope);
            else
                context.removeAttribute(returnVar, returnScope);
        }
        if (debugFlag) {
            JspWriter out = context.getOut();
            out.println("<!-- calltag debug info");
            out.println("Class: " + object.getClass().getName());
            out.print("Call: " + methodName + "(");
            Iterator paramIterator = paramValues.iterator();
            while (paramIterator.hasNext()) {
                Object value = paramIterator.next();
                out.print(value != null ? value.toString() : "null");
                if (paramIterator.hasNext())
                    out.print(", ");
            }
            out.println(")");
            if (returnVar != null)
                out.println("Return: "
                    + (returnValue != null ? returnValue.toString() : "null"));
            out.println("-->");
        }
    }
}

The MethodCaller class

The MethodCaller class uses the Java Reflection API to invoke JavaBean methods. It also uses a few classes taken from the Apache JSTL implementation to handle type conversions:

package com.devsphere.articles.calltag;
import com.devsphere.articles.calltag.jstl.Coercions;
import com.devsphere.articles.calltag.jstl.ELException;
import com.devsphere.articles.calltag.jstl.Logger;
import javax.servlet.jsp.JspException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.List;
public class MethodCaller {

The findMethod() static method gets the array of Method instances describing the JavaBean class's public methods. Then, it iterates over this array and returns the first Method that has the given name and the given number of parameters:

    private static Method findMethod(
            Object object, String methodName, int paramCount)
            throws JspException {
        Class clazz = object.getClass();
        Method pubMethods[] = clazz.getMethods();
        for (int i = 0; i < pubMethods.length; i++) {
            Method m = pubMethods[i];
            if (methodName.equals(m.getName())
                    && m.getParameterTypes().length == paramCount)
                return m;
        }
        throw new JspException("Method not found: "
            + clazz.getName() + "." + methodName + "()");
    }

The coerceParamValuesToParamTypes() method coerces the <ct:call>'s dynamic attribute values to the JavaBean method's parameter types. The Coercions class's coerce() method developed by Apache does the hard work. This method may throw an ELException, whose message and root cause are wrapped within a JspException:

    private static Object[] coerceParamValuesToParamTypes(
            Method method, List paramValues)
            throws JspException {
        Class paramTypes[] = method.getParameterTypes();
        Object coercedValues[] = new Object[paramTypes.length];
        Iterator paramIterator = paramValues.iterator();
        Logger logger = new Logger(System.err);
        for (int i = 0; i < paramTypes.length; i++) {
            Object paramValue = paramIterator.next();
            Class paramClass = paramTypes[i];
            if (paramValue == null || paramValue.getClass() != paramClass)
                try {
                    paramValue = Coercions.coerce(
                        paramValue, paramClass, logger);
                } catch (ELException e) {
                    throw new JspException(e.getMessage(), e.getRootCause());
                }
            coercedValues[i] = paramValue;
        }
        return coercedValues;
    }

The call() method receives as parameters the JavaBean object, the name of the method that must be invoked, and a list containing the parameter values. It invokes findMethod() to get the Method instance. It invokes coerceParamValuesToParamTypes() to apply type conversions. And finally, it invokes the JavaBean method. The CallTag class uses the public call() method:

    public static Object call(
            Object object, String methodName, List paramValues)
            throws JspException {
        Method method = findMethod(object, methodName, paramValues.size());
        Object args[] = coerceParamValuesToParamTypes(method, paramValues);
        try {
            return method.invoke(object, args);
        } catch (InvocationTargetException e) {
            throw new JspException(e.getTargetException());
        } catch (IllegalAccessException e) {
            throw new JspException(e);
        } catch (IllegalArgumentException e) {
            throw new JspException(e);
        }
    }
}

The TestCallTag JSP

The TestCallTag.jsp page uses the <%@taglib%> directive to declare that it uses the tag library described by CallTag.tld:

<%@ taglib prefix="ct" uri="http://devsphere.com/articles/calltag/CallTag.tld"%>

Like the previous JSP pages, TestCallTag.jsp creates a bean instance with <jsp:useBean>:

<jsp:useBean id="obj" class="com.devsphere.articles.calltag.TestBean"/>

The TestBean class's method is called twice using the generic <ct:call> tag. The values returned by testMethod() are obtained through two JSP variables named ret and ret2:

<HTML>
<BODY>
<ct:call object="${obj}" method="testMethod" debug="true"
    text="abc" number="123" logic="true"
    return="ret"/>
${ret}
<HR>
<ct:call object="${obj}" method="testMethod" debug="true"
    text="${obj.text}" number="${obj.number}" logic="${obj.logic}"
    return="ret2"/>
${ret2}
</BODY>
</HTML>

The TestCallTag.jsp page produces the following HTML output. Some debugging information is generated within HTML comments because the debug flag is true:

<HTML>
<BODY>
<!-- calltag debug info
Class: com.devsphere.articles.calltag.TestBean
Call: testMethod(abc, 123, true)
Return: abc 123 true
-->
abc 123 true
<HR>
<!-- calltag debug info
Class: com.devsphere.articles.calltag.TestBean
Call: testMethod(abc, 123, true)
Return: abcabc 246 true
-->
abcabc 246 true
</BODY>
</HTML>

The CallTag TLD

The CallTag.tld describes the <ct:call> tag. The object and method attributes are required. The return, scope, and debug attributes are optional. The <dynamic-attributes> element must contain true so that the method parameters (text, number, and logic) can be accepted as dynamic attributes at runtime instead of declaring them like in TestMethodTag.tld:

<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-jsptaglibrary_2_0.xsd"
    version="2.0">
    <tlib-version>1.0</tlib-version>
    <short-name>ct</short-name>
    <uri>http://devsphere.com/articles/calltag/CallTag.tld</uri>
    <tag>
        <name>call</name>
        <tag-class>com.devsphere.articles.calltag.CallTag</tag-class>
        <body-content>empty</body-content>
        <dynamic-attributes>true</dynamic-attributes>
        <attribute>
            <name>object</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>
        </attribute>
        <attribute>
            <name>method</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>
        </attribute>
        <attribute>
            <name>return</name>
            <required>false</required>
            <rtexprvalue>false</rtexprvalue>
        </attribute>
        <attribute>
            <name>scope</name>
            <required>false</required>
            <rtexprvalue>false</rtexprvalue>
        </attribute>
        <attribute>
            <name>debug</name>
            <required>false</required>
            <rtexprvalue>true</rtexprvalue>
        </attribute>
    </tag>
</taglib>

The tag library is declared in the web.xml application descriptor:

<taglib>
    <taglib-uri>
        http://devsphere.com/articles/calltag/CallTag.tld
    </taglib-uri>
    <taglib-location>
        /WEB-INF/CallTag.tld
    </taglib-location>
</taglib>

You may use the <ct:call> tag to invoke methods of your own JavaBeans from JSP pages. You just have to include the CallTag library in your Web application. No customization is needed.

Go scriptless

In this article, you've learned how to call JavaBean methods from scriptless JSP pages. The JSP code based on functions is more compact and similar to the equivalent Java code, but you have to declare all functions in TLD files and provide wrappers for nonstatic methods. The use of custom tags makes the code more readable because the names of the method parameters appear as attribute names. The generic call tag requires a little more typing for the code that makes the call, but you don't have to declare any function, provide wrappers, or develop a tag handler for each JavaBean method.

Andrei Cioroianu is the founder of Devsphere, a provider of custom Java Web development services and tools, specializing in Java XML solutions based on free open source software. Andrei is the author of the XML JSP Tag Library (XJTL), which complements the JSP Standard Tag Library (JSTL) with XML-related features such as SAX-based parsing, mixed SAX-DOM parsing, DOM serialization, and dynamic XML output. Currently, Andrei is migrating XJTL (and several Web applications based on it) from JSP 1.2 to JSP 2.0. Andrei is also coauthor of Java XML Programmer's Reference (2001; ISBN: 1861005202) and Professional Java XML (2001; ISBN: 186100401X) published by Wrox Press.

Learn more about this topic

| 1 2 3 Page 3