Wicket 6: empty PageParameters when recreating a p

2019-07-26 03:54发布

问题:

We've encountered a problem with Wicket 6 (namely version 6.22.0). It looks like it is what was fixed here: https://issues.apache.org/jira/browse/WICKET-5068 In a few words: after the page expires, Wicket tries to reconstruct it by calling a constructor with page class and PageParameters as arguments, but PageParameters is (erroneously) empty even though some parameters were sent with the request.

After Wicket session timeout - pageParameters are null seems to be relating to the same issue.

WICKET-5068 has a fix for Wicket 7, but we are having Wicket 6 and we need a fix for it.

Following is a lengthy explanation of our findings and some questions.

Here is what happens:

  1. User opens a page (it is stateful) and leaves it open in a browser tab.
  2. User opens other pages
  3. The original page from step 1 is evicted from Page Store (i.e. gets expired) although the session is still up.
  4. User returns to the initial browser tab and clicks on a link. Here is the code of the link:

    AjaxLink<Void> link = new AjaxLink<Void>("link") {
        @Override
        public void onClick(AjaxRequestTarget target) {
            showWindow(dataModel, window, target);
        }
    };
    add(link);
    
  5. While BookmarkableMapper builds IRequestHandler from the request, the following method is called (AbstractBookmarkableMapper:294):

    protected PageParameters getPageParametersForListener(PageInfo pageInfo, PageParameters pageParameters)
    {
        if (pageInfo.getPageId() != null)
        {
            // WICKET-4594 - ignore the parsed parameters for stateful pages
            return null;
        }
        return pageParameters;
    }
    

    So the ListenerInterfaceRequestHandler that is built from the request has null for PageParameters.

  6. Wicket starts processing the click. It tries to restore the page to which the clicked link belongs, this is done in the following method (PageProvider, starts at line 252):

    private void resolvePageInstance(Integer pageId, Class<? extends IRequestablePage> pageClass,
        PageParameters pageParameters, Integer renderCount)
    {
        IRequestablePage page = null;
    
        boolean freshCreated = false;
    
        if (pageId != null)
        {
            page = getStoredPage(pageId);
        }
    
        if (page == null)
        {
            if (pageClass != null)
            {
                page = getPageSource().newPageInstance(pageClass, pageParameters);
                freshCreated = true;
            }
        }
    
        if (page != null && !freshCreated)
        {
            if (renderCount != null && page.getRenderCount() != renderCount)
            {
                throw new StalePageException(page);
            }
        }
    
        pageInstanceIsFresh = freshCreated;
        pageInstance = page;
    }
    

    As the page is evicted from page store, the following statement's condition holds:

    if (page == null)
    

    So it tries to create a page instance from class and page parameters:

    page = getPageSource().newPageInstance(pageClass, pageParameters);
    

    But pageParameters is null here (because of getPageParametersForListener() from item 5). So the page constructor gets empty PageParameters and fails as it expects some id.

Here is the code to extract an id from PageParameters in the page constructor:

pageParameters.get("id").toLong()

Here is the exception that is produced (only showing the top lines as the rest is not relevant):

org.apache.wicket.util.string.StringValueConversionException: Unable to convert 'null' to a long value
    at org.apache.wicket.util.string.StringValue.toLong(StringValue.java:664)

So in our case getPageParametersForListener() method breaks a possibility to restore processing of an expired page.

Trying to fix this, we've replaced the BookmarkableMapper with our custom implementation:

public class BookmarkableMapperThatSavesPageParametersForListener extends BookmarkableMapper {
    @Override
    protected PageParameters getPageParametersForListener(PageInfo pageInfo, PageParameters pageParameters) {
        return pageParameters;
    }
}

which we mount in WebApplication#init() method:

mount(new BookmarkableMapperThatSavesPageParametersForListener());

It seems to fix the problem we were faced with: the link click does not fire the handler (onClick() method), but at least the page does not explode and just refreshes itself.

The questions are:

  1. Does this happen because we do something wrong or it is a bug in Wicket?
  2. Is the fix we applied eligible? I guess the change introduced by https://issues.apache.org/jira/browse/WICKET-4594 was not made just for fun
  3. Would our fix break anything knowing that we only have stateful pages?

回答1:

This is a limitation of Wicket 6.x that has been implemented in 7.x. 6.x didn't get this change because we were not sure whether it won't break someone's application silently. IIRC it is possible to override a method in 7.x to revert to the old behavior if needed during upgrade. AFAIK no one complained for this change in 7.x so I guess it is OK to backport it to 6.x (6.27.0) but none of the active developers of Wicket uses 6.x anymore and the chances someone to do it are rather low. You are recommended to upgrade to 7.x. It is stable and has many new features and bug fixes. Until then I guess your option is to use your custom version of this request mapper.



标签: java wicket