OVal - the object validation framework for Java™ 5 or later


Table of Contents

1. What is OVal?
2. Dependencies
3. Using OVal for property validation
3.1. Declaring constraints for class fields
3.2. Declaring constraints for getter methods' return value
3.3. Declaring conditional constraints using expression languages
3.4. Interpreting EJB3 JPA annotations
4. Using OVal for programming by contract
4.1. Preparing your project
4.2. Working with preconditions
4.3. Working with postconditions
4.4. Working with invariants
4.5. Using the probe mode to simplify UI user input validation
4.6. Converting ConstraintsViolatedExceptions
5. Creating custom annotation based constraints
6. Expressing complex class specific constraints
6.1. Using @ValidateWithMethod
6.2. Using @CheckWith
7. XML based configuration
8. Additional configuration and customization options
8.1. Constraint profiles
8.2. Collection factory
8.3. Adding additional expression languages
8.4. Spring integration
9. API documentation
10. Download
11. References
12. Trademark disclaimer

OVal is a pragmatic and extensible general purpose validation framework for any kind of Java objects (not only JavaBeans) and allows you:

  • to easily validate objects on demand,

  • to specify constraints for class fields and getter methods,

  • to validate objects based on certain EJB3 JPA annotations (namely all field annotations that require a not-null value),

  • to configure constraints via annotations, POJOs and/or simple XML files,

  • to express constraints using scripting languages such as Groovy, BeanShell, and JavaScript

  • to easily create custom constraints, and

  • to develop new constraint configuration mechanisms

When using AspectJ certain programming by contract (aka Design By Contract™ or DBC) features are available:

  • specifying constraints for constructor parameters that are automatically checked when a constructor is called (preconditions),

  • specifying constraints for method parameters that are automatically checked when a method is called (preconditions),

  • requiring a certain object state before a method is called (preconditions)

  • enforcing object validation after an object has been created (invariants),

  • enforcing object validation before/after a method of an object is/has been called (invariants),

  • specifying constrains for a method's return value that are automatically checked after a method has been executed (postconditions),

  • requiring a certain object state after a method is called (postconditions).

Missing a useful feature? Let's discuss it in the OVal forum

OVal requires Java 5 or later - mainly for annotation support but other new language features (generics, for each loop, etc.) are used across the OVal source code too. Java 5 is actually the only hard requirement, depending on the features you want to use additional libraries are required:

  • AspectJ is required if you want to use the above mentioned programming by contract features.

  • BeanShell is required if you want to define constraints via BeanShell expressions.

  • Groovy is required if you want to define constraints via Groovy expressions.

  • Mozilla Rhino is required if you want to define constraints via JavaScript expressions.

  • MVEL is required if you want to define constraints via MVEL expressions.

  • OGNL is required if you want to define constraints via OGNL expressions.

  • XStream is required if you want to configure OVal via XML configuration files.

  • GNU Trove is required if you want to have OVal to internally use the GNU Trove high performance collections.

  • Javolution is required if you want to have OVal to internally use Javolution's high performance collections.

  • JUnit and all other libraries are required if you want to run the test cases.

OVal's configuration mechanism is highly customizable. Using the net.sf.oval.configuration.Configurer interface you can write your own constraint configurers configuring OVal based on other XML schemas, other sets of annotations or anything else you like.

OVal comes with a configurer that is capable of translating certain EJB3 JPA annotations into equivalent OVal constraints. The net.sf.oval.configuration.annotation.JPAAnnotationsConfigurer interprets the EJB3 JPA annotations as follows:

  • @javax.persistence.Basic(optional=false) => @net.sf.oval.constraints.NotNull

  • @javax.persistence.OneToOne(optional=false) => @net.sf.oval.constraints.NotNull

  • @javax.persistence.OneToOne => @net.sf.oval.constraints.AssertValid

  • @javax.persistence.OneToMany => @net.sf.oval.constraints.AssertValid

  • @javax.persistence.ManyToOne(optional=false) => @net.sf.oval.constraints.NotNull

  • @javax.persistence.ManyToOne => @net.sf.oval.constraints.AssertValid

  • @javax.persistence.Column(nullable=false) => @net.sf.oval.constraints.NotNull (only applied for fields not annotated with @javax.persistence.GeneratedValue or @javax.persistence.Version)

  • @javax.persistence.Column(length=5) => @net.sf.oval.constraints.Length

@Entity
public class MyEntity
{
  @Basic(optional = false)
  @Column(length = 4)
  public String id;

