Skip to main content

A Little Persistence Framework for Wicket

Posted by timboudreau on November 29, 2007 at 11:13 AM PST

I've contributed a little persistence framework to WicketStuff.

Basically, over the summer I needed to write a couple of web applications. My friend Jon suggested I try out db4o, an object database.

One of the things I like about Wicket is its simplicity - no XML config files, straightforward HTML that maps to components, and every component has a Model that represents one object. One of the painful things in Swing is that, if you decide you want a Tree, not a List, you have to rewrite your models because every different flavor of component comes with its own flavor of model. In this, Wicket gets right a thing that Swing got dead-wrong. Also very nice is the fact that wicket components can automagically look up properties on an object - once you have the concept that every component has a model, and any model represents exactly one object, there are a lot of very useful things you can do. So if I want to show the Date property of a POJO, it is as simple as:

class MyPanel extends Panel {
    MyPanel (String id) {
       super (id);
       setModel (new Model (lookUpThePojo()));
       Label dateLabel = new Label ("date");
       add (dateLabel);
    }
}

and no special glue is needed - if the ID matches a bean property on the POJO, I'm done (if I don't like the overhead of reflection, of course I can also write a custom model).

Hey! Wait a second! What does that lookUpThePojo() method do?! Isn't that where the magic happens? Yes. That is the precisely the problem I am setting out to solve...

As the application evolved, I found myself really wanting a similar simplicity for database access - I was ending up with far too much code dotted all over the application, which contained fairly similar code for looking up POJOs for Users, Events, Presentations, etc. - the app was a tool that allows Java Users Groups to vote on what we should talk about at NetBeans Day events.

Wicket also has a concept of detachable models - a model which looks up an object, but can dispose of it when it is not needed any more. This is particularly important because Wicket uses serialization to (really completely!) solve the back-button problem. Particularly in a clustered environment, you don't want to serialize an entire object graph for some POJO and send it over the wire to the rest of the cluster - you want to just save, say, a unique ID for the POJO and look it up again when something actually needs to call the POJO.

Canary (?) in Foz do Iguacu, Brazil

So the problem really was - how can I transparently and easily create IModels for POJOs on demand, keep the database lookup code in one place, and keep session size small. So a framework for doing this started to evolve out of this application. It comes as three libraries:

  • A persistence facade API - a persistence facade is just an object which manages a POJO's lifecycle. Under the hood, a persistence facade has one of a number of possible lookup strategies - for example, if the object is modified but unsaved, then it will be cached; on the other hand, if it is unmodified, then the lookup strategy used will probably revert to just holding an ID for the object when nothing is interested in it. The persistence facade piece has an SPI (Service Provider Interface) that can be implemented over any database (probably simpler for db4o but certainly doable over hibernate or even using straight JDBC) - the db4o implementation is completely separate. It's basically a matter of implementing two (admittedly complex) interfaces.
  • An implementation of the persistence facade SPI over db4o
  • An API that allows you to create Wicket models from a various types of queries, and model classes for modelling a single object or a collection of objects, that do the same kind of property lookup magic, but manage object lifecycle so that the persistence facade ends up in a minimum-size state when it is not actively in use by Wicket components

To be clear, the goal was not to create something with magical dynamic proxies or POJOs that transparently get persisted or anything like that; the persistence facade library simply attempts to cleanly express the problem of managing the lifecycle of a persisted object. No magic, it's just a thing you can then build some magic on top of.

The result is simple to use (I would be interested in feedback on how to make it even simpler). You write some POJOs (they should be serializable and implement equals() and hashCode() correctly). You get a ModelBuilder for a particular type of query. Say I already have a POJO and I want a model for it:

Person person = new Person ("Henry", "Story");
QueryBuilder builder = Queries.EXISTING_OBJECTS.builder(Person.class);
PojoModel model = builder.single(getDatabase());

or say that I want a collection of models for all instances of Person that are persisted:
QueryBuilder builder = Queries.OF_TYPE.builder(Person.class);
PojoCollectionModel polyModel = builder.multi(getDatabase());

