Custom Annotation Processing for Web Application

To know about how to declare custom annotations, what are the properties and options please refer to this post, which is very clear and simple.

What I will be discussing here is after you have declared and used your custom annotations, how to process them in run time for any web application.
Our aim is to find all the annotated classes and process their annotations at the time of servlet initialization i.e. in the init() method of the servlet. After getting those annotation attribute values we will be printing them just for this example. Lets build our custom annotation and one class annotated with that.

package com.annotated.classes.annotations;

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

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Task {

	String id();

	String name();

	String uri();
}
package com.annotated.classes.example;

@Task(
		id = "Task1", 
		name = "Task1", 
		uri = "#task1"
)
public class Task1 {

}

The first thing we need to learn is how to get the class files from ‘WEB-INF/classes’ folder as well as all the classes from jars residing in ‘WEB-INF/lib’ folder. We are going to use ‘ServletContext’ to get the list of class files. We will be looking for all the class files under a specified package. So lets declare it at the very beginning with some other top level variables.

private String packageName = "com.annotated.classes";
private String packageNamePath = packageName.replace(".", "/");
private String classesPath = "/WEB-INF/classes/" + packageNamePath;
private String jarsPath = "/WEB-INF/lib";

‘WEB-INF/classes’
The ‘getResourcePaths’ method of ‘ServletContext’ gives us a list of resources (.class files and folders) under a specified path. If we encounter a sub folder under the path we need to get resources from that folder also. So its better to have a recursion to traverse the folder structure to get all the .class files.

private Set<String> getClassFiles(String path, ServletContext context, Set<String> finalList) {

	Set<String> resourceSet = context.getResourcePaths(path);
	if (resourceSet != null) {
		for (Iterator<String> iterator = resourceSet.iterator(); iterator.hasNext();) {
			String resourcePath = (String) iterator.next();

			if (resourcePath.endsWith(".class")) {
				finalList.add(resourcePath.substring(resourcePath.indexOf(packageNamePath)));
			} else {
				getClassFiles(resourcePath, context, finalList);
			}
		}
	}
	return finalList;
}

The method will be called like

ServletContext context = servletConfig.getServletContext();
Set<String> classFiles = new HashSet<String>();
classFiles = getClassFiles(classesPath, context, classFiles);

‘WEB-INF/lib’
Here we will be opening each jar we have in our lib folder and scan for the same package declared above and get the list of class files under them.

Set<String> libJars = context.getResourcePaths(jarsPath);
Set<String> allClasses = new HashSet<String>();

if (libJars != null) {
	for (String jar : libJars) {
		JarInputStream jarInputStream = new JarInputStream(context.getResourceAsStream(jar));
		JarEntry jarEntry;

		while ((jarEntry = jarInputStream.getNextJarEntry()) != null) {
			if ((jarEntry.getName().startsWith(packageNamePath)) && (jarEntry.getName().endsWith(".class"))) {
				allClasses.add(jarEntry.getName());
			}
		}
	}
}

Now we have all the class files we expect to have annotated with our custom annotation. To find what classes are annotated with our annotation we may use java reflection. But here we will be using Javassist as its much lighter. The tutorial can be found here and download from here.

First we need to add our class paths to the ClassPool object to be able to get the reference of the classes.

ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(CustomAnnotationProcessor.class));
pool.appendClassPath(jarsPath);

The CtClass object from Javassist will give us the reference of the class. It expects the input as the fully qualified class name. We need to do some string manipulation to get the fully qualified class names from the list we got from above. Once we have the CtClass object we can check for its annotations and if present get the instance of the annotation with attribute values.

CtClass cc = pool.get(classFile);
Object[] annotations = cc.getAnnotations();
for (Object annotation : annotations) {
	if (annotation instanceof Task) {
		Task task = (Task) annotation;
		
		System.out.println("Id : " + task.id());
		System.out.println("Name : " + task.name());
		System.out.println("URI : " + task.uri());
	}
}

And with all it together we have

package com.annotated.classes.annotation;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;

public class CustomAnnotationProcessor {

	private String packageName = "com.annotated.classes";
	private String packageNamePath = packageName.replace(".", "/");
	private String classesPath = "/WEB-INF/classes/" + packageNamePath;
	private String jarsPath = "/WEB-INF/lib";

	public void processAnnotations(ServletConfig servletConfig) {

		ServletContext context = servletConfig.getServletContext();
		Set<String> classFiles = new HashSet<String>();
		classFiles = getClassFiles(classesPath, context, classFiles);

		Set<String> libJars = context.getResourcePaths(jarsPath);
		Set<String> allClasses = new HashSet<String>();

		if (!classFiles.isEmpty()) {
			allClasses.addAll(classFiles);
		}

		try {
			// Javassist to get classes from class paths
			ClassPool pool = ClassPool.getDefault();
			pool.insertClassPath(new ClassClassPath(AnnotationProcessor.class));
			pool.appendClassPath(jarsPath);

			// Get classes from Jar files
			if (libJars != null) {
				for (String jar : libJars) {
					JarInputStream jarInputStream = new JarInputStream(context.getResourceAsStream(jar));
					JarEntry jarEntry;

					while ((jarEntry = jarInputStream.getNextJarEntry()) != null) {
						if ((jarEntry.getName().startsWith(packageNamePath)) && (jarEntry.getName().endsWith(".class"))) {
							allClasses.add(jarEntry.getName());
						}
					}
				}
			}

			// Read Annotations
			for (String classFile : allClasses) {

				classFile = classFile.replace("/", ".").substring(0, classFile.indexOf(".class"));

				CtClass cc = pool.get(classFile);
				Object[] annotations = cc.getAnnotations();
				for (Object annotation : annotations) {
					if (annotation instanceof Task) {
						Task task = (Task) annotation;

						System.out.println("Id : " + task.id());
						System.out.println("View : " + task.viewClass());
						System.out.println("URI : " + task.uri());
					}
				}
			}
			
		} catch (NotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (MalformedURLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	private Set<String> getClassFiles(String path, ServletContext context, Set<String> finalList) {

		Set<String> resourceSet = context.getResourcePaths(path);
		if (resourceSet != null) {
			for (Iterator<String> iterator = resourceSet.iterator(); iterator.hasNext();) {
				String resourcePath = (String) iterator.next();

				if (resourcePath.endsWith(".class")) {
					finalList.add(resourcePath.substring(resourcePath.indexOf(packageNamePath)));
				} else {
					getClassFiles(resourcePath, context, finalList);
				}
			}
		}
		return finalList;
	}

}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: