Vita Rara: A Life Uncommon

Using Dynamic Method Invocation to "Script" Java


Categories:

Although Java isn't thought of as a dynamic language now a days, what with Ruby and Groovy being all the rage, Java does have support for dynamic features. (See my previous blog post on the subject and the solution I came up with.)

A Use Case for Dynamic Java

Currently I'm wrapping about a hundred EJB 2.1 LocalHome classes in DAO's, and having them transform local EJB entities into POJO's. Much of the code is largely boiler plate. Actually it's mind numbingly boiler plate. Here's a sample of wrapping a finder:

public Endowment findById ( String id ) {
	try {
		// get the local home
		EndowmentLocalHome endowmentLH = EndowmentUtil.getLocalHome ();
		// lookup the entity
		EndowmentLocal endowmentL = endowmentLH.findByPrimaryKey ( id );
		// return the entity
		return createEndowment ( endowmentL );
	} catch ( Exception e ) { throw new VRRemoteException ( e ); }
}

So, five lines of code just to call a finder, then there's the createEndowment() method that actually does the transformation into a POJO. Additionally every method is cluttered with try {} catch {} constructs, further blurring the intent of the code.

By using reflection and dynamic method calls I've been able to reduce the boilerplate to:

public Member findById ( String id ) {
    return (Member) mixin.find ("findById", new Object[] { id } );
}

If by chance my finder returns a Collection, all I need to do is cast the result to Collection.

Each DAO is specific to a "type." Not type as in a Class, but type as in a "business type," ie the snipped deals with the "Endowment" type. For each type there are a number of EJB 2.1 and POJO classes collaborating to make the whole thing work. For the Endowment type the critical classes are:

  • EndowmentUtil: A utility class for looking up local and remote home interfaces using JNDI. This utility class shields us complete from JNDI. :) This class is generated by XDoclet.
  • EndowmentLocalHome: The local home for the Endowment EJB.
  • EndomentLocal: A local EJB entity of an endowment.
  • EndowmentValue: A value bean representing an endowment. This is generated by XDoclet.
  • EndowmentDAO: An interface for a persistence agnostic data access object.
  • EndowmementDAOEjbImpl: An implementation of EndowmentDAO that wraps the EJB 2.1 LocalHome.
  • Endowment: A POJO in a new clean package that has no connection to our old EJB 2.1/XDoclet code.

The other thing we're doing is making our POJO's transparently navigable, just like JPA entities are when they are managed by an EntityManager. (ie: We can do contactMechanism.getParty().getMember().)

The major issue is that I had repeating patterns like the findById() example above that are the same except that the "type" differed. The convention is the same, over and over. Reflection and my dynamic method calling to the rescue. (NB: This app needs to run on a Java 1.4 server, otherwise I'd have taken a look at generics to solve some of these issues.)

The Solution

Using the findById sample the pattern is easily identifyable:

public Endowment findById ( String id ) {
	try {
		// get the local home
		EndowmentLocalHome endowmentLH = EndowmentUtil.getLocalHome ();
		// lookup the entity
		EndowmentLocal endowmentL = endowmentLH.findByPrimaryKey ( id );
		// return the entity
		return createEndowment ( endowmentL );
	} catch ( Exception e ) { throw new VRRemoteException ( e ); }
}

Simplifying this to pseudo code can help in seeing the pattern:

public #TYPE# findById ( String id ) {
	try {
		// get the local home
		#TYPE#LocalHome localHome = #TYPE#Util.getLocalHome ();
		// lookup the entity
		#TYPE#Local localEjb = localhome.findByPrimaryKey ( id );
		// return the entity
		return create#TYPE# ( localEjb );
	} catch ( Exception e ) { throw new VRRemoteException ( e ); }
}

We can even simplify this further, because the pattern is really the same with any finder. Look up a #TYPE#Util, call getLocalHome() on it, getting a #TYPE#LocalHome, call the finder method and transform the result into a POJO.

