Mittwoch Nov 11, 2009

A sample of Spring AOP usage

In the last couple of years I've come across this problem several times: you've created a business application for a client with various business / domain objects, all is well and then the client decides they need to somehow be able to group various of those objects together and then they need to be able to set global filters to make sure you only see and work with objects belonging to a certain grouping. Now there is two problems here:

  • The grouping isn't restricted to one of your domain objects. You need to be able to throw all your various domain objects into those groups, i.e you can create a group that consists of people and things and emails and boxes and …
    Unfortunately that isn't a trivial thing to model in your classical rdbms/hibernate persistence application. Relational databases aren't particularly well suited to store relations from one thing (table) to all kinds of other things (tables) while still keeping it easy to query those relationships and also maintaining relational integrity.
    Well, I haven't really solved this problem. I recently implemented a simple solution that is good enough for my project albeit not very elegant I think - if someone has solved this in a better way, please let me know or point me to a good resource. Anyway I have solved it by creating link objects for each thing that needs to be grouped. The link objects form a class hierarchy that ist mapped onto a single table. That allows me to quite easily query those relationships at the cost of the link table having to have quite a few nullable columns - which C.J.Date wouldn't approve of I guess
  • The second problem is that you then depending on some contextual information in the user session of your application you need to filter all listings of your domain objects. You could of course have some if or switch statements in your controller logic but that smells. It also means that you need to touch potentially lots and lots of places in your code if anything with your grouping changes.
Now what I'd like to talk about here is how I have tried to solve the second problem in a sensible way. In my current project things in the app are grouped by some sort of 'scope'. A user can choose the scope he is currently working on or leave the scope as the global scope. This setting is attached to the current user session and from then on all views showing listings of domain objects need to filter these by the selected scope. Here is how I've done it, but first some background on the app: It's a webapp, built with spring 3, springMVC, hibernate, postgreSQL. I have a package containing all my DAOs called 'dao'. I have configured spring to autoproxy by specifying this in my application context:
<aop:aspectj-autoproxy />
(You need to configure the aop schema obviously) I have then create an Aspect class like this
…

import java.lang.reflect.Method;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.CodeSignature;


@Aspect
public class ScopeAspect {
	final static Logger logger = LoggerFactory.getLogger(ScopeAspect.class);
	
	@Pointcut("within(dao..*)") 
	public void inDataAccessLayer() {}
	
	@Pointcut("execution(public * scoped*(..))")
	public void scoped(){}
	
	@Around(value = "inDataAccessLayer() && scoped() && target(target)")
	public Object addScopeIfNeeded(ProceedingJoinPoint joinPoint, GenericDao target) throws Throwable
	{
		Scope scope = ScopeUtils.getCurrentScope();
		if(scope != null) {
			logger.debug("rewriting dao method to use scope!");
			
			// gather the argument values and types
			Object[] originalArgs = joinPoint.getArgs();
			Object[] modifiedArgs = new Object[originalArgs.length + 1];
			modifiedArgs[0] = scope;
			
			// prepend the 'scope' arg
			System.arraycopy(originalArgs, 0, modifiedArgs, 1, originalArgs.length);
			
			// calculate the new argument type list
			Class[] modifiedArgTypes = new Class[modifiedArgs.length];
			modifiedArgTypes[0] = Project.class;
			Class[] originalArgTypes = ((CodeSignature)joinPoint.getSignature()).getParameterTypes();
			System.arraycopy(originalArgTypes, 0, modifiedArgTypes, 1, originalArgTypes.length);
			
			// find the method that has the same signature apart from the preceding scope type argument
			Method m = target.getClass().getMethod(joinPoint.getSignature().getName(), modifiedArgTypes);
			
			return m.invoke(target, modifiedArgs);
		}
		else
		{
			return joinPoint.proceed();
		}
		
		
	}
	
}
So what does that do? This aspect is now called for all methods whose names starts with 'scoped' in Classes living in the 'dao' package branch. when such a method is called, the aspect wraps itself around the method call, determines whether it needs to do anything (if scope != null) and if so it uses relection to find a method with the same name and signature apart from am additional first argument of type 'Scope'. What I like about this
  • It really is an aspect of my app and therefore it is nice that this lives in exactly one place.
  • It means my I can achive the deired effect by convention rather than configuration: I just need to code multiple methods with the same name in my DAOs
  • It also means that the dao methods still fully encapsulate the query specifics and if different entities have different requirements for the queries used to list them by scope that is fine
  • Since only certain methods of my DAO's are affected I can still have lots of methods left that won't ever get touched by the aspect and so I can have getAll() or list() or whatever methods that I might need for an admin backend.
