Struts Shale is a proposal for a next-generation web development framework. I’ve spent a couple days scouring the internet trying to find a simple getting started guide to build a new Shale application, but there just doesn’t seem to be one yet. Shale’s website promises that a future milestone release will include a blank template ready for customization, but in the meantime, we’re left to reading JavaDoc to figure out how to get going. This tutorial will walk you through building a very simple Hello / GoodBye world application using Shale and JavaServer Faces technology from scratch.
Creating a Directory Layout
The first thing we’ll do is create our project layout. I’m a fan of Maven, and like they’re standard directory layout templates, but for simplicities sake, we’ll ignore it for now (I’ll come back to Maven later regarding Shale). Instead, lets create the following directories:
helloworld/ src/ webapp/ WEB-INF/ lib/
This will be enough to get us started.
A Plethora of Dependencies
The next thing we’ll need to do is download the plethora of dependencies our simple project will have. First download our direct dependencies:
Then you’ll need to download the libraries that those project are dependent on:
- Commons BeanUtils 1.6.1
- Commons Chain
- Commons Codec 1.2
- Commons Collections 3.1
- Commons Digester 1.5
- Commons EL
- Commons Lang
- Commons Logging
- Commons Validator
- Jakarta ORO
*Phew*. Copy the library jars into your WEB-INF/lib directory that we created before. Here’s my directory listing for reference:
> ls commons-beanutils-1.7.0.jar commons-digester-1.7.jar commons-logging-1.0.4.jar myfaces-all-1.1.1.jar commons-chain-1.0.jar commons-el-1.0.jar commons-validator-1.1.4.jar oro-2.0.8.jar commons-codec-1.3.jar commons-fileupload-1.0.jar jstl-1.1.2.jar shale-core.jar commons-collections-3.1.jar commons-lang-2.1.jar
Setting up web.xml
The next step is to create our helloworld/WEB-INF/web.xml
descriptor file for our hello world web application. Copy the following into your web.xml
.
It sets up your web application to use the Shale Application Filter,
the Commons Chain Listener, and the Shale Servlet mapping. All this
amounts to is using Shale as your application’s controller.
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" version="2.4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http:/java.sun.com/dtd/web-app_2_3.dtd"> <display-name>Hello/Goodbye World!</display-name> <!-- Determines wether state is saved on the server or on the client. The tradeoff here is server memory versus network bandwidth. --> <context-param> <param-name>javax.faces.STATE_SAVING_METHOD</param-name> <param-value>server</param-value> </context-param> <!-- Sets up the shale application filter --> <filter> <filter-name>shale</filter-name> <filter-class> org.apache.shale.faces.ShaleApplicationFilter </filter-class> </filter> <filter-mapping> <filter-name>shale</filter-name> <url-pattern>*.faces</url-pattern> </filter-mapping> <!-- Set up the Commons Chain listener --> <listener> <listener-class> org.apache.commons.chain.web.ChainListener </listener-class> </listener> <servlet> <servlet-name>faces</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>faces</servlet-name> <url-pattern>*.faces</url-pattern> </servlet-mapping> </web-app>
Framework Config Files
Now that we have our dependencies in place and a webapp that is
configured to use Shale, we’ll add some blank configuration files under helloworld/WEB-INF
that Shale and JSF will need in order to properly initialize themselves.
chain-config.xml
<?xml version="1.0" encoding="UTF-8"?> <catalogs> <catalog name="shale"> <!-- Disallow direct access to JSP and JSFP resources --> <command className="org.apache.shale.application.ContextRelativePathFilter" includes="\S*\.xml,\S*\.faces,\S*\.html,\S*\.gif,\S*\.jpg,/index\.jsp" excludes="\S*\.jsp,\S*\.jspf"/> </catalog> </catalogs>
dialog-config.xml
<!DOCTYPE dialogs PUBLIC "-//Apache Software Foundation//DTD Shale Dialog Configuration 1.0//EN" "http://struts.apache.org/dtds/shale-dialog-config_1_0.dtd"> <dialogs> </dialogs>
We only define one rule in our Chain’s shale catalog, and that’s to
dissallow direct access to our JSP files. This is because we want to
force our users to access the site through the JSF Servlet. The
digest-config.xml file is required as of the 20051208 nightly build of
Shale – the default application filter won’t initialize without it. I
don’t know if this is a bug or not, but since dialog-config doens’t
concern us in this example, we’ll just create an empty one. We’ll also
need a faces-config.xml
file, but we’ll revisit that in a bit.
Finally, the Web App
I know that seems like a lot of work to have gotten nowhere so far; if you ever worked with Struts from scratch I think you’d have thought the same thing about that. We’re getting there though, it’s finally time to write some code and make some JSPs.
First we’ll create a simple PersonBean class:
package com.timfanelli.jsfsample; public class PersonBean { private String name; public void setName( final String name ) { this.name = name; } public String getName() { return this.name; } }
Then we’ll create some JSPs. One will prompt for your name, and populate the person bean with the value you enter. The others will say hello or goodbye, depending on which action the user chooses.
welcome.jsp
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <html> <head> <title>Hello/GoodBye World!</title> </head> <body> <f:view> <h1><h:outputText value="Welcome!"/></h1> <h:form id="helloform"> <h:outputText value="Please enter your name:"/> <h:inputText value="#{personBean.name}"/> <h:commandButton action="sayhello" value="Say Hello"/> <h:commandButton action="saybyebye" value="Say Goodbye"/> </h:form> </f:view> </body> </html>
hello.jsp
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <html> <head> <title>Hello/Goodbye World!</title> </head> <body> <f:view> <h3>Hello, <h:outputText value="#{personBean.name}"/></h3> </f:view> </body> </html>
byebye.jsp
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <html> <head> <title>Hello/Goodbye World!</title> </head> <body> <f:view> <h3>GoodBye, <h:outputText value="#{personBean.name}"/></h3> </f:view> </body> </html>
If you’re not familiar with JSF already, you’re probably wondering
how all of this is going to tie together… How does personBean get into
my JSP’s and how do the input buttons on welcome.jsp get me to
hello.jsp or byebye.jsp? That’s where we come back to the faces-config.xml
file.
Setting up faces-config.xml
Create a file called helloworld/WEB-INF/faces-config.xml
and copy the following into it:
<?xml version="1.0"?> <!DOCTYPE faces-config PUBLIC "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN" "http://java.sun.com/dtd/web-facesconfig_1_1.dtd"> <faces-config> <navigation-rule> <from-view-id>/welcome.jsp</from-view-id> <navigation-case> <from-outcome>sayhello</from-outcome> <to-view-i>d>/hello.jsp</to-view-id> </navigation-case> <navigation-case> <from-outcome>saybyebye</from-outcome> <to-view-id>/byebye.jsp</to-view-id> </navigation-case> </navigation-rule> <managed-bean> <managed-bean-name>personBean</managed-bean-name> <managed-bean-class>com.timfanelli.jsfsample.PersonBean</managed-bean-class> <managed-bean-scope>request</managed-bean-scope> </managed-bean> </faces-config>
faces-config
sets up our navigation rules. In
particular, if we’re coming from /welcome.jsp and the “outcome” is
“sayhello”, go to the hello.jsp view; if the “outcome” is “saybyebye”,
go to the byebye.jsp view. The “outcome” is determined by the action
property of the commandButton elements your “from-view”, welcome.jsp.
Additionally, you can see we’ve created a managed-bean
called personBean
that is an instance of our com.timfanelli.jsfsample.PersonBean
class. personBean has request scope, and is therefore available
throughout processing of the entire request. Its properties can be
accessed and set using the #{beanname.propertyname}
convention, as shown in all three JSPs.
Build, Package, Deploy
With all this in place, you should now be able to compile, build, deploy and run your web application. I’m going to assume that you’re comfortable enough on this process to bang out a quick ant script (or maven pom) to do the job for you.
Once you can access your webapp, please keep reading! (I’m writing this based off the sample project I just completed, so if you find that I missed a step in my writeup, please post comments!)
You can access your sample app by browsing to your context-root /welcome.faces
. That will trigger the JSF servlet to find and load a view named “welcome”, which corresponds to your “welcome.jsp”
WARNING: No ViewController for viewId
If you’re like me, you watched your logs carefully while running your little sample app, and you’d have noticed several warnings telling you there’s new view controller for view id found under name . This is where we get to start (not) using Shale’s features. Shale provides a 1:1 relationship between views (your jsps) and backing beans (think controller). It’s not an error for there to be no backing bean, but the relationship is one-to-one, not one-to-atmost-one. So we’ll create a couple quick empty controller classes and tie them in. For brevity, I’ll only show you one. You should be able to handle the other two on your own then.
First, create a new class that extends AbstractViewController:
package com.timfanelli.shalesample.viewcontrollers; import org.apache.shale.view.AbstractViewController; public class WelcomeViewController extends AbstractViewController { public WelcomeViewController() { super(); } public void init() { // Obtain generic resources here - that is, resource that you need regardless // of the context of the request you're going to process. } public void preprocess() { if ( ! isPostBack() ) return; // Obtain resources you'll need to process the postback - for instance, database // connections. } public void prerender() { // Use this method to acquire resources (such as database connections, // or performing queries) that you will need if this view is the one to // be rendered. } public void destroy() { // Clean up any resources you obtained in previous event handlers. } }
Then add a managed-bean to your faces config:
... <managed-bean> <managed-bean-name>welcome</managed-bean-name> <managed-bean-class>com.timfanelli.shalesample.viewcontrollers.WelcomeViewController</managed-bean-class> <managed-bean-scope>request</managed-bean-scope> </managed-bean> ...
And that’s it! The magic is in the naming convention. We have a view called “welcome.jsp”. Shale will try to find a backing-bean for your view named “welcome”. Note that there’s no actual requirement for your backing bean to implement the ViewController interface (AbstractViewController is a convenience base class) – but Shale guarantees that if it does, you’ll get extra “freebies” from the framework. For more information on using ViewController, See Here
Add similar empty view controllers to your “hello” and “byebye” views to make the rest of your warnings go away.
Conclusion
While we used virtually none of Shales features, this should be more than enough to get you on your way to using the newest (and potentially hottest) web development framework out there.
Resource Roundup
I found the following resource helpful to get myself started using JSF and Shale: