Java Development News:

A Stripes 1.5 Test Drive

By Frederic Daoud

01 Dec 2008 | TheServerSide.com

Stripes is a simple and powerful action-based Java web application framework. Version 1.5 has recently been released with lots of cool features, so why don't we take it out for a test drive? We'll get a taste of how easy it is to develop a web application with Stripes by writing a simple example, where a user fills out a form to register and then logs in to access the application.

Action Beans

The first thing to know about Stripes is that you write action beans to respond to requests. Action beans are so-called because they encapsulate both an action (a request that occurred) and one or more beans (references to your model data). In terms of code, an action bean is a class that implements the ActionBean interface:

 public interface ActionBean { public ActionBeanContext getContext(); public void setContext(ActionBeanContext context); }

Just a getter and a setter for the action bean context, which Stripes automatically provides to action beans. The action bean context contains such information as the HttpServletRequest and HttpServletResponse objects, the ServletContext , the Locale associated to the current request, and so on. While Stripes does not require you to work directly with these objects, it's nice to know that they are there for easy access if you do need them.

Usually, you'll want to implement the ActionBean interface once and for all in a base class:

 BaseActionBean.java public abstract class BaseActionBean implements ActionBean { private ActionBeanContext context; public ActionBeanContext getContext() { return context; } public void setContext(ActionBeanContext context) { this.context = context; } }

All your concrete action beans can now extend BaseActionBean . This also gives you a place for code that you want to make available to all your action beans.

Event Handlers

