Call JavaBean methods from JSP 2.0 pages

Develop custom tags with dynamic attributes

Page 2 of 3

The TestMethodTag2 handler class resembles TestMethodTag. Both have the same fields and methods, but the doTag() method is implemented differently:

package com.devsphere.articles.calltag;
import javax.servlet.jsp.JspContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
public class TestMethodTag2 extends SimpleTagSupport {
    private TestBean object;
    private String text;
    private int number;
    private boolean logic;
    public void setObject(TestBean object) {
        this.object = object;
    }
    public void setText(String text) {
        this.text = text;
    }
    public void setNumber(int number) {
        this.number = number;
    }
    public void setLogic(boolean logic) {
        this.logic = logic;
    }

The TestMethodTag2's doTag() method calls testMethod() and uses setAttribute() to create a JSP variable (named ret) that holds the object returned by the bean method within the page scope.

The TestMethodTag.jsp page uses the <tm:testMethod2> tag to call the bean method. Within <tm:testMethod2>'s body, the JSP page uses the ${ret} construct to get the value returned by the bean method.

The tag handler uses getJspBody() to obtain a JspFragment object that represents the body of the <tm:testMethod2> action. The JspFragment's invoke() method executes the Java code generated from the <tm:testMethod2>'s JSP body. In this example, the resulting HTML output goes to the JSP's writer, but the output could have been redirected to another writer by passing to invoke() a java.io.Writer object instead of null:

    public void doTag() throws JspException, IOException {
        String ret = object.testMethod(text, number, logic);
        JspContext context = getJspContext();
        context.setAttribute("ret", ret);
        JspFragment body = getJspBody();
        body.invoke(null);
    }
}

If you want a better understanding of the way in which the tag handler methods are invoked, look at the Java servlet generated by your application server to execute the JSP page. Tomcat 5.0 places the generated Java servlets in TOMCAT_HOME/work/Catalina/localhost/calltag/org/apache/jsp.

The TestMethodTag JSP

The TestMethodTag.jsp page uses the <%@taglib%> directive to specify the prefix of the tags and the library's URI:

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

The <jsp:useBean> tag creates an instance of the TestBean class stored as a JSP page attribute named obj:

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

The bean method is called twice with the help of the two custom tags handled by TestMethodTag and TestMethodTag2. The first call gets some constant parameters, while the second call gets the values of the bean properties as parameters. Both calls output the value returned by testMethod():

<HTML>
<BODY>
<tm:testMethod object="${obj}" text="abc" number="123" logic="true"/>
<HR>
<tm:testMethod2 object="${obj}"
    text="${obj.text}" number="${obj.number}" logic="${obj.logic}">
-- ${ret} --
</tm:testMethod2>
</BODY>
</HTML>

The TestMethodTag.jsp page produces the following HTML output:

<HTML>
<BODY>
abc 123 true
<HR>
-- abcabc 246 true --
</BODY>
</HTML>

The TestMethodTag TLD

The TestMethodTag.tld file describes the <tm:testMethod> and <tm:testMethod2> tags. Both have the same list of attributes: object, text, number, and logic. All attributes are required (none are optional). Any attribute's value can be either a constant or an expression (because rtexprvalue is true). The first tag's body must always be empty, while the second tag's body may contain scriptless JSP code:

<?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>tm</short-name>
    <uri>http://devsphere.com/articles/calltag/TestMethodTag.tld</uri>
    <tag>
        <name>testMethod</name>
        <tag-class>com.devsphere.articles.calltag.TestMethodTag</tag-class>
        <body-content>empty</body-content>
        <attribute>
            <name>object</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>
        </attribute>
        <attribute>
            <name>text</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>
        </attribute>
        <attribute>
            <name>number</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>
        </attribute>
        <attribute>
            <name>logic</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>
        </attribute>
    </tag>
    <tag>
        <name>testMethod2</name>
        <tag-class>com.devsphere.articles.calltag.TestMethodTag2</tag-class>
        <body-content>scriptless</body-content>
        <attribute>
            <name>object</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>
        </attribute>
        <attribute>
            <name>text</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>
        </attribute>
        <attribute>
            <name>number</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>
        </attribute>
        <attribute>
            <name>logic</name>
            <required>true</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/TestMethodTag.tld
    </taglib-uri>
    <taglib-location>
        /WEB-INF/TestMethodTag.tld
    </taglib-location>
</taglib>

Use a generic tag

So far, this article has presented two ways for calling JavaBean methods from scriptless JSP pages. Both ways require some work that may be worthwhile for frequently called methods. In the case of JSP functions, you must provide static wrappers for nonstatic methods, and you must declare the functions in a TLD file. If you prefer to use custom tags, you have to code the tag handler classes and describe the custom tags in a TLD file. A third way for calling JavaBean methods doesn't require extra work. You may use a generic tag based on the Java Reflection API and the new JSP 2.0 dynamic attributes.