You could also do a prototype-based query (not sure if this should actually stay in the API or not - with db4o it's easy; with other databases perhaps not since it involves some magic):
Person prototype = new Person();
prototype.setName("Henry");
QueryBuilder builder = Queries.PROTOTYPE.builder(Person.class);
builder.setObject (prototype);
//find all the Henry's in the database
PojoCollectionModel model = builder.multi(getDatabase());

or if I want all of the Henry's who live in France, I can do a complex query based on persisted fields of objects:
FieldQueryElement nameQuery = new FieldQueryElement ("firstname", String.class, "Henry", false);
FieldQueryElement franceQuery = new FieldQueryElement ("address.country", String.class, "FR", false);
QueryElement theQuery = nameQuery.and(franceQuery);
QueryBuilder builder = Queries.COMPLEX.builder(Person.class);
builder.setObject (theQuery);
PojoCollectionModel model = builder.multi(getDatabase());

(If you're wondering what the getDatabase() method is, an instance of Db is owned by the Application object, and I included a couple of handy Panel and Application subclasses to look it up - it is the equivalent of ((DbApplication) Application.get()).getDb()).

You may have noticed above that I specified a field as "address.country" - yes, you can drill through the object graph this way - so you're not limited to only looking at fields directly on a persisted object. Also, you are not restricted to exact matches - you can use a RangeValue for a range of numbers, or StringContainsValue, StringEndsWithValue or StringStartsWithValue.

Application code is not terribly tied to the database implementation, but you can directly access it if you want to. There is a class called Db which takes a generic type parameter - for example, the db4o implementation produces a Db<com.db4o.ObjectContainer>; but database access does not have to be tied to that database unless it is useful to you for it to be. You can implement DbJob and be passed the actual ObjectContainer instance and do what you want directly with the database, or have the only thing tied to the type of the database implementation be the thing that instantiates it.

So what can we do with all this?

Well, Wicket already has repeater components - very handy for showing a collection of data. And we can use property lookup and just give components ids that match properties. So, say I want to show all of the Person objects in my database:

class PeoplePanel extends Panel {
  public PeoplePanel (String id) {
     super (id);
     PojoCollectionModel model = Queries.OF_TYPE.builder(Person.class).multi(getDatabase());
     setModel (model);
     DataView repeater = new DataView ("repeater", model.createDataProvider()) {
          public void populateItem (Item item) {
              item.add (new Label ("firstName"));
              item.add (new Label ("lastName"));
          }
     }
    add (repeater);
}

That's it! The process of getting the domain objects is abstracted into creating a model based on a particular query; the UI code is no longer littered with database-related code, and is nice and simple.


wicketDbApp.png

A quickie diagram of what's going on

Now, say that I want a form to add a person:

class AddPersonForm extends Form {
   TextField firstName = new TextField ("firstName");
   TextField lastName = new TextField ("lastName");
  AddPersonForm (String id) {
     super (id);
     //Will create a new instance via Class.newInstance() - or I could pass a factory
     setModel (Queries.NEW_OBJECTS.builder(Person.class).single(getDatabase()));
     add (firstName);
     add (lastName);
   }

   public void onSubmit() {
       ((PojoModel) getModel()).modified(); //this line will not be necessary in the future - a small thing that needs fixing :-/
       ((PojoModel) getModel()).save();
   }
}

Now, I'm not trying to solve world hunger here - these libraries scratch a particular itch, and will not necessarily be the right tool for every job. And surely other minds will find places where things could be simplified and improved - thoughts and contributions are very, very welcome!

The libraries can be built with Maven (I'm pretty new to Maven, so I'm sure some things could be improved here); if you do a checkout of WicketStuff, you can find all of this, plus a demo app, in the quickmodels directory (thanks to Wicket maintainer Eelco Hillenius for suggesting the name). Presumably we'll get continuous builds of it running sometime soon.

The code is licensed under the Apache license, with the exception of the db4o implementation of the persistence SPI, which is dual-licensed under Apache and GPLv2 (since you get db4o either under a commercial license or under GPLv2, for license-compatibility, if you use db4o under the GPL, then you must use the code that links to db4o under GPL; if you have a commercial license of db4o, then you can use the persistence implementation over db4o under whichever license you prefer).

Feedback on the design, API, and whether you find this stuff useful is very welcome.

Related Topics >>