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.

 
In Struts controllers are called Actions. Our pages are handled by a RegistrationAction. Here are the JSPs our controller is routing to:

[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:

That’s all for today and thanks for reading till the end. Next time, we take a look at the front-end and discuss client-side implementation. Stay tuned.
Categories: Java

Tomas Zezula

Hello! I'm a technology enthusiast with a knack for solving problems and a passion for making complex concepts accessible. My journey spans across software development, project management, and technical writing. I specialise in transforming rough sketches of ideas to fully launched products, all the while breaking down complex processes into understandable language. I believe a well-designed software development process is key to driving business growth. My focus as a leader and technical writer aims to bridge the tech-business divide, ensuring that intricate concepts are available and understandable to all. As a consultant, I'm eager to bring my versatile skills and extensive experience to help businesses navigate their software integration needs. Whether you're seeking bespoke software solutions, well-coordinated product launches, or easily digestible tech content, I'm here to make it happen. Ready to turn your vision into reality? Let's connect and explore the possibilities together.