Wicket in Action

Removing Fragile String Expressions From Wicket Code

25 November 2009, by ivaynberg

The most used model in Wicket is the PropertyModel and its derivatives. This model allows the user to quickly and easily navigate object graphs using string-based property-path expressions, and is used extensively when displaying data or creating forms.

However, the PropertyModel and its string-based expressions are also responsible for adding the most fragility to Wicket code. The standard Java tooling does not support refactoring strings that happen to contain references to java constructs such as methods or fields; thus errors in these string expressions are not discovered until runtime.

For example, the string used in the code below:

new Label("state",
        new PropertyModel(personModel, "address.state.code"));

will not be updated by the tooling when the getState() method is renamed into getArea() and will thus cause an error only discoverable at runtime.

So why is PropertyModel used so often in Wicket code even though it causes so many problems? The answer is simple: it is, by far, the easiest and most convenient way to achieve the functionality. We chose to sacrifice robustness of our code for development ease and convenience. What if there was another way to achieve the same, but without using strings?

Enter Bindgen, a Java 6 apt processor [1][2] capable of generating code that can represent the same property-expressions but using real Java objects instead of strings.

Bindgen allows the developer to transform code like this:

new Label("state",
        new PropertyModel(personModel, "address.state.code"));

into code like this:

new Label("state",
        BindingModel.of(personModel,
                new PersonBinding().address().state().code()));

No more strings! And although standard Java tooling will not automatically rename state() when the field or method it references is renamed, the compiler will immediately report all places that need to be updated via a compilation error.

So what does Bindgen get you? It gets you the same ease and convenience of PropertyModels but with the added compile-time safety. Too good to be true? Try it out for yourself…

Setting up the environment

The following demonstrates how to set up a project that is built using maven and eclipse. This code is experimental, and there is not much documentation yet (see Sources section below), but what Bindgen does is worth trying.

Adjust your pom as described here: http://code.google.com/p/bindgen-wicket/wiki/ConfiguringMaven2

Now run mvn eclipse:eclipse and the project should be ready to go using both eclipse and regular maven builds.

Using Bindgen

Using Bindgen itself is simple. Annotate a class whose properties you wish to access using the @org.bindgen.Bindable annotation and Bindgen will generate a <ClassName>Binding class that can be used to create Binding instances that represent property expressions. Eclipse will generate these classes as soon as the class with @Bindable is saved, so the <ClassName>Binding class should be available as soon as you press Ctrl+S after adding the @Bindable annotation.

For example:

Given

public class Address { public String city; }

@Bindable public class Person { public Address address; }

Bindgen will generate classes AddressBinding and PersonBinding enabling code like this:

Binding binding=new PersonBinding().address().city();
String city=binding.get();
binding.set("Beverly Hills");
assertEquals("address.city", binding.getPath());

Configuring Bindgen

Bindgen will generate bindings for all classes reachable from the annotated class. Most of the time this is not desirable, eg we do not necessarily want binding classes generated for java.lang.String or for com.caucho.hessian.io.SerializerFactory just because they are reachable from a class with the @Bindable annotation. Bindgen provides a way to limit the generation of binding classes to certain packages by specifying a comma separated list of these packages. To do this, create a file called bindgen.properties in the project’s base folder (the one that contains the pom file) and add the scope property, eg

scope=com.myproject.mypackage,com.myproject.myotherpackage

Using Bindgen with Wicket

The bindgen-wicket module provides classes that facilitate integration between Wicket and Bindgen. These are BindingRootModel and BindingColumn.

BindingRootModel is the replacement for the PropertyModel, and its usage looks like this:

add(new Label("state",
        new BindingRootModel(personModel,
                new PersonBinding().address().state().code()));

Or using a convenience wrapper:

add(new Label("state",
        BindingModel.of(personModel,
                new PersonBinding().address().state().code()));

The BindingColumn class allows binding expressions to be used for creating DataTable columns. Eg:

new BindingColumn(
                 new PersonBinding().address().state().code())
         .setHeader("column.state")

Conclusion

As we can see, Bindgen is able to remove one of the biggest pain points in Wicket code, and do so in a manner mostly transparent to the developer. Wicket is not the only framework that can benefit from Bindgen, any place in code where property expressions are kept in strings can be upgraded to a compile-safe Bindgen binding and if that is not an option at least the property expression string can itself be generated from a binding using the Binding#getPath() method.

It is important to keep in mind that a lot of the code that makes all this possible is still fresh, and so there may be a bug here or there. Your feedback would be very appreciated.

Sources

Bindgen website: http://www.bindgen.org

My fork of bindgen repository which contains my latest tweaks: http://github.com/ivaynberg/bindgen

Bindgen official repository, this is where my changes eventually get merged: http://github.com/stephenh/bindgen

Bindgen-Wicket integration project: http://code.google.com/p/bindgen-wicket

Demo project: http://bindgen-wicket.googlecode.com/svn/trunk/bindgen-wicket-examples/

-->