|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 4 of 4
Spring partitions its functionality into several JAR files, so your WAR file can include only the functionality you need. It's up to you to track down all of the JAR files that you do need. The sample application needs the following Spring resources in its WEB-INF/lib folder, which you can find in Spring's dist/modules directory:
spring-beans.jarspring-context.jarspring-core.jarspring-web.jarspring-webmvc.jarIt also needs these resources, located in Spring's lib subdirectories:
commons-codec.jar (lib/jakarta-commons)
commons-logging.jar (lib/jakarta-commons)
jstl.jar (lib/j2ee)
standard.jar (lib/jakarta-taglibs)
log4j-1.2.15.jar (lib/log4j)
Finally, the Dao class uses JDOM, so you need to download JDOM. Then add jdom.jar (in JDOM's build directory) and xerces.jar (in JDOM's lib directory) to your WAR file.
With these JAR files in your CLASSPATH, you can compile the sample application with the Ant build.xml file included in the source-code download. This file loads a build.properties file that defines the following properties (which you must update to match your environment):
tomcat.home: The location where you have Tomcat installed; if you want to use a different application server then you may need to update
the build.xml file to include the JAR file that contains the Servlet API..
jdom.home: The location where you have JDOM 1.1 installed
spring.home: The location where you have Spring installed.
In the end your WAR file should contain the following files:
index.html
article.jsp
footer.jsp
header.jsp
home.jsp
post.jsp
postSuccess.jsp
css/geeknews.css
images/articlebackground.jpg
images/blockquotebackground.gif
images/codebackground.gif
WEB-INF/web.xml
WEB-INF/geeknews-dao.xml
WEB-INF/geeknews-services.xml
WEB-INF/geeknews-servlet.xml
WEB-INF/classes/log4j.properties
WEB-INF/classes/com/geekcap/geeknews/core/GeekNewsService.class
WEB-INF/classes/com/geekcap/geeknews/core/NewsArticle.class
WEB-INF/classes/com/geekcap/geeknews/dao/FileSystemNewsArticleDaoImpl.class
WEB-INF/classes/com/geekcap/geeknews/dao/NewsArticleDao.class
WEB-INF/classes/com/geekcap/geeknews/web/HomePageController.class
WEB-INF/classes/com/geekcap/geeknews/web/LoadArticleController.class
WEB-INF/classes/com/geekcap/geeknews/web/PostArticleFormController.class
WEB-INF/classes/com/geekcap/geeknews/web/command/ArticleCommand.class
WEB-INF/classes/com/geekcap/geeknews/web/validator/NewsArticleValidator.class
WEB-INF/lib/commons-codec.jar
WEB-INF/lib/commons-logging.jar
WEB-INF/lib/jdom.jar
WEB-INF/lib/jstl.jar
WEB-INF/lib/log4j-1.2.15.jar
WEB-INF/lib/spring-beans.jar
WEB-INF/lib/spring-context.jar
WEB-INF/lib/spring-core.jar
WEB-INF/lib/spring-web.jar
WEB-INF/lib/spring-webmvc.jar
WEB-INF/lib/standard.jar
WEB-INF/lib/xerces.jar
WEB-INF/tld/c.tld
WEB-INF/tld/fmt.tld
WEB-INF/tld/fn.tld
WEB-INF/tld/permittedTaglibs.tld
WEB-INF/tld/scriptfree.tld
WEB-INF/tld/sql.tld
WEB-INF/tld/x.tld
If you're running Tomcat, you can deploy the application by invoking the Ant deploy target in the project build script. The deploy target copies the WAR file to Tomcat's webapps directory.
Start Tomcat, open your Web browser, and open http://localhost:8080/GeekNews/home.htm. Or you can exclude home.htm from the URL to invoke the index.html file, which automatically redirects you to the home page.
To make the example clearer, I decided to store articles in XML files in the directory of your choice, rather than in a database.
To make it work properly, you must create an article directory and update the articleDirectory property defined on the newsArticleDao bean in the geeknews-dao.xml file. When you first launch the application, it won't have any content. So, after you build the source code for the entire
project, click on Post and complete the form (the date needs to be of the format: MM/DD/YYYY HH:MM AM or PM, for example: "03/10/2009 09:00 PM").
The content will be dropped right onto the Web page, so you are free to use HTML markup such as <p>, <blockquote>, <pre>, <code>, and so forth. I don't recommend adding extra <html> or <div> tags, but the basics work.
The HomePageController that you've finished building is essential for the mini-CMS application, but it's only a start. The full application also
needs a controller that responds to request parameters, and one for submitting content. Next you'll build the LoadArticleController, which responds to a single request parameter; after that, you'll implement the form controller for article submissions.
The LoadArticleController accepts a single request parameter -- id -- and uses the GeekNewsService to load the contents of the article that has that identifier. Looking back at the HomePageController, you could simply obtain the id parameter from the request object as you would do in a servlet:
String id = ( String )request.getAttribute( "id" );
But Spring provides a more elegant controller that obtains that parameter (and any others you need) from the request, in the
form of a command object. Listing 5 shows a command object that wraps the id attribute.
package com.geekcap.geeknews.web.command;
public class ArticleCommand {
private String id;
public ArticleCommand() {
}
public ArticleCommand(String id) {
this.id = id;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
ArticleCommand is a simple POJO with a single parameter: id. When the POJO is paired with the appropriate controller type, Spring examines the request and tries to populate each field
in the POJO. For a single parameter this might seem like overkill, but if you had 10 parameters of varying types, you would
otherwise need to extract them manually as Strings and convert them to the appropriate types. You might as well take advantage of Spring's capability to do this for you.
Listing 6 shows the source code for the LoadArticleController.
package com.geekcap.geeknews.web;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractCommandController;
import com.geekcap.geeknews.core.GeekNewsService;
import com.geekcap.geeknews.core.NewsArticle;
import com.geekcap.geeknews.web.command.ArticleCommand;
public class LoadArticleController extends AbstractCommandController {
/**
* Provides access to GeekNews business methods
*/
private GeekNewsService service;
public LoadArticleController() {
setCommandName( "article" );
setCommandClass( ArticleCommand.class );
}
@Override
protected ModelAndView handle(HttpServletRequest req, HttpServletResponse res, Object command, BindException errors ) throws Exception {
// Load our article command
ArticleCommand articleCommand = ( ArticleCommand )command;
// Load the article from the GeekNewsService
NewsArticle article = service.getArticle( articleCommand.getId() );
// Build and return our ModelAndView
return new ModelAndView( "article", "article", article );
}
/**
* Injected by Spring
* @param service
*/
public void setGeekNewsService( GeekNewsService service ) {
this.service = service;
}
}
LoadArticleController extends Spring's AbstractCommandController. In two steps, you configure AbstractCommandController to auto-populate the command POJO of your choice:
setCommandClass() and pass it the command's Class object. The additional call to setCommandName() makes the command object available to the view, in the model, under the specified name.
handle() method. The handle() method includes a command object that can be cast to your command class.
Now, the GeekNewsService can be invoked to find the article with the specified identifier by calling the command's getId() method.
The LoadArticleController is defined in the Spring XML configuration file in much the same way as the homePageController: define the bean with the injected GeekNewsService and add it to the urlMapping:
<bean id="loadArticleController" class="com.geekcap.geeknews.web.LoadArticleController">
<property name="geekNewsService" ref="geekNewsService" />
</bean>
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/home.htm">homePageController</prop>
<prop key="/article.htm">loadArticleController</prop>
</props>
</property>
</bean>
The LoadArticleController returns a NewsArticle object in the model, which is redirected to the "article" page. Recall that the view resolver prefixes article with / and suffixes it with .jsp, so the view page can be found at /article.jsp. The article.jsp page -- shown in Listing 7 -- is similar to the home.jsp page, except that it shows only one article and displays its content rather than its summary.
<?xml version="1.0" encoding="UTF-8" ?>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="com.geekcap.geeknews.core.*,java.util.List"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Article: <c:out value="${article.title}" /></title>
<link type="text/css" rel="stylesheet" href="css/geeknews.css" />
</head>
<body>
<%@ include file="header.jsp" %>
<div id="mainContent">
<p><a href="home.htm">Home</a></p>
<div id="articleContentHeading">
<h2><c:out value="${article.title}" /></h2>
<p>By <span class="articleAuthor"><c:out value="${article.author}" /></span>
on <span class="articleDate"><fmt:formatDate value="${article.date}" type="both" /></span>
</p>
</div>
<div id="articleContent">
<c:out value="${article.content}" escapeXml="false" />
</div>
</div>
<%@ include file="footer.jsp" %>
</body>
</html>
Figure 2 shows a screen shot of the article page.
Now you'll build the PostArticleFormController, which handles a form posting, complete with simple form validation. Figure 3 shows a screen shot of the Geek News article-submission
page.
The submission page prompts the user for an article title, author, submission date, summary, and content (with HTML markup).
It is backed by a NewsArticle object, which is a POJO that wraps these parameters, shown in Listing 8.
package com.geekcap.geeknews.core;
import java.util.Date;
public class NewsArticle implements Comparable {
private String id;
private String title;
private String author;
private String summary;
private String content;
private Date date;
public NewsArticle() {
}
public NewsArticle(String title, String author, Date date, String summary, String content ) {
this.title = title;
this.author = author;
this.summary = summary;
this.content = content;
this.date = date;
}
public NewsArticle(String id, String title, String author, Date date, String summary, String content) {
this.id = id;
this.title = title;
this.author = author;
this.date = date;
this.summary = summary;
this.content = content;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public String getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
/**
* Sorts article by descending dates
*/
public int compareTo(Object o) {
// Comparing the other date to ours should order descending
int result = ( ( NewsArticle )o ).getDate().compareTo( date );
if( result == 0 ) {
// Ensure that we don't lose any articles that have the same date
return 1;
}
return result;
}
}
The NewsArticle class is a straightforward POJO. The only thing unusual about it is that it is Comparable and implements the compareTo() method. This enables articles to be sorted by their date, from most recent to least recent.
Listing 9 shows the source code for the PostArticleFormController.
package com.geekcap.geeknews.web;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.validation.BindException;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;
import com.geekcap.geeknews.core.GeekNewsService;
import com.geekcap.geeknews.core.NewsArticle;
public class PostArticleFormController extends SimpleFormController {
/**
* Provides access to GeekNews business methods
*/
private GeekNewsService service;
public PostArticleFormController() {
setCommandClass( NewsArticle.class );
setCommandName( "newsArticle" );
}
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy hh:mm aa");
binder.registerCustomEditor(Date.class, new CustomDateEditor(format, true));
}
protected ModelAndView onSubmit(Object command, BindException bindException) throws Exception {
NewsArticle article = ( NewsArticle )command;
service.addArticle( article );
return new ModelAndView( getSuccessView() );
}
/**
* Injected by Spring
* @param service
*/
public void setGeekNewsService( GeekNewsService service ) {
this.service = service;
}
}
The PostArticleFormController extends Spring's SimpleFormController and registers the NewsArticle as its command class, which in turn will be the container for its form data. The SimpleFormController class behaves differently depending on whether the Web request sent to it is a GET or a POST. If the request is a GET, it
redirects the request to the form to be completed. If the request is a POST, the form object (NewsArticle in this example) is optionally validated and then passed as the command object to the onSubmit() method. In the PostArticleFormController, the onSubmit() method, knowing that its article is valid, simply invokes the GeekNewsService's addArticle() method to add the article to the CMS repository and then returns a ModelAndView object that references the getSuccessView() view.
The SimpleFormController defines two views: the form view and the success view. Users who have not successfully completed the form are redirected
to the form view, otherwise, on successful submission, they are redirected to the success view. The form and success views
are defined in the XML bean definition:
<bean id="postArticleFormController" class="com.geekcap.geeknews.web.PostArticleFormController">
<property name="formView" value="post" />
<property name="successView" value="postSuccess" />
<property name="geekNewsService" ref="geekNewsService" />
<property name="validator">
<bean class="com.geekcap.geeknews.web.validator.NewsArticleValidator" />
</property>
</bean>
Understanding how the view resolver works, you can probably already determine that the form view resolves to /post.jsp and the success view resolves to /postSuccess.jsp. The GeekNewsService is injected as usual, but there is a new property: validator. The validator defines a class that's responsible for validating the command object (NewsArticle in this example.) Listing 10 shows NewsArticleValidator's source code.
package com.geekcap.geeknews.web.validator;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import com.geekcap.geeknews.core.NewsArticle;
public class NewsArticleValidator implements Validator {
@Override
public boolean supports( Class clazz ) {
return clazz.equals( NewsArticle.class );
}
@Override
public void validate( Object command, Errors errors ) {
NewsArticle article = ( NewsArticle )command;
// Validate required fields
ValidationUtils.rejectIfEmptyOrWhitespace( errors, "title", "article.missingTitle", "A title must be specified" );
ValidationUtils.rejectIfEmptyOrWhitespace( errors, "author", "article.missingAuthor", "An author must be specified" );
ValidationUtils.rejectIfEmptyOrWhitespace( errors, "summary", "article.missingSummary", "A summary must be specified" );
ValidationUtils.rejectIfEmptyOrWhitespace( errors, "content", "article.missingContent", "Content must be specified" );
}
}
Validators must report the classes that they validate through the supports() method. The NewsArticleValidator compares the class passed to it with the NewsArticle class. If the classes are the same, then it returns true, meaning that Spring can safely invoke validate() on this validator, passing it a NewsArticle.
The NewsArticleValidator performs only simple validation checking to see if the title, author, summary, and contents have values in them. You might
want to extend this validation to look for invalid characters in the title, validate that the author currently exists in the
system, validate that the date is valid and after "now," and so forth. This is your entry point to do so. The validator makes
use of Spring's ValidationUtils class, which provides a handful of helper methods to check whether or not fields are populated. Spring also supports the
Jakarta Commons Validator library's more-robust validation capabilities. The important thing to note about this example is
that the errors object is updated with an error condition if a problem occurs. This tells the SimpleFormController to redirect the user back to the form, with error messages.
The third parameter passed to the ValidationUtils methods is a key into a properties file from which Spring can look up the value to display as the error message. This is
for internationalization, which is beyond the scope of this article. Just know that Spring supports internationalization,
and this is one example of where it does so.
Listing 11 shows the source code for the post.jsp file.
<?xml version="1.0" encoding="UTF-8" ?>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="com.geekcap.geeknews.core.*,java.util.List"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Post a New Article</title>
<link type="text/css" rel="stylesheet" href="css/geeknews.css" />
</head>
<body>
<%@ include file="header.jsp" %>
<div id="articleForm">
<p><a href="home.htm">Home</a></p>
<p id="articleFormInstructions">Enter the information for an article posting and press "Post"</p>
<form:form action="post.htm" method="POST" commandName="newsArticle" >
<table>
<tr>
<th rowspan="2">Title:</th>
<td><form:input path="title" size="60" /></td>
</tr>
<tr>
<td><form:errors path="title" cssClass="validationError"/></td>
</tr>
<tr>
<th rowspan="2">Author:</th>
<td><form:input path="author" size="60" /></td>
</tr>
<tr>
<td><form:errors path="author" cssClass="validationError"/></td>
</tr>
<tr>
<th rowspan="2">Date:</th>
<td><form:input path="date" size="60" /></td>
</tr>
<tr>
<td><form:errors path="date" cssClass="validationError"/></td>
</tr>
<tr>
<th rowspan="2">Summary:</th>
<td><form:textarea path="summary" rows="3" cols="90"></form:textarea></td>
</tr>
<tr>
<td><form:errors path="summary" cssClass="validationError"/></td>
</tr>
<tr>
<th rowspan="2">Content:</th>
<td><form:textarea path="content" rows="20" cols="90"></form:textarea></td>
</tr>
<tr>
<td><form:errors path="content" cssClass="validationError"/></td>
</tr>
<tr>
<th></th>
<td><input type="submit" value="Post" /></td>
</tr>
</table>
</form:form>
</div>
<%@ include file="footer.jsp" %>
</body>
</html>
The important observation to make about post.jsp is that it uses Spring form tags as opposed to HTML form tags. It begins by importing the Spring form tag library:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
Then it's possible to preface normal form tags with form:. For example, the <form> tag is substituted with <form:form>. These tags also include some additional parameters:
<form:form> tag's additional commandNameparameter should match up to your SimpleFormController's setCommandName() parameter.
<form:input> and <form:textarea> tags have a path parameter instead of a name parameter. The path parameter is used during form validation to repopulate the value of a tag if the form needs to be redisplayed. If you look
at the generated HTML, you can see that it is translated to name.
There's also a new element: <form:error>. If an error occurs during form validation, the error tag for the specified path is populated with the error message defined
by the form validator. In the post.jsp page, the errors are presented in new table rows that follow the field, but you are free to put them anywhere, such as at
the top of the form. Use the strategy that fits best into your user interface design.
In a few easy lessons, you've built a fully functioning Spring MVC application from scratch. You started by using a simple controller to construct a model and pass it to a JSP file for presentation. Then you added a command controller and a form controller for form validation.
Spring MVC is a highly capable framework and a powerful extension to the Spring Framework. It provides several ways to map request URIs to controllers, defines several controller base classes to suit your business needs, and gives you multiple options for mapping view names to views. If you are building Spring applications, Spring MVC certainly should be at the top of your short list of Web frameworks.
Steven Haines is the founder and CEO of GeekCap, Inc., which provides technical e-learning solutions for software developers. Previously he was the Java EE Domain Expert at Quest Software, defining software used to monitor the performance of various Java EE application servers. He is the author of Pro Java EE 5 Performance Management and Optimization, Java 2 Primer Plus, and Java 2 From Scratch. He is the Java host on InformIT.com and a Java Community Editor on InfoQ.com. Steven has taught Java at the University of California, Irvine, and Learning Tree University.
Read more about Tools & Methods in JavaWorld's Tools & Methods section.