Flylib.com

Books Software

 
 
 

JSP and Velocity


JSP and Velocity

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 methods for accessing beans from the scope. The default method is to allow access to any bean in each scope. You should try to avoid namespace conflict with the names of beans. But if you do have the same name for beans in different scopes, Velocity searches the scopes in the following order:

  1. Page

  2. Request

  3. Session

  4. Application

This presents a problem if you have a bean named user in the request scope, but you actually want to access the user bean in the application scope. The tag library provides a built-in Velocity object in the context called the $scopeTool , which can retrieve objects out of a given scope. If you would like to keep access to your beans restricted by scope, you can set the strictaccess attribute on the velocity tag to true , which disables the default access to any bean in any scope. All bean access will have to go through the scope tool.

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.



Creating the JavaEdge RSS Feed 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 demonstrate this clever tool

  • 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 user .

  • 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 path /news.rss, and dynamically generate the RSS file every time the web site is hit.

  • 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 requests for a path with /news.rss.

The first design seems like the easiest solution, but we could end up with a jumbled application architecture if we add code to create an RSS file into the post story Struts actions. In addition, if we create an interface for editing or deleting posted stories we will have to include code in these new actions to handle the RSS file creation. Also, we could run into a thread safety problem if we aren't careful when more than one person posts a story to JavaEdge.

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 specifically for RSS. We'll need to choose a URL for the RSS file. Generally, this URL is simple because it will have to be copied into other applications. We will use the following URL: http://localhost:8080/JavaEdge/execute/news.rss.

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 open to leverage Struts better in the future, so we'll stick with our Struts-based design.

The next part of the puzzle is to decide which parts of the RSS specification to implement. There are many optional elements and attributes we could include in our RSS file. We'll create an example RSS file that will demonstrate the elements and attributes we intend to use. By referencing the RSS specification, you can determine which fields should be added to our solution to fit your requirements.

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 crowd . The language element is optional; refer the RSS 2.0 documentation for more information. The next element, docs , points to the RSS specification from Dave Winer at Userland. The generator should be the name of our application, and it's an optional element:

<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 escapes character data for safe use in XML or HTML, by replacing four special characters ( <, >, ", & ) with their XML entities.

Installing Velocity and the Tag Library

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.

Implementing the RSS Feed

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

{% if main.adsdop %}{% include 'adsenceinline.tpl' %}{% endif %}

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 evaluates the template with the context. The output of the merge is displayed in the JSP page.

Configure struts-config.xml

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>

RSSSetupAction.java

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 lifting is done by the story DAO, which we discussed in Chapter 5. We also move the description into the action, to be put into the context and pulled out by the template. We did this just to demonstrate how we could change the description or any of the other fields on the fly.

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 methods from it in the Velocity template.

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")); } }

rss.jsp

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 clash problem:

<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>

Output: News.rss

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 for

beginners

.</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>