| Sign In/My Account | View Cart |
Validating Objects Through MetadataMany 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.
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:
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 (the Employee object and EmployeeForm).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.
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
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:
Annotation and implement a ValidateHandler class to provide our annotation's actual behavior.Validate) so that our validation framework can find validator implementations at runtime using reflection.ValidateHandler interface allows an annotation to delegate its own behavior that will be used during processing.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:
setEmail(String)).Validate annotation of its own.Validate annotation is found, use its value to create a new instance of ValidateHandler.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.
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.
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 |
Showing messages 1 through 8 of 8.
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.
public static class ReportName extends ValidatedString {
public ReportName(String string) throws ValidatedStringException {
super(string, StringTester.ANS, 20, 1);
}
}
...
private ReportName name;
<converter>
<converter-for-class>ValidatedString</converter-for-class>
<converter-class>ValidatedStringConverter</converter-class>
</converter>
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.