4/27/2012

Avoid Null Parameters With Annotation


In JEE6 there is a NotNull annotation which is of very limitted functionality. In my recent project I came across a requirement to check methods parameters for null. Writing if blocks in all the methods is not a good solution. So I decided to go with an AOP solution. I used AspectJ for all my AOP stuff in this blog. Here is what I did, I created an annotation called NotNull and an Aspect with Around pointcut to intercept method calls which has its parameters annotation with NotNull annotation.

Now I will walk through the code.
Code Snippet for NotNull Annotation:
 package org.naveen.maven.research.annotations;

 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;

 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.PARAMETER)
 public @interface NotNull {
  /**
   * Message set in the exception thrown when parameter is null.
   */
  String message() default "Parameter value is not nullable.";
  
  /**
   * Will specify how null values have to be treated. 
   */
  NotNullPolicy policy() default NotNullPolicy.THROW_EXCEPTION;
  
  /**
   * If you want to throw a custom exception. 
   * By default this will throw an IllegalArgument Exception.
   */
  Class<? extends Throwable> exception() default IllegalArgumentException.class;
 }
 

Here is the NotNullPolicy enumeration:
 package org.naveen.maven.research.annotations;

 public enum NotNullPolicy {
  /** Irrespective of method body, method will return null if all arguments with this policy is null. */
  RETURN_NULL, 
  /** Will throw an exception. [Default] */
  THROW_EXCEPTION, 
  /** Don't do anything and will continue with method body */
  CONTINUE,
  /** Irrespective of method body, method will return null. */
  RETURN_NULL_IMMEDIATE
 }
 

And here comes the backbone, the Aspect.
 package org.naveen.maven.research.aspects;

 import java.lang.annotation.Annotation;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.logging.Level;
 import java.util.logging.Logger;

 import org.aspectj.lang.ProceedingJoinPoint;
 import org.aspectj.lang.Signature;
 import org.aspectj.lang.annotation.Around;
 import org.aspectj.lang.annotation.Aspect;
 import org.aspectj.lang.reflect.MethodSignature;
 import org.naveen.maven.research.annotations.NotNull;
 import org.naveen.maven.research.annotations.NotNullPolicy;

 @Aspect
 public class NotNullAspect {
  
  private static final Logger logger = Logger.getLogger(NotNullAspect.class.getName());

  @Around("execution(* org.naveen..*(..,@NotNull (*),..)) && " +
    "(!within(org.naveen.maven.research.annotations.*))")
  public Object doNullParamCheck(ProceedingJoinPoint jp) throws Throwable {
   List<Boolean> lazyNullRtValue = new LinkedList<Boolean>();
   boolean hasToContd = false;
   Object[] args = jp.getArgs();
   Signature sig = jp.getSignature();
   if(sig instanceof MethodSignature) {
    MethodSignature mtdSig = MethodSignature.class.cast(sig);
    Method mtd = mtdSig.getMethod();
    logger.log(Level.INFO, String.format("Checking parameters for method --> %s", mtd.getName()));
    Annotation[][] anns = mtd.getParameterAnnotations();
    
    for(int i=0;i<anns.length;++i) {
     if(anns[i].length > 0) {
      if(anns[i][0] instanceof NotNull) {
       NotNull ntnAnn = (NotNull) anns[i][0];
       logger.log(Level.INFO, String.format("NotNull handling policy is set to %s", ntnAnn.policy()));
       if(args[i] == null) {
        switch(ntnAnn.policy()) {
        case THROW_EXCEPTION:
         Class<? extends Throwable> expCls = ntnAnn.exception();
         logger.log(Level.INFO, String.format("Exception class set is %s", expCls.getName()));
         if(expCls == IllegalArgumentException.class) {
          throw new IllegalArgumentException(
           String.format("In Method %s --> %s", 
             mtd.getName(), ntnAnn.message()));
         } else {
          Constructor<? extends Throwable> ct = expCls.getConstructor(String.class);
          Throwable t = ct.newInstance(String.format("In Method %s --> %s", 
                  mtd.getName(), ntnAnn.message()));
          throw t;
         }
        case RETURN_NULL_IMMEDIATE:
         return null;
        case RETURN_NULL:
         logger.log(Level.WARNING, "One NotNull annotated argument has null value. Continuing with lazy checking.");
         lazyNullRtValue.add(false);
         break;
        case CONTINUE:
         hasToContd = true;
         lazyNullRtValue.add(true);
         break;
        }
       } else {
        hasToContd = true;
        if(ntnAnn.policy() == NotNullPolicy.RETURN_NULL) 
         lazyNullRtValue.add(true);
       }
      }
     }
    }
   }
   logger.log(Level.INFO, String.format("Argument length is %d and LazyReturn list is %s and hasToContd %s", 
            args.length, lazyNullRtValue, hasToContd));
   if(hasToContd && checkLazyNull(lazyNullRtValue)) 
    return jp.proceed();
   return null;
  }
  
  private boolean checkLazyNull(List<Boolean> list) {
   if(list.size() == 0) return true; 
   for(Boolean b : list)
    if(b) return b;
   return false;
  }
 }
 

 @Around("execution(* org.naveen..*(..,@NotNull (*),..)) && " +
   "(!within(org.naveen.maven.research.annotations.*))")
 
