Tobias Gierke
2018-08-27 13:00:39 UTC
Hi,
A collegue of mine just came across a rather interesting bug in our
Wicket application.
1. We have a simple page with a repeater (ListView) that displays a
table and on each row, some buttons to perform actions on the item shown
on this row (edit/delete/etc.)
2. The underlying data source (a database table) gets updated
concurrently by another process running on another machine
3. The table is supposed to always show the latest data at the very top,
so the page uses a LoadableDetachableModel to always hit the database on
every request
The bug:
Users complained that performing actions on the data items would
actually affect an item different from what they clicked.
The explanation:
Since the list model got detached at the end of the previous request,
clicking any of the action buttons would re-populate the data model,
fetching previously unseen rows from the database. Since (according to
my collegue,didn't double-check) the ListView associates the item models
only based on their list index, the action button on the very first row
now all of a sudden referred to a database row the user didn't even know
about.
His fix:
Instead of
view = new ListView<Data>("listView" , dataProvider )
{
  @Override
  protected void populateItem(ListItem<PCAPFile> item)
  {
      add(new AjaxButton( "delete , item.getModel() ) // use model from ListItem (model gets detached after request)
      {
           public void onClick(AjaxRequestTarget target) {
               delete( getModelObject() );
           }
      });
      // ... more stuff
  }
}
he changed the code to read:
view = new ListView<Data>("listView" , dataProvider )
{
  @Override
  protected void populateItem(ListItem<PCAPFile> item)
  {
      add(new AjaxButton( "delete , item.getModelObject() ) // capture model object when constructing the button
      {
           public void onClick(AjaxRequestTarget target) {
               delete( getModelObject() );
           }
      });
      // ... more stuff
  }
}
This obviously is a rather subtle issue and - depending on the size of
your model objects - also comes with a certain performance/memory cost
because of the additional serialization for the model items the repeater
components are now holding onto.
Is this the recommended approach for dealing with dynamically changing
data or is there a better way to do it ?
Thanks,
Tobias
A collegue of mine just came across a rather interesting bug in our
Wicket application.
1. We have a simple page with a repeater (ListView) that displays a
table and on each row, some buttons to perform actions on the item shown
on this row (edit/delete/etc.)
2. The underlying data source (a database table) gets updated
concurrently by another process running on another machine
3. The table is supposed to always show the latest data at the very top,
so the page uses a LoadableDetachableModel to always hit the database on
every request
The bug:
Users complained that performing actions on the data items would
actually affect an item different from what they clicked.
The explanation:
Since the list model got detached at the end of the previous request,
clicking any of the action buttons would re-populate the data model,
fetching previously unseen rows from the database. Since (according to
my collegue,didn't double-check) the ListView associates the item models
only based on their list index, the action button on the very first row
now all of a sudden referred to a database row the user didn't even know
about.
His fix:
Instead of
view = new ListView<Data>("listView" , dataProvider )
{
  @Override
  protected void populateItem(ListItem<PCAPFile> item)
  {
      add(new AjaxButton( "delete , item.getModel() ) // use model from ListItem (model gets detached after request)
      {
           public void onClick(AjaxRequestTarget target) {
               delete( getModelObject() );
           }
      });
      // ... more stuff
  }
}
he changed the code to read:
view = new ListView<Data>("listView" , dataProvider )
{
  @Override
  protected void populateItem(ListItem<PCAPFile> item)
  {
      add(new AjaxButton( "delete , item.getModelObject() ) // capture model object when constructing the button
      {
           public void onClick(AjaxRequestTarget target) {
               delete( getModelObject() );
           }
      });
      // ... more stuff
  }
}
This obviously is a rather subtle issue and - depending on the size of
your model objects - also comes with a certain performance/memory cost
because of the additional serialization for the model items the repeater
components are now holding onto.
Is this the recommended approach for dealing with dynamically changing
data or is there a better way to do it ?
Thanks,
Tobias