I've been doing a little bit of JSTL over the past week, especially custom tags. I've written custom tags in Grails before, and there you use actual Groovy code. I guess this was how custom tags used to be written (in Java), but now you can can build your own custom tags using the standard tag library. The standard tag library is still pretty useful when it comes to building custom tags. Since it's not straight Java, it forces you think really hard about your logic. You don't want to put any business or application logic in your tag, and you want to restrict everything to view or presentation logic. A side effect of it not being Java is that if you want to do anything extremely complicated, you're probably better off writing the tag in Java (making sure that you don't let any business logic creep in).
While writing my own custom tag, I noticed that although instanceof is a reserved word in the JSTL EL (expression language), it is not supported as an operator. The reason I wanted to use the instanceof operator is that I have an attribute that could either be a List or a Map and depending on the type, I wanted to do different things.
Another thing I was trying to do, was to inspect the incoming object to see if it had a certain property (reflection). JSTL uses reflection so that you can access the properties of an object via dot notation, if they follow the JavaBean naming-convention. However, there was no way for me to see if an object had a certain property. To solve both these problems, I wrote my own JSTL functions.
The first function I wrote was one that performed the instanceof operation. I created a class called TagUtils that contained static methods that returned primitive types (like boolean, String, or Integer). instanceof presents a special challenge because you can't have the second parameter be dynamic. For example, assuming that you have an object named acura that's an instance of the class Car and a String named className that holds the value "Car", you can't do acura instanceof className. You have to use reflection and create a Class object using the forName static method and then check to see if acura is an instance by using the isInstance method.
The second function I wrote is the hasProperty function which uses reflection to check whether the supplied object has a particular property. To be precise, I don't explicitly check for the existence of the object directly. Rather, I check and see if there is a getter for that property. For example, if the property is called firstName, then there should be a getter called getFirstName().
The code for the functions looks like this:
package util.tag; public class TagUtils { //Checks to see if Object 'o' is an instance of the class in the string "className" public static boolean instanceOf(Object o, String className) { boolean returnValue; try { returnValue = Class.forName(className).isInstance(o); } catch(ClassNotFoundException e) { returnValue = false; } return returnValue; } //Checks to see if Object 'o' has a property specified in "propertyName" public static boolean hasProperty(Object o, String propertyName) { boolean methodFound = false; int i = 0; Class myClass = o.getClass(); String methodName = "get" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1); Method[] methods = myClass.getMethods(); while(i < methods.length && !methodFound) { methodFound = methods[i].getName().compareTo(methodName) == 0; i++; } return methodFound; } } [/sourcecode] <del datetime="2009-12-12T21:35:27+00:00">For the <em>hasProperty</em> function, you can see that I used the <em>getMethod</em> method. The second argument is an array of parameter types. Since the getter accepts no parameters, we pass in an empty array (of type <em>Class</em>).</del> Using exceptions in business logic is really bad, because it slows down the JVM. So the rendering will also become really slow if there are a lot of exceptions being thrown. To fix this problem, I used the <em>getMethods</em> method, which returns an array of <em>Method</em> objects. I then search the array to see if the getter I want, exists. After you create your functions, you have to put them in a <em>tld</em> file. I created a one called <em>tagutils.tld</em> that I put in <em>WEB-INF</em>: [sourcecode language="xml"] <?xml version="1.0" encoding="UTF-8"?> <taglib xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd" version="2.1"> <tlib-version>1.0</tlib-version> <short-name>function</short-name> <uri>http://tagutils</uri> <function> <name>instanceOf</name> <function-class>util.tag.TagUtils</function-class> <function-signature>boolean instanceOf(java.lang.Object, java.lang.String)</function-signature> </function> <function> <name>hasProperty</name> <function-class>util.tag.TagUtils</function-class> <function-signature>boolean hasProperty(java.lang.Object, java.lang.String)</function-signature> </function> </taglib>
Once you create that file, you can use your new functions in your JSP files. To test the hasProperty function, I created a simple class called Person:
package domain; public class Person { private String firstName; private String lastName; public Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public void setFirstName(String firstName) { this.firstName = firstName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getFirstName() { return firstName; } public String getLastName() { return lastName; } public String toString() { return firstName + " " + lastName; } }
Now I put it all together:
<%@ taglib prefix="function" uri="http://tagutils"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page import="java.util.Map" %> <%@ page import="java.util.LinkedHashMap" %> <%@ page import="java.util.List" %> <%@ page import="java.util.ArrayList" %> <%@ page import="domain.Person" %> <% Map<String, String> myMap = new LinkedHashMap<String, String>(); pageContext.setAttribute("myMap", myMap); List<Person> myList = new ArrayList<Person>(); pageContext.setAttribute("myList", myList); pageContext.setAttribute("person", new Person("Holly", "Hoo")); %> myMap is an instance of Map: <b> <c:choose> <c:when test="${function:instanceOf(myMap, 'java.util.Map')}">true</c:when> <c:otherwise>false</c:otherwise> </c:choose> </b><br/> myList is an instance of List: <b> <c:choose> <c:when test="${function:instanceOf(myList, 'java.util.List')}">true</c:when> <c:otherwise>false</c:otherwise> </c:choose> </b><br/><br/> myMap is an instance of List: <b> <c:choose> <c:when test="${function:instanceOf(myMap, 'java.util.List')}">true</c:when> <c:otherwise>false</c:otherwise></c:choose> </b><br/> myList is an instance of Map: <b> <c:choose> <c:when test="${function:instanceOf(myList, 'java.util.Map')}">true</c:when> <c:otherwise>false</c:otherwise> </c:choose> </b><br/><br/> Person has a property called firstName: <b> <c:choose> <c:when test="${function:hasProperty(person, 'firstName')}">true</c:when> <c:otherwise>false</c:otherwise> </c:choose> </b><br/> Person has a property called lastName: <b> <c:choose> <c:when test="${function:hasProperty(person, 'lastName')}">true</c:when> <c:otherwise>false</c:otherwise> </c:choose> </b><br /> Person has a property called id: <b> <c:choose> <c:when test="${function:hasProperty(person, 'id')}">true</c:when> <c:otherwise>false</c:otherwise> </c:choose> </b><br />
Note: I have used a scriptlet in the above example simple to demonstrate the usage of the functions. The use of scriptlets in JSP is bad practice since it probably means that you are putting your business logic in your JSP. The proper place for such code is in a Controller or a Service.
As you can see from the above example, the usage is pretty simple. If everything went well, the output should look something like this:
myMap is an instance of Map: true
myList is an instance of List: truemyMap is an instance of List: false
myList is an instance of Map: falsePerson has a property called firstName: true
Person has a property called lastName: true
Person has a property called id: false
Note: Using the JSP above, the true and false values will actually be on separate lines. I broke the code up in my example for readability. In my actual code, The whole logic expression is in one line.
One important thing to note is that when you use the instanceOf function, you have to provide the fully-qualified class-name. Otherwise, the method will return false.
UPDATE
thetoolman suggested some improvements that uses BeanInfo instead of the reflection API:
public class TagUtils { public static boolean instanceOf(Object o, String className) { if (o == null || className == null) { return false; } try { return Class.forName(className, false, Thread.currentThread().getContextClassLoader()).isInstance(o); } catch (ClassNotFoundException e) { return false; } } public static boolean hasProperty(Object o, String propertyName) { if (o == null || propertyName == null) { return false; } BeanInfo beanInfo; try { beanInfo = java.beans.Introspector.getBeanInfo(o.getClass()); } catch (IntrospectionException e) { return false; } for(final PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) { if (propertyName.equals(pd.getName())) { return true; } } return false; } }
Thanks for posting this example Vivin.
No problem! Glad you found it useful.
Thanks for posting; here is some improvements for your code:
public static boolean instanceOf(Object o, String className) {
if (o == null || className == null) {
return false;
}
try {
return Class.forName(className, false, Thread.currentThread().getContextClassLoader()).isInstance(o);
} catch (ClassNotFoundException e) {
return false;
}
}
public static boolean hasProperty(Object o, String propertyName) {
if (o == null || propertyName == null) {
return false;
}
BeanInfo beanInfo;
try {
beanInfo = java.beans.Introspector.getBeanInfo(o.getClass());
} catch (IntrospectionException e) {
return false;
}
for (final PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
if (propertyName.equals(pd.getName())) {
return true;
}
}
return false;
}
insanceOf now uses the callers classloader, and hasProperty now uses BeanInfo for inspection instead of doing it by hand 🙂
Thanks for the improvements! I’m adding them to the original post. 🙂
Thanks for this post which was most useful for me today. FYI org.apache.commons.beanutils.PropertyUtils could futher simplify your code (if you are happy with the dependency of course):
public static boolean hasProperty(Object o, String propertyName) {
if (o == null || propertyName == null) {
return false;
}
try
{
return PropertyUtils.getPropertyDescriptor(o, propertyName) != null;
}
catch (Exception e)
{
return false;
}
}
A very good example. Thanq !!!
Thank you sir.