Handling Validation Error Messages in Struts2

I've recently been developing a web project using the Struts2 framework. A problem that came up during development was validation. More specifically, the way error messages and page redirects are sent back to the user. In this blog post I'll explain the problems I encountered, and some solutions to those problems.

Struts' Validation Framework

Struts already has an excellent validation framework, using an XML-based solution to perform validation on specific fields. The pre-defined validation tests are robust and generally fit most validation cases. For example, take the follow excerpt, where we specify that the username is a required string:

<validators>
<field name="username">
<field-validator type="requiredstring">
<message key="username.required" />
</field-validator>
</field>
</validators>

We also define that the message should be looked up from a key (username.required) in a separate properties file, making string externalization very simple. There are a lot of pre-defined field validators to choose from, and they will generally fit most of your needs.

If they don't, adding a custom validation check is easy and intuitive. Simply implement the validate method on the action you wish to perform validation on and specify your logic there. For example, if we want to ensure that the username doesn't already exist in the database, we could implement validate like so:

public class CreateUserAccountAction extends ActionSupport {
@Override
public void validate() {
if(database.contains(getUsername()) {
addFieldError("username", "Username already exists!");
}
}
}

String externalization isn't as simple, but we still get the ability to perform more complicated validation.

Struts' validation framework is excellent. Should you hit a validation error, the workflow interceptor will return the string "input", and avoid executing the action. It's pretty safe to say you can trust Struts to handle all your validation provided you've configured it correctly.

Error Messages

You start to run into problems when you try to report the error messages to the user. Struts will attempt to display field errors next to the fields of a form, or you can display the messages in the page like so:

  <fielderrors />

The errors are added as HTML elements and can be styled with CSS fairly simply. However, I ran into the following issues with this system:

  • If you want to do anything with the errors other than display them in static HTML elements, you have to work for it. For example, I wanted to use jQuery to animate the errors over the form they were for, and highlight the culprit fields red. Doable, but unnecessarily complicated. It'd require listening for any errors that're placed into the form, removing them, and then adding them to your own custom elements. DOM manipulation alarm bells should be ringing.
  • Things get messy when your "input" result redirects to another action. Firstly, you'll lose the error messages. Wordpress user Glindholm outlined an excellent solution to this problem here.
  • Secondly, any subsequent action calls (in the same request) after the validation error will have the result "input". So if your result page pulls in other pages by calling their actions, you'll have to define a result "input" for each of those too. Similarly, if you populate any fields in an action's execute method before returning the page, they won't be populated (execute is never called on an action during "input").
  • Things get even messier when you don't want to perform a full page refresh every time a validation error occurs on one form. If you wanted to return the form again with the error messages, you'd have a problem as the messages won't be visible to anything other than the form, limiting where you can display them.

These problems motivated me into finding an alternative solution to display Struts' error messages whilst still using their validation framework.

Validate in Java, report in JavaScript

I made the decision to handle displaying the errors in JavaScript. It's the middleman between the page and the server, and I think is well suited to this problem. I had the following idea:

  • Intercept form submission with a submit handler.
  • Mock the form submission with AJAX
  • Analyse the return result.
  • Display the errors if they exist, otherwise mock the form submission result

Intercepting and mocking form submission

Intercepting and mocking a form submission is easy, just define a handler like below using jQuery.live:

$('form').live('submit', function(submitEvent) {
submitEvent.preventDefault();

var form = $(this);

// when you use s:form, the target is stored on the result attr
// this tells us where to send the request
var action = form.attr('action');

// we also need to construct an object of the inputs
var inputs = {};

// populate inputs
form.find(':input').each(function() {
if(this.name !== "") {
inputs[this.name] = $(this).val();
}
});

// and now we mock the form submission using ajax
$.ajax({
url : action,
data : inputs,
success : function(e, status, xhr) {
// e contains our result value
}
});

return false;
});

Analysing the return result

The result is, as we discussed before, whatever comes back from the "input" result. This is a bit of a problem, as we don't have any way of accessing the errors through the "input" page (it'll be a string representing the return page). The solution is to return a JSON object of the errors. This is very easy to do with the json return type in Struts. I modified my struts.xml to read:

<struts>
<package name="myPackage" extends="struts-default json-default">
<global-results>
<result name="input" type="json">
<param name="root">fieldErrors</param>
<param name="wrapPrefix"><![CDATA[{ "errors" : ]]></param>
<param name="wrapSuffix"><![CDATA[}]]></param>
</result>
</global-results>
<!-- remainder of struts.xml defined here -->

Firstly, adding "json-default" to the package declaration gives me access to the json result type. I've then defined a global result for the type input, meaning that any uncaught input results will redirect to here.

The type is JSON, and I've specified the root to be fieldErrors (which is a bean property, and thus would map to getFieldErrors()). Finally, I've added a prefix and suffix to wrap the produced JSON in another object for nicer encapsulation. Using the example above, when calling the action with a username that already exists I receive the following object:

{
"errors" : {
"username" : ["Username already exists!"]
}
}

Perfect! In my JavaScript, I can now access these errors like so:

// inside the success function of the ajax request
if(e.errors) {
// do whatever I like with the error messages
}

Mocking the form success behaviour

This is not a trivial task. Usually when a form submits, it redirects the user to a certain page. By preventing the default action to replace it with our own ajax request, we've lost that functionality.

All we have is the string representation of the result page. We could always just replace the body innerHTML with this string, but what if we only need to refresh part of the page? We won't be replacing the innerHTML of body anymore, but some element which varies depending on the form. Similarly, just loading the HTML wouldn't parse the page correctly, as well as prevent onload listeners from firing.

The solution will differ for some situations. If every form on your site does the same thing, you can just code that functionality straight into the ajax success. However, when you have multiple forms refreshing different parts of the page, you need some way of differentiating between them.

My solution was to create an object in JavaScript which mapped the ID of the forms to a function which would be called when the form submission was successful. For example, if I wanted to redirect to loggedIn.jsp after the form succeeded, I'd define the function like this:

com : {
scottlogic : {
logInForm : function() {
window.location = "/loggedIn.jsp";
}
}
}

And then you can call the function from the success body like so:

if(e.errors) {
// display errors
} else {
// get the ID from the form, which maps to a function to call
com.scottlogic[form.attr('id')]();
}

Obviously, this solution poses obvious security risks. It wouldn't be too hard for the client to do something like:

com.scottlogic.logInForm = function() {
window.location = "/somethingImportantAndPrivate.jsp"
};

and have direct access to the page. Don't do this! The solution is to have any actions that require entire page redirects return the page they want to redirect to. This would take the value of 'e' in the success function, which you could pass to the logInForm function.

Conclusion

My aim was to utilize the Struts built-in validation with a more flexible error message framework, which I believe I have achieved. I didn't want to go into the interceptors that handle the validation as they already work so well, I was mainly concerned with the error messages. The final stage, wherein you define a function for each form, is by no means perfect, and I am still looking for improvements to defining individual form submission behaviour.

The important thing to note is that, even though we're doing quite a lot on the client-side, all of the validation is done by the server. It's essentially safe behind closed doors, where it can evaluate the input and choose to execute the action or not. So even if there is a problem in the implementation of error messages being shown, you can be safe in the knowledge that your forms will still be validated by Struts. An interesting extension would be mixing in some client-side validation too.. but that's another blog post!

MORE BY STEVEN

View Inheritance in Backbone

A Webapp By Another Name III

blog comments powered by Disqus