13.5 Validation


A frequently asked question is how to implement data validation with JDO. Before looking into a possible technique based on JDO instance callbacks, it is important to briefly think about the layer of an application at which data validation could take place:

  • At the persistent object level.

  • Before accessing the persistent objects, in a business-logic method or at UI level (simple validations can sometimes be delegated to UI clients, e.g., Web browsers with Java scripts, or Swing clients ).

  • Datastore level, e.g., using SQL constraints such as NOT NULL , and so on.

While some fervently argue in favor of only one of these options, e.g., "validate at the UI layer only," it often makes sense to place validation logic at several layers . The reasons can be manifold for example, to save round-trips when validating at the browser client level in a Web-based architecture. It does, however, often make sense to validate at the domain model layer as well (or only), to ensure that validation takes place if a domain model is modified in several places from application logic.

Ideally, the validation code should be shared across layers to avoid duplication. This is sometimes possiblefor example, when calling the validation logic written as Java method from both the persistent code, as well as from a UI component such as Swing listener or similar Web (e.g., Struts or JSF Action) component. Other times, this is more difficult to implementfor example, sharing validation rules expressed in Java between a JavaScript-able browser and an SQL datastore. One possible solution in that case is to express validation as higher-level "constraint" instead of directly in Java code. The next few paragraphs focus only on how to practically enforce validation logic from JDO domain model classes.

Validation logic can mean many things, such as the following:

  • Certain field(s) should not be null.

  • Certain numeric fields should be within a certain range.

  • Certain string fields should match a given regular expression.

  • Certain string fields should not be longer than x number of characters .

  • A field should be unique within an Extent or within a Query on that Extent.

  • Any arbitrary other "check" not covered by the above, e.g., "The sum of two numeric fields has to larger than another field" or "Field temp cannot be higher than the current temperature on the peak of the Matterhorn."

The JDO API does not currently offer much support to express such domain model constraints. Only the above mentioned "cannot be null" can be expressed with a declaration like this in the JDO XML mapping descriptor:

 
 <field name="lastName" null-value="exception" /> 

This declaration requests the JDO implementation to throw a JDOUserException if this field contains a null value at runtime when the instance must be stored.

However, any of the more complex requirements above cannot be expressed simply by declaring something in the XML mapping descriptor. They are, however, easily written in Java code. The trick is when and how such respective Java validation code is called.

13.5.1 Leveraging InstanceCallback 's jdoPreStore method