The CallTag class

The generic tag has the following syntax:

<ct:call object="..." method="..." 
    return="..." scope="..." debug="..."
    param1="..." param2="..." ... />

The CallTag class extends SimpleTagSupport and overrides the doTag() method like the tag handlers from the earlier sections. In addition, the CallTag class implements the DynamicAttributes interface, whose setDynamicAttribute() method lets you handle attributes not described in the TLD file:

package com.devsphere.articles.calltag;
import javax.servlet.jsp.JspContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.DynamicAttributes;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
public class CallTag extends SimpleTagSupport implements DynamicAttributes {

The CallTag class declares a field for each tag attribute. The paramValues list will maintain the dynamic attributes' values, which are actually the values that will be passed as parameters to the JavaBean method:

    private Object object;
    private String methodName;
    private String returnVar;
    private int returnScope;
    private boolean debugFlag;
    private List paramValues;

Some of the fields are initialized with default values within the CallTag() constructor. The other fields don't need initialization because their corresponding attributes are required or because the default Java values such as null and false are suitable for them. Here is the CallTag() constructor:

    public CallTag() {
        returnScope = PageContext.PAGE_SCOPE;
        paramValues = new LinkedList();
    }

The object attribute specifies the bean instance whose method must be called. Usually, you create or find an object with <jsp:useBean id="someID" ... > and then call its methods using <ct:call object="${someID}" ... >:

    public void setObject(Object object) throws JspException {
        if (object == null)
            throw new JspException("Null 'object' attribute in 'call' tag");
        this.object = object;
    }

The method attribute specifies the JavaBean method name that must be called:

    public void setMethod(String methodName) throws JspException {
        if (methodName == null)
            throw new JspException("Null 'method' attribute in 'call' tag");
        if (methodName.length() == 0)
            throw new JspException("Empty 'method' attribute in 'call' tag");
        this.methodName = methodName;
    }

The return attribute is optional and specifies the JSP variable name that will hold the value returned by the JavaBean method:

    public void setReturn(String returnVar) throws JspException {
        if (returnVar == null)
            throw new JspException("Null 'return' attribute in 'call' tag");
        if (returnVar.length() == 0)
            throw new JspException("Empty 'return' attribute in 'call' tag");
        this.returnVar = returnVar;
    }

The scope attribute is optional too and specifies the return JSP variable's scope. The default scope is page. Other valid scopes are request, session, and application:

    public void setScope(String returnScope) throws JspException {
        if (returnScope.equalsIgnoreCase("page"))
            this.returnScope = PageContext.PAGE_SCOPE;
        else if (returnScope.equalsIgnoreCase("request"))
            this.returnScope = PageContext.REQUEST_SCOPE;
        else if (returnScope.equalsIgnoreCase("session"))
            this.returnScope = PageContext.SESSION_SCOPE;
        else if (returnScope.equalsIgnoreCase("application"))
            this.returnScope = PageContext.APPLICATION_SCOPE;
        else
            throw new JspException("Invalid 'scope' in 'call' tag: "
                + returnScope);
    }

If the debug attribute's value is true, the doTag() method includes an HTML comment in the JSP output that offers information about the method call: class name, method name, parameter values, and returned value. By default, debug is false:

    public void setDebug(boolean debugFlag) throws JspException {
        this.debugFlag = debugFlag;
    }

The above set methods are called from the servlet automatically generated from the JSP page by the application server. The JavaBean method's parameters are obtained as dynamic tag attributes that aren't described in CallTag.tld. For each of them, the generated servlet calls the setDynamicAttribute() method, which adds the attribute value to the paramValues list. The attribute name is ignored. Normally, you use the parameter name as the attribute name to make the code easy to understand. However, if the JavaBean method has a parameter called object, method, return, scope, or debug, you can use some other string as the attribute name:

    public void setDynamicAttribute(String uri, String localName, Object value)
        throws JspException {
        paramValues.add(value);
    }

The doTag() method is invoked in the generated servlet after all attributes are set. The call to the JavaBean method is delegated to the MethodCaller class described in the next section. If the return attribute is present and the JavaBean method returns a primitive value or a non-null object, doTag() creates a JSP variable holding the returned value. As mentioned earlier, if debug is true, some debugging information is outputted within an HTML comment. You can find out the JavaBean's class name, method name, parameter values, and the returned value, if you view the HTML code produced by a JSP that uses <ct:call ... debug="true" ... >:

| 1 2 3 Page 2