Why it's not perfect
  • Well if you forget about the aspect and forget to code the partner 'scoped' method in a dao you'll get a NoSuchMethodException
  • I doubt this, but it could be, that it affects performance
  • You still have to remember using your 'scoped' methods in your controller
  • It might actually drive you nuts if you come back to this code in 2 years time, having forgotten about this aop business, and put a breakpoint in the method called inyour controller only to wait endlessly for that method to ever be called …
If you have found a particular nice way to solve this kind of problem, especially the first part of it, please write a comment, send me a pointer, whatever. It's really bugging me that I'm not sure whether there is 'the' solution out there.

Dienstag Nov 10, 2009

trouble with spring and maven assembly plugin

I've built a couple of small java tools recently that do some background processing and get run from cron. These tools are too complex for a maintainable groovy or python script (at least for my taste) and I therefore implement them in Java using spring for its wonderful JDBC template, database connection handling and some other niceties. Now to make for a simple deploy I pack them up using mavens assembly plugin and here is where the fun starts. If you do this you will get an error like this:

Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace
The reason is that the assembly plugin copies stuff from META-INF dirs of dependency jars into the META-INF dir of the resulting jar and quite a few of the spring jars have spring.handlers and spring.schemas files in their META-INF directory. Now when the assembly plugin packages all these together only the files of teh last jar it deals with end up in the resulting jar which causes above error when you try to run the jar. After some googling I found a couple of hints on the web, e.g. http://forum.springsource.org/showthread.php?p=190246#post190246 and http://forum.springsource.org/showthread.php?p=195655#post195655 and cooked up a fix that works ok for me. I first concatenated the contents of all spring.handlers files into a new spring.handlers file all spring.schemas into a new spring.schemas and put the resulting files into my src/main/resources directory. I then created the file src/main/assembly/my-jar-with-dependencies.xml with the following contents:
<assembly>
  <id>my-jar-with-dependencies</id>
  <formats>
    <format>jar</format>
  </formats>
  <includeBaseDirectory>false</includeBaseDirectory>
  <dependencySets>
    <dependencySet>
      <unpack>true</unpack>
      <scope>runtime</scope>
      <unpackOptions>
	      <excludes>
	      	<exclude>**/spring.handlers</exclude>
	      	<exclude>**/spring.schemas</exclude>
	      </excludes>
      </unpackOptions>
    </dependencySet>
  </dependencySets>
  <fileSets>
    <fileSet>
      <directory>${project.build.outputDirectory}</directory>
    </fileSet>
    <fileSet>
		<directory>target/classes</directory>
		<outputDirectory>META-INF</outputDirectory>
		<includes>
			<include>spring.handlers</include>
			<include>spring.schemas</include>
		</includes>
	</fileSet>
  </fileSets>
</assembly>
and added this to the plugins section in my pom.xml
<plugin>
  <artifactId>maven-assembly-plugin</artifactId>
  <configuration>
    <descriptors>
      <descriptor>src/main/assembly/my-jar-with-dependencies.xml</descriptor>
     </descriptors>
     <archive>
       <manifest>
         <mainClass>my.main.Class</mainClass>
       </manifest>
     </archive>
  </configuration>
</plugin>
If I now run 'mvn assembly:assembly' it created the file 'target/myProject-my-jar-with-dependencies.jar' which I can run without getting the 'Unable to locate …' message any longer.
P.S. There is also a JIRA issue on this: MASSEMBLY-360