November 09, 2004

Replacing BeanUtils with Spring's BeanWrapper

I always wind up designing a system where I need to set generic properties on an object. (Whether this is a good or bad thing I’m not sure.) Using JavaBean property syntax helps out a lot with this, and rather than rolling my own and carting it around with me I tend to use Spring’s functions for this, or the BeanUtils package when Spring isn’t available.

So I was happily writing my unit tests to ensure bad configurations would blow up with something like this:

public void testMinCharsNonNumeric() {
    config.addChildren( new ParamSpec[] {
        new ParamSpec( "MinChars" ).add( "value", "ZZZ" ),
    });
    try {
        new FooItem( config );
        fail( "Should have thrown an exception on invalid number" );
    } catch ( Exception e ) {}
}

But... it didn't fail. WTF?! The code that calls BeanUtils to do the conversion is very simple:

try {
    BeanUtils.copyProperty( this, propertyName, value );
}
catch ( Exception e ) {
    badState( "Cannot assign value for '" + attributeName + "' to " +
              "property '" + propertyName + "': " + e.getMessage() );
}

That's not the problem. (I tried setProperty() too.) Looking into the BeanUtils source pointed me to ConvertUtils, which registers an IntegerConverter using a default value. This tells the converter to use the default if it cannot convert the value. Okay, no problem -- I'll just register a new converter without the default and everything will blow up as expected. Right? Wrong -- for some reason my converter wasn't registered with the recommended register() call.

This is a turning point people who use third-party libraries come to frequently -- do I figure out why it's working or do something else? And typically this 'do something else' results in yet another static method added to the internal 'Utils' class which everybody winds up developing.

But, at least in this case, there is another way. I'd used Spring to do this sort of thing before but was reluctant to bring the whole framework in to do this little thing. Fortunately Spring segments its jars, so adding the 'spring-core.jar' to the project and replacing the above code with:

try {
    BeanWrapper wrapper = new BeanWrapperImpl( this );
    wrapper.setPropertyValue( propertyName, value );
}
catch ( Exception e ) {
    badState( "Cannot assign value for '" + attributeName + "' to " +
              "property '" + propertyName + "': " + e.getMessage() );
}
Next: Security warning with TWiki
Previous: The hate continues