简体   繁体   中英

Wicket serialization/deserialization issue

Assume following component tree:

  • A
    • B
    • C

A has a private field (called filter) and the reference to the filter is passed on to B and C. In class B a property of the filter is modified through an AjaxLink (so no page refresh). I see (through) logging that after each ajax call wicket will serialize A, B and C.

Now, everything works fine, clicking in the AjaxLinks in B will nicely update the filter and will refresh C and show the correct information in C (based on the filter).

However, assume if click an AjaxLink in B which will update a property of the filter to (for instance) value 2. Afterwards I press F5, it still works and the page (and all components in it) is deserialized (and again serialized afterwards). I then click on an AjaxLink the changes the value of the mentioned property to 3; this still works fine. If, however, I then do a page refresh (F5) the value of that property suddenly becomes 2 again.

It seems that on the page refresh (when wicket will load the page from disk) wicket is deserializing an older version of the filter.

Schematically:

  1. Page initial load:
    => filter.value = 3 -> serialized
  2. AjaxLink:
    => filter.value = 2 -> serialized
  3. Page refresh:
    => filter.value = 2 -> deserialized/serialized
  4. AjaxLink:
    => filter.value = 3 -> serialized
  5. Page refresh:
    => filter.value = 2 -> deserialized/serialized

It seems that on action 5 the serialized version after action 4 is ignored and the serialized version after action 3 the loaded.

Hoping for a cause, explanation and if possible also the solution :-)

CODE

public class A extends Panel { //creates the filter and passes the reference on 
        Filter filter = new Filter(TypeEnum.ALL);

    public A(final String id) {
        super(id);

    add(new B("filterPanel", filter));
    add(new C("datatable", filter));
}



    public class B extends Panel { //updates the filter

        Filter filter;;
        public B(final String id, Filter filter) {
            super(id);
            this.filter = filter;
            add(new IndicatingAjaxLink<Void>("other") {

            @Override
            public void onClick(AjaxRequestTarget target) {
                filter.setType(TypeEnum.OTHER);
                            ...
            }
        };
        }

    }

public class C extends Panel { //uses the filter

    Filter filter;;
    public C(final String id, Filter filter) {
            super(id);
            this.filter = filter;
    }

    public void populateRepeatingView() {
         final List<? extends WallEntry> result = service.find(filter);
         ...
    }    
}

Code has been simplified and I retained only the (I assume) pertinent stuff.

UPDATE

If I add following in my page class then it works:

@Override
public boolean isVersioned() {
    return false;
}

Not sure however about the implications of this and why this makes it work. Isn't the page (de)serialized anymore?

UPDATE

I added following to my Page class:

private void writeObject(ObjectOutputStream oos) throws IOException { 
oos.defaultWriteObject(); 
System.err.println("Writing " + this + something to print out the type of the filter); 
} 

private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { 
ois.defaultReadObject(); 
System.err.println("Reading " + this + something to print out the type of the filter); 
} 
  1. When the Page is loaded first it prints (actually it prints this 5 times, not sure if it's normal): Writing [Page class = com.bnpp.ecom.homepage.web.Homepage, id = 0, render count = 1]: type = ALL

  2. When I click on AjaxLink 'ALL' (that will update the filter) it still prints: Writing [Page class = com.bnpp.ecom.homepage.web.Homepage, id = 0, render count = 1]: type = ALL

  3. When I click on AjaxLink 'DISCUSSIONS' (that will update the filter) it still prints: Writing [Page class = com.bnpp.ecom.homepage.web.Homepage, id = 0, render count = 1]: type = DISCUSSIONS

  4. When I refresh the page (F5) the pageid is updated: Writing [Page class = com.bnpp.ecom.homepage.web.Homepage, id = 1, render count = 2]: type = DISCUSSIONS

  5. When I click on AjaxLink 'ALL' (that will update the filter) it prints: Writing [Page class = com.bnpp.ecom.homepage.web.Homepage, id = 1, render count = 1]: type = ALL

  6. So far so good but when I refresh the page now (F5) this is printed out: Reading [Page class = com.bnpp.ecom.homepage.web.Homepage, id = 0, render count = 1]: type = DISCUSSIONS Writing [Page class = com.bnpp.ecom.homepage.web.Homepage, id = 2, render count = 2]: type = DISCUSSIONS

The url never changes, it stays http://.../?0

So it deserializes the page with id 0 although the last known page id was 1 and all changes that were done for version 1 are ignored (in this case switching the type from DISCUSSIONS to ALL).

Created a issue in the wicket jira for this: https://issues.apache.org/jira/browse/WICKET-4360

Watch the browser's url When you refresh the page. It probably includes the Wicket page version, so when you refresh a second time, a specific version of the page is deserialized.

Since you keep the filter in your page, it is serialized/deserialized along with its containing components.

A solution would be to put the filter into the session.

As mentioned in the jira issue: https://issues.apache.org/jira/browse/WICKET-4360 you need to setreuseitems on listviews to true or an ajax call will dirty your page when the listview is repopulated:

On F5 Wicket reads (once) the current page. But then it writes a the page with a new page id because of the usage of ListView (PropertyListView in DataPanel). By adding "commentsListView.setReuseItems(true);" all is fine. See WICKET-4286 for a discussion about the ListView problem.

In your log statements, it seems like its only reading on the fifth request. That's probably why it works on the first page refresh.

Wicket serializes the page each time, but it keeps the last version of the page in memory and only de-serializes it if a previous version is requested.

If you check the hashCode of the Filters in the A, B, and C Components, you'll probably see they are different objects after deserialization. Instead of passing around a Filter, pass around a model which returns the Filter from Component A. If component A is a Page, it's really easy because every Component can invoke getPage().getDefaultModel() or getDefaultModelObject(). If Component A is a Panel, try navigating to it with getPage().get("A").getDefaultModel().

I think if the app is using the same Filter object, serialization wont matter.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM