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
ActionFormwithin 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 (theEmployeeobject 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
Annotationand implement aValidateHandlerclass 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
ValidateHandlerinterface 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
Validateannotation of its own. - If a
Validateannotation 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
ValidateHandlerfor 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 |
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 8 of 8.
-
Multiple occurance of Annotation..
2005-02-15 04:19:08 vinAnnotation [Reply | View]
I would like to know whether its possible to declare multiple times a same annotation:
for e.g.
If I have a annotation say:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation
{
String Name();
}
and use this annotation as following:
@MyAnnotation(Name = "First")
@MyAnnotation(Name = "Second")
public class TestAnnotation
{
}
Is the above usage possible? How?
In C#.NET the above can be done as follows:
[AttributeUsage(AttributeTargets.Class,AllowMultiple=true)]
public class MyAnnotation: Attribute
{
}
and used as:
@MyAnnotation(Name = "First")
@MyAnnotation(Name = "Second")
public class TestAnnotation
{
public TestAnnotation(string name)
{
}
}
Anyone knowing how to achieve the above...
Thanks in advance...
vinAnnotation
-
Alternative: generate AspectJ or Java code
2005-02-04 07:13:11 deanwampler [Reply | View]
This technique of adding behavior to annotations is interesting, although it begs the question why the language won't let you do it "natively". <br/>I recently implemented an alternative approach to support "Design by Contract". I use Sun's Annotation Processor Tool (APT) to parse source, looking for special annotations that define contract tests, and then I generate AspectJ code that inserts the tests in the runtime jar. See Contract4J.org for more information.
-
Coincidence
2005-01-23 07:17:20 fforwATgmxDOTde [Reply | View]
I have just released an webapplication toolkit and engine which uses a very similar metadata based validation method.
Documentation of my project
Documentation of the validation feature
-
Sounds nice, but...
2005-01-20 07:29:15 edburns [Reply | View]
Here are some questions about your approach:
In general, with the simplicity of baking your configuration data into
the Java source file, you lose the flexibility to make changes to the
configuration data without recompilation.
JavaServer Faces was targeted at the corporate developer. The very
concept of annotations and leveraging them for validation seems to go
beyond the realm of what the corporate developer can easily handle.
How slow is the reflection involved in the public static void validate()
method?
Is the complexity worth it?
In faces, currently a validator can be
1 a separate java class that implements the Validator interface. This
class is identified to the system by a combination of validatorId and
FQCN.
2 a method on a pojo that conforms to the signature of
Validator.validate(). The class on which this method is defined is
identified to the system by a combination of managed-bean-name and
FQCN.
In your system, you have the same amount of Java code to write, but
you can bake the validator itself into the bean that it is
validating. This is nice because logically it makes sense for the
validation logic to be tightly coupled to the bean...sometimes.
Note that you can achieve this same coupling using method 2 above.
-
Sounds nice, but...
2006-01-20 12:27:29 dchandler [Reply | View]
I believe there is a third way to handle validation in a domain-centric way, and it is one of the things I love about JSF. In my managed bean, I've replaced native types like String with a richer type extending ValidatedString. For example,
public static class ReportName extends ValidatedString {
public ReportName(String string) throws ValidatedStringException {
super(string, StringTester.ANS, 20, 1);
}
}
...
private ReportName name;
The ReportName constructor specifies the allowed character set (ANS = alphanumeric + spaces) and min length / max length.
Then I register a custom converter to handle all ValidatedString types in faces-config:
<converter>
<converter-for-class>ValidatedString</converter-for-class>
<converter-class>ValidatedStringConverter</converter-class>
</converter>
The getAsObject() method of ValidatedStringConverter calls a method on ValidatedString to actually perform the validation.
This way, all validation logic is specified in the domain model, my view template requires no validation tags or rules, and all values on a form get validated, regardless of what kind of component they are bound to.
I've also written Hibernate custom type converters so I can use rich types in my domain model from end-to-end.
Now, I think annotations are a more compact way to express the validation rules than creating an inner class for each property as I've shown above. However, I don't know where I'd hook into JSF to actually run the validation, as the converter wiring depends on the model property extending some class.
Any ideas?
By the way, Ed, I'm a corporate developer, and I grok annotations no problem. In fact, I think it's easier than what I've already done with custom types. I love the richness and extensibility of JSF. I've been doing Web development for 11 years and JSF is the first framework I've seen that makes me feel like I don't need to learn another framework. You guys have absolutely nailed it.
Either way, I think the concerns about needing to modify validation rules at run time are quite valid. In my current project, this is necessary because many of our field defintions are dynamic based on run-time properties. At any rate, I think this indirection is easily done using either annotations or the custom converter approach. Instead of specifying the rule itself, the annotation or custom type would simply contain a key to a rules definition somewhere else.
Cheers!
/dmc -
Sounds nice, but...
2006-01-20 13:10:15 dchandler [Reply | View]
Please allow me to clarify the question in my earlier post.
Is there a way to hook annotated validations into JSF in such a way that I don't have to put one or more calls to Validator.validator in my managed beans?
I'm looking for something I can register once in faces-config like the custom converter I wrote about.
Thanks in advance,
/dmc -
Sounds nice, but...
2005-01-20 07:51:23 hookomjj [Reply | View]
In general, with the simplicity of baking your configuration data into the Java source file, you lose the flexibility to make changes to the configuration data without recompilation.
Ant deploy scripts and team production cycles, make modifying of configuration files vs. java files with recompilation a moot point in my experience.
How slow is the reflection involved in the public static void validate() method?
Since we are dealing with compiled annotations, just like caching reflected properties of beans (quite common), I could also see caching of validation data on a per type basis. The reflection only occurs once then, caching both the annotation state and the ValidateHandler. If you look at how much reflection already exists within frameworks, reflection for annotations (once) really isn't that big of a deal. I left it out of the sample code as to not confuse the meat of this implementation.
Is the complexity worth it?
As stated in goal of this example framework, the end result is extremely easy for developers to grasp (just drop a @ValidateRequired). Developing new JSF validators w/ tag support is just as complex as adding a new annotation (not that either is overly difficult IMHO).
Along the lines of coupling framework behavior such as validation to beans-- based on the fact that annotations are "read" from objects, not "written", the coupling framework metadata won't affect the generic goals of your business objects.






From a configuration standpoint, this isn't providing anything that is configurable (at least not from the examples). Suppose I want to validate that an account deposit is not larger than N dollars. Where would a user (not developer)configure that validation how would that configuration carry through to the annotation?
If the intent is not to provide user configuration, why would my validations not be included as part of the business object? Annotations in this case are no less intrusive since they have to be entered and compiled with the code. Is there really a case where you would want to strip out validations from the business logic? Think about a vending machine that no longer validates the coins it receives. Shouldn't validation be intrinsic to the object?
I could be wrong but I don't think annotations were intended to replace business logic. They're great for EJB3 entities and Plumbing Web Services where functionality outside of the nature of the business object is being added, but I'm not buying this use case just yet.