Archivo de la categoría: Robust Code: best practices

Bean Validator: a custom validator that checks against other field validations

Sometimes I just want to apply to a property exactly the same Bean Validator validations I apply to another property.

Point in case: I have a password property in a User class, and there is a ChangePassword data transfer object somewhere else that happens to have currentPassword, newPassword and repeatPassword fields, used precisely to change User.password.

It is obvious that I will want to apply the validations for User.property to all these fields. And, of course, I want changes to validation rules in the original property to be taken into account, automatically.

Copying them is a no-no, because we will violate the DRY (Don’ Repeat Yourself) rule, and creating a custom validation annotation, say @Password, might be overkill. And it would not convey the right meaning, or that’s how I see it.

Creating some kind of DelegateValidation validator that replicates other validations makes a lot of sense, as it clearly conveys the intention and keeps code DRY.

Well, here is the code for the validation annotation itself:


@Target( { ElementType.METHOD, ElementType.FIELD, 
           ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = DelegateValidationValidator.class)
@Documented
public @interface DelegateValidation {  
   Class<?> clazz();
   String   property();

   Class<? extends Payload>[] payload() default {};
   String message() default "";
   Class<?>[] groups() default {};
}

Here clazz and property are the class and property whose validations we will replicate to validate our own property.

For example, if we have a ValidatedClass like this one,

public static class ValidatedClass {
   public static final String OTHER_PROPERTY= "otherProperty";
      
   @Length( min = 3)
   @NotNull
   @Size(min = 4)
   public String otherProperty;
      
   @NotNull
   public String getSecondProperty() {
      return "hi";
   }
}

we can apply the validations in that class otherProperty and secondProperty (corresponding to the getSecondProperty getter) to other properties in our MyClass class as follows:

public static class MyClass {
   @DelegateValidation(clazz=ValidatedClass.class, 
                       property=ValidatedClass.OTHER_PROPERTY)
   public String value;

   @DelegateValidation( clazz=ValidatedClass.class, 
                        property="secondProperty")
   public String value;
}

Here is the validator implementation:

public class DelegateValidationValidator implements ConstraintValidator<DelegateValidation, Object> {

   private static transient Validator globalValidator;
   
   public static void setGlobalValidator(Validator v ) {
      assert v != null;
      assert globalValidator == null;
      
      globalValidator = v;
   }
   
   @Inject
   private transient javax.validation.Validator validator;
   
   private Validator getValidator() {
      if( this.validator == null ) {
         assert globalValidator != null: 
            "A validator has not been injected: please, supply one via " +
            "setGlobalValidator";
         return globalValidator;
      }
      return this.validator;
   }
   
   
   private Class<Object> referencedClass;
   
   private String property;

   // To make happy very demanding compiler with regard to generic parameters
   @SuppressWarnings("unchecked") 
   @Override
   public void initialize(DelegateValidation constraintAnnotation) {
      // To make happy very demanding compiler with regard to generic parameters
      this.referencedClass = (Class<Object>)constraintAnnotation.clazz();
      this.property = constraintAnnotation.property();
   }

   @Override
   public boolean isValid(Object value, ConstraintValidatorContext context) {
      Set<ConstraintViolation<Object>> constraints = 
          getValidator().validateValue(this.referencedClass,
                                       this.property, value);
      if( constraints.isEmpty() ) {
         return true;
      }

      context.disableDefaultConstraintViolation();
      for( ConstraintViolation<Object> c : constraints) {
         context.buildConstraintViolationWithTemplate(
            c.getMessage()).addConstraintViolation();
      }
      return false;
   }
}

Here we must get access to the Validator we are using to peform validations to ask it to perform validations against the tracked property. Since I use CDI, as well as the Seam validtor module, I rely on them to inject the right Validator instance in my validator implementation.

However, if you are not using CDI/Seam, or just to make testing easier, I am providing a way to supply a validator via the setGlobalValidator method, that you should call before any validations are performed.

Last but not least, let me tellyou that, if you provide a wrong property name for the validator, the validation will fail with a ValidationException, as attested by some of the following tests.

The tests

For the sake of completeness, here are my tests, written against TestNg:

public class DelegateValidationTest {

   public static class ValidatedClass {
      public static final String OTHER_PROPERTY= "otherProperty";
      
      @Length( min = 3)
      @NotNull
      @Size(min = 4)
      public String otherProperty;
      
      @NotNull
      public String getSecondProperty() {
         return "hi";
      }
      
      private Integer propertyWithNoValidations;
   }

   private Validator validator;
   
   @BeforeClass
   void classSetUp() {
      if( this.validator == null ) {
        this.validator = Validation.buildDefaultValidatorFactory().getValidator();
        DelegateValidationValidator.setGlobalValidator(this.validator);
      }
   }
   
   public static class MyClass {
      @DelegateValidation(clazz=ValidatedClass.class, property=ValidatedClass.OTHER_PROPERTY)
      public String value;
   }
   
   @Test
   public void testValueValidation() {
      Assert.assertEquals( this.validator.validateValue(
         MyClass.class, "value", null).size(), 1);
      Assert.assertEquals( this.validator.validateValue(
         MyClass.class, "value", "ab").size(), 2);
      Assert.assertEquals( this.validator.validateValue(
         MyClass.class, "value", "abc").size(), 1);
      Assert.assertTrue( this.validator.validateValue(
         MyClass.class, "value", "abcd").isEmpty());
   }

   @Test
   public void testWholeBeanValidation() {
      ConstraintViolation<MyClass> firstV;
      ConstraintViolation<MyClass> secondV;
      Iterator<ConstraintViolation<MyClass>> it;
      
      MyClass mc = new MyClass();
      mc.value = "ab";
      Set<ConstraintViolation<MyClass>> violations = 
         this.validator.validate(mc);
      Assert.assertEquals( violations.size(), 2);
      it = violations.iterator();
      firstV = it.next();
      secondV = it.next();
      
      // We expect MyClass, even if validations are run 
      // against ValidatedClass
      Assert.assertEquals( firstV.getRootBeanClass(), 
         MyClass.class);
      // We expect "value", even if validations are run 
      // against "otherProperty"
      Assert.assertEquals( firstV.getPropertyPath().toString(), 
         "value");
      // We expect MyClass, even if validations are run 
      // against ValidatedClass
      Assert.assertEquals( secondV.getRootBeanClass(), 
         MyClass.class);
      // We expect "value", even if validations are run 
      // against "otherProperty"
      Assert.assertEquals( secondV.getPropertyPath().toString(), 
         "value");
   }
   
   public static class MyClassWithWrongDelegationProperty {
      @DelegateValidation(clazz=ValidatedClass.class, 
                          property="wrongPropertyName")
      public String value;
   }
   
   @Test(expectedExceptions={ValidationException.class})
   public void testReferencePropertyDoesNotExist() {
      this.validator.validateValue(
         MyClassWithWrongDelegationProperty.class, "value", "ab");      
   }
   
   public static class MyClassTrackingGetter {
      @DelegateValidation( clazz=ValidatedClass.class, 
                           property="secondProperty")
      public String value;
   }
   
   @Test
   public void testWorksWithGetters() {
      Assert.assertEquals( this.validator.validateValue(
         MyClassTrackingGetter.class, "value", null).size(), 1);
      Assert.assertTrue( this.validator.validateValue(
         MyClassTrackingGetter.class, "value", "a").isEmpty());
   }
   
   public static class MyClassTrackingPropertyWithNoValidations {
      @DelegateValidation( clazz=ValidatedClass.class, 
                           property="propertyWithNoValidations")
      public Integer value;
   }
   
   @Test
   public void testWorksWithPropertyHavingNoValidations() {
      Assert.assertTrue( this.validator.validateValue(
         MyClassTrackingPropertyWithNoValidations.class, "value", null).
            isEmpty());
   }
}
Anuncios

Making sure Hibernate does not leak sessions/entity managers

Resource cleanup is a must for all applications, especially long-running ones.

The most precious Hibernate resource requiring cleanup is the Session. If you are using JPA, as I frequently do, a session is wrapped by an EntityManager, but is still there.

In order for your apps to behave, you will need to make sure you are not forgetting to release sessions, or you’ll end up having trouble sooner than later.

To ensure that, you’ll need a way to check whether, after finishing some self contained operation, you have released all sessions you allocated -but not more.

To do that, I created a utility class, called SessionAllocationChecker. How do you use it? The typical usage is as follows:

SessionAllocatorChecker checker = new SessionAllocatorChecker( 
   myHibernateSessionFactory );
try {
  // ... code that opens and closes hibernate sessions/entity managers
}
finally {
  checker.dispose();
}

Here, if there are 5 sessions open when checker is created, and 8 sessions open when dispose is called, you will get an AssertionError, telling you you’ve got 3 extra sessions open you probably forgot to close.

How does it do that?

Now, how does it work? When an instance of SessionAllocationChecker is allocated, it checks the statistics maintained by Hibernate and notes the number of open sessions vs. the number of closed sessions at the moment: the difference will be the number of effectively open sessions at creation time.

If Hibernate statistics are not enabled, it will enable them -and quietly disable them when you call dispose.

Next, when you call getOutstandingSessions or hasOutstandingSessions, it performs the same operation, to check the number of currently open sessions. getOutstandingSessions will return the number of sessions currently open minus the number of session open at creation time, that is, the number of additional sessions you have open and not closed yet.

When you call the dispose method, it ensures Hibernate statistics enabled state is restored to its old status. Besides, it will check whether you have session leakage, by calling getOutstandingSessions , but only if you are executing you app with the JVM -enableassertions option. If that’s the case, an AssertionError exception will be thrown.

If the -enableassertions flag is not set, you will have to check the number of outstanding sessions by calling getOutstandingSessions on you own.

While rough, I found this trick extremely useful: in fact, I use this class to check that I never forget to release sessions/entity managers in my own tests.

Another place where you can put this to good use is in web app filters, to ensure that your requests do not leak sessions.

Here is the full source code:

package com.softwarementors.bzngine.engines.hibernate;

import javax.persistence.EntityManagerFactory;

import org.hibernate.SessionFactory;
import org.hibernate.ejb.HibernateEntityManagerFactory;
import org.hibernate.stat.Statistics;

public class SessionAllocationChecker {

  private boolean statisticsEnabledOnEntry;

  private long outstandingSessionsOnEntry;

  private SessionFactory factory;

  private boolean disposed;

  public SessionAllocationChecker( SessionFactory factory ) {
    assert factory != null;
    
    this.factory = factory;
    Statistics statistics = factory.getStatistics();
    this.statisticsEnabledOnEntry = statistics.isStatisticsEnabled();
    statistics.setStatisticsEnabled(true);
    this.outstandingSessionsOnEntry = 
       statistics.getSessionOpenCount() 
       - statistics.getSessionCloseCount();
  }
  
  public SessionAllocationChecker(EntityManagerFactory factory) {
    this( ((HibernateEntityManagerFactory) factory).getSessionFactory() );
  }
  
/*  
  public SessionAllocationChecker( EntityManager entityManager ) {
    this( entityManager.getEntityManagerFactory());
  }
  
  public SessionAllocationChecker( Session session ) {
    this(session.getSessionFactory());
  }
*/  

  public boolean isDisposed() {
    return this.disposed;
  }
  
  public long getOutstandingSessions() {
    assert !isDisposed();

    Statistics statistics = this.factory.getStatistics();
    assert statistics.isStatisticsEnabled() : 
       "Somebody else disabled the statistics: avoid that";
    long outstandingSessions = statistics.getSessionOpenCount() 
       - statistics.getSessionCloseCount();
    long sessionsNotReleasedAfterCreation = outstandingSessions 
       - this.outstandingSessionsOnEntry;

    assert sessionsNotReleasedAfterCreation >= 0 : 
       "Somebody else played with the statistics " + 
       "(maybe cleared/disabled them?): avoid that";
    return sessionsNotReleasedAfterCreation;
  }

  public boolean hasOutstandingSessions() {
    assert !isDisposed();
    
    return getOutstandingSessions() > 0;
  }

  public void dispose() {
    if (!isDisposed()) {     
      assert !hasOutstandingSessions() : "There are " 
         + getOutstandingSessions() + 
         " additional sessions open and not closed";

      this.disposed = true;
      
      // Reset statistics to the state they were before we started to
      // be interested in open/closed session count
      Statistics statistics = this.factory.getStatistics();
      statistics.setStatisticsEnabled(this.statisticsEnabledOnEntry);
    }
  }
}

If you take a look at the code, you will find I have added lots of sanity checks via assertions: using the Hibernate statistics as I do is not completely foolproof, if you clear them or disable them between the moment you created the checker and you disposed it, you will get incoherent readings that will fool SessionAllocationChecker.

While I have provided lots of checks, they are not completely safe -not could they be, given the way statistics are implemented by Hibernate. Keep an open eye, and all will be well.

You’ll have noticed that I use assertions all around: you can read this article if you are interested in knowing why I’m such a fan of assertions.

By the way, if you don’t have at hand a SessionFactory or a EntityManagerFactory to pass it to the SessionAllocationChecker constructor, all is not lost: you can get them from a Session or an EntityManager. How to do it is part of the code: just look at the commented code.

Believe it or not, the code is commented because I just have no tests for it in the library where SessionAllocationChecker belongs.

No test, no public exposure -at least, as part of a high quality library.

Robust code best practices: the context

From now on, I intend to publish a series on best practices that help create robust code.

It is very important to take into account that these best practices might not be best practices in a different context: techniques that help you write robust code might very well be detrimental to write code at top speed.

So, what’s the context for these “robust code” best practices:

  • Projects that must run reliably, with almost no downtime.
  • Projects that need to be able to evolve in a very reliable way.
  • Public APIs that should remain as stable as possible.
  • Projects used by very many users.

You will have to keep this in mind to make sense of some of the best practices I’ll write about!