Next, an event handler is a method in an action bean that gets invoked by Stripes to respond to a request. You can have one or more event handlers in an action bean. There are no restrictions on the method name nor the exceptions that it may throw. What's important is that the method have no parameters and return a Resolution (more on resolutions in a minute), as in:

 public Resolution doSomething() { // ... }

Event handlers have a name, which is the name of the method by default ( doSomething in the example above). You can override the name with the @HandlesEvent annotation, as in the example below where the event will be named anotherName :

 @HandlesEvent("anotherName") public Resolution doSomething() { // ... }

You use event handler names in request-triggering elements such as links and form submit buttons. For example, you can create a link that invokes the doSomething event defined in the org.stripesbook.article.action.SomeActionBean class with this code in your JSP:

 <s:link beanclass="org.stripesbook.article.action.SomeActionBean" event="doSomething"> Click me </s:link>

Here's the equivalent for a form submit button:

 <s:form beanclass="org.stripesbook.article.action.SomeActionBean"> <%-- form input controls... --%> <s:submit name="doSomething" value="Press me"/> </s:form>

Notice how clear and simple that is. The fully qualified class name of the action bean and the name of the event handler method are right there. No guesswork, and no configuration files.

Resolutions

A resolution is returned by an event handler to tell Stripes where to go next to produce a response to the client. Resolution is a simple interface:

 public interface Resolution { void execute(HttpServletRequest request, HttpServletResponse response) throws Exception; }

Stripes provides a few ready-to-use implementations. The most commonly used is probably the ForwardResolution , which forwards the request to a view template, such as a JSP:

 return new ForwardResolution("/path/to/your.jsp");

(Note: I'm using JSP in this article, but Stripes works equally well with FreeMarker.)

You can also forward to another action bean and event:

 return new ForwardResolution(SomeActionBean.class, "doSomething");

Again, you can see how clear it is to have the path to the JSP or the action bean class and method name right there instead of buried in a configuration file.

Stripes also includes RedirectResolution to redirect the request, StreamingResolution to stream data back to the client, JavaScriptResolution to return data as a JavaScript object, and ErrorResolution to return an HTTP error code.

A Typical Register-Activate-Login Example

That's enough theory. Let's take Stripes out for a test drive by building a simple register-activate-login example. A user must first register, then activate the account with an activation code (normally sent by email to confirm that the address is valid, but we'll just use a hardcoded activation code for this example), and finally login to enter the secured area of the application, as illustrated below.

We'll start with a simple model, the User class:

 User.java package org.stripesbook.article.model; public class User { private String email; private String username; private String password; private String activationCode; private boolean activated; /* Getters and setters... */ }

Next, let's build the registration page.

The Registration Page

The registration page is where the user creates an account. Here's a screenshot of the page:

We'll start by writing the action bean for managing registration, RegisterActionBean.java :

 RegisterActionBean.java package org.stripesbook.article.action; @UrlBinding("/register") public class RegisterActionBean extends BaseActionBean { private User user; private String confirmPassword; /* Getters and setters for user and confirmPassword */ @DefaultHandler public Resolution view() { return new ForwardResolution("/WEB-INF/jsp/register.jsp"); } public Resolution register() { // Set random activation code; we'll just hardcode a value for this example user.setActivationCode("A1B2"); // Save the user to the "database" Database.addUser(user); return new RedirectResolution(ActivateActionBean.class) .addParameter("user.username", user.getUsername()); } public User getUser() { return user; } public void setUser(User user) { this.user = user; } public String getConfirmPassword() { return confirmPassword; } public void setConfirmPassword(String confirmPassword) { this.confirmPassword = confirmPassword; }

With the @UrlBinding that we defined, we can access the registration page with [http://localhost:8080/stripes-article/register]. That targets the action bean's default event handler, which is view because it is annotated with @DefaultHandler . The method forwards to register.jsp , which displays the registration form.

Here's what the interesting part of the JSP for the registration page looks like:

 register.jsp <%@ taglib prefix="s" uri="http://stripes.sourceforge.net/stripes.tld" %> <s:form beanclass="org.stripesbook.article.action.RegisterActionBean"> Email:<br> <s:text name="user.email"/><br><br> Username:<br> <s:text name="user.username"/><br><br> Password:<br> <s:password name="user.password"/><br><br> Confirm password:<br> <s:password name="confirmPassword"/><br><br> <s:submit name="register" value="Register"/> </s:form> <p> If you've registered but haven't activated your account, <s:link beanclass="org.stripesbook.article.action.ActivateActionBean"> click here </s:link> </p> <p> If your account is already active, you can proceed to <s:link beanclass="org.stripesbook.article.action.LoginActionBean"> login </s:link> </p>

Notice the declaration of the Stripes tag library at the top. Next, we have a form with the four text fields for entering the email, username, password, and password confirmation. The values entered by the user are bound to the corresponding properties in the action bean, according to each field's name= attribute. This works even for nested properties, such as user.email . Stripes will bind the value by calling getUser().setEmail(value) on the action bean and will even create a new User object automatically. You just declare the properties in the action bean without worrying about dreaded NullPointerException s.

Since the name= of the <s:submit> button is register , Stripes invokes register() when the user presses the Register button. That creates the user in the database and redirects to the activation page.

Before going any further, let's discuss how we add some validation to the registration page.

Validation

Let's add these validations to the registration form:

  • All fields are required
  • The email must be in valid email address format
  • The password and confirm password must match.

We can achieve this quite easily with validation annotations on the user field:

RegisterActionBean.java

 @ValidateNestedProperties({ @Validate(field="email", required=true, converter=EmailTypeConverter.class), @Validate(field="username", required=true), @Validate(field="password", required=true, expression="${this eq confirmPassword}") }) private User user;

Validation annotations must be in an action bean, but we can still validate fields within a model object by using @ValidateNestedProperties as in the code above. Next, @Validate validates a field and has built-in elements for common types of validation. Here we are using required=true to make a field required, and converter=EmailTypeConverter.class to validate the format of the email address. We can validate that the passwords match with ``expression="${this eq confirmPassword}" , where ``this automatically refers to the field being validated, and confirmPassword is the corresponding property in the action bean. The syntax of the expression is the same as a regular JSP EL expression.

Note that you don't have to worry about extra spaces at the beginning or at the end of the input, because Stripes trims all input strings by default. Of course, you can override that behavior as needed.

Now that we've added some validation, Stripes will not invoke the targetted event handler method if any of the validations fail. Instead, the form will be redisplayed. We need the <s:errors/> tag in the JSP to show the error messages:

 register.jsp <s:form beanclass="org.stripesbook.article.action.RegisterActionBean"> <s:errors/>

For example, if the user submits a blank form, the registration page gets redisplayed as shown below:

Notice that Stripes is smart enough not to display redundant error messages. For example, with the email field left blank, of course it is not in valid email address format either. But there's no point in displaying two error messages in thise case. Stripes runs validations in a logical order, and doesn't bother running additional validations on the same field when previous validations fail.

Built-in validations are nice, but you might also need to run your own custom validations. You can do this simply by adding a method in the action bean that takes a ValidationErrors parameter, and is annotated with @ValidationMethod :

 RegisterActionBean.java @ValidationMethod public void validateUniqueUsername(ValidationErrors errors) { if (Database.userExists(user.getUsername())) { errors.add("user.username", new SimpleError("The username is already taken")); } }

Here we're checking with the database to see if the username is already taken by another user, and we add a validation error to the ValidationErrors object if that's the case. Stripes automatically returns to the registration form and displays the error. What's also nice is that Stripes only calls validation methods if built-in validations have passed, so you can count on required fields being filled in when your validation code is invoked.

With these validations in place, we must "turn off" validation for the view event. That's easy to do: just annotate the method with @DontValidate :

 RegisterActionBean.java @DontValidate @DefaultHandler public Resolution view() { return new ForwardResolution("/WEB-INF/jsp/register.jsp"); }

Strict Binding

The User class contains a boolean field named activated to indicate whether or not the user has activated the account by entering the activation code that would be sent by email. If you've got a keen eye on security, you've noticed that a malicious user could fake a registration form with a field named user.activated having a value of true to bypass our activation mechanism. Fortunately, Stripes provides an easy way to prevent users from binding additional values beyond those that we expect to bind. This is called strict binding, and is achieved by annotating the action bean with @StrictBinding :

 RegisterActionBean.java @StrictBinding public class RegisterActionBean extends BaseActionBean {

What this does is allow binding of values for validated fields only. This includes the email , username , and password fields. Attempts to bind other fields will be blocked.

If you've been following closely, you'll have noticed that we now have a problem: the confirmPassword field is not validated directly, since it is validated by the expression= validation in the password field. This will prevent the confirmPassword field from being bound. We can fix that by adding an empty @Validate annotation to the confirmPassword field:

 RegisterActionBean.java @Validate private String confirmPassword;

This doesn't add any validation to the field, but effectively allows the value to be bound. You can also use allow and deny in @StrictBinding to set which fields you want to bind; see the Javadoc for more details.

Our registration page is now complete. Let's create the activation page.

The Activation Page

After registration, the user goes to the activation page to enter the activation code that was sent by email. For the purposes of our example, we'll skip the details of generating a random activation code and emailing it, and instead we'll just use a hardcoded activation code, A1B2 .

 activate.jsp <p> You will receive an activation code by email.<br> Please enter it below along with your username to activate your account. </p> <p> [The activation code is A1B2] </p> <s:form beanclass="org.stripesbook.article.action.ActivateActionBean"> <s:errors/> Username:<br> <s:text name="user.username"/><br><br> Activation code:<br> <s:text name="user.activationCode"/><br><br> <s:submit name="activate" value="Activate"/> </s:form>

Notice how the username field is prepopulated. We achieved this by adding a parameter when we redirected from the registration page to the activate page:

 RegisterActionBean.java return new RedirectResolution(ActivateActionBean.class) .addParameter("user.username", user.getUsername());

Of course, the user can also access the activation page directly, by clicking on the link from the registration page, in which case the username would initially be blank.

Now, here is the action bean for the activation page:

 ActivateActionBean.java package org.stripesbook.article.action; @UrlBinding("/activate") @StrictBinding public class ActivateActionBean extends BaseActionBean { @ValidateNestedProperties({ @Validate(field="username", required=true), @Validate(field="activationCode", required=true) }) private User user; /* Getters and setters for user */ @DefaultHandler @DontValidate public Resolution view() { return new ForwardResolution("/WEB-INF/jsp/activate.jsp"); } @ValidationMethod public void validateUser(ValidationErrors errors) { if (!Database.userExists(user.getUsername())) { errors.add("user.username", new SimpleError("The username is not valid")); } else { User existingUser = Database.getUser(user.getUsername()); if (!existingUser.getActivationCode().equals( user.getActivationCode().toUpperCase())) { errors.add("user.activationCode", new SimpleError("The activation code is not valid")); } } } public Resolution activate() { Database.getUser(user.getUsername()).setActivated(true); getContext().getMessages().add( new SimpleMessage("Your account has been activated!")); return new RedirectResolution(LoginActionBean.class); } }

Pretty straightforward. Notice how we can validate different fields as being required with the @Validate annotations; for example, the email was required in the registration page, but isn't required in the activation page.

In the activate() method, we add a "Your account has been activated!" confirmation message and redirect to the login page. Let's see how we display such messages in the login page.

The Login page

Here is the JSP for the login page:

 login.jsp <s:form beanclass="org.stripesbook.article.action.LoginActionBean"> <s:messages/> <s:errors/> Username:<br> <s:text name="user.username"/><br><br> Password:<br> <s:password name="user.password"/><br><br> <s:submit name="login" value="Login"/> </s:form> <p> Don't have a username and password? <s:link beanclass="org.stripesbook.article.action.RegisterActionBean"> Register here </s:link> </p> <p> Need to activate your account? <s:link beanclass="org.stripesbook.article.action.ActivateActionBean"> Activate here </s:link> </p>

Notice the <s:messages/> tag. This displays any information messages that we have created. For example, after successfully activating the account, the user sees the confirmation message in the login page.

Again, we've prepopulated the username field for the user's convenience.

The action bean that manages the login doesn't contain anything that we haven't seen before. It's worth mentioning that when the user successfully logs in, we put the User object in the HTTP session:

 LoginActionBean.java package org.stripesbook.article.action; @UrlBinding("/login") @StrictBinding public class LoginActionBean extends BaseActionBean { /* rest of the code.... */ public Resolution login() { User existingUser = Database.getUser(user.getUsername()); if (!existingUser.isActivated()) { return new RedirectResolution(ActivateActionBean.class) .addParameter("user.username", existingUser.getUsername()); } getContext().getRequest().getSession(true).setAttribute("user",existingUser); return new RedirectResolution(SecuredActionBean.class); } }

A Secured Page

Once the user has successfully logged in, we show a simple page that is secured, meaning that the user must be logged in to view the page.

 secured.jsp <p> Congratulations, you have accessed the secured page! </p> <p> <s:link beanclass="org.stripesbook.article.action.SecuredActionBean" event="logout"> Click here to logout </s:link> </p>

The code for the action bean that corresponds to the secured page, shown below, has one annotation that we haven't seen yet. Can you spot it?

 SecuredActionBean.java package org.stripesbook.article.action; @UrlBinding("/secured/page/{$event}") @HttpCache(allow=false) public class SecuredActionBean extends BaseActionBean { @DefaultHandler public Resolution view() { return new ForwardResolution("/WEB-INF/jsp/secured.jsp"); } public Resolution logout() { getContext().getRequest().getSession().removeAttribute("user"); return new RedirectResolution(LoginActionBean.class); } }

The @HttpCache(allow=false) annotation is how we indicate that the page should not be cached by the browser. That way, when the user logs out, pressing the browser's Back button will not redisplay the secured page.

Also notice the special {$event} parameter on the @UrlBinding annotation; this appends the event handler name to the generated URL. The Click here to logout link will be displayed as http://localhost:8080/stripes-article/secured/page/logout. See the UrlBinding Javadoc for more details.

So the question is, how do we prevent users from seeing the secured page if they are not logged in?

One way to do this is with an interceptor. Interceptors are executed by Stripes before and/or after one or more of its lifecycle stages. Here's how we define an interceptor to send a user back to the login page if the user tries to access a secured page without being logged in:

 SecurityInterceptor.java package org.stripesbook.article.ext; @Intercepts(LifecycleStage.ActionBeanResolution) public class SecurityInterceptor implements Interceptor { public Resolution intercept(ExecutionContext execContext) throws Exception { Resolution resolution = execContext.proceed(); ActionBean actionBean = execContext.getActionBean(); UrlBinding urlBinding = UrlBindingFactory.getInstance().getBindingPrototype( actionBean.getClass()); if (urlBinding.getPath().startsWith("/secure")) { HttpSession session = execContext.getActionBeanContext().getRequest() .getSession(false); if (session == null || session.getAttribute("user") == null) { resolution = new RedirectResolution(LoginActionBean.class); } } return resolution; } }

The interceptor is executed during the action bean resolution stage, which is when Stripes determines which action bean is targetted by the request. The code before the call to execContext.proceed() runs before the stage, and the code after the call runs after the stage. In our case, all the code runs after the action bean has been resolved. After getting a reference to the action bean's URL binding, we check if it starts with /secure . This is arbitrary; you could use whatever convention that you wish to indicate that an action bean is part of the secured area of your web application. Finally, we check the session for the presence of the "user" attribute as a way to determine whether or not the user is logged in. If not, we redirect to the login page by returning a different resolution than the one returned from the call to execContext.proceed() .

Now, how do we configure Stripes to use this interceptor? As we'll see below, it turns out that a couple of simple one-time configurations in web.xml gets Stripes to automatically load all of your action beans and extensions, which are modules that you write in order to add behavior to the framework, such as interceptors.

Setting up Stripes

I deliberately postponed the details of setting up the web.xml file to the end of the article, because I wanted to focus on the Stripes code that you work with on a daily basis - action beans and JSPs for example. Of course, the first thing you need to do is set up the web.xml file, but I want to emphasize that this is a set it and forget it type of configuration - you won't need to change this very often.

Here is the web.xml file used for this example:

 web.xml <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <filter> <filter-name>StripesFilter</filter-name> <filter-class> net.sourceforge.stripes.controller.StripesFilter </filter-class> <init-param> <param-name>ActionResolver.Packages</param-name> <param-value>org.stripesbook.article.action</param-value> </init-param> <init-param> <param-name>Extension.Packages</param-name> <param-value>org.stripesbook.article.ext</param-value> </init-param> </filter> <filter> <filter-name>DynamicMappingFilter</filter-name> <filter-class> net.sourceforge.stripes.controller.DynamicMappingFilter </filter-class> </filter> <filter-mapping> <filter-name>DynamicMappingFilter</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> <dispatcher>INCLUDE</dispatcher> </filter-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>

The ActionResolver.Packages parameter tells Stripes where you're going to put your action bean classes, and the Extension.Packages , your extensions, such as the interceptor that we wrote earlier. In both cases, you can list several packages by separating them with commas, but note that indicating a package automatically includes all subpackages of that package. Once you've defined the packages for action beans and extensions, you can add, move, and remove action beans and extensions without worrying about keeping any configuration in sync. Stripes automatically discovers and loads classes from those packages.

The Dynamic Mapping Filter intercepts all requests, and determines which ones to send to the Stripes Filter. This is what enables us to use URL bindings such as " /register "; notice that there is no need for a particular prefix or suffix. Stripes also supports a convention-based URL mapping scheme, where you don't need @UrlBinding annotations in your action beans and instead use the Stripes-generated defaults. Those URLs normally end in .action . You can read about the default URL mapping convention here.

Finally, we've set up index.jsp to be the default file, which shows the registration page as the starting page for the application:

 index.jsp <jsp:forward page="/register"/>

That's all there is to it!

Conclusion

Stripes is a simple yet powerful framework. I hope I've given you an idea of how you develop a web application with Stripes and that you're interested in learning more. Please see the Resources below to find out where to go next.

Acknowledgements

Thanks to Chris Herron for reviewing the article, and to TheServerSide.com for publishing the article.

Resources