Problems with Defensive Collection Getters and JAXB

1:28 PM , , 1 Comments

A good general practice with collection getters is that they a) return a reference to the collection or b) return an unmodifiable wrapper around the collection. Further, it's often desirable to have an addXXX and removeXXX instead of a collection setter to complete the encapsulation picture.



public class Order {
private List items;

public List getItems() {
Collections.unmodifiableList(items);
}

// .. no setter

public void addItem(Item item) {
items.add(item);
}

public void removeItem(Item item) {
items.remove(item);
}
}

This practice runs awry when using this object for marshalling XML with JAXB. What you will notice when it tries to unmarshal and XML message representing this object is that the resulting items list will be empty.

That's really too bad.

What is happening is that JAXB in the absence of a collection setter is calling the getter in the hopes of calling "add" on the reference that it gets back. Unfortunately, since we are returning an unmodifiable list, JAXB silently fails by not adding any items at all and moving on.

The solution is actually pretty simple. We just need to tell JAXB to look at the field instead of the method. You do this with two annotations:

public class Order {
@XmlElementWrapper("items") @XmlElement("item") private List items;

...
}

And that's all! Now, you can keep your defensive collection encapsulation and still allow JAXB to do it's unmarshalling. Enjoy!

1 comments:

Adding paths to your class loader

8:52 AM , , 0 Comments

So, a class loader is immutable in Java, right? At least, there are no setter methods in the public API (except the assert stuff) and there is no obvious way to specify what class loader you might want to use where.

This came to bug me while creating a maven plugin the other day in which the plugin needed to read a specific classpath resource from the project it was running in. Since maven plugins run in their own class loader, I wasn't going to be able to access project classpath resources.

I might have been able to add the @requiresDependencyResolution metadata annotation to resolve the problem, but we really didn't want to box ourselves in to needing an enclosing project to run the plug-in.

The Maven Exec Plugin gave me an idea.

The Maven Exec Plugin is the maven-y way to run command-line java through a maven goal. It can either be run within the maven process as a separate thread or as a separate process. Either way, it has the same challenge of propagating the enclosing project's classpath on to a separate and distinct classpath context.

In the in-maven-process case, what they do is create their own classloader, and then run the invocation of the main method inside a separate thread, setting that threads context classloader along the way:



private void executeWithClassLoader(Runnable runnable, ClassLoader classLoader) throws MojoExecutionException {
IsolatedThreadGroup threadGroup = new IsolatedThreadGroup( runnable.getClass().getName() );

Thread bootstrapThread = new Thread( threadGroup, runnable, runnable.getClass().getName() + ".run()" );
bootstrapThread.setContextClassLoader(classLoader);
bootstrapThread.start();

try {
bootstrapThread.join();
} catch ( InterruptedException e ) {
Thread.currentThread().interrupt(); // good practice if don't throw
getLog().warn( "interrupted while joining against thread " + bootstrapThread, e ); // not expected!
}

synchronized ( threadGroup )
{
if ( threadGroup.uncaughtException != null )
{
throw new MojoExecutionException( "An exception occured while executing the Java class. "
+ threadGroup.uncaughtException.getMessage(),
threadGroup.uncaughtException );
}
}
}


The Runnable that is passed in is the section of code where you actually need access to the project's classpath. The ClassLoader is created like this:

URL outputDirectory = new File( project.getBuild().getOutputDirectory() ).toURI().toURL();
ClassLoader classLoader = new URLClassLoader( new URL[] { outputDirectory } );
this.executeWithClassLoader(runnable, classLoader);

Of course, there may be other directories or artifacts that you need to add for your runnable to function correctly, but that is the basic idea.

For more ideas, check out http://svn.codehaus.org/mojo/tags/exec-maven-plugin-1.2/src/main/java/org/codehaus/mojo/exec/ExecJavaMojo.java

0 comments:

EasyMock and varargs

8:57 AM , 2 Comments

EasyMock is a neat tool for creating mocks at runtime for unit tests. It does some pretty cool things, including writing their main API using Fluent Pattern.

For example, when adding behavior to your mocks, you would call the following:


EasyMock.expect(...).andReturn(...).times(3).andReturn(...).times(2)...


Awesome.

There is one feature, though, that is lacking that can send you for a loop if you are unaware, which is varags.

If you need to add a behavior to a method that takes var args, you will need to how many parameters are going in at test time.

For example, say I have the following method:


Object myMethod(Object... args)


That I want to supply behavior for. In EasyMock, there isn't a way to say "this is a var args method". So, you will need to expand it according to your test case:


EasyMock.expect(myMethod(arg0, arg1, arg2)).andReturn(...)...


The reason is that EasyMock's strategy for method matching is to count the number of arguments in your behavior with the number of arguments in the method invocation. If you just say


EasyMock.expect((String[])EasyMock.anyObject())...


or something like that, it will see that there is only one parameter, whereas the method invocation parameters will never match.

2 comments: