ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Validating Objects Through Metadata Validating Objects Through Metadata

by Jacob Hookom
01/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:

  1. 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 (the Employee object and EmployeeForm).
  2. 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 a ValidateHandler 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:

  1. We grab all annotations from the element (setEmail(String)).
  2. We iterate over all annotations and see if each annotation declares a Validate annotation of its own.
  3. If a Validate annotation is found, use its value to create a new instance of ValidateHandler.
  4. 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

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