JEE Series: Struts 2 – Actions and Input Validation
In this post we take a closer look at the basics of a popular MVC framework Apache Struts. Today, we explore the server-side and discuss the concept of actions and validation. As usual, an example app is provided and you are more than welcome to code along.
Update: Please note that this post has been updated. The registration action now contains a minimum amount of code as all of the validation config has been moved to the User entity. There is no longer a need for @SkipValidation either.
Let’s start with generating a skeleton of the app. There is a plenty of Maven archetypes to choose from, here is how I did it:
[bash]
mvn archetype:generate -B -DgroupId=org.zezutom.blog.series.jee
-DartifactId=simple-web-struts
-DarchetypeGroupId=org.apache.struts
-DarchetypeArtifactId=struts2-archetype-convention
-DarchetypeVersion=2.3.20
-DremoteRepositories=http://struts.apache.org
[/bash]
We only need a minimum of Struts 2 dependencies – pom.xml:
[xml]
<properties>
<struts2.version>2.3.20</struts2.version>
…
</properties>
…
<dependencies>
…
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
<version>${struts2.version}</version>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-convention-plugin</artifactId>
<version>${struts2.version}</version>
</dependency>
…
</dependencies>
[/xml]
The Core API lies at the heart of the MVC framework, whereas the Convention Plugin reduces configuration effort – convention over explicit configuration.
Let’s take a look at our app. As you can see it’s a simple registration form. Once submitted it displays a confirmation page with captured details. Validation rules apply as we will see in a minute.
[bash]
WEB-INF
├── content
│ ├── registration-input.jsp
│ └── registration-success.jsp
└── web.xml
[/bash]
Note the location of where the pages reside as well as their names. Following these simple conventions we don’t need any XML wiring and can skip adding application logic into struts.xml.
Here is how the routing would look like in the simplest possible case:
[java]
import com.opensymphony.xwork2.Action;
public class RegisterAction implements Action {
@Override
public String execute() {
return “success” // register-success.jsp
// return “input” // register-input.jsp
}
}
[/java]
When it comes to controllers, implementing the Action interface is the only requirement. Optionally, you can extend the ActionSupport class and get some common functionality for free:
[java]
import com.opensymphony.xwork2.ActionSupport;
public class RegisterAction extends ActionSupport {
@Override
public String execute() {
return SUCCESS;
}
}
[/java]
As you can see now we can leverage predefined return values instead of declaring our own literals.
It’s fair to say that with simple apps like the one we are building in here, the benefits of tight integration with the framework aren’t too obvious. That’s especially true with ever growing popularity of annotations and convention-above-all attitude in general.
At this point, you have probably noticed that actions are very data-centric. An action executes returning essentially PASS / FAIL depending on the state of the model the action is built around. Actions are therefore inevitably stateful. Maintaining the state (model) helps you decide what to do when the respective action is executed.
In our case the model is a simple User entity:
[java]
public class User {
private String name;
private String email;
private String password;
// constructors, getters and setters skipped for brevity
}
[/java]
The RegisterAction maintains a reference to a User instance:
[java]
public class RegisterAction extends ActionSupport {
private User user;
// execute() etc.
}
[/java]
Before we move on, let’s stop and think about what to return upon action execution. Well, the first time the home page is loaded, we want to present a simple registration form (INPUT). Once the form is filled in and submitted, let’s disregard challenges with invalid input for a moment, the user should land on the confirmation page (SUCCESS):
[java]
@Override
public String execute() {
return (user == null) ? INPUT : SUCCESS;
}
[/java]
Let’s move onto the User class and add constraints on its fields. There are many ways of how to go about adding validation to your core logic. To me, annotation based validation is the easiest and the most straightforward approach. By annotating the getters we can not only make the fields mandatory (@RequiredStringValidator), but we can also add custom validation rules, such as that an email address must be formally correct (@EmailValidator):
[java]
import com.opensymphony.xwork2.validator.annotations.EmailValidator;
import com.opensymphony.xwork2.validator.annotations.RequiredStringValidator;
..
@RequiredStringValidator(key = “name.required”)
public String getName() {
return this.name;
}
@RequiredStringValidator(key = “email.required”)
@EmailValidator(key = “email.invalid”)
public void setEmail(String email) {
this.email = email;
}
…
[/java]
The password field deserves a little bit of an extra care. A password must comprise 6 characters at a minimum and contain at least one digit and one or more uppercase characters.
[java]
import com.opensymphony.xwork2.validator.annotations.RegexFieldValidator;
..
@RequiredStringValidator(key = “password.required”)
@RegexFieldValidator(key = “password.rules”,
regexExpression = “^(?=.*[0-9])(?=.*[A-Z]).{6,}$”)
public void setPassword(String password) {
this.password = password;
}
..
[/java]
Courtesy: Stackoverflow
Notice the key attribute – email.invalid, password.required etc.? As you might have guessed that’s to do with internationalization, a.k.a i18n. This is also a subject to naming and location conventions.
Suppose our only controller is fully qualified as com.example.RegisterAction. Now, provided we want our app speak English (by default) and Spanish, we go and create property files with translations as follows:
[bash]
src/main/resources/com/example
├── RegisterAction.properties
└── RegisterAction_es.properties
[/bash]
Finally, enjoy the results of all the hard work: