
Validating Objects Through Metadata
by Jacob Hookom01/19/2005
Many developers have been complaining about having to manage numerous XML configuration files alongside their Java code. With the recent addition of metadata to Java, common configuration details in frameworks can now be rolled right into your Java files through annotations. For a brief introduction to metadata and annotations, see Sun's article "J2SE 5.0 in a Nutshell."
In this article, we will summarize how configuration data is managed today and what metadata will offer in the future, followed by an implementation of how annotations can be used in a simple validation framework.
Using Frameworks Today
We all use frameworks in our day-to-day tasks to handle things such as persistence, user input, validation, web services, and workflows. In order to work with many of these frameworks, we have to be able to tie in our business objects through various methods. A couple of the ways developers tie into a framework are:
- Implement or extend classes provided by the framework. An example would be extending
ActionForm
within Struts in order to handle user input. This is fairly intrusive and requires developers to write and maintain specialized business objects just to be able to use the framework (theEmployee
object andEmployeeForm
). - Maintain a separate configuration file that maps Java objects and methods to the framework. Hibernate, Struts, and JavaServerFaces use XML configuration files heavily. While being fairly unobtrusive to our Java code, we lose compile-time validation and we now have to maintain data in multiple locations--in the XML file and in various Java files.
What Will Metadata Offer?
Metadata allows us to bind framework-related configuration data to our business objects without changing or extending any of the objects' implicit responsibilities. I like to compare metadata to Javadoc comments. If you change a Javadoc comment, it's not going to change the way your code behaves, except when you actually use the Javadoc
command.
Conceptually, metadata operates the same way. You can add configuration data to your object without changing the way the code behaves, unless you are explicitly looking for that particular metadata.
Since metadata operates in this manner, you can use Annotations
(Java metadata) to support persistence alongside Annotations
that support your web application framework. Consider this example:
@Column('usrEmail')
@ValidateEmail
public void setEmail(String email) {
this.email = email;
}
In the above example, @Column('usrEmail')
is an Annotation
that would be used within your persistence framework, while @ValidateEmail
is used within your web framework for validating user input.
Note that we haven't needed to change the setEmail(String)
method at all to support both frameworks, thereby retaining its original, generic simplicity.
Lastly, as touched on before, the major benefit is that configuration data can be maintained within your Java objects in a type-safe manner. No more cutting and pasting your class names and method names into a separate XML file while making sure the names match up with the Java code. Instead, just declare the configuration data on that exact method in your code through annotations.
Validating User Input
An excellent example of using metadata is in validating user input with a simple framework. With this framework, the end result is to allow developers to decorate objects like so:
@ValidateRequired
@ValidateEmail
public void setEmail(String email) {
this.email = email;
}
@ValidateRequired
@ValidateLength(min=6,max=12)
public void setPassword(String password) {
this.password = password;
}
Also, developers should be able to validate input for an annotated bean property:
Validator.validate(loginBean, "email", "yourname@onjava.com");
Validator.validate(loginBean, "password", ""); // invalid
Implementing Metadata Validation
Let's look at being able to define multiple kinds of validation.
Here's an example of what a ValidateLength
and a ValidateExpr
annotation would look like:
// Example @ValidateLength(min=6,max=8)
public @interface ValidateLength {
int min() default 0;
int max() default Integer.MAX_VALUE;
}
// Example @ValidateExpr("^(\\w){0,2}$");
public @interface ValidateExpr {
String value();
}
A couple of issues arise when using annotations within a framework. First, we can't bind any behavior or operations to annotations, just state.
Secondly, there's no way to tell if some arbitrary Annotation
is used for validation. This is because there isn't any inheritance allowed (extends
or implements
), which means no instanceof
capabilities during introspection of annotations.
So how are we going to be able to plug arbitrary validation metadata into our framework?
By simply annotating the annotation!
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Validate {
Class<? extends ValidateHandler> value();
}
As you can see above, the Validate
annotation is to be retained for runtime reflection (@Retention(RetentionPolicy.RUNTIME)
) and it's to be declared on other annotations (@Target(ElementType.ANNOTATION_TYPE)
).
In addition, the Validate
annotation has a single Class
variable that will be our way of specifying an instance of ValidateHandler
, which will handle the validation logic.
Before we go too much further, let's look at how Validate
will be applied to our ValidateExpr
annotation:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Validate(ValidateExprHandler.class)
public @interface ValidateExpr {
String value();
}
Three annotations have been added to our ValidateExpr
annotation. The first two are self-explanatory, but the third one, @Validate(ValidateExprHandler.class)
, solves the two previously mentioned issues with using annotations within a framework.
We've now provided a way to mark all of our validators with a Validate
annotation that can be found through reflection.
We've also provided a Class
type that will support the processing of the ValidateExpr
annotation by ValidateExprHandler
.
Let's look at how ValidateExprHandler
is implemented (this and all other examples are in the sample source code .zip in the Resources section at the bottom of the article):
// Interface as used in the Validate annotation
public interface ValidateHandler<T extends Annotation>
{
public void validate(T settings, Object value)
throws ValidationException;
public Class<T> getSettingsType();
}
// Handler used with the ValidateExpr Annotation
public class ValidateExprHandler
implements ValidateHandler<ValidateExpr>
{
public void validate(ValidateExpr settings,
Object value)
throws ValidationException
{
String i = (value != null)
? value.toString()
: "";
if (!Pattern.matches(settings.value(), i))
{
throw new ValidationException(i
+ " does not match the pattern "
+ settings.value());
}
}
public Class<ValidateExpr> getSettingsType()
{
return ValidateExpr.class;
}
}
To quickly summarize what we've accomplished so far:
- For each validator, we define an
Annotation
and implement aValidateHandler
class to provide our annotation's actual behavior. - Since there isn't any inheritance with Java metadata, we use a marker annotation (
Validate
) so that our validation framework can find validator implementations at runtime using reflection. - The
ValidateHandler
interface allows an annotation to delegate its own behavior that will be used during processing.
Processing Validators
Now it's time to handle the processing of our validation annotations.
Earlier in the article, I showed an example of a Validator
utility that would take care of finding and processing validators declared on your beans.
Validator.validate(loginBean, "email", "yourname@onjava.com");
Validator.validate(loginBean, "password", "");
Internally, the goal is to take loginBean
and find the setter method for email
(setEmail(String)
).
In J2SE 5.0, Method
implements AnnotatedElement
, allowing us to write a generic annotation-processing method for our validators:
public static void validate(AnnotatedElement element,
Object value)
throws ValidationException
{
Validate v;
ValidateHandler vh;
Annotation a;
// grab all Annotations
Annotation[] ma = element.getAnnotations();
for (int i = 0; i < ma.length; i++) {
// if an annotation has a Validate Annotation
v = ma[i].annotationType().getAnnotation(Validate.class);
if (v != null) {
try {
// use the Validate's value to create a ValidateHandler
vh = v.value().newInstance();
// use the current Annotation as state for the
// ValidationHandler, can throw a ValidationException
vh.validate(ma[i], value);
} catch (InstantiationException ie) {
} catch (IllegalAccessException iae) {
}
}
}
}
To describe the processing in more detail:
- We grab all annotations from the element (
setEmail(String)
). - We iterate over all annotations and see if each annotation declares a
Validate
annotation of its own. - If a
Validate
annotation is found, use its value to create a new instance ofValidateHandler
. - We use the original annotation from the array and pass it to the instance of
ValidateHandler
for processing.
That's all there is to it. The goal was to simplify processing details in an unobtrusive manner for users of your framework by allowing them to easily declare a type-safe annotation on their bean's properties. Really, all that counts is making it easier for others to use your framework.
Where Do We Go from Here?
Specifications such as EJB 3.0 are already leveraging metadata to support persistence mapping. Also, a lot of developers are already familiar with the provisions of XDoclet and how using Javadoc metadata provides configuration details to many existing frameworks. With the popularity of XDoclet, I'm surprised framework developers aren't more committed to providing annotation support in their future releases.
In reference to JavaServerFaces, John Reynolds recently blogged about where validation logic should be placed and disagreed with the current approach used.
By modifying some of the code we wrote for the validation framework in this article, you could incorporate the concept of a UIComponent
into validation processing.
This would allow programmers to declare validation metadata right on their beans instead of scattering it in JSP pages.
Think about what it takes to get your objects to work within frameworks these days, or what APIs and specifications you have to learn in able to meet the needs of your employer. XML and Java reflection was one progression made in order to simplify the way we develop applications and work with frameworks. Now let's go that next step and take a serious look at Java metadata.
Resources
- Sample source code for the validation utility
- "J2SE 5.0 in a Nutshell with an Introduction to Metadata"
- J2SE 5.0 Annotations API
Jacob Hookom is a developer with McKesson Medical-Surgical, a contributor to Sun's JavaServerFaces RI, and an active member of the JavaServerFaces Expert Group.
Return to ONJava.com
![]() |
Related Reading Java 5.0 Tiger: A Developer's Notebook |
