As part of a look into JSP and the associated XML configuration files that I recently did, I coded up an interesting tag which I call ReflectiveTag
. The goal of the experiment was to use reflection to dynamically load a tag handler to use in executing a tag on a page. As the name suggests, this is accomplished with decidedly un-fancy Java reflection. The tag’s one required attribute is a string representing the fully-qualified class name of the tag handler you wish to instantiate. A map containing attribute names and values is optionally passed in, representing the underlying tag’s expected attributes, the ones you would normally define in its TLD entry.
Once the class is instantiated, its attributes are set using reflection. This is made easy because tag handlers must at least support JavaBean property setXYZ(...)
methods for each of their attributes. One small “gotcha” is that it isn’t guaranteed that the setters for attributes will be called before the other setters in the class. This means that the handler class string might not be available to instantiate the handler before the other settings are set. Because of this. I wasn’t able to set all of the handler settings up front, opting instead to store them for later and setting them just before I execute the tag for the first time (see the bottom of the ReflectiveTag class). A more defensive approach might be to store all values this way, preventing null pointer exceptions and ensuring that the code will execute correctly in all containers.
.....
private void instantiateHandler(String clazz) {
try {
Object obj = Class.forName(clazz).newInstance();
if (obj instanceof BodyTagSupport) {
_bodyHandler = (BodyTagSupport) obj;
_basicHandler = _bodyHandler;
} else if (obj instanceof TagSupport) {
_basicHandler = (TagSupport) obj;
}
} catch (Exception ex) {
throw new RuntimeException(ex);
//TODO or something better
//or just return null and let an NPE occur
}
}
/**
* set all of the handler's attributes where possible.
*/
private void setHandlerAttributes() {
Class clazz = _basicHandler.getClass();
for (Map.Entry<String, Object> entry : _attributes.entrySet()) {
try {
String methodName = Character.toUpperCase(entry.getKey().charAt(0)) + entry.getKey().substring(1);
methodName = "set" + methodName;
Class[] types = new Class[] { entry.getValue().getClass() };
Method method = clazz.getMethod(methodName, types);
method.invoke(_basicHandler, entry.getValue());
} catch (Exception ex) {
; //nothing
}
}
}
private void injectHeldValues() {
_basicHandler.setPageContext(_heldContext);
_basicHandler.setParent(_heldTag);
}
.....
injectHeldValues()
, towards the end of the snippet, is called right before the tag executes, which we can assume is guaranteed to be after the handler is properly set up.
In addition to creating the reflective handler, an entry must be made in a TLD to expose the expected attributes and set the handler class. The class name of the target handler to instantiate is required, but the attribute map is not. As a convenience, up to three attributes can optionally be set directly in the tag without using a map. This has the added advantage of allowing JSP Expression Language to be utilized instead of being forced to use a scriplet to create a map on the page each time the tag is called.
<tag>
<description>Runs the specified handler with the specified parameters.</description>
<name>reflective</name>
<tag-class>com.vodori.cms.component.navigation.tag.ReflectiveTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<description>fully qualifed class name of tag handler to use</description>
<name>handler</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<description>map of attributes to apply</description>
<name>attributes</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<description>convenience attribute key</description>
<name>attr1K</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<description>convenience attribute value</description>
<name>attr1V</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<description>convenience attribute key</description>
<name>attr2K</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
....
So now that we have our reflective tag set up, we need to make a tag handler to test it. Actually we need to make two handlers, because of the differences between body tags and simple tags. For the first type, I’ve created the ColorFilterTag
which searches its inner content for hex color codes and then modifies them according to the tag attributes. The attributes are passed as +/- RGBA values in the attribute map. Because this tag manipulates its body content, it extends the BodyTagSupport class. The reflective tag also extends this class but accommodates both body tags and simple tags. (The source for the ColorFilterTag is available with the other files at the bottom of this post.) Assuming everything is now in place, let’s use the ReflectiveTag
to run the ColorFilterTag
.
<html>
<head><title>Reflective Tag Test</title></head>
<body>
<%
// could pull the map from anywhere, but we'll define it here
Map map = new HashMap();
map.put("red", 80);
map.put("green", 60);
map.put("blue", -60);
pageContext.setAttribute("map", map);
// String array, to show that el can be processed in the tag.
pageContext.setAttribute("strings", new String[]{"Galaxy","Solar System","World"});
%>
<b>Original:</b><br/>
<center>
<h1 style="color: #CC0033;">Hello Galaxy!</h1>
<h2 style="color: #3300AA;">Hello ${strings[1]}</h2>
<h3 style="color: #10CC30;">Hello World!</h3>
</center>
<br/><br/>
<b>Modified:</b><br/>
<vodori:reflective handler="com.vodori.cms.component.navigation.tag.ColorFilterTag" attributes="${map}">
<center>
<h1 style="color: #CC0033;">Hello ${strings[0]}!</h1>
<h2 style="color: #3300AA;">Hello Solar System!</h2>
<h3 style="color: #10CC30;">Hello World!</h3>
</center>
</vodori:reflective>
</body>
</html>
After setting up a map with the tag’s expected attributes, I pass it along with the ColorFilterTag
’s class name to the ReflectiveTag
. The body content is then processed by the ColorFilterTag
. Unfortunately my current webhost does not support tomcat instances, so here is a screenshot of the results:
As I mentioned before, the tag class passed to the ReflectiveTag
does not have to be a body tag. To that end, I’ve made a simple HelloTag
to test out the ReflectiveTag
’s automatic switching between the two. This tag takes either a single name or an array of names and greets them pleasantly.
<html>
<head><title>Reflective Tag Test 2</title></head>
<body>
<b>Regular:</b><br/>
<%
Map map = new HashMap();
map.put("names", new String[]{"Eric", "Karl", "Julie", "Steven", "Linda"});
pageContext.setAttribute("map", map);
%>
<vodori:reflective handler="com.vodori.cms.component.navigation.tag.HelloTag" attributes="${map}"/>
<br/>
<b>Nested:</b><br/>
<c:forEach items="${map.names}" var="name" varStatus="status">
${status.count}.
<vodori:reflective handler="com.vodori.cms.component.navigation.tag.HelloTag" attr1K="name" attr1V="${name}">
<<c:out value="br"/>/>
</vodori:reflective>
</c:forEach>
</body>
</html>
This time I’ve added a few more features to the test, such as nesting and the use of inner content. The first test prints an array of names using the attibute map, while the second test uses JSP to iterate the array and passes a single string via one of the convienience attributes. The output is fairly straightforward:
So there you have it! I think the ReflectiveTag
idea is a great tool for fast prototyping. Your tag handler class files will be picked up and compiled automatically when created or changed, and the TLD does not have to be altered each time. If you add new attributes to a tag, simply update your call to the reflective tag and the associated attribute map. As fun and handy as this is, I would not expect to see it in production code since it can potentially cause a lot of headache and heartache, as well the fact that it exposes The Power of Reflection quite late in the game. On the other hand, I can see a more constrained version being put to great use in a templating system. The class string and attribute map could be passed via the requests’s implicit model map, allowing different tags to be used to process the same html body content. This would allow a designer to write normal markup right on the template page, enclose it in the reflective tag, and let the developer decide which tag to use for that particular request. The designer doesn’t have to know anything at all about the expected attributes or how the tag works.
I hope that you, the viewing public, have enjoyed this post. After a lengthy three month hibernation I fully intend this entry to mark the resumption of a normal posting schedule. There’s plenty more to come, so consider subscribing to my RSS feed to be automatically notified when new entries appear. Comments are always welcome, and thanks for reading!
Example code used in this post:
reflectivetag-examples.tar.gz
reflectivetag-examples.zip