Posts tagged ‘enhancer’

As you probably noticed there are few entries in my blog that deal with usage of cglib. There was one thing though that kept me a bit frustrated – why wasn’t it possible to use JDK 1.5 generics when creating eg. new Ehnancer.

So I decided to add generics which could also get me more familiar with this library. My first step was to download source code from sourceforge and import into cglib-jdk15 repository on my github account. Afterwards I found out that AbstractClassGenerator contains inner Source class which uses caching mechanism for generated classes but unfortunately it wasn’t possible to add generic WeakHashMap without altering the code. Luckily this didn’t seem that much complicated – I just split cache into two separate caches – one for classes and one for References.

After everything was in place I could update Junit Tests and my code snippets blog entries with no more explicit casts.

Than I had a look at ant build script- in order to put generics version into the repository I wanted to use maven’s classifier but to achieve this a build script needed some more attention. It looked like its task could be replaced with maven tasks/conventions. It all worked – I could even generate „nodep” package but in this case I think that project structure still needs some attention.

Finally in order to build the project you can issue following command:

mvn clean install javadoc:javadoc jxr:jxr jxr:jxr-test site

As usual modfied source code available on github.

Binaries in my repository.

Problem description

Imagine you have a project with hierarchical data structure with 4 levels, where 1st level serves as reference data for 2nd and so on. So your task as a developer is to present this on a web form with a checkbox for each value, where „checked” means that it has its own value and „unchecked” means that value of such a field should be taken from it’s parent, with exception for 1st level, which should use only its own values. Furthermore the DTO used for form should pass its state into remote EJB.

Requirements

So let’s summarize it as requirements:

I want to be able to bind a command object with pair of fields – one used for remote EJB and one for determining if field is available on this level. In case field is not available remote EJB object should be passed a null value.

Solution discussion

Simple if/else

The easiest solution is to use regular if/else block and in case field is not available set null passed value otherwise, eg:


if (myCommand.isFieldAvailable()) {
   myRemote.setField(myCommand.getField());
} else {
   myRemote.setField(null);
}

It does the job but it’s a bit of an overhead when you have 20 fields – all 100 lines look alike:

  • check if field is available
  • pass its value to ejb
  • set null otherwise

An experienced OOP developer, which I’m sure you are, sees a needless repetition here and it’s obvious that there’s got to be a better way.

Reflection

Another apporach is to use reflection with property names passed as strings and a helper method which retrieves these using reflection and applies them accrodingly, eg:


fromCommandToEJB("field", command, ejb);

Internally this method will encapsulate logic for checking if available flag is set and reacts to it. This version is much better but it still has one flaw – property name is passed as string and compiler will not warn you when any change in the interface takes place.

Delegating proxy

Proxy may sound a bit intimidating but cglib’s Ehnancer makes it very easy – the only requirement is that you can’t use final classes.

So the work-horse of this solution is MethodInterceptor that has 3 tasks:

  1. Intercept call to a method
  2. Determine if call should be handled (method should be a JavaBean getter).
  3. Handle supported method call (find suffixed method and execute it in order to check what value should be returned).

The first point is handled by cglib internally, so we’re not going to spend any more time on it.

Second point is the decission making part – which  is a slightly modified version of isGetter method presented by Jakob Jenkov in his article on reflection. We’re going to intercept both get and is methods but exclude the ones ending with Available suffix and instead of returning a boolean our method returns a String where null means that method should not be handled.

	private String getHandledPropertyName(Method method) {
		String methodName = method.getName();
		if (!(methodName.startsWith("get") ||
			(methodName.startsWith("is") &&
			!methodName.endsWith(suffix)))) {
			return null;
		}
		else if (method.getParameterTypes().length != 0 ||
			void.class == method.getReturnType()) {
			return null;
		}
		else if (methodName.startsWith("get")) {
			return methodName.substring(3);
		} else {
			return methodName.substring(2);
		}
	}

As you can see returned String is important so we can have decission making (get/is prefix) and propoperty name reading (everything following is/get prefix).

There is a small overhead – every property needs to have an isAvailable method, which in case of compound properties means that each call must be redirected to the „real” checker.

Let’s try an example here – your bean contains start/endDate method so endDate is not valid without startDate, which means that there should be a common method checking if both dates are set and only afterwards passing it for further processing but with proxy you need to have isStartDateAvailable and isEndDateAvailable which might make code analysis a bit harder, so remember to use proper comments to make others work easier.

Third point is very simple – for given property name, find correspoint isAvailable method, check its return value and either call original method, passing its return value or return null.

	private Object handleGetter(Method method, String propertyName) throws Throwable {
		String conditionPropertyName = StringUtils.uncapitalize(propertyName) + suffix;
		PropertyDescriptor conditionDescriptor = PropertyUtils.getPropertyDescriptor(myBean, conditionPropertyName);
		if (conditionDescriptor == null) {
			throw new NoSuchMethodException("Missing is"+StringUtils.capitalize(conditionPropertyName) + " method for property: " + StringUtils.uncapitalize(propertyName));
		}
		Method condition = conditionDescriptor.getReadMethod();
		if (condition == null) {
			throw new NoSuchMethodException("Missing is"+StringUtils.capitalize(conditionPropertyName) + " method for property: " + StringUtils.uncapitalize(propertyName));
		}
		if ((Boolean) condition.invoke(myBean)) {
			return method.invoke(myBean);
		}
		return null;
	}

I’ve added also a small utility static method, to make wiring the whole stuff a bit easier.

	public static <T> T create(T myBean, String suffix) {
		return (T) Enhancer.create(myBean.getClass(), new ConditionalPropertyInterceptor<T>(myBean, suffix));
	}

There’s just one last thing that needs attention – how to make sure that each getter has a corresponding isAvailable method. Yes you can take your chances and wait until application is deployed… but it’s much better to have so me kind of automatic testing – in this case I recommend using dozer library exclude transient properties and after excluding transient properties all the rest should pass.

Sources available on github