This is the fifth in a series of short blogs describing how the Hibernate Validator allows you to define validation rules directly on domain objects. In the fourth article I explained why I don't use several of the standard Hibernate validators. In this article I'll show how to bypass Hibernate validation and when that makes sense.

As indicated by the title of this article, the primary use case I've seen for bypassing standard validation on an object is to allow saving objects in a draft state. For example, suppose you have a lengthy form that a user can fill out, such as a mortgage application or insurance claim, and you want to allow users to save their work as a draft and be able to come back and complete at a later time. In this case, you probably don't want to apply all the validation rules, since it is almost a given that if they haven't finished filling in the form, that it won't pass the validation rules. In this use case bypassing Hibernate validation makes perfect sense. By the way, I am not going to get into the argument of whether storing draft data means that the database must have relaxed constraints, and whether this is good or bad. If you don't relax the database constraints (e.g. allow null values in columns that should be required), then where do you store the draft data? Probably in a separate table that looks almost identical to the "real" table except with all the constraints relaxed (e.g. you might have a loan_applications table as well as a draft_loan_applications table). Assuming, like me, that you don't like that solution and would prefer to save draft objects in the same database table as non-draft objects, read on.

In order to allow the Hibernate event-based validation to be bypassed, you need to create your own validation event listener and provide a way to disable validation temporarily. Since the actual validation logic doesn't change, and the only extra logic we need is to allow clients to turn validation off and then back on, we can extend Hibernate's ValidateEventListener. The following class is all you need (some lengthy JavaDocs have been omitted for brevity):

package com.nearinfinity.common.hibernate.validator.event;

import org.hibernate.event.PreInsertEvent;
import org.hibernate.event.PreUpdateEvent;
import org.hibernate.validator.event.ValidateEventListener;

/**
 * Extension of Hibernate's <code>ValidateEventListener</code> that allows you to bypass the normal validation performed
 * by Hibernate for a specific thread.
 *
 * @author Andrew Avenoso
 * @author Scott Leberknight
 */
public class OptionalValidateEventListener extends ValidateEventListener {

    private static ThreadLocal<Boolean> shouldValidateThreadLocal = new ThreadLocal<Boolean>() {
        @Override
        protected Boolean initialValue() {
            return Boolean.TRUE;
        }
    };

    /**
     * Perform validation before insert, <code>unless</code> {@link #turnValidationOff()} has been called for the
     * currently executing thread.
     *
     * @param event the PreInsertEvent
     * @return Return true if the operation should be vetoed
     */
    @Override
    public boolean onPreInsert(PreInsertEvent event) {
        return isCurrentlyValidating() && super.onPreInsert(event);
    }

    /**
     * Perform validation before update, <code>unless</code> {@link #turnValidationOff()} has been called for the
     * currently executing thread.
     *
     * @param event the PreUpdateEvent
     * @return Return true if the operation should be vetoed
     */
    @Override
    public boolean onPreUpdate(PreUpdateEvent event) {
        return isCurrentlyValidating() && super.onPreUpdate(event);
    }

    /** Call this method to explicitly turn validation on for the currently executing thread. */
    public static void turnValidationOn() {
        OptionalValidateEventListener.shouldValidateThreadLocal.set(Boolean.TRUE);
    }

    /** Call this method to bypass validation for the currently executing thread. */
    public static void turnValidationOff() {
        OptionalValidateEventListener.shouldValidateThreadLocal.set(Boolean.FALSE);
    }

    /** @return <code>true</code> if we need to validate for the current thread */
    public static Boolean isCurrentlyValidating() {
        return OptionalValidateEventListener.shouldValidateThreadLocal.get();
    }
}

The most important things about the above class are:

  • Validation is turned on/off on a per-thread basis using a ThreadLocal variable.
  • If validation is turned off for a specific thread by a client, the client should ensure validation is turned back on after performing persistence operations.

The reason this class uses a ThreadLocal to determine whether to perform validation is because it assumes usage in a multi-threaded environment; for example, you would definitely not want turn turn off validation for all threads in a web application serving lots of concurrent users even for a very short duration! In addition, the reason it is important for clients to reset the validation state for a thread is because the thread might be reused for subsequent client requests, as in many application servers in a JEE environment which pool and reuse threads. (Thanks to Jeff Kunkle for pointing this out.)

The other important thing here has nothing to do with the above code and is instead a configuration issue. If you read my second article in this series you know that Hibernate auto-registers the default ValidateEventListener if it is present in the CLASSPATH, which it will be when you have the Hibernate Validator JAR in your project. So you will need to do two things (refer back to the second article for the configuration details):

  1. Configure the OptionalValidateEventListener for "pre-insert" and "pre-update" events.
  2. Disable automatic registration of the Hibernate ValidateEventListener by setting the hibernate.validator.autoregister_listeners property to "false."

Once that's done you only need to figure out where you need to bypass validation and use code like the following, which is taken from one of our base Spring MVC web controller classes in my current project:

 if (shouldPerformHibernateValidation()) {
     return onSubmitInternal(request, response, command, errors);
 }
 else {
     OptionalValidateEventListener.turnValidationOff();
     try {
         // The following method call would run without any Hibernate validation! 
         return onSubmitInternal(request, response, command, errors);
     }
     finally {
         OptionalValidateEventListener.turnValidationOn();
     }
 }

In the above, the most important thing is that we use a finally block to ensure validation is turned back on regardless of what happened inside the onSubmitInternal() method. We essentially hid this code in a "framework" class so that it is not littered in all the places where we need to bypass validation. The implementation of shouldPerformHibernateValidation() method could be implemented any number of ways, but the point is that you need some way to decide to perform validation or bypass it. Once that decision is made it is easy to turn validation off, execute the code where the bypass occurs, and finally (pun intended) turn validation back on.

In the next and final article in this series, I'll describe how the Hibernate Validator can be integrated into web applications so that validation errors propagate seamlessly from the data access code back up through the web tier and to the end user as nicely formatted error messages.



Post a Comment:
Comments are closed for this entry.