|
A core J2EE design pattern that is still very useful these days in lightweight Java web application development is the View Helper. Frequent presentation tier changes cause software maintenance headaches when business logic and presentation formatting logic are woven together in the same classes. The web application becomes inflexible, less reusable, less modular, and generally less resilient to change when presentation and business logic are mingled together.
The View Helper pattern specifies the use of helpers to adapt domain model data to the presentation layer of the web application. A view can delegate to helper classes implemented as JavaBeans or custom tags to help render information from the domain object model. Helpers also store the view's intermediate data model and serve as business data adapters. JSP tags are one such implementation of the View Helper pattern. This wiki entry describes how to integrate JSP tags into a Spring MVC application. The JSP tag implementation classThe tag implementation that I demonstrate here is JSP 2.0 tag library that utilizes the javax.servlet.jsp.tagext.SimpleTagSupport class. This class makes creating JSP tags very easy. Below is a custom tag implementation that takes a Wine domain object, builds a message property key from the WineType enumeration value, and retrieves the message value from a Spring Framework MessageSource bean. 1 package springmvc.examples.web.tags; 2 3 import org.springframework.context.ApplicationContext; 4 import org.springframework.context.MessageSource; 5 import org.springframework.web.context.support.WebApplicationContextUtils; 6 import springmvc.examples.domain.Wine; 7 8 import javax.servlet.ServletContext; 9 import javax.servlet.jsp.JspException; 10 import javax.servlet.jsp.JspWriter; 11 import javax.servlet.jsp.PageContext; 12 import javax.servlet.jsp.tagext.SimpleTagSupport; 13 import java.io.IOException; 14 import java.util.Locale; 15 import java.text.MessageFormat; 16 17 /** 18 * @author Christopher Bartling, Pintail Consulting LLC 19 * @since Sep 2, 2008 9:34:22 PM 20 */ 21 public class WineTypeDescriptionTag extends SimpleTagSupport { 22 23 private Wine wine; 24 25 public WineTypeDescriptionTag() { 26 } 27 28 public Wine getWine() { 29 return wine; 30 } 31 32 public void setWine(Wine wine) { 33 this.wine = wine; 34 } 35 36 @Override 37 public void doTag() throws JspException, IOException { 38 final ServletContext servletContext = ((PageContext) getJspContext()).getServletContext(); 39 final ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext); 40 final MessageSource messageSource = (MessageSource) context.getBean("messageSource"); 41 42 final JspWriter writer = getJspContext().getOut(); 43 if (wine != null) { 44 String key = MessageFormat.format("wine.type.{0}", wine.getWineType().name()); 45 String value = messageSource.getMessage(key, null, "Unknown wine type", Locale.getDefault()); 46 writer.print(value); 47 } 48 } 49 } WineType enumerationI've included the WineType enumeration for reference purposes. Nothing really special here. 1 package springmvc.examples.domain; 2 3 /** 4 * @author Christopher Bartling, Pintail Consulting LLC 5 * @since Jun 12, 2008 10:08:31 PM 6 */ 7 public enum WineType { 8 9 CHARDONNAY(1), 10 CABERNET_SAUVIGNON(2), 11 PINOT_GRIGIO(3), 12 MERLOT(4), 13 UNKNOWN(-1); 14 15 16 private int value; 17 18 WineType(int value) { 19 this.value = value; 20 } 21 22 // the identifierMethod 23 public int toInt() { 24 return value; 25 } 26 27 // the valueOfMethod 28 public static WineType fromInt(int value) { 29 switch (value) { 30 case 1: 31 return CHARDONNAY; 32 case 2: 33 return CABERNET_SAUVIGNON; 34 case 3: 35 return PINOT_GRIGIO; 36 case 4: 37 return MERLOT; 38 default: 39 return UNKNOWN; 40 } 41 } 42 43 public String toString() { 44 String result = "Unknown wine type"; 45 switch (this) { 46 case CHARDONNAY: 47 result = "Chardonnay"; 48 break; 49 case CABERNET_SAUVIGNON: 50 result = "Cabernet Sauvignon"; 51 break; 52 case PINOT_GRIGIO: 53 result = "Pinot Grigio"; 54 break; 55 case MERLOT: 56 result = "Merlot"; 57 break; 58 } 59 return result; 60 } 61 62 public static WineType[] getValues() { 63 return new WineType[] {WineType.CABERNET_SAUVIGNON, WineType.CHARDONNAY, WineType.MERLOT, WineType.PINOT_GRIGIO, WineType.UNKNOWN}; 64 } 65 66 public String getValue() { 67 return String.valueOf(toInt()); 68 } 69 70 public String getLabel() { 71 return toString(); 72 } 73 } 74 Tag library descriptor fileI've included the entire TLD file contents because, personally, these aren't easy to write and I always long for simple instructions. OK, cut and paste away. One thing to note though: The uri element is very important. It value will be used in the JSP to correctly reference this taglib configuration. The TLD file resides in the WEB-INF directory and the servlet container will automagically recognize it and load it without any other intervention. No need to mess around with the web.xml file adding deployment descriptor XML markup to link in the TLD configuration. That's a welcome "convention over configuration" change! 1 <taglib xmlns="http://java.sun.com/xml/ns/j2ee" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd" 4 version="2.0"> 5 6 <tlib-version>1.0</tlib-version> 7 <short-name>winery</short-name> 8 <uri>WineryTags</uri> 9 10 <tag> 11 <name>wineTypeDescription</name> 12 <tag-class>springmvc.examples.web.tags.WineTypeDescriptionTag</tag-class> 13 <body-content>empty</body-content> 14 <attribute> 15 <name>wine</name> 16 <type>springmvc.examples.domain.Wine</type> 17 <rtexprvalue>true</rtexprvalue> 18 <required>true</required> 19 </attribute> 20 </tag> 21 </taglib> JSP usageIn the JSP below, the important parts of the markup are highlighted in italicized blue font. Note the uri attribute on the taglib directive towards the top of the JSP file. It must match the uri element in the taglib configuration in the TLD file. I then use the foobar:wineTypeDescription taglib within a JSTL forEach loop, passing in the current Wine reference. Probably note reusable outside of this domain, but nonetheless valuable for keeping business logic out of the presentation layer. 1 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 2 <%@ page contentType="text/html;charset=UTF-8" language="java" %> 3 4 <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %> 5 <%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt" %> 6 <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> 7 <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> 8 <%@ taglib prefix="custom" tagdir="/WEB-INF/tags" %> 9 <%@ taglib prefix="foobar" uri="WineryTags" %> 10 11 12 <html> 13 <head> 14 <title>Wine Connesuir</title> 15 <link rel="stylesheet" href="../../../css/default.css" media="screen" type="text/css"/> 16 </head> 17 <body> 18 <h1>Wine Connesuir</h1> 19 20 <%@ include file="../header.jsp" %> 21 22 <h2>Wine Listing</h2> 23 24 <div class="messageArea">${message}</div> 25 <br/> 26 27 28 <table> 29 <tr class="headerRow"> 30 <td>Wine ID</td> 31 <td>Winery name</td> 32 <td>Type</td> 33 <td>Name</td> 34 <td>Year bottled</td> 35 <td></td> 36 </tr> 37 <c:forEach var="wine" items="${wines}" varStatus="rowCounter"> 38 <c:choose> 39 <c:when test="${rowCounter.count % 2 == 0}"> 40 <c:set var="rowStyle" scope="page" value="oddRow"/> 41 </c:when> 42 <c:otherwise> 43 <c:set var="rowStyle" scope="page" value="evenRow"/> 44 </c:otherwise> 45 </c:choose> 46 <tr class="${rowStyle}"> 47 <td align="center" class="${rowStyle}">${wine.id}</td> 48 <td class="${rowStyle}">${wine.winery.name}</td> 49 <td class="${rowStyle}"><foobar:wineTypeDescription wine="${wine}" /></td> 50 <td class="${rowStyle}"><a href="/app/edit/wine?id=${wine.id}">${wine.name}</a></td> 51 <td align="center" class="${rowStyle}">${wine.yearBottled}</td> 52 <td align="center" class="${rowStyle}"><a href="/app/wine?method=delete&id=${wine.id}">Delete</a></td> 53 </tr> 54 </c:forEach> 55 </table> 56 57 58 </body> 59 </html> |