Wicket in Action

Fixing Wicket Property Models Using Salve

25 January 2009, by ivaynberg

Introduction

Ironically, property models are the most convenient and the most troublesome feature of wicket. Lets use the following code as an example:

new TextField(zip, 
    new PropertyModel(person, addressBook.addresses.0.zip));

They are convenient because they make it trivial to bind components to domain objects using one line of code.

They are troublesome because they create code that is easily broken and whose breakage is not apparent until run time. For example: if the AddressBook#getAddresses() method used in the example above is renamed to AddressBook#getLocations() it will not cause a compilation error, but rather a runtime error when the page is rendered. This is a big problem unless every page of the webapp is covered by a unit test.

Possible Solutions

There are a couple of ways to fix the problem:

  • Do not use property models; instead create models that implement get/setObject methods using plain java code. Advantages: easy, refactor-safe, compile time errors. Disadvantage: extremely verbose, no default null handling in intermediate return values.
  • Use proxies to generate dynamic builders. Advantages: refactor-safe, compile time errors. Disadvantages: runtime overhead (negligible), use of proxies limits code features(cannot intercept final methods, etc), most likely cannot be inlined thus requires at least two lines of code instead of one.
  • Use a source code analyzer to check the expression string at compile time. Advantages: compile time errors, most visibility into code – can take advantage of generic types that are erased at runtime. Disadvantages: not refactor-safe, requires source for all classes used in the expression, extra compilation step.
  • Use apt to generate metadata about classes and use that metadata instead of the expression string. Advantages: transparent code generation in java 6, compile time errors. Disadvantages: not refactor-safe, only works for classes compiled with apt processor.
  • Use a bytecode analyzer to check the expression string at compile time or test time. Advantages: compile or test time errors, no extra compilation step if used in test time. Disadvantages: not refactor-safe.

Salve's solution

The bytecode analyzer is what I chose to implement as part of Salve. It is implemented as a unit test that scans for use of salve.expr.Pe class that represents property expressions in Salve.

A property model usage with Salve’s Pe class can look something like this:

new TextField(zip, 
    new PropertyModel(person,
        new Pe(Person.class, addressBook.addresses.0.zip).toString()));

While the unit test can be installed by creating a simple subclass of Salve’s PropertyExpressionTest within the project being checked:

public class ExpressionValidatorTest extends PropertyExpressionTest {}

When executed the unit test will scan for .class files in the project folder, look for instantiations of Pe class and check the string expression. Any error will cause the test to fail.

Using Pe class does make the code somewhat longer. The checker supports definitions of custom classes. For example we can create a more convenient version of the PropertyModel like so:

public class PeModel<T> extends PropertyModel<T>
{
    public PeModel(Object modelObject, Class< ? > clazz, String expression)
    {
        // we do not use the clazz param, it is only here for bytecode analyzer
        super(modelObject, expression);
    }
}

With usage:

new TextField(zip, 
    new PeModel(person, Person.class, addressBook.addresses.0.zip));

And let the unit test know about it by specifying a matching rule:

public class ExpressionValidatorTest extends PropertyExpressionTest
{
   
    @Override
    protected Set<Rule> getRules()
    {
        Set<Rule> rules = super.getRules();
       rules.add(new Rule("commons/web/util/model/PeModel", Part.TYPE, Part.PATH));
        return rules;
    }
}

Rules match on the last x parameters. In the above example the rule will match instantiations of the PeModel class with constructor signature of (*,Class rootType, String path).

Another convenient usage pattern is binding components to fields of the parent via: new PropertyModel(this, “name”);. Salve’s checker supports this pattern like so:

    public PeModel(Object modelObject, String expression)
    {
        super(modelObject, expression);
    }

    rules.add(new Rule("commons/web/util/model/PeModel", Part.THIS, Part.PATH));

Installing Salve:

svn checkout http://salve.googlecode.com/svn/trunk/ salve
cd salve
mvn install
&lt;dependency&gt;
  &lt;groupId&gt;salve&lt;/groupId&gt;
  &lt;artifactId&gt;salve-expr-validator&lt;/artifactId&gt;
  &lt;version&gt;0.10-SNAPSHOT&lt;/version&gt;
&lt;/dependency&gt;

Conclusion

The code to all this is still brand new, raw, unfinished, undocumented, untested, etc. This is mainly a call out for feedback and ideas. Let me know what you think.

-->