The interested reader will likely think of the JDO InstanceCallback interface and its jdoPreStore() method, covered in earlier chapters. And indeed, leveraging this mechanism often is a solution in a first go of an application, because it's simple and obvious. Although we will criticize and enhance this approach in just a few paragraphs below, let's look at how this would be implemented in practice. The example chosen for illustration here is a validation rule that enforces that an email address of a persistent Author class matches a regular expression pattern of Internet email addresses:

 
 public class Author1 implements InstanceCallback {    private String email;    public String getEmail() { return email; }    public void setEmail(String e) { email = e; }    public void jdoPreStore() {       if ( !Pattern.matches("[a-z01-9_\-.]+@" +                             "[a-z01-9_\-.]+\.[a-z]+",              getEmail() != null ? getEmail() : ""  ) {         throw new SampleValidationException(this, "email");       }    } } 

With the persistence-aware domain class written like this, we will always throw a non-checked SampleValidationException if the email field is not correct, as in this usage example:

 
 (...) Author1 a = new Author1(); a.setEmail("authors@corejdo.com"); pm.currentTransaction().begin(); pm.persist(a); pm.currentTransaction().commit(); pm.currentTransaction().begin(); try {    // Invalid email, not matching RegExp pattern    a.setEmail("authors-corejdo;com");    pm.currentTransaction().commit();    throw new Error("Validation broken; should not reach!"); } catch (SampleValidationException ex) {    // Good!    if ( pm.currentTransaction().isActive() ) {      pm.currentTransaction().rollback();    } } pm.close(); 

Saying that "directly having jdoPreStore() call (or indeed just perform, as above) the validation usually works well" is not entirely correct. Let's take a minute to glance over what the JDO specification says regarding the preStore method: "This method is called before the values are stored from the instance to the datastore." So actually, our validation could be called multiple times on one instance in a single transaction. You don't care, not a problem, you say? We beg to differ . Think about this scenario:

  1. The application begins a transaction, creates an instance, and then immediately persists the instance, but has not yet set all fields to valid values.

  2. JDO implementation chooses to write modified current in-memory instances of persistent objects to the datastore, for whatever reason, and thus invokes the preStore() method as per the JDO specification.

  3. The application sets all fields of the object persisted in Step 1 to valid values.

  4. The application commits the transaction.

This is a perfectly valid scenario, because within a transaction we can have persistent objects in a state that would fail the validation. If we couldn't, it may not even be possible to create, for example, an Author instance without immediately setting the email field from the constructor. Although this would be possible in this example, generally speaking it would not be.

However, if a JDO implementation actually chose to also write instances to the datastore in the middle of a transaction instead of only at commit time (which would be perfectly valid according to the JDO specification), our validation check from the above example would throw the SampleValidationException in Step 2 and make it impossible for the application to get to Step 3 and beyond. Indeed, what we really want is to perform the validation at the time that the transaction commits.

13.5.2 Using Synchronization's beforeCompletion() method

The javax.transaction.Synchronization interface holds the key to implementing this second approach: JDO enables us to register a callback object implementing this interface by calling setSynchronization() on a transaction and calls this interface's beforeCompletion() method from where we can perform the validation.

So if this is the more reliable way to implement validation, why did we not do it from the start? There isn't really a catch in using transaction synchronization, but there are two added complexities that can be addressed:

  • Transaction management and registration of synchronization callbacks is different in the non-managed ( javax.jdo.Transaction#setSynchronization) versus the managed JTA-based ( javax.transaction.Transaction#registerSynchronization ) environment. In a managed environment, the application may not have (or should not have) access to the javax.transaction.Transaction object, only a javax.transaction.UserTransaction . This is not an issue if using only a non-managed environment, but it makes this approach unsuitable or more complicated for validation logic that should be usable in a managed environment as well.

  • Once within the beforeCompletion() method of the callback object implementing the javax.transaction.Synchronization interface, how is the persistent object (or objects) to be validated found?

The first point is clarified in Chapter 11, but the second point can be addressed here. The basic problem is that, unlike in the InstanceCallback#jdoPreStore() method above, where we simply validate this, we lost track of which persistent object(s) to validate by the time control reaches Synchronization#beforeCompletion() .

13.5.2.1 Combining both approaches

Combining the InstanceCallback idea with the Synchronization idea shows a promising approach: If a list of modified instances had been created and maintained in jdoPreStore() , but no validation is done yet, then the beforeCompletion method, having access to this list, could validate all modified instances.

So the Author persistence-capable class would change slightly to what's shown next. Note that the actual validation code moved into a newly introduction validate() method defined in the Validatable interface and that jdoPreStore() now simply delegates to an external helper method:

 
 public class Author2                  implements InstanceCallback, Validatable {    (...)    public void validate() {       if ( !Pattern.matches("[a-z01-9_\-.]+@" +                             "[a-z01-9_\-.]+\.[a-z]+",              getEmail() != null ? getEmail() : ""  ) {         throw new SampleValidationException(this, "email");       }    }    public void jdoPreStore() {       ValidationHelper.register(this);    } } 

The new ValidationHelper class would then keep a list of modified persistent objects per transaction. Suppose that an instance of itself would be registered with the respective PersistenceManager and would look something like this:

 
 public class ValidationHelper {    private Set modifiedObjects = new HashSet();    public Set getModifiedObjects() {       return modifiedObjects;    }    public static void register(Validatable object) {       PersistenceManager pm;       pm = JDOHelper.getPersistenceManager(object)       Object userObj = pm.getUserObject();       ValidationHelper helper;       if ( userObj != null ) {          helper = (ValidationHelper)userObj;       }       else {          helper = new ValidationHelper();          pm.setUserObject(helper);       }       helper.modifiedObjects.add(object);    } } 

Registering the ValidationHelper as UserObject of the PersistenceManager is one possibility of where to keep the list of modified persistent objects of a transaction, and is probably a much better design than using a global static hash map sort of thing associated with the current thread. The registered Synchronization object could then look something like this:

 
 public class ValidationSynchronization          implements javax.transaction.Synchronization {    private ValidationHelper helper;    public ValidationSynchronization(ValidationHelper vh) {       helper = vh;    }    public void beforeCompletion() {       Set set = vh.getModifiedObjects();       Iterator it = set.iterator();       while ( it.hasNext() ) {          Validatable v = (Validatable)it.next();            v.validate();       }    } } 

The ValidationHelper and ValidationSynchronization classes are separated simply for clarity here, but its methods could also be intermixed in one single class.

Usage of this solution is not entirely transparent to the application anymore and would require the registration of the ValidationSynchronization when a new transaction is started. Furthermore, the application should catch the SampleValidationException (of this example) and roll back if the validation failed, unless there is a generic when-catch(Exception ex)-then-rollback() mechanism in place. It would be possible to do this in an application-specific implementation of the JDO PersistenceManager interface that delegates to the original PersistenceManager of the JDO implementation.



Core Java Data Objects
Core Java Data Objects
ISBN: 0131407317
EAN: 2147483647
Year: 2003
Pages: 146

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net