Above code snippet will instruct AOP weaver to weave classes which satifies following conditions:
  • Which has methods which is declared inside classes which are declared in org.naveen package or sub package of it.
  • Which has methods whoose paramters are annotated with NotNull annotation.
  • Dont weave classes inside org.naveen.maven.research.annotations package or sub package of it.

Different Usage examples:
Throws custom exception:
 public String test(String message, @NotNull(exception=ParameterException.class) String name) {
  return message + name;
 }
 

Throws default exception with custom message:
 private String privateTest(@NotNull String message, @NotNull(message="Name cannot be null.") String name) {
  return message + name;
 }
 
Method exists with Null:
 private String privateTest(@NotNull(policy=NotNullPolicy.RETURN_NULL_IMMEDIATE) String greet) {
  System.out.println("Message received is " + greet);
  return "Hello World " + greet;
 }
 
If you call above privateTest() method with a null argument, you don't see that System out getting executed.

Suppose if you have a scenario like this:
  • You have a method which will accept 2 arguments.
  • Method returns null, if both arguments are null.
  • Execute method, if one of the argument is not null.
You can use NotNullPolicy.RETURN_NULL policy to instruct NotNull aspect to do a lazy null checking.
 private String privateTest(@NotNull(policy=NotNullPolicy.RETURN_NULL) String message, 
          @NotNull(policy=NotNullPolicy.RETURN_NULL, message="Name cannot be null.") String name) {
  System.out.println("Message received is " + message);
  return "Hello World " + name;
 }
 

You can combine both NotNullPolicy.RETURN_NULL and NotNullPolicy.RETURN_NULL_IMMEDIATE together.
 private String privateTest(@NotNull(policy=NotNullPolicy.RETURN_NULL) String message, 
          @NotNull(policy=NotNullPolicy.RETURN_NULL_IMMEDIATE) String greet,
                      @NotNull(policy=NotNullPolicy.RETURN_NULL, message="Name cannot be null.") String name) {
  System.out.println("Message received is " + message);
  return "Hello World " + name;
 }
 

In the above example, we are saying :
  • Execute method body, if either of the arguments 'message' or 'name' is not null AND greet is not null.
  • Return Null, if both arguments 'message' and 'name' is null
  • Return Null, if argument 'greet' is Null


All Resources used in this project are available in My GitHub Repo.


4/25/2012

Templatize Java Objects


In my recent project, there is so many places we are using templates. Like web pages with user based custom messages and information, email and letter templates etc. In Java we have MessageFormat class to do some simple templating. But in our case we need to fill these templates from pre-populated java objects. Something similar to JasperReports will do with jrxml templates.
I know we can write a utility to do this in java using Reflection. If I'm using reflection, then I will end up in writing so many lines of code, which I really hate. Groovy has this magical MOP which comes very handy. So I choose groovy to build this utility.

Here is the groovy code for the utility:
 @Log4j
 class Templatizer {
      
        def static final PATTERN = ~/[\$]{1}\{([^}]*)\}/
 
        def static String toString(final String template, def target) {
            def tempStr = template
            PATTERN.matcher(template).findAll {it ->
                try {
                    tempStr = tempStr.replace(it[0], target."${it[1]}")
                } catch(MissingPropertyException e) { }
            }
            tempStr
        }

        def static String toString(String template, List target) {
            toString(template, target, "\n")
        }
 
        def static String toString(String template, List target, String seperator) {
            def output = new StringBuilder()
            target.each { output.append(toString(template, it)).append(seperator) }
            output.toString()
        }
 }
 

Here is how we use it (Groovy Version):
 EmployedPerson p = new EmployedPerson([name: "David Foo", dob: new Date(), salary: 90000.00])
 println Templatizer.toString("Templatizing Test: My Name is ${name}. I born in ${dob} and I am earning a salary of ${salary}", p)
 

Java Version:
 EmployedPerson p = new EmployedPerson("David Foo", new Date(), 90000.00);
 System.out.println(Templatizer.toString("Templatizing Test: My Name is ${name}. I born in ${dob} and I am earning a salary of ${salary}", p));
 


You can compile this class and you can use with in your java objects. You can use Ant to compile groovy code. For more details on groovy-ant compilation, refer my previous blog