Details
Description
When I uses component.info() method to display a message, my program stopped by OutOfMemoryError or StackOverflowError.
I create a sample application to show this problem. Open attached tar.gz file(including a maven project) and run.
check 'submit continuously' checkbox and click 'register' button.
The program will display current session size continuously on console. the size will be increased, and finally program will be
stopped with OutOfMemoryError or StackOverflowError.
But if you changes only one line, this program will not be stopped.
--original code--
private SubmitLink insertLink = new SubmitLink("insertLink") {
public void onSubmit() {
info("message");
setResponsePage(new Test(testFormBean));
Session session = Session.get();
long size = session.getSizeInBytes();
LOGGER.info("SESSION SIZE: {}", size);
}
};
---------------------
--changed---------
private SubmitLink insertLink = new SubmitLink("insertLink") {
public void onSubmit() {
Session.get().info("message"); //CHANGED!!!
setResponsePage(new Test(testFormBean));
Session session = Session.get();
long size = session.getSizeInBytes();
LOGGER.info("SESSION SIZE: {}", size);
}
};
--------------------
so component's info() method is the reason of this problem. If you commented out 'info()' line, this program never crashed.
We found out the reason of this problem in a static inner class 'MessageListView' in FeedbackPanel.
MessageListView uses annonimous inner class of Model (named ad 'replacementModel'), and it imports a FeedbackMessage object from
enclosing instance. FeedbackPanel holds this annonimous inner class and the annonimous inner class holds a FeedbackMessage.
When we use component's info() method, the component is assigned into FeedbackMessage object as a 'reporter' object. so, all of
FeedbackMessage objects have a component instance inside of himself as 'reporter' (only one exception: if you use Session.get().info()
method instead of component's info() method, 'reporter' object become null).
All already-displayed FeedbackMessages will be purged at 'detach' time from Session object. But FeedbackPanel holds FeedbackMessages.
So when page is serialized, all FeedbackMessages, all 'reporter' components is serialized. This is the reason of this problem.
We can solve this problem if we do not hold FeedbackMessage instance in the annnonimous inner class.
change the code of FeedbackPanel as bellow (this code is based on FeedbackPanel class of wicket 1.4-rc7, line 70):
---- original code -----
@Override
protected void populateItem(final ListItem<FeedbackMessage> listItem)
{
final FeedbackMessage message = listItem.getModelObject();
message.markRendered();
final IModel<String> replacementModel = new Model<String>()
{
private static final long serialVersionUID = 1L;
/**
- Returns feedbackPanel + the message level, eg 'feedbackPanelERROR'. This is used
- as the class of the li / span elements.
- @see org.apache.wicket.model.IModel#getObject()
*/
@Override
public String getObject() { return getCSSClass(message); }};
final Component label = newMessageDisplayComponent("message", message);
final AttributeModifier levelModifier = new AttributeModifier("class", replacementModel);
label.add(levelModifier);
listItem.add(levelModifier);
listItem.add(label);
}
--------------------
---- fixed code ----
@Override
protected void populateItem(final ListItem<FeedbackMessage> listItem)
{
//FIXED message must not be 'final'. It must not be used in inner class.
//If message could be used in inner class, the instance could be hold by
//inner class tacitly and never cleared at detach time and will be serialized.
FeedbackMessage message = listItem.getModelObject();
message.markRendered();
final IModel<String> replacementModel = new Model<String>()
{
private static final long serialVersionUID = 1L;
/**
- Returns feedbackPanel + the message level, eg 'feedbackPanelERROR'. This is used
- as the class of the li / span elements.
- @see org.apache.wicket.model.IModel#getObject()
*/
@Override
public String getObject()Unknown macro: { //FIXED -- retrieve a FeedbackMessage object from ListView's Model. // never hold it. @SuppressWarnings("unchecked") List<FeedbackMessage> list = (List<FeedbackMessage>) MessageListView.this.getDefaultModelObject(); FeedbackMessage feedbackMessage = null; int index = listItem.getIndex(); if(index < list.size()) { feedbackMessage = list.get(index); } if(feedbackMessage == null) return ""; return getCSSClass(feedbackMessage); //UNTIL HERE }};
final Component label = newMessageDisplayComponent("message", message);
final AttributeModifier levelModifier = new AttributeModifier("class", replacementModel);
label.add(levelModifier);
listItem.add(levelModifier);
listItem.add(label);
}
--------------------