Struts helps you organize your Java-based web applications by providing a framework based on a version of the Model-View-Controller design pattern. With Struts (or more formally, the Jakarta Project’s Struts Framework), you can combine Servlets, JSP, custom Struts tag libraries and other components using a unified framework that helps you design, create, and deploy stable web applications quickly. It requires a bit more work initially, to set everything up, but the value of the framework is realized later, when the ordered and componentized nature of your code lets you maintain, update, and reuse it easily.
Design Patterns and the Model-View-Controller Pattern
Design patterns are conceptual structures that you can use to better organize how you think about, plan, and talk about your code. Many applications, especially web applications, have core elements in common. Design patterns give developers a shared vocabulary, can help keep us from re-inventing versions of the same solutions for each new application.
Apple’s development environments have used the Model-View-Controller pattern for many years. Apple’s documentation includes a discussion of how the Model-View-Controller applies to Cocoa applications.
The Model-View-Controller pattern is used (whether implicitly or explicitly) in nearly every sophisticated web application. It’s a sensible way to organize your application architecture. Simply put, the Model is your data and your application logic, the View corresponds to your presentation elements (HTML, JSP, display beans, etc.), and the Controller(s) handle the application’s flow.
Installing and Using Struts on Mac OS X
To complete the installation steps below, you’ll need items on the Apple Developer Tools CD. So if you haven’t installed it yet, do so now.
To obtain Struts, go to http://jakarta.apache.org/struts/acquiring.html. In these examples, I’m using the current binary distribution, which at the time of this writing is 1.1. If you’re using a later version, then, of course, you’ll have to change some of the paths in the commands below.
First, use curl to download the Struts archive:
liz@localhost:/usr/local/src> curl -O
http://jakarta.apache.org/builds/jakarta-struts/release/v1.1/jakarta-struts-1.1.tar.gz
Next, extract the files using gnutar. The version of tar shipped with Mac OS X frequently does not work with tarfiles from the Apache project, but gnutar always will.
liz@localhost:/usr/local/src> gnutar -xzvf jakarta-struts-1.1.tar.gz
Before installing, check Jakarta’s list of prerequisites to make sure you have everything you need.
Struts requires a Java2 Java Development Kit - version 1.2 or later. Since you have installed the Apple Developer Tools, you’re all set. Struts also requires a servlet container. I’ve chosen to use the Apache Project’s Tomcat servlet container, and to run it as a stand-alone server for simplicity’s sake. At the time of this writing, the latest stable version was 4.1.24, although another major version is on the horizion. No matter which version you use, your installation process should be much like mine below.
Since the recent Developer Tools include a JDK greater than 1.4 (type java -version to find out which you have), I can install a lightweight version of Tomcat. I want the server to live under /usr/local/tomcat/, and the source tarball to stay in /usr/local/src.
liz@localhost:/usr/local/src> curl -O http://jakarta.apache.org/builds/jakarta-tomcat-4.0
/release/v4.1.24/bin/jakarta-tomcat-4.1.24-LE-jdk14.tar.gz
liz@localhost:/usr/local/src> gnutar -xzvf jakarta-tomcat-4.1.24-LE-jdk14.tar.gz
liz@localhost:/usr/local/src> sudo mkdir /usr/local/tomcat
Password:
liz@localhost:/usr/local/src> sudo mv jakarta-tomcat-4.1.24-LE-jdk14 /usr/local/tomcat
liz@localhost:/usr/local/src> export
CATALINA_HOME=/usr/local/tomcat/jakarta-tomcat-4.1.24-LE-jdk14
liz@localhost:/usr/local/src> cd $CATALINA_HOME/bin
liz@localhost:/usr/local/tomcat/jakarta-tomcat-4.1.24-LE-jdk14/bin> ./startup.sh
Now I can confirm that Tomcat is running on port 8080 on my local machine by going to http://localhost:8080 in a browser:
The next prerequisite is the Ant build system. Note that I’m still using gnutar to extract files, and not Apple’s standard tar executable. I’m installing Ant under /usr/local/ant, and adding its binary files directory to my $PATH environment variable.
liz@localhost:/usr/local/src> curl -O
http://apache.mirrorcentral.com/dist/ant/binaries/apache-ant-1.5.3-1-bin.tar.gz
liz@localhost:/usr/local/src> gnutar -xzvf apache-ant-1.5.3-1-bin.tar.gz
liz@localhost:/usr/local/src> sudo mkdir /usr/local/ant
liz@localhost:/usr/local/src> sudo mv apache-ant-1.5.3-1 /usr/local/ant/
liz@localhost:/usr/local/src> export ANT_HOME=/usr/local/ant/apache-ant-1.5.3-
liz@localhost:/usr/local/src> export PATH=$PATH:$ANT_HOME/bin/
At this point, if you’ve been following along with me, you’ve set up two environment variables ($CATALINA_HOME and $ANT_HOME) and changed one ($PATH). When you exit your terminal session, these variables will go away. No fun. You’ll probably want to set up a shell startup file that loads these variables for you on each new terminal session. Since I use the bash shell, my startup file lives in /Users/liz/.bash_profile and looks (in part) like this:
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/libexec:/System/Library/CoreServices
export JAVA_HOME=/usr
export PS1="\u@\h:\w> "
Since I wanted to keep the new environment variables for future use, I changed my .bash_profile to contain the lines below. You can also see that I added aliases for starting and stopping tomcat.
export ANT_HOME=/usr/local/ant/apache-ant-1.5.3-1
export CATALINA_HOME=/usr/local/tomcat/jakarta-tomcat-4.1.24-LE-jdk14
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/libexec:
/System/Library/CoreServices:$ANT_HOME/bin
export JAVA_HOME=/usr
alias start_tomcat='$CATALINA_HOME/bin/startup.sh'
alias stop_tomcat='$CATALINA_HOME/bin/shutdown.sh'
If you’re using the bash shell, you can copy and use this file as is. Otherwise, see the manpage for your favorite shell program (for example, man tcsh). To use bash and the .bash_profile file I’m using, open the Terminal application, click Preferences, and set your preferences to be like these:
It’s finally time to install Struts itself. The version of Tomcat I’m using comes with an integrated version of Struts. Still, there are some tag library descriptors and example files that I want to install. Note: If you aren’t using Tomcat (or if you’re using an older version of Tomcat without Struts), follow the Struts installation guide. You’ll just have to copy a few extra files - it’s not much harder.
Here are the commands I used to add the extra Struts files to my new Tomcat installation. Again, if you aren’t using Tomcat with Struts built-in, see the installation link in the line above.
liz@localhost:~> stop_tomcat
liz@localhost:~> cd /usr/local/src/jakarta-struts-1.1
liz@localhost:/usr/local/src/jakarta-struts-1.1>
cp lib/*.tld $CATALINA_HOME/server/webapps/admin/WEB-INF/
liz@localhost:/usr/local/src/jakarta-struts-1.1>
cp webapps/*.war $CATALINA_HOME/webapps
liz@localhost:/usr/local/src/jakarta-struts-1.1> start_tomcat
With that step completed, you should be able to view the example applications (which came bundled in those .war files referenced above). For example, look at http://localhost:8080/struts-example/index.jsp
”Hello World”
Now we can build a “hello world” mini-application, using a few Struts components. Since the setup process is fairly complicated, this simple application won’t take advantage of many of Struts’ useful features. I’ll focus on those in the next section.
One of the .war files in your Struts source directory is called struts-blank.war. This file is meant to serve as a template for new Struts applications. I want to extract the archive so I can modify it to make my own simple “Hello World” application. So I copy struts-blank.war into a new empty folder, and type jar -xf struts-blank.war. Here are the contents:
You can see that the archive includes the struts.jar file (necessary for any Struts application), some tag libraries, the standard web.xml and manifest files, and a configuration file called struts-config.xml. This config file is actually functionally empty, but it includes lots of useful examples in commented sections.
Let’s look at the other two files in the archive. First, the index.jsp file that comes with the blank Struts application. In this file, you can see the Struts tag libraries in use. (If you aren’t familiar with the taglibs concept yet, you may want to read Sun’s explanation.) One interesting thing to note is that all the English text on the page is stored separately and presented via a custom tag. While this is by no means required in a Struts application, it does simplify Internationalization or text changes. You don’t need to touch any Java components in order to alter the page copy.
<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<html:html locale="true">
<head>
<title><bean:message key="index.title"/></title>
<html:base/> </head> <body bgcolor="white">
<logic:notPresent name="org.apache.struts.action.MESSAGE" scope="application">
<font color="red">
ERROR: Application resources not loaded -- check servlet container
logs for error messages.
</font>
</logic:notPresent>
<h3><bean:message key="index.heading"/></h3>
<p><bean:message key="index.message"/></p>
</body>
</html:html>
The copy for the file above is stored in ApplicationResources.properties. Since I’m making my own application, I’ll start by altering that text. Here are the contents of my new properties file:
index.title=My Own Hello World, Using Struts index.heading=Heya
Next, I want to make a simple Java Bean that my application can reference. It’s about as simple as a Bean gets - it stores a hard-coded Magic Number, and tells the user whether or not they guessed that number correctly.
package foo;
public class GuessBean
{
public int magicNumber;
public GuessBean()
{
this.magicNumber = 2;
}
public String evaluateGuess(int g)
{
if (g == this.magicNumber)
{
return "You guessed it!";
}
else
{
return "Sorry, wrong number.";
}
}
}
Now I can edit the index.jsp file and add a basic form, and create a new file called guess.jsp which calls the bean’s evaluateGuess() method. (You can find those files in the hello.war archive.)
The final step is to create a build.xml file that Ant can use to bundle the all application’s files into a WAR archive, and deploy them to Tomcat. If you are using a Java IDE that can build WAR files for you, you won’t need to bother with this section. Otherwise, here’s the build file:
<project name="hello world" default="build-all" basedir=".">
<property environment="env"/>
<property name="appname" value="hello"/>
<property name="src.dir" value="${basedir}/WEB-INF/src"/>
<property name="app.name" value="hello"/>
<property name="app.path" value="/${app.name}"/>
<property name="app.version" value="0.1-dev"/>
<property name="build.dir" value="${basedir}/build"/>
<property name="classes.dir" value="${basedir}/WEB-INF/classes"/>
<property name="tomcat.home" value="${env.CATALINA_HOME}"/>
<property name="servlet.jar" value="${tomcat.home}/common/lib/servlet.jar"/>
<property name="deploy.dir" value="${tomcat.home}/webapps"/>
<path id="build.path">
<fileset dir="./WEB-INF/lib/">
<include name="**/*.jar"/>
</fileset>
<pathelement path="${src.dir}"/>
<pathelement path="${servlet.jar}"/>
<pathelement path="${classes.dir}"/>
</path>
<target name="cleanlocal">
<delete dir="${build.dir}" includeEmptyDirs="true" />
<delete file="${appname}.war" />
<mkdir dir="${build.dir}"/>
</target>
<target name="compile">
<javac srcdir="${src.dir}" destdir="${classes.dir}" debug="on" deprecation="on">
<include name="**/*.java"/>
<classpath refid="build.path"/>
</javac>
</target>
<target name="cleanserver">
<delete file="${deploy.dir}/${appname}.war" />
<delete dir="${deploy.dir}/${appname}" includeEmptyDirs="true" />
</target>
<target name="war">
<war warfile="${appname}.war" webxml="./WEB-INF/web.xml">
<fileset dir="./" includes="**/*.*" excludes="*.war, **/*.nbattrs,
.xml, **/WEB-INF/**/*.*, **/project-files/**/*.*"/>
<webinf dir="./WEB-INF" includes="**/*" excludes="web.xml,
**/*.jar, **/*.class"/>
<lib dir="./WEB-INF/lib"/>
<classes dir="${classes.dir}" cludes="**/*.properties, **/*.class" />
</war>
</target>
<target name="build-all" depends="cleanlocal, cleanserver, compile, war">
<copy file="${appname}.war" todir="${tomcat.home}/webapps"/>
</target>
</project>
To deploy my new application, I simply need to run Ant and restart Tomcat.
liz@localhost:~/hello> ant liz@localhost:~/hello> stop_tomcat
liz@localhost:~/hello> start_tomcat
Sample Application: A RSS Newsfeed Consolidator
Many news sites and weblogs make their headlines available as RSS feeds. The RSS format is (more-or-less standardized) XML, generally made available over HTTP. You can read more about RSS, for example, on the O’Reilly xml.com site and the webreference.com site (and elsewhere).
In this section, I’ll show you how you can use Servlets, JSP, and Struts to create an application that will allow a user to log in, assemble a list of weblogs and/or news sites, and read headlines fetched and displayed by the application. Headlines are links to the remote content. For simplicity’s sake, I’ve left off several real-world aspects of the application, most notably the database backend. User data is simply stored in the session, and vanishes when the user closes their browser or you re-start Tomcat.
Let’s look at the application with the Model-View-Controller design paradigm in mind. In this case, the model is the session data plus the application logic. The view corresponds to the JSP pages and beans used for display, and the controller is a special SiteAction class. The SiteAction class is a subclass of a Struts Action class, and it is responsible for handling user input and dispatching requests to the correct places. Here’s an overview of the files which make up the application. (You can download the WAR file and a tarball of the whole application.)
In this application, I want to take advantage of a few of Struts’ cooler features. For instance, I can create custom Form Beans and tie them to Action objects. Let’s start with a Form Bean. The bean’s properties map to form fields, and each property has appropriately-named accessor methods.
package foo;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
public final class SiteForm extends ActionForm
{
private String index = null;
private String url = null;
private String title = null;
private String dispatch = null;
// accessor methods
public void setUrl(String url)
{
this.url = url;
}
public void setTitle(String title)
{
this.title = title;
}
public void setIndex(String index)
{
this.index = index;
}
public void setDispatch(String dispatch)
{
this.dispatch = dispatch;
}
public String getUrl()
{
return (this.url);
}
public String getTitle()
{
return (this.title);
}
public String getIndex()
{
return (this.index);
}
public String getDispatch()
{
return (this.dispatch);
}
public void reset(ActionMapping mapping, HttpServletRequest request)
{
this.url = null;
this.title = null;
this.index = null;
}
// for this application, we aren't validating user input, but you could easily extend
this method to do form validation public ActionErrors validate(ActionMapping mapping,
HttpServletRequest request)
{
// I'm leaving this example code in place so you can
see how form validation would be done using Struts
ActionErrors errors = new ActionErrors();
// if (url == null)
// errors.add("url", new ActionError("error.url.required"));
return errors;
}
One of the most interesting things about Struts is its set of Action classes. The code below extends the Struts Action class, and uses simple conditionals to dispatch user requests. There are other kinds of Action classes, notably DispatchAction, which you can read about in the Struts user guide and on Ted Husted’s husted.com.
// this controller responds to user input, handles it, and redirects
// to a location specified in struts-config.xml package foo;
import java.io.IOException; import java.util.Vector;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
public final class SiteAction extends Action
{
public ActionForward perform(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException
{
ActionErrors errors = new ActionErrors();
// get the RSS Site List object from the session,
or create a new one if necessary
HttpSession session = request.getSession();
SiteList siteList = (SiteList)session.getAttribute("siteList");
if (siteList == null)
{
siteList = new SiteList();
}
String dispatch = ((SiteForm) form).getDispatch();
String forward = "alter";
if ("add".equals(dispatch))
{
String url = ((SiteForm) form).getUrl();
String title = ((SiteForm) form).getTitle();
if (url != null && title != null)
{
SiteListItem it = new SiteListItem();
it.setURL(url);
it.setTitle(title);
siteList.addSite(it);
}
}
else if ("show".equals(dispatch))
{
String index = ((SiteForm) form).getIndex();
if (index != null)
{
// put the selected site index in the
session for use by the next page
session.setAttribute("siteIndex",index);
forward = "view";
}
}
else if ("delete".equals(dispatch))
{
String index = ((SiteForm) form).getIndex();
if (index != null)
{
int ind = Integer.parseInt(index);
siteList.removeSite(ind);
}
}
// Forward to the specified success URL
return (mapping.findForward(forward));
}
}
At this point, we tie the Action and Form objects together using directives in struts-config.xml
<?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>
<!-- ========== Form Bean Definitions =================================== -->
<form-beans>
<!-- our SiteList controller -->
<form-bean name="SiteForm" type="foo.SiteForm"/>
</form-beans>
<!-- ========== Action Mapping Definitions ============================== -->
<action-mappings>
<action path="/siteAction" type="foo.SiteAction" name="SiteForm">
<forward name="alter" path="/index.jsp"/>
<forward name="view" path="/show.jsp"/>
</action>
</action-mappings>
</struts-config
Now we can use Struts to create a JSP page with user input elements that call the SiteAction object.
<%@ page language="java" %>
<%@ page import="foo.SiteListItem" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<head>
<title>Manage Sites</title>
<html:base/>
</head>
<body bgcolor="white">
<logic:notPresent name="org.apache.struts.action.MESSAGE" scope="application">
<font color="red">
ERROR: Application resources not loaded -- check servlet container
logs for error messages.</font>
</logic:notPresent> <br />
<jsp:useBean id="login" scope="session" class="foo.LoginBean" />
<jsp:useBean id="siteList" scope="session" class="foo.SiteList" />
<h1>RSS Site Manager</h1>
<%// user validation is being handled by a session bean if (login.validate() == false)
{
out.println("<script language=\"javascript\">
document.location='/headlines/login.jsp'</script>");
return;
}
int size = siteList.getSize();
for (int i=0; i < size; i++)
{
SiteListItem it = siteList.getItemAt(i);
out.println("<a href=\"/headlines/siteAction.do?dispatch=show&index="
+ i + "\">" + it.getTitle() + "
</a><br>");
}
%>
<br>
<hr>
<h2>Add a Site</h2>
<html:form type="foo.SiteAction" name="SiteForm" action="/siteAction.do" focus="url">
<table border="0"><tr>
<td>RSS URL</td>
<td><input type="text" name="url" size="60"/></td> </tr> <tr>
<td>Title</td> <td><input type="text" name="title" size="60"/></td>
</tr> <tr> <td colspan="2"> <input type="submit" value="Submit"/></td> </tr>
</table> <input type="hidden" name="dispatch" value="add"> </html:form> </body>
</html:html>
These files work together with beans that store and parse the list of RSS links and manage user login. Those helper objects are available in the tar archive. Note that in a production-quality application, user logins would have been handled by an Action object instead of the lightweight bean I’m using for example purposes.
With all these pieces assembled, you can compile and deploy the application using the same build.xml file referenced above. Just change the project name and appname at the top of the file, and you’re good to go. Here’s the RSS manager in action:
Conclusion, and Suggestions for Further Reading
This article has given you the information you need to get up and running with Struts on Mac OS X. Still, there’s much more to Struts than I can cover in one article. If you’re interested in learning more, visit the Struts User Guide. You might also want to pick up Programming Jakarta Struts, by Chuck Cavaness, or Struts in Action, by Ted Husted. Finally, I would be remiss in not recommending the now-classic book Design Patterns: Elements of Reusable Object-Oriented Software by Gamma, Helm, Johnson, and Vlissides.
No comments:
Post a Comment