JSP can be used with Velocity through the Velocity tag library . If Velocity syntax matches your project needs better than JSP scriptlets or the expression language, it's a simple task to integrate Velocity with your existing JSP-based application. The Velocity tag library doesn't depend on Struts or any other MVC frameworks to work.
There is only one tag for Velocity – the contents of the tag are parsed as a Velocity template. The Velocity context contains the beans in the page, request, session, and application scopes. Velocity provides two different
Page
Request
Session
Application
This
There currently isn't a way to use JSP tags directly inside a Velocity template. The best practice for using JSP tags with Velocity is to factor out the logic code from the tag into a simple bean that can be added to Velocity's context for use. Another solution would be to write an object that wraps the existing Java methods of your tag into a cleaner API for use with Velocity.
We have several requirements for this piece of the JavaEdge application:
Create an RSS feed that reflects the last stories to be added to JavaEdge
Include all of the stories on the JavaEdge home page
Use Velocity to create the solution, so we can
Follow the RSS 2.0 specification
The first piece of the design puzzle is to determine how we are going to create the RSS feed. Other web sites will need a permanent URL on our JavaEdge application that we can give them to pull our content headlines from.
Here are some possible designs for this problem:
Create a static file on the file system and update it every time a story is added by a
Create a static file on the file system and update it at fixed intervals by a scheduler.
Use a Struts action on URLs that contain the
Use the same design as above, except the RSS file will be cached for a fixed interval, such as 10 minutes.
Create another servlet and make it solely responsible for handling RSS requests. This servlet could handle any
The first design seems like the
The second design could hook into the Struts plug-in system to run as a scheduled process. We use this approach in the chapter on Lucene to run the search engine indexer. It would be nice to have "late- breaking" news immediately show up on our RSS feed, so we won't use this solution. This design would be more appropriate for a high-traffic site like Slashdot because the RSS creation process wouldn't run on every RSS request.
The third design is the one that we will use for our web site. By building everything into Struts, we keep the application architecture simple. At the same time, we can create actions and templates
The fourth design is too complicated for our site; it is similar to the second design, in that it is more appropriate for a high-traffic web application.
The last design, the independent servlet, is worth looking into. This would free us from any Struts requirements we might have on making our functionality into an action. We could generate the RSS from a template directly inside the servlet. We'd like to leave the option
The
The root element of the RSS file is going to be the <rss version="2.0"> element. The <rss> element has a <channel> element. The <channel> element contains all of the metadata for our JavaEdge content feed, along with all of the news items we are publishing:
<?xml version="1.0"?> <rss version="2.0"> <channel>
The title will be used by the web site that consumes the RSS file. The link should point back to our JavaEdge installation:
<title>JavaEdge: Late Breaking Headlines</title> <link>http://localhost:8080/JavaEdge/execute/</link>
Our
description
element could end up being used – we picked one that will stand out in the
<description>The hard-hitting Java journalism you demand.</description> <language>en-us</language> <docs>http://backend.userland.com/rss</docs> <generator> Wrox JavaEdge Application (Struts and Velocity) </generator>
After the above metadata, we get to the news headlines that we syndicate out. Each headline is wrapped in an item element. The title is used to generate a link from the consuming link, and we provide a URL to our site that will point to the story. We will need to generate the storyId , but we'll leave the rest of the URL in the template, so we can use whatever URL we want the external world to know. The description will be the story introduction from the JavaEdge database. We'll need to expose a publication date so others can determine how new our content is:
<item> <title>JSTL tutorial available</title> <link>http://localhost:8080/JavaEdge/execute /storyDetailSetup?storyId=17</link> <description>We are pleased to announce that our editors have created a JSTL tutorial and made it available for everyone to use.</description> <pubDate>Wed, 1 Jan 2003 12:04:31 GMT</pubDate> </item> <item> <title>Latest release of Turbine</title> <link>http://localhost:8080/JavaEdge/execute /storyDetailSetup?storyId=18</link> <description>The Turbine developers have released another great version of their Turbine framework, and it is now up for download on Jakarta Apache</description> <pubDate>Wed, 8 Jan 2003 12:04:31 GMT</pubDate> </item> </channel> </rss>
That's all that is required for the sample RSS file.
The last design decision is how to make sure the RSS file only contains valid XML. Velocity ships with a tool called
Anakia
, which is used for transforming XML into other formats using Velocity. It was originally conceived of as a documentation-generation tool. Anakia comes with a simple class called
org.apache.velocity.anakia.Escape
, which only has one method,
getText(String st)
. This method
The Velocity distribution can be downloaded from its home page at http://jakarta.apache.org/velocity. Download the binary archive for the latest released version (currently Velocity-1.3.1-rc2), and unzip it to a handy directory. Velocity comes with two pre-built JAR files for easy deployment. The smaller JAR file only contains classes in the org.apache.velocity package and sub-packages. The larger file contains all of the classes that Velocity depends on. These are from the Jakarta Apache ORO, Commons, and Avalon LogKit projects. If you don't already have these libraries in your project, you can use the JAR that contains the dependencies for simplicity. If you want to keep the use of Java libraries granular, the needed JAR files are all contained in the build/lib subdirectory of the Velocity installation.
The Velocity tag library can be downloaded from the Wrox web site: http://wrox.com/books/1861007817.htm. The Velocity home page also points to the tag library. If you get the tag library source code out of CVS and build it yourself, be sure that you are using a compatible version of Velocity, as the source code is always being updated.
There are three steps that we need to take to implement the RSS feed:
Add our action to struts-config.xml
Create a setup action for RSS
Create a JSP that uses Velocity to generate the XML for the RSS
The template is contained inside the opening and closing tags of the Velocity tag. When the JSP page is executed, the Velocity tag creates a context out of the objects in the page, request, session, and applications scopes and then
We need to add an action to struts-config.xml . As the URL for our RSS file is going to be http://localhost:8080/JavaEdge/execute/news.rss, the action is mapped to the / news.rss path. This action is simple; it only calls an Action class ( RSSSetupAction ) that puts the necessary data into the request scope. If that is successful, it calls a JSP page ( rss.jsp ):
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.0//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_0.dtd"> <struts-config> ...
<action path="/news.rss" type="com.wrox.javaedge.struts.rss.RSSSetupAction"> <forward name="rss.success" path="/WEB-INF/jsp/rss.jsp"/> </action>
... </struts-config>
This
Action
class puts all of the needed data and utility classes into the request scope. These objects are going into the request scope so they can be used to build up the RSS page. Any objects in the page, request, session, or application scopes become accessible to our Velocity template through the context. We need the collection of story objects from the home page in the request scope, so we borrowed some code from the home page setup action to do this. All the heavy
We also add an object to the request scope to handle escaping XML entities, so that our RSS file is always valid XML. This is a utility object, and we can just use the
We decided to match the date format used in the RSS 2.0 documentation, which required a date format class. We used the SimpleDateFormat class to set up a formatter object that would give us the output we needed. The date format object we created was also put into the request scope:
package com.wrox.javaedge.struts.rss; import org.apache.struts.action.Action; import org.apache.struts.action.ActionMapping; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.log4j.Logger; import org.apache.velocity.anakia.Escape; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.wrox.javaedge.story.dao.*; import com.wrox.javaedge.common.*; import java.util.*; import java.text.SimpleDateFormat;
We are going to extend the Action class and override the perform() method to add several objects to the request scope. The Velocity tag will pull these objects out of the request scope and put them into the Velocity context for the template we will discuss in the section called rss.jsp :
/* * Retrieves the top stories from JavaEdge and puts them in the session for * the RSS to use. */ public class RSSSetupAction extends Action { private static Logger logger = Logger.getLogger(RSSSetupAction.class); /* * The perform() method comes from the base Struts Action class. We * override this method and put the logic to carry out the user's * request in the overridden method * @param mapping An ActionMapping class that will be used by the * Action class to tell the ActionServlet where to send the end-user. * * @param form The ActionForm class that will contain any data submitted * by the end-user via a form. * @param request A standard Servlet HttpServletRequest class. * @param response A standard Servlet HttpServletResponse class. * @return An ActionForward class that will be returned to the * ActionServlet indicating where the user is to go next. */ public ActionForward perform(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) { try {
We are going to get the Story Data Access Object. Our RSS feed will use the same stories we show on the home page, and we can reuse the findTopStory() method that we already built for the home page in Chapter 3:
/* * Creating a Story Data Access Object and using it to retrieve * the top stories. */ StoryDAO storyDAO = new StoryDAO(); Collection topStories = storyDAO.findTopStory();
The top stories from the DAO are going to go into the request, so we can access them from the Velocity template in the page we are going to show the user:
/* * Putting the collection containing all of the stories into * the request. */ request.setAttribute("topStories", topStories);
We'll also put a hard-coded description into the request scope. This is here to demonstrate how easy it is to get a string into a Velocity template from a Struts action:
/* * Put the description into the request. */ request.setAttribute("description", "The hard-hitting Java journalism you demand.");
We're going to use an XML entity escaping object, called Escape , from Velocity to ensure that we have valid XML inside the RSS feed. This helper object can go right into the request scope:
/** * Put the escape object into the context * to escape XML entities. */ request.setAttribute("escape", new Escape());
We will include a date formatter in the request scope for the RSS 2.0 feed. Much like the above XML escaper, it is a helper object:
/** * Create a date format to match the RSS 2.0 dates * Ex. Sun, 19 May 2002 15:21:36 GMT * http://backend.userland.com/rss */ String pattern = "EEE, dd MMM yyyy HH:mm:ss z"; SimpleDateFormat dateFormat = new SimpleDateFormat(pattern); /** * Stick the date format into the request */ request.setAttribute("dateFormat",dateFormat);
If we had a problem retrieving the top stories out of the Story data access object, we catch the exception, log it as an error, and then return a forward for the main application error page. Otherwise, we return a forward that will take us to the rss.jsp file. These mappings were set up in the strutsconfig.xml configuration file:
} catch(DataAccessException e) { logger.error("Data access exception",e); return (mapping.findForward("system.error")); } return (mapping.findForward("rss.success")); } }
We'll walk through the JSP page we need to make the RSS file. This is the declaration for the Velocity tag library:
<%@ taglib uri="/WEB-INF/veltag.tld" prefix="vel" %>
We're going to enclose our entire Velocity template in between the Velocity JSP tag. The Velocity context will contain all of the beans in the page, request, session, and application scopes. The template will be merged with the context, and the output will be put into the JSP page just like any other JSP tag. We could have set the
strictaccess
attribute to
true
, and then any objects we needed out of the JSP scopes would have to be retrieved with the Velocity scope tool first. There are different methods for each scope, so there won't be a naming
<vel:velocity>
This is the declaration for RSS version 2.0:
<?xml version="1.0"?> <rss version="2.0">
There is only one channel for each RSS file:
<channel>
This is the title and link others are going to use when they create links to JavaEdge:
<title>JavaEdge: Late Breaking Headlines</title> <link>http://localhost:8080/JavaEdge/execute/</link>
We'll use Velocity's #if … #end directive to only add a description element if one exists. We are checking to make sure the description has been placed into the context:
#if ($description)
Here, we're building an RSS <description> element. We're using the Escape utility class to make sure the description text is valid XML. If either the escape or the description objects don't exist in the context, we won't display the Velocity code. We accomplish this by using $! for our Velocity references:
<description>$!escape.getText($!description)</description>
End the #if directive, and put in elements for the language (English), and the location of the RSS documentation (Userland):
#end <language>en-us</language> <docs>http://backend.userland.com/rss</docs>
We're showing off our JavaEdge application here, so that when people use our RSS, they can see that it was generated using our application:
<generator> Wrox JavaEdge Application (Struts and Velocity) </generator>
The #foreach … #end directive is useful for getting each single value out of a collection or array, and then doing some work with it. The collection is $topStories , and the single value is $story :
#foreach ($story in $topStories)
The <item> element corresponds to a content piece, or a news story:
<item>
For the title, we're going to use the same escape tool as for the description above. We're going to access the storyTitle property on the $story reference, which is actually a StoryVO object. Velocity has shortcuts for getters and setters on beans, so we can access it with just the property name:
<title>$!escape.getText($!story.storyTitle)</title>
The <link> element contains a URL that points back to the story. We set the story ID dynamically from the story object:
<link>http://localhost:8080/JavaEdge/execute /storyDetailSetup?storyId=$!story.storyId</link>
The story introduction becomes the RSS description. The code is similar to the above <title> element.
#if ($story.storyIntro) <description>$!escape.getText($!story.storyIntro)</description> #end
Here we add the publication date for RSS. The story submission dates don't have times, only dates, so this will always read 12:00 for the time. The date formatter we built in the object is used to format the submission date, if the date exists on the story object:
#if ($story.submissionDate) <pubDate>$!dateFormat.format($!story.submissionDate)</pubDate> #end
Close out all the XML tags and the #foreach … #end directive:
</item> #end </channel> </rss> </vel:velocity>
We can test our RSS functionality by opening the URL http://localhost:8080/JavaEdge/execute/news.rss in our web browser. Here is the news.rss file that is generated by our project:
<?xml version="1.0"?> <rss version="2.0"> <channel> <title>JavaEdge: Late Breaking Headlines</title> <link>http://localhost:8080/JavaEdge/execute/</link> <description>The hard-hitting Java journalism you demand.</description> <language>en-us</language> <docs>http://backend.userland.com/rss</docs> <generator> Wrox JavaEdge Application (Struts and Velocity) </generator> <item> <title>This is a story title jcc</title> <link>http://localhost:8080/JavaEdge/execute /storyDetailSetup?storyId=17</link> <description>This is a unit test story intro.</description> </item> <item> <title>This is a story title jcc</title> <link>http://localhost:8080/JavaEdge/execute /storyDetailSetup?storyId=16</link> <description>This is a unit test story intro.</description> </item> <item> <title>This is a story title jcc</title> <link>http://localhost:8080/JavaEdge/execute /storyDetailSetup?storyId=15</link> <description>This is a unit test story intro.</description> </item> <item> <title>This is a story title jcc</title> <link>http://localhost:8080/JavaEdge/execute /storyDetailSetup?storyId=14</link> <description>This is a unit test story intro.</description> </item> <item> <title>storyTitle</title> <link>http://localhost:8080/JavaEdge/execute /storyDetailSetup?storyId=4</link> <description>storyIntro</description> <pubDate>Thu, 16 Jan 2003 00:00:00 CST</pubDate> </item> <item> <title>J2EE vrs. .NET</title> <link>http://localhost:8080/JavaEdge/execute /storyDetailSetup?storyId=3</link> <description>Found an interesting article comparing J2EE vrs. Microsofts .NET</description> <pubDate>Thu, 16 Jan 2003 00:00:00 CST</pubDate> </item> <item> <title>New Book Released: Open Source for Beginners</title> <link>http://localhost:8080/JavaEdge/execute /storyDetailSetup?storyId=2</link> <description>New book available on Open Source Development. A must have forbeginners .</description> <pubDate>Thu, 16 Jan 2003 00:00:00 CST</pubDate> </item> <item> <title>Knoppix Linux Rocks</title> <link>http://localhost:8080/JavaEdge/execute /storyDetailSetup?storyId=1</link> <description>I ran across a great linux distribution. It's called Knoppix. Completely boots off a CD. Check it out at: http://www.knoppix.org/</description> <pubDate>Thu, 16 Jan 2003 00:00:00 CST</pubDate> </item> </channel> </rss>