  @Column(nullable = false)
  public String descr;

  @ManyToOne(optional = false)
  public MyEntity parent;
}

Example usage:

// configure OVal to interprete OVal constraint annotations
// as well as EJB3 JPA annotations Validator
validator = new Validator(new AnnotationsConfigurer(), new JPAAnnotationsConfigurer());

MyEntity entity = new MyEntity();

entity.id = "12345"; // violation - the max length is 4
entity.descr = null; // violation - cannot be null
entity.parent = null; // violation - cannot be null

// collect the constraint violations
List<ConstraintViolation> violations = validator.validate(entity);

By utilizing AspectJ OVal provides support for several aspects of programming by contract - however it is not a full blown programming by contract implementation.

With OVal you can

  • enforce that a parameterized constructor/method is invoked only if the given arguments satisfy prior defined constraints (precondition)

  • enforce that a method is invoked only if the object is in a certain state (precondition/invariant)

  • enforce that the return value of a method must satisfy prior defined constraints (postcondition)

  • enforce that the object must be in a certain state after a method has been executed (postcondition/invariant)

The easiest way to getting started is to use the Eclipse IDE in conjunction with the AspectJ plug-in .

Create a new AspectJ project or add AspectJ support to an existing Java project by right-clicking the project in the Package Explorer and selecting Convert To AspectJ Project

Add the net.sf.oval_x.x.jar file to your library path.

Create a new aspect via File -> New -> Aspect that extends the abstract aspect net.sf.oval.guard.GuardAspect . When the new aspect is created the AspectJ builder will automatically weave the validation related code into your compiled classes annotated with @net.sf.oval.guard.Guarded .

Now you can create all your business classes, add the @net.sf.oval.guard.Guarded annotation and define the required constraints using the built-in or custom constraint annotations.

You can apply the constraints specified for a field in the same or a super class to any constructor or method parameter by using the @net.sf.oval.constraints.AssertFieldConstraints annotation.

If you do not specify a field name within the @net.sf.oval.constraints.AssertFieldConstraints annotation the constraints of the field with the same name as the annotated parameter are applied to the parameter.

If you specify a field name within the @net.sf.oval.constraints.AssertFieldConstraints annotation the constraints of the field with the specified name are applied to the annotated parameter.

@net.sf.oval.guard.Guarded
public class BusinessObject
{
  @NotNull
  @NotEmpty
  @Length(max=10)
  private String name;

  public void setName(@FieldConstraints String name)
  {
    this.name = name;
  }

  public void setAlternativeName(@FieldConstraints("name") String altName)
  {
    this.alternativeName = altName;
  }

  ...
}

Example usage:

BusinessObject bo = new BusinessObject();

bo.setName(""); // throws a ConstraintsViolatedException because parameter is empty

bo.setAlternativeName(null); // throws a ConstraintsViolatedException because parameter is null

If you like to apply the constraints of all fields to their corresponding setter methods you can alternatively set the applyFieldConstraintsToSetters property of the Guarded annotation to true . This is especially useful if you have a lot of setter methods and you are following the JavaBean convention. Important: The setter method must be declared within the same class as the property.

@net.sf.oval.guard.Guarded(applyFieldConstraintsToSetters=true)
public class BusinessObject
{
  @NotNull
  @NotEmpty
  @Length(max=10)
  private String name;

  public void setName(String name)
  {
    this.name = name;
  }
  ...
}

Another convenience option is the applyFieldConstraintsToConstructors property of the Guarded annotation. If set to true, OVal applies the specified field constraints to the corresponding parameters of the declared constructors within the same class. In this context, a corresponding parameter is a constructor parameter with the same name and type as the field.

For declaring scripted postconditions you can use the @net.sf.guard.Post annotation which works Similar to @net.sf.guard.Pre for preconditions.

@Guarded public class Transaction
{
  private BigDecimal amount;

  // ensure that amount after calling the method is greater than it was before
  @Post(expr = "_this.amount>_old", old = "_this.amount", lang = "groovy")
  public void increase(BigDecimal amount2add)
  { 
    amount = amount.add(amount2add);
  }
}

The expr parameter holds the script to be evaluated. If the script returns true the constraint is satisfied. OVal provides special variables for use within the expression:

  • _args[] - array holding the method arguments

  • _this - is a reference to the current object

  • _returns - the method's return value

  • _old - see the description of the old parameter below

  • additionally variables matching the parameter names are available

