Wicket in Action

Building a smart EntityModel

30 September 2008, by ivaynberg

A lot of Wicket applications use an ORM framework to work with the database. Because ORMs provide a generic mechanism for loading entities we can create a generic Wicket model that can simplify binding between ORM and Wicket. In this article I will walk you through creating an EntityModel that will make using Wicket and ORM fun, read inside...


In order to load an entity from the database we need two pieces of information: entity’s class and id. Since there is no standard way to get an id from an object we will create an interface that will standardize the process:

1
2
3
4
public interface Identifiable<T extends Serializable>
{
 T getId();
}

The simplest entity model can then look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public abstract class AbstractEntityModel<T extends Identifiable< ? >>
 extends
 LoadableDetachableModel<T>
{
 private Class clazz;
 private Serializable id;

 public AbstractEntityModel(T entity)
 {
 clazz = entity.getClass();
 id = entity.getId();
 }

 public AbstractEntityModel(Class< ? extends T> clazz, Serializable id)
 {
 this.clazz = clazz;
 this.id = id;
 }

 @Override
 protected T load()
 {
 return load(clazz, id);
 }

 protected abstract T load(Class clazz, Serializable id);
}
  • 26: the method used to perform the actual loading of entity from the ORM

This model can load any entity given an instance or a class and id, and will detach itself at the end of request. The model is abstract because the way to retrieve a handle to the ORM session varies from application to application. For example, in my project that uses Salve's dependency injection and Hibernate a concrete implementation will look like this:

1
2
3
4
5
6
7
8
9
10
11
public class EntityModel<T> extends AbstractEntityModel<T>
{
 @Dependency
 private Session session;

 @Override
 protected Identifiable load(Class clazz, Serializable id)
 {
 return session.get(clazz, id);
 }
}
  • 3: Salve annotation that will make sure field session is injected with the current hibernate session
  • 7-10: concrete implementation of the #load() method using hibernate Session

Handling Errors

There are cases when a user deletes an object from the database that another user is currently viewing in the UI. In this case when the UI tries to render and calls getObject() on our EntityModel it will return null. There is nothing worse in the log then a cryptic NullPointerExcepton. A much better approach is to check for this condition and throw a type of exception that can be intercepted and processed appropriately by the UI.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public abstract class AbstractEntityModel<T extends Identifiable< ? >>
 extends
 LoadableDetachableModel<T>
{
 
 @Override
 protected T load()
 {
 T entity = load(clazz, id);
 if (entity == null)
 {
 throw new EntityNotFoundException(clazz, id);
 }
 return entity;
 }

 }
  • 10-13: built-in null check for better error reporting

Then in our RequestCycle subclass:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyRequestCycle extends WebRequestCycle
{
 @Override
 public Page onRuntimeException(Page page, RuntimeException e)
 {
 if (e instanceof EntityNotFoundException)
 {
 return new EntityNotFoundErrorPage((EntityNotFoundException)e);
 }
 else
 {
 return super.onRuntimeException(page, e);
 }
 }

}

EntityNotFoundErrorPage can present a user-friendly message explaining why the error happened.

Using EntityModel to bind to Forms

Suppose the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class EditPersonPage extends WebPage
{
 public EditPersonPage(Long personId)
 {
 this(new EntityModel<Person>(Person.class, personId));
 }

 private EditPersonPage(IModel<Person> model)
 {

 Form<Person> form = new Form<Person>("form", new CompoundPropertyModel<Person>(model))
 {
 @Override
 public boolean process()
 {
 beginTransaction();
 if (super.process())
 {
 commitTransaction();
 return true;
 }
 else
 {
 rollbackTransaction();
 return false;
 }

 }
 };

 add(form);

 form.add(new TextField<String>("name"));
 }
}

When the form is submitted the following series of calls will be made:

  • Form#process()
    • beginTransaction()
    • super.proces()
      • Person p=EntityModel.getObject()
      • p.setName(name);
      • Form#onSubmit()
    • commitTransaction();
  • EntityModel#detach();

This is interesting because the Person entity is loaded and the TextField sets the value on the loaded Person inside the same transaction, which means this value will be persisted automatically by the ORM when the transaction ends. Any changes done to this form"s data are automatically reflected in the database without any extra code.

Our AbstractEntityModel is already good enough to handle this scenario, but what if we wanted to reuse our form to create a new entity instance instead of just editing one? If we had a way to do this we can then reuse any Form to create or edit an entity. To achieve this we need to add two things to our model:

  1. Ability to hold on to a transient entity instance (disable detaching while the entity is transient)
  2. Allow the model to intelligently begin detaching if a transient entity instance has become persistent between getObject() and detach()

Since it is not possible to override LoadableDetachableModel’s detach() we will have to convert our model to implement IModel directly – giving us full control:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public abstract class AbstractEntityModel<T extends Identifiable< ? >> implements IModel<T>
{
 private final Class clazz;
 private Serializable id;

 private T entity;

 public AbstractEntityModel(T entity)
 {
 clazz = entity.getClass();
 id = entity.getId();
 this.entity = entity;
 }

 public AbstractEntityModel(Class< ? extends T> clazz, Serializable id)
 {
 this.clazz = clazz;
 this.id = id;
 }

 public T getObject()
 {
 if (entity == null)
 {
 if (id != null)
 {
 entity = load(clazz, id);
 if (entity == null)
 {
 throw new EntityNotFoundException(clazz, id);
 }
 }
 }
 return entity;
 }

 public void detach()
 {
 if (entity != null)
 {
 if (entity.getId() != null)
 {
 id = entity.getId();
 entity = null;
 }
 }
 }

 protected abstract T load(Class clazz, Serializable id);

 public void setObject(T object)
 {
 throw new UnsupportedOperationException(getClass() 
 " does not support #setObject(T entity)");
 }
}
  • 6: cache for the entity instance that we keep for the request, only the first #getObject() call will populate it
  • 12: notice we initialize the cache if we have the instance
  • 25: guard so we only call #load() if we have a valid id
  • 39: guard from preventing detaching a detached model
  • 41: guard to make sure we do not null out a transient entity instance, we want to keep it until it is persisted
  • 43: if a transient instance was persisted keep its id so it can be reloaded
  • 44: now that we have a valid id we no longer need to keep the cache

From now on our EntityModel will hold on to the transient entity until it is persisted. If we subclass the Form in the previous example and override its onSubmit() to persist the entity the model will switch into persistent mode after the form is submitted. So, it doesn"t matter if the EntityModel given to that form contains a transient or a persistent instance of the entity, the model and therefore also the form will simply do the right thing.

That’s it for now. Perhaps if there is more interest in such models I will revisit this and add a couple of other useful features.

-->