public #TYPE# find (Object[] args ) {
	try {
		// get the local home
		#TYPE#LocalHome localHome = #TYPE#Util.getLocalHome ();
		// lookup the entity
		#TYPE#Local localEjb = localhome.find ( args );
		// return the entity
		return create#TYPE# ( localEjb );
	} catch ( Exception e ) { throw new VRRemoteException ( e ); }
}

We call some find method with some collection of arguments, represented by the Object[], which could also be absent for an no-argument finder.

With the pattern identified let's "script" it using the dynamic method calling and the fact that the related class names all follow a convention.

Here's the code that will call any finder using the pattern above:

public Object find (String finder, Object[] args) {
	Object result = sendMessage (finder, getLocalHome(), args);
	if (result instanceof Collection) {
		try {
			Collection originalResultList = (Collection) result;

			// If the result is empty there is nothing to convert. Return the empty collection.
			if (originalResultList.size() == 0) {
				return result;
			}

			Collection resultList = new ArrayList ( originalResultList.size () );
			for (Iterator itr = originalResultList.iterator (); itr.hasNext (); ) {
				Object ejb = itr.next ();
				resultList.add (createPojo (ejb) );
			}
			result = resultList;
		} catch (Exception e) {
			throw new VRUtilityException ("ERROR: Post-processing a collection into POJO's for type = '" + getType() + "'.", e);
		}	
	} else {
		// Build a POJO from the EJB entity returned by the finder.
		return createPojo (result);
	}
	
	return result;
}

Most of the code in the method is involved with transforming Collection results into POJO's.

To access the findById method I'd do:

return (TYPE) find ("findById", new Object[] { id } )

Getting the LocalHome interface is done by convention:

/** Get the local home appropriate to this mixin. */
protected Object getLocalHome () {
	// Determine utility class name.
	Class utilClass = findClass (getType() + "Util");
	if (utilClass == null) {
		throw new VRUtilityException ("ERROR: Could not look up utility class for type = '" + getType () + "' by convention.");
	}
	try {
	       return utilClass.getMethod ("getLocalHome", null).invoke (null, null);
	} catch (Exception e) {
		throw new VRUtilityException ("ERROR: Could isntantiate local home for class of type = '" + getType () + "' by convention using Method object.");
	}
}

Because I know my Util class is #TYPE#Util I can rely on that by convention to get my utility class and instantiate a LocalHome to be used. (In my case the findClass() method looks for the #TYPE#Util class in one of three packages by convention. I could have search the classpath, but that seemed like overkill under the circumstances, when I know it's in one of those three packages.)

Using the same recognition of patterns and convention allowed the replacement of boilerplate code in our persist method to.

Before:

public void persist ( Endowment endowment ) {
	try {
		// get the local home
		EndowmentLocalHome endowmentLH = EndowmentUtil.getLocalHome ();
		// create the entity
		EndowmentLocal endowmentL = endowmentLH.create ();
		// ensure that the foreign keys are set
		setForeignKeys ( endowment );
		// populate the entity
		endowmentL.setEndowmentValue ( endowment );
		// update the ID
		endowment.setId( endowmentL.getId() );
	} catch ( Exception e ) { throw new VRRemoteException ( e ); }
}

After:

public void persist ( Endowment endowment ) {
	mixin.persist (endowment);
}

The reflection code that does it:

public void persist (Object bean) {

	// Create an entity EJB bean using the localHome.
	Object ejb = sendMessage ("create", getLocalHome (), null);
		
	// Populate foreign ID's from associated POJO's.
	setForeignKeys (bean);
		
	// Populate the entity.
	setValueOnEjb (bean, ejb);
		
	// Get the id from the ejb.
	String id = (String) sendMessage ("getId", ejb, null);
		
	// Set the id on the bean (POJO).
	sendMessage ("setId", bean, new Object[] { id });
}

The nice thing is I wrote the reflection version of the persist once and I can reuse it with one line of code. Bye bye boiler plate.

Conclusion

If you find yourself writing repetitive code, such as in my case when wrapping some API you want to abstract out, find the patterns and factor out that repetitive boilerplate code using reflection. You don't need to waste your life writing repetitive code.