Wicket in Action

A comprehensive guide for Java developers building Wicket-based web applications

Building a smart EntityModel

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.

26 Comments »

  1. [...] It is a great day: Igor Vaynberg, the most prominent committer for Apache Wicket (he has the most commits I think) has written up an article about building a smart EntityModel. You can read it at the official Wicket blog: wicketinaction.com [...]

    Pingback by First Wicket article by Igor Vaynberg | A Wicket Diary — September 30, 2008 @ 8:57 am

  2. Hi,
    Great article…

    Small improvement… It is possible to deduct the class of T by using this expression: (Class) ((ParameterizedType) getClass() .getGenericSuperclass()).getActualTypeArguments()[0]. By using this trick, you don’t need to include the clazz parameters in the constructor and in the load method.

    Cheers,
    Bernard.

    Comment by Bernard — September 30, 2008 @ 10:46 am

  3. Nice article. Shouldn’t Identifiable extends Serializable?

    Comment by Marcell — September 30, 2008 @ 11:06 am

  4. Thanks Igor, some useful tricks here, especially for someone like myself new to both Wicket & Hibernate. I’d love to see a followup article to see how you handle more complex situations, eg collections of entities filtered by certain criteria.

    Comment by Chris — September 30, 2008 @ 12:08 pm

  5. Bernard: that works in the simple case however if you have a deeper class hierarchy where the type argument is declared elsewhere (on either a superclass or an interface) the code gets a more complex. You have to recurse up via getSuperclass() and check both the class and any interfaces for the appropriate param. With multiple type params it’s even messier, you have to recurse to the top of the hierarchy then search downwards, filling in the type params as you find them (otherwise you don’t know which actual type param corresponds to which parameter).

    Comment by Chris — September 30, 2008 @ 12:22 pm

  6. @Marcell: There are two things here: 1) Identifiable should not extend Serializable because serialization of the object is not Identifiable’s concern. It simply represents something with a serializable id. If you expect all your identifiables to also be serializable you should probably create a new interface that extends both. 2) Serializable is only required if you give the model a transient entity, so only those entities need to be serializable.

    My original code had the serialization check built into detach(), but the article was getting a little too long – thats why I hinted at a second part if there was interest.

    Comment by ivaynberg — September 30, 2008 @ 4:20 pm

  7. @Chris: I dont think a follow up to this particular article would include querying, but you can definitely take a look at wicket-phonebook project in wicket-stuff svn. It demonstrates filtering resultsets.

    Comment by ivaynberg — September 30, 2008 @ 4:24 pm

  8. @Bernard: I agree with Chris, deducing would be nice but hairy. Are there any utility libraries out there that have the entire tracing built into a convenient function?

    Comment by ivaynberg — September 30, 2008 @ 4:26 pm

  9. Igor I have a similar model already in my app. But, there’s a gotcha regarding to using it with the DiskStore. Imagine I have a non-persistent entity, where the getId() would return null. Now the model would keep the entity in memory instead of the id. I start editing the model, pass it to another page, in that page i edit it, and then return to the previous page instance. This instance would fetch the model from the DiskStore, which would have the previous version of my entity. So all my changes when I get back is lost. Do you think we can provide a solution for this ?

    Comment by Iman Rahmatizadeh — October 1, 2008 @ 1:12 pm

  10. @Iman: Unfortunately passing objects between pages is a known limitation with the diskstore. Currently the stored data would look like this [page1 [model]] [page2 [model']], in order to make this work we would have to rewrite the store to be smarter and store the objects like so [page1] [page2] [model], but this is a pretty big rewrite. You should open a JIRA issue and provide a quickstart, I dont think we will get to it within 1.3 or even 1.4 timeframe, but we will definitely try. In the meanwhile I tend to use Panels instead of Pages for situations like these; its not ideal but at least its a feasible workaround.

    Comment by ivaynberg — October 2, 2008 @ 2:29 am

  11. @Igor: Then at detach you check if the entity has no id and is Serializable. What do you do if the entity does not implement Serializable? I mean besides raising a error :P

    @Iman: I am using Seam with Wicket and I can say that your problem is a good use case to use conversational context provided by Seam.

    Comment by Marcell — October 3, 2008 @ 1:41 pm

  12. @Marcell: There isn’t much you can do if it is not Serializable, that is the contract of the EntityModel if you choose to put in a transient entity. In my actual model I have checks that enforce the contract by trying to serialize the entity in dev mode. Perhaps I will revisit this article and add those checks.

    I also do not think seam’s convesational context would be much help to Iman. The problem is that the IModel that goes from page A to page B is cloned and now page A is no longer aware of the changes to the model that page B made. Its a much more deeply rooted problem then just ensuring that the Session instance is the same.

    Comment by ivaynberg — October 3, 2008 @ 6:12 pm

  13. @Igor: I wasn’t aware of any workaround with panels. As I use panels extensively in my app, would you care to elaborate how it would help in this scenario ?

    Comment by Iman Rahmatizadeh — October 4, 2008 @ 6:22 am

  14. @Iman: its not really a workaround, more of a way you build your app. The problem here is that the model is shared between multiple pages, so the solution is to eliminate multiple pages. Instead of navigation consisting of going from page to page you can instead keep the user on the same page and accomplish the same navigation by swapping out the “content” panel.

    eg

    class ViewUserPanel extends Panel {
      public ViewUserPanel(String id, IModel user) {
          add(new Link("edit") {
             onclick() {
                 onEditUser(getModel());
             }
          });
       }
     
       private void onEditUser() {
            replaceWith(new EditUserPanel(getId(), getModel()));
       }
    }

    So, instead of calling setResponsePage(new EditUserPage()) you swap the panel…

    Comment by ivaynberg — October 4, 2008 @ 6:31 am

  15. @Igor: When I mean to use the conversational context you wouldn’t store the transient Model in the EntityModel. You probably would do at detach and attach:

    if (entity != null) {
    if (if (entity.getId() != null) {

    } else {
    Context.getConversationContext().set(this.getClass().getName()+”#entity”,entity);
    }
    }

    And at getObject():

    if (entity == null) {
    if (id != null) {

    } else {
    entity = (T) Context.getConversationContext().get(this.getClass().getName()+”#entity”);
    }
    }

    Comment by Marcell — October 4, 2008 @ 11:36 am

  16. @Marcell: Sure. I can do the same thing with the Session and a uuid token stored in the model. The big advantage of just using the model is that it is its own conversational scope. Eg, the user does whatever they need and the conversation ends, then the user presses back button 4 times and tries to do something – error because conversation has already been closed. This wouldnt happen with the model because it would be stored with the page and retrieved again.

    Comment by ivaynberg — October 4, 2008 @ 6:27 pm

  17. Whats the adavantage of overriding process and doing a rollback when super.process returns false, instead of just overriding submit? Submit gets called only when the submit is valid, so no transaction gets started unless the submit is actually valid.

    Comment by Jörn Zaefferer — October 5, 2008 @ 11:38 pm

  18. @Jörn: We want the entity loaded, updated, and saved all in the same transaction. onSubmit() is called after wicket has already applied user inputs to the model object, at that point you would have to session.merge() your changes instead of having them transparently persisted.

    Comment by ivaynberg — October 6, 2008 @ 6:19 am

  19. Okay, that makes sense. Thanks for clarifying.

    Comment by Jörn Zaefferer — October 6, 2008 @ 3:33 pm

  20. [...] Das NewsModel ist nicht von LoadableDetachableModel abgeleitet, da wir keinen schreibenden Zugriff auf das zu speichernde Objekt hätten, sondern dieses immer über load() geladen werden müsste. Dennoch ist die Model-Klasse recht einfach und übersichtlich. Es speichert die ID und die News selbst, beim Speichern in der Session wird die News aber nicht serialisiert. Werden die Daten wieder aus der Session geholt (z.B. durch ein Neu Laden der Seite), so wird die News anhand der ID erneut aus der Datenbank geladen. Eine allgemein gehaltenere Version eines solchen Models beschreibt Igor Vaynberg, einer der Wicket-Mitentwickler, im Blogeintrag “Building a smart EntityModel”. [...]

    Pingback by Persistenz für den Feedreader - rattlab.net — October 28, 2008 @ 10:01 pm

  21. @Igor
    In reference to comment #18,
    I’m trying to find a way to achieve this in Wicket 1.3, and having a hard time. It appears that onSubmit() is not called within form.process(), and a lot of the methods I might try to override for this are declared final. Can you (or anyone) suggest where I can begin and end the transaction? THanks

    Comment by Paul Mogren — December 15, 2008 @ 6:42 pm

  22. @Paul: I had to do a lot of refactoring in 1.4 to get this working correctly. Unfortunately I cannot port this back to 1.3 because it has production apps running on it that might depend on the functionality this changed.

    It is pretty easy to set up a single-transaction-per-request pattern in wicket. It is not as granular as a transaction for the form submit, but it should still work.

    public class WicketRequestCycle extends WebRequestCycle
    {
        private TransactionStatus txn;
     
        @Dependency
        private PlatformTransactionManager txm;
     
        public WicketRequestCycle(WebApplication application, WebRequest request, Response response)
        {
            super(application, request, response);
        }
     
        @Override
        protected void onBeginRequest()
        {
            txn = txm.getTransaction(new DefaultTransactionDefinition());
            super.onBeginRequest();
        }
     
        @Override
        protected void onEndRequest()
        {
            super.onEndRequest();
            if (txn != null)
            {
                txm.commit(txn);
                txn = null;
            }
        }
     
        @Override
        public Page onRuntimeException(Page page, RuntimeException e)
        {
            if (txn != null)
            {
                txm.rollback(txn);
                txn = null;
            }
            return super.onRuntimeException(page, e);
        }
    }

    Comment by ivaynberg — December 15, 2008 @ 8:49 pm

  23. [...] Implemented what I hope is the exact equivalent of the above using only Wicket and JPA, also Maven-ized and Jetty-fied. Decided to also experiment with some of the ideas in this blog post. [...]

    Pingback by Seam / JSF vs Wicket: performance comparison « Incremental Operations — January 14, 2009 @ 5:41 pm

  24. I’ve tried the approach of commiting the transaction in onEndRequest in the request cycle. The question is what happens if the commit fails – because the response has already been sent at that stage. Depending on your database and configuration, commits can fail due to IO failures, uniqueness constraint failures, collisions, etc. I would still like to do one commit per request since this seems ideal for DB4O, but am not sure where to put the commit.

    Comment by Sams — May 14, 2009 @ 12:24 am

  25. Update: I think I’ve solved this by using a custom RequestCycleProcessor and doing the commit in the respond method. Working well so far.

    Comment by Sams — May 15, 2009 @ 2:10 pm

  26. With the example EntityModel code, I’m getting a compile error because load() doesn’t return a T but instead, an Identifiable.

    Am I missing something?

    Comment by ReinoutS — June 4, 2009 @ 5:08 pm

RSS feed for comments on this post. TrackBack URL

Leave a comment

With this book, Wicket will become the greatest territory the Dutch have settled since Manhattan.

Nathan Hamblen
Senior Software Engineer, Teachscape Inc.

This is the complete and authoritative guide to Wicket, written and reviewed by the core members of the Apache Wicket team. If there's anything you want to know about Wicket, you are sure to find it in this book.

Jonathan Locke
Founder and Architect of Apache Wicket, Foreword Wicket in Action

Without question, Wicket in Action... is the be-all and end-all when it comes to Wicket.

Geertjan Wielenga, Wicket Netbeans Plugin Author

The tutorial and conversational tone of the writing makes the book very approachable.

Nick Heudecker
System Mobile

Loved the sample application—it tied everything together.

Phil Hanna
Senior Software Developer, SAS Institute

The essential guide for learning and using Wicket.

Erik van Oosten
Lead programmer and Project Manager, JTeam

Finally, the Web Framework of web frameworks, Apache Wicket, now has a bible of its own.

Per Ejeklint
Senior Software Architect, Heimore group

Wicket is an innovative evolution of the MVC programming with simple roots, but without a primer such as this, it can be more challenging than it needs to be.

Brian Topping
Founder, Bill2 Inc.

Wicket In Action glues the areas of web development with Apache Wicket together and gives a great overview of Apache Wicket...it will make a great compendium.

Nino Martinez Wael
Java Specialist, Jayway Denmark