The lang parameter specifies the scripting language you want to use. In case the required libraries are loaded, OVal is aware of these languages:

  • bsh or beanshell for BeanShell,

  • groovy for Groovy,

  • js or javascript for JavaScript (via Mozilla Rhino),

  • mvel for MVEL,

  • ognl for OGNL, or

  • ruby or jruby for Ruby (via JRuby)

The old parameter is optionally, it can hold another expression that is evaluated before the method is executed. The result is made available in the post constraint expression as a special variable called _old . The old expression can also return an array or a map allowing you to store multiple values. This way you can "remember" the old state of multiple properties of an object. An expression like old = "[amount:_this.amount, date:_this.date]" in Groovy returns a map with the keys amount and date holding the values of the object's properties amount and date . These values then can be used in the constraint expression like this: expression = "_this.amount>_old.amount && _this.date>_old.date" .

OVal provides a so called probe mode in which you can execute all methods of an object and the guard will only check the preconditions (e.g. parameter constraints) and not execute the method.

This is especially useful if you want to test input values received from the end user in the UI layer against the setter methods of a business object. You can simply pass the values to the corresponding setters and have any detected violations collect by a ConstraintsViolatedListener . Afterwards you can report all violations back to the UI layer and have them displayed to the end user.

Example business object:

@net.sf.oval.guard.Guarded
public class Person
{
  @NotNegative
  private int age;

  @Min(5)
  private String name = "";

  @Length(min=5, max=5)
  private String zipCode = "";

  public void setAge(@FieldConstraints int age)
  {
    this.age = age;
  }

  public void setName(@FieldConstraints String name)
  {
    this.name = name;
  }

  public void setZipCode(@FieldConstraints String zipCode)
  {
    this.zipCode = zipCode;
  }
  ...
}

Example usage:

/* *****************************************************
 * somewhere in the UI layer
 * *****************************************************/
 inputForm.setName("1234");
 inputForm.setAge(-4);
 inputForm.setZipCode("123");

...

/* *****************************************************
 * later in the model layer
 * *****************************************************/
public Person createPerson(PersonInputForm inputForm) throws ConstraintsViolatedException
{
  Person person = new Person();

  Guard guard = MyGuardAspect.aspectOf().getGuard();

  // enable the probe mode in the current thread for the person object
  guard.setInProbeMode(person, true);
  ConstraintsViolatedAdapter listener = new ConstraintsViolatedAdapter();
  guard.addListener(listener, p);

  // simulate applying the values to the person bean
  applyInputValues(person, inputForm);

  // disable the probe mode in the current thread for the person object
  guard.setInProbeMode(person, false);
  guard.removeListener(listener, person);

  // check if any constraint violations occured
  if(listener.getConstraintViolations().size() > 0)
  {
    // throw a ConstraintsViolatedException that contains the collected constraint violations
    throw new ConstraintsViolatedException(listener.getConstraintViolations());
  }
  else
  {
    // really apply the values to the person bean
    applyInputValues(person, inputForm);
    dao.save(person);
    return person;
  }
}

private void applyInputValues(Person person, PersonInputForm inputForm)
{
  person.setName(inputForm.getName());
  person.setAge(inputForm.getAge());
  person.setZipCode(inputForm.getZipCode());

  ...
}

Developing custom annotation based constraints is fairly easy. All you need to do is:

  1. Create a constraint check class that implements net.sf.oval.AnnotationCheck or extends net.sf.oval.AbstractAnnotationCheck .

    public class UpperCaseCheck extends AbstractAnnotationCheck<UpperCase>
    {
      public boolean isSatisfied(Object validatable, Object value)
      {
        if (value == null) return true;
    
        String val = value.toString(); return val.equals(val.toUpperCase());
      }
    }
  2. Create an annotation for your constraint and annotated it with @net.sf.oval.Constraint . Specify your check class as value for parameter "check".

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
    @net.sf.oval.Constraint(check = UpperCaseCheck.class)
    public @interface UpperCase
    {
      /**
       * message to be used for the ConstraintsViolatedException 
       *
       * @see ConstraintsViolatedException
       */
      String message() default "must be upper case";
    }
  3. Use the custom constraint annotation in your code.

    public class BusinessObject
    {
      @UpperCase
      private String userId;
    
      ...
    }

