ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Java Web Development with Stripes

by Mark Eagle
01/24/2007

Stripes is an open source, action-based Java web framework designed around the principles that web development should be simple and productive for developers. Traditional Java web development focused on versatility through decoupling, which resulted in multiple configuration files, additional objects, and other fragmented resources. These difficulties subjected many developers to a higher learning curve and reduced productivity. As a result, some Java developers have been lured away by non-Java frameworks such as Ruby on Rails or Django. Java web frameworks, like Stripes, are beginning to learn from the successes of alternative frameworks that streamline development. This article will show how Stripes distinguishes itself from other action based Java web frameworks, such as Struts, while supplying some of the simplicity available in Ruby on Rails.

Figure 1 shows the normal flow of events and components that are typical for applications that are written with Stripes.

Stripes overview
Figure 1. Typical Stripes flow

As you can see, this is pretty much what you would expect from an MVC framework. One of the main differences between Stripes and other action-based frameworks is the absence of external configuration files. As we will see, Stripes uses annotations and convention over configuration to allow more development and less clutter.

Building Your First Stripe Action

Let's jump right into the Stripes framework by creating a "Hello World" example to understand how things fit together. The HelloWorldAction class will prompt the user for a first and last name and then echo it out in a separate view. First, we will write the controller Java class.

public class HelloWorldAction implements ActionBean {    

  @ValidateNestedProperties({
    @Validate(field = "firstName", required = true, 
              on = {"hello"}),
    @Validate(field = "age", required = true, minvalue = 13, 
              on = {"hello"}) 
  })
  private Person person;
  private ActionBeanContext context;
    
  @DefaultHandler 
  public Resolution index() {
    return new ForwardResolution("Hello.jsp");
  }
        
  public Resolution hello() {
    return new ForwardResolution("SayHello.jsp");
  }

  public void setPerson(String person) {this.person = person;}
  public String getPerson() { return person;}
  public void setContext(ActionBeanContext c) {this.context = c; }
  public ActionBeanContext getContext() {return context; }
}

The controller class resembles a POJO (Plain Old Java Object) that implements a Stripes-specific interface called ActionBean. All Stripes actions need to implement this interface to allow the StripesDispatcher servlet to inject an ActionBeanContext object into the current action being serviced. The ActionBeanContext object allows you to access servlet API objects such as the request, response, and servlet context. Most of the time it is not necessary to access these low-level API objects in a Stripes application. The ActionBeanContext class also allows you to get state information about the current action as well as add informational messages and error messages from the current action. The ActionBeanContext field and accessors can be stored in a base class since all Stripes actions will require this implementation.

The rest of the controller class should be familiar to any Java developer. There is a Person object with accessors that will be used to read and write our person's first and last name to our views. While this is a simple nested object, Stripes allows more sophisticated data binding with Java collections, generics support, and indexed properties. Since Stripes can handle complex data binding, your domain objects can be reused in other layers that need them. For example, it is easy to collect information in a domain object via Stripes and make persistent changes with other POJO frameworks like Hibernate or EJB 3.

A simple Stripes validation annotation has been added to the person field to ensure the user enters a first and last name when invoking the hello method. If the user does not enter these required fields, they will be returned to the source page and shown an error message related to this validation. This validation will only be checked when the hello event is requested, as specified in the annotation attribute (on = {"hello"}). Stripes will also generate an error message using sensible defaults based on the type of validation and the name of the field. For example, if the required firstName field of the Person object is not provided when the form is submitted, the user will see the following:

Person First Name is a required field.

This message is constructed by taking the object graph component for Person.firstName and making it more human readable. These validation error messages can be overridden if necessary to provide more customization.

There is also an Integer field called age, which is a property of the Person object. Stripes will first perform type conversion on the request parameter value for person.age for the Integer field and bind the value in the Person object. After the value is bound to the age field in the Person object, Stripes will validate that the Integer value is less than 13. If the user enters a string instead of an integer the user will see this message:

The value (Mark) entered in field Person Age must be a valid number.

If the user enters an integer but the value is less than 13 the user will see this message:

The minimum allowed value for Age is 13.

Again, we did not need to provide any external configuration for these error messages. The annotation that provided this validation is inline with your field which makes it easy for developers to locate the validation, understand what validation will occur, and make maintenance changes to the validation.

There are also two methods (called events) in this Stripes action that can be invoked. An event is represented by a method in an ActionBean class with the following signature:

public Resolution eventName

Notice that the index method is marked with the @DefaultHandler annotation. Since there are multiple events in this action one of them needs to be specified as the default event. If the URL that calls this action does not specify an event then Stripes will look for an event that is annotated with the @DefaultHandler annotation and execute it.

The View

Now let's add our view logic to the Hello World example. By default, Stripes supports JSP as the standard view technology, however, you can also use other view technologies such as FreeMarker. There is nothing really new to learn here except for the Stripes tag library. The initial view, called Hello.jsp, will allow the user to enter and submit their first name.

        
<%@ taglib prefix="stripes" 
          uri="http://stripes.sourceforge.net/stripes.tld" %>
<html>
  <head>
    <title>Stripes Hello World</title>
  </head>
  <body>
    <stripes:errors/>
    <stripes:form 
        beanclass="com.
                   myco.
                   web.
                   stripes.
                   action.
                   example.
                   HelloWorldAction">
    Say hello to: <br>
    First name: <stripes:text name="person.firstName"/>
    <br>
    Age:<stripes:text name="person.age"/><br>
    <stripes:submit name="hello" value="Say Hello"/>
    </stripes:form>
  </body>
</html>

This JSP is simple to read and maintain. The Stripes tags used for the form and input fields are very close to their HTML counterparts. The stripes:form tag contains an attribute called beanclass which is the fully qualified name of the controller class we previously defined. We could have substituted the action attribute in the stripes:form tag instead of the beanclass attribute. However, the beanclass attribute makes things easier if you need to perform refactoring on a Stripes action. Here is what the stripes:form tag would look like if you use the action attribute:

<stripes:form action="/example/HelloWorld.action">

One of the stripes:input tags specifies a name attribute of person.firstName which will be used to store the value of the input field into the firstName field of the Person object in the controller. Finally, the stripes:submit tag specifies a name attribute which will be used to instruct the Stripes HelloWorldAction class to use the hello event.

Now we are setup to submit a first and last name value to the HelloWorldAction. All we need to do is echo it back out in a separate view.

        
<%@ taglib prefix="stripes" 
       uri="http://stripes.sourceforge.net/stripes.tld" %>
<html>
  <body>
    <stripes:errors/>
    <h2>Hello ${actionBean.person.firstName} your age is 
              ${actionBean.person.age} </h2>
    <p/>
    <stripes:link beanclass="com.myco.web.stripes.action.
                                   example.HelloWorldAction">
      Say Hello Again
    </stripes:link>
  </body>
</html>

This JSP will echo out the person's first and last name field contents by accessing a reference to the action itself. Stripes automatically includes an actionBean request attribute for this purpose which can be accessed with JSTL. Finally, we use a stripes:link tag to create a link back to the HelloWorldAction class so we can enter a different name to echo. We could have also created the stripes:link like this to explicitly reference the index event:

  
<stripes:link 
  beanclass="com.myco.web.stripes.action.
                 example.HelloWorldAction" 
  event="index">Say Hello Again</stripes:link>

Since we annotated the index method with @DefaultHandler, Stripes knows which method to execute without the event attribute.

Convention Over Configuration

Now that we have our Java components in place, we will configure the action mapping to a URL and link it to our two views. Wait a minute this is Stripes, we do not need any external configuration!

While this might sound too good to be true, it is one of the most productive features in Stripes. Stripes uses convention over configuration to map actions to URLs. We also do not need to use an external configuration file to map a symbolic name to the actual views. This means that developers do not have to flip back and forth between configuration files to determine how to navigate between symbolic names, like SUCCESS, to the actual path of the view. There is no need for external wiring of Java and view components. This leads to better maintainability and more productivity.

How does Stripes provide implicit URL mappings to Java action classes without having to configure each action externally or another annotation? This can be explained with the configuration of Stripes in the web.xml file and how it uses sensible defaults to create the URL mappings. First, we need to discuss the Servlet filter called StripesFilter. Here is the default configuration of the StripesFilter in the web.xml file:

        
<filter>
  <display-name>Stripes Filter</display-name>
  <filter-name>StripesFilter</filter-name>
  <filter-class>
    net.sourceforge.stripes.controller.StripesFilter
  </filter-class>
    <init-param>
    <param-name>ActionResolver.UrlFilters</param-name>
    <param-value>/WEB-INF/classes</param-value>
  </init-param>
</filter>

When the Servlet container is started, the StripesFilter performs initialization of its init-param elements. One of the most important init-param elements is the ActionResolver.UrlFilters parameter. This tells Stripes where to look for Stripes relates classes. In this case, Stripes will look for all classes that implement the ActionBean interface in the default /WEB-INF/classes path. Each ActionBean class located is added to a Map along with the default binding URL for that class.

Let's look at an example of what Stripes will do with our Hello World example class. Since the HelloWorldAction class is in the /WEB-INF/classes path and implements ActionBean, it will be recognized as a Stripes servlet. In our example, the fully qualified name of the class is com.myco.web.stripes.action.example.HelloWorldAction. The fully qualified name is then translated to a URL binding by performing these rules:

  1. Substring the fully qualified class name with each occurrence of strings that match www, web, stripes, or action. In our example, we have three of the four matches in the package name. This will leave us with "example.HelloWorldAction".
  2. Remove the strings "Action" and " Bean" from the end of the class name if they exist. This will result in "example.HelloWorld" since our class ended with Action.
  3. Now we substitute slashes for periods which results in "example/HelloWorld"
  4. Finally, we add the binding suffix to the end (.action by default) which completes the URL binding. The final result is "example/HelloWorld.action".

Now that Stripes found the ActionBean class and created a URL binding for it, they will be cached in a java.util.Map<String, Class<? extends ActionBean>> where the key is the URL binding and the value is the Class that implements ActionBean. Here is what our example will look like in the Map:

URL Binding ActionBean class
/example/HelloWorld.action com.myco.web.stripes.action.example.HelloWorldAction

The second component we need to discuss is how Stripes will translate the URL binding back to the ActionBean class you are trying to work with. This is responsibility of the Stripes dispatcher Servlet which is configured in the web.xml file like this:

  
 <servlet>
     <servlet-name>StripesDispatcher</servlet-name>
     <servlet-class>
        net.sourceforge.stripes.controller.DispatcherServlet
    </servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>StripesDispatcher</servlet-name>
  <url-pattern>*.action</url-pattern>
</servlet-mapping>

One of the responsibilities of the StripesDispatcher is to resolve a URL to a Stripes ActionBean class. When the user invokes the URL http://host/uri/example/HelloWorld.action, the Stripes dispatcher servlet will look inside the URL binding Map and find the com.myco.web.stripes.action.example.HelloWorldAction class and instantiate an instance of it. Finally, the index method will be invoked since it was defined annotated as the default handler and an event was not specified in the URL.

What if we wanted to execute the hello method in the HelloWorldAction class directly? The URL would need to include the event name as a request parameter like this:

http://host/uri/example/HelloWorld.action?hello=&firstName=Mark&age=13

Notice that we do not specify a value for the hello request parameter. In this case, the StripesDispatcher will recognize a match for the hello request parameter name and a method name in HelloWorldAction class that has the method signature public Resolution hello(). The method names are cached at initialization in a separate Map for performance.

We have now seen the basics of Stripes and how to create simple actions and some details about how the framework operates. By doing some initial configuration in web.xml, we are able to avoid having a separate XML configuration files to wire together our presentation components. This is important for several reasons. First, you can look at a URL and instantly know what class to look at if you need to make any modifications. Second, we do not need a separate tool to assist us when the configuration file becomes large and unmanageable. By eliminating this configuration file, we no longer have to feed the framework with lots of metadata. Finally, we do not need to supply ongoing maintenance of a separate file that describes how our components are related to each other.

Ajax

So what about more advanced functionality? Let's see how Stripes handles Ajax. We will modify the Hello World example above with an Ajax call that refreshes content in place. This example will demonstrate how to use Prototype to provide the Ajax call to the Stripes action. The source for this example can be referenced in the resources for this article. First, let's modify the Hello.jsp to include the Prototype JavaScript library references. We will also add a JavaScript function for the Ajax call and change the submit button to reference a new button with an onclick event :

        
<%@ taglib prefix="stripes" 
            uri="http://stripes.sourceforge.net/stripes.tld" %>
<html>
  <head>
    <title>Stripes Hello World</title>
    <script 
        src="${pageContext.request.contextPath}/js/prototype.js"
        type="text/javascript"></script> 

    <script type="text/javascript">
       function sayHelloAjax() {
          var myAjax = new Ajax.Updater('hello',
          "<stripes:url 
               beanclass="com.
                          myco.
                          web.
                          stripes.
                          action.
                          example.
                          HelloWorldAction"
               event="sayHelloAjax"/>",
          {
              method: 'get',
              parameters: Form.serialize('helloForm')
          });
        }
    </script>
  </head>
  <body>
    <stripes:errors/>
    <stripes:form 
        beanclass="com.
                   myco.
                   web.
                   stripes.
                   action.
                   example.
                   HelloWorldAction" 
        id="helloForm">
        Say hello to: <br>
        First name: <stripes:text 
                          name="person.firstName"/><br>

        Age:<stripes:text name="person.age"/><br>

        <stripes:button
                name="helloAjax"
                value="Say Hello" 
                onclick="sayHelloAjax()"/>

        <div id="hello"></div>

    </stripes:form> 
  </body>
</html>

The stripes:button has an onclick event that will call the sayHelloAjax method in the HelloWorldAction class and return the results in the div tag called hello. Here is the new method that we need to introduce in the HelloWorldAction class:

 
public Resolution sayHelloAjax(){
  return new ForwardResolution("SayHelloAjax.jsp");  
}

This method does not have to do much since the binding of the first and last name is taken care of by Stripes. Therefore, the only responsibility of this method is to forward to a partial page called SayHelloAjax.jsp. Here is the contents of SayHelloAjax.jsp:

        
<h2>Hello ${actionBean.person.firstName} your age is ${actionBean.person.age}!</h2>

Spring Integration

Stripes also has built in integration with Spring. You can inject Spring beans or services into your Action automatically. In Stripes fashion, this does not require external configuration aside from your Spring context configuration. If we have a bean defined in our Spring configuration like this:

  
<bean id="personService" parent="abstractTxDefinition">
  <property name="target">
    <bean class="com.myco.service.impl.PersonServiceImpl"/>
  </property>
</bean>

To inject the person service into a Stripes action, add a property and setter that matches the name of the Spring bean. Stripes provides the @SpringBean annotation to locate the correct Spring bean to inject into the action class. Here is an example of what needs to be included in the Stripes action:

  
private PersonService personService;

@SpringBean
public void setBlogService(BlogService blogService) {
  this.blogService = blogService;
}

This article cannot cover all of the advanced features of Stripes. However, the Stripes documentation is very comprehensive. Stripes also includes a layout manager similar to Tiles without external configuration. Additionally, interceptors can also be used across life-cycle events, file upload, and a lot more.

Conclusion

Stripes is a powerful yet simple Java web framework. Stripes takes advantage of Java 5 features such as annotations and generics, which allows Java developers to avoid maintaining external configuration files and increase productivity. Stripes makes difficult web development tasks easy and makes simple tasks even easier.

Resources

Mark Eagle is a Senior Software Engineer at MATRIX Resources, Inc. in Atlanta, GA.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.