Localization of the constraint violation message can be achieved as follows:

  1. Specify a unique message key for the default message string of the constraint annotation, e.g.

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
    @net.sf.oval.Constraint(check = UpperCaseCheck.class)
    public @interface UpperCase
    {
      /**
       * message to be used for the ConstraintsViolatedException 
       *
       * @see ConstraintsViolatedException
       */
      String message() default "UpperCase.violated";
    }
  2. Create custom message bundles (one per language) and specify the translated sting in each bundle:

    UpperCase.violated={context} must be upper case

    There exist two default variables that can be used in the message string:

    • {context} = the validation context (e.g. a field, a method return value or a constructor/method parameter

    • {invalidValue} = the value that has been checked

    If required, you can introduce additional variables - e.g. which reflect additional configuration properties of your constraint such as {max}, {min}, {size} - by overriding the getMessageVariables method of your custom check class:

    @Override
    public Map<String, String> getMessageVariables()
    {
      Map<String, String> messageVariables = new HashMap<String, String>(2)
      messageVariables.put("max", Integer.toString(max));
      messageVariables.put("min", Integer.toString(min));
      return messageVariables;
    }

    The message variables can then be used in the corresponding message strings.

  3. Register your message bundle with OVal:

    net.sf.oval.MessageResolverImpl mr = (net.sf.oval.MessageResolverImpl) Validator.getMessageResolver();
    mr.addMessageBundle(ResourceBundle.getBundle("mypackage/CustomMessages"))

    If you would like to store your message strings other than in message bundles (e.g. in a database): Either implement the MessageResolver interface or extend the MessageResolverImpl and then configure OVal using the static Validator.setMessageResolver(...) method to use an instance of your custom message resolver instead.

If you have to express a rather complex constraint that is used only within one class you might not want to implement it as a custom constraint. You have the following two alternatives expressing such class specific constraints in a convenient way.

By default the constraints configuration is done by adding annotations representing the constraints to the respective locations in the source code. Alternatively constraints can also be declared via XML - either for a complete configuration or to overwrite the annotations based constraint configurations for specific classes, fields, etc.

You can used the net.sf.oval.configuration.xml.XMLConfigurer for loading constraint definitions from an XML file:

XMLConfigurer xmlConfigurer = new XMLConfigurer(new File("oval-config.xml"));
Guard guard = new Guard(xmlConfigurer);

Here is an example XML configuration:

<?xml version="1.0" ?>
<!DOCTYPE oval PUBLIC "-//OVal/OVal Configuration DTD 1.2//EN" "http://oval.sourceforge.net/oval-configuration-1.2.dtd">
<oval>
  <!-- define a constraint set -->
  <constraintSet id="user.userid">
    <notNull />
    <regEx>
      <pattern pattern="^[a-z0-9]{8}$" flags="0" />
    </regEx>
  </constraintSet>

  <!-- define checks for the acme.model.User class -->
  <!-- overwrite=false means already defined checks for this class will not be removed -->
  <class type="acme.model.User" overwrite="false" applyFieldConstraintsToSetter="true">

    <field name="firstName">
      <length min="0" max="3" />
    </field>

    <field name="lastName">
      <length min="0" max="5" />
    </field>

    <!-- overwrite=true means previously defined checks for this field will be overwritten by the checks defined here -->
    <field name="managerId" overwrite="true">
      <!-- use the checks defined for the constaint set "user.userid" -->
      <assertConstraint id="user.userid" />
    </field>

    <field name="userId" overwrite="true">
      <!-- use the checks defined for the constaint set "user.userid" -->
      <assertConstraintSet id="user.userid" />
    </field>

    <!-- define constructor parameter checks -->
    <constructor>
      <!-- parameter1 -->
      <parameter type="java.lang.String">
        <notNull />
      </parameter>

      <!-- parameter 2 -->
      <!-- the types of all parameters must be listed, even if no checks are defined -->
      <parameter type="java.lang.String" />
    </constructor>

    <!-- define method parameter checks -->
    <method name="setPasswordExpirationDays">
      <!-- parameter 1 -->
      <parameter type="int">
        <notNull />
      </parameter>
     </method>
  </class>
</oval>

You can browse OVal's Javadoc here .

You can download the latest OVal binaries and source code from the project page at http://sourceforge.net/projects/oval/

There exist some articles and blog entries talking about and/or referencing OVal:

The following projects are using OVal:

Java™ and all Java-based trademarks are trademarks of Sun Microsystems, Inc. in the United States, other countries, or both. For more information please refer to: http://www.sun.com/policies/trademarks/

This site is independent of Sun Microsystems, Inc.

All other trademarks are the sole property of their respective owners.