How can I make Wicket's "AjaxLink" Stateless?
Asked Answered
C

2

8

I am building a Wicket web application which is going to have to handle a lot of simultaneous requests. I've setup a test environment and some jmeter scripts to do load testing and I notice I can reduce the CPU and memory footprint of my application if I make most pages Stateless.

I've added code to the onBeforeRender() method of the biggest page to show me which of the components are causing my page to be stateful. This is the code I have for detecting that:

@Override
protected void onBeforeRender() {    
    if (!getSession().isTemporary()) {
        visitChildren(Component.class, new IVisitor<Component>() {
            @Override
            public Object component(Component component) {
                String pageClassName = AbstractStatelessBasePage.this.getClass().getName();
                if (!component.isStateless()) {

                    String msg = pageClassName+" is stateful because of stateful component " + component.getClass().getName() + " with id " + component.getId() + ".";

                    List<IBehavior> behaviourList = component.getBehaviors();
                    for (IBehavior iBehavior : behaviourList) {
                        if (!iBehavior.getStatelessHint(component)) {
                            msg += "\n\t" + "The component has stateful behaviour: " + iBehavior.getClass().getName();
                        }
                    }
                    LOG.error(msg);
                }

                checkedPages.add(pageClassName);
                return CONTINUE_TRAVERSAL;
            }
        });
    }
}

In the output I see that the stateful behavior is caused by AjaxLinks used by some of the existing components in the pages:

ERROR - AbstractStatelessBasePage$1.component(45) | HomePage is stateful because of stateful component InfoGrid$InfoButton with id infoButton.
    The component has stateful behaviour: org.apache.wicket.ajax.markup.html.AjaxLink$1

I have tried to add getStatelessHint() methods returning "true" in a few places, but it doesn's seem to help. I've also checked the Wicket source code of AjaxLink, its superclasses and some surrounding code, but I can't seem to discover why AjaxLink needs to be stateful in all cases.

In my case, the AjaxLink is in an otherwise Stateless page and the link does not store state. How can I make Wicket understand that this AjaxLink can be stateless?

Thanks for your help, Rolf

Edit: Accepted answer works with Wicket 1.4.19.

Added the following to the maven pom.xml:

<dependency>
    <groupId>com.jolira</groupId>
    <artifactId>wicket-stateless</artifactId>
    <version>1.0.8</version>
</dependency>

Changed all components which extended "AjaxLink" to extend "StatelessAjaxFallbackLink".

Don't forget to add the following to your WicketApplication class, it will save you some troubleshooting time:

@Override
protected IRequestCycleProcessor newRequestCycleProcessor() {
    return new StatelessWebRequestCycleProcessor();
}

Please note that StatelessForm and other stateless stuff does not work from within a repeater (like "ListView") for some reason.

Cither answered 14/5, 2012 at 11:38 Comment(0)
K
10

The page becomes stateful when you add an Ajax behavior to it (AjaxLink uses AjaxEventBehavior). This is because when you click a link Wicket tries to find the page instance at the server, then find the link component inside it, and finally execute its callback method - e.g. onClick(). Without storing the page there is no way how to find the ajax behavior instance and execute its callback method.

You can use Jolira's Ajax behaviors and components (https://github.com/jolira/wicket-stateless). They work a bit differently - when you click on Jolira's AjaxLink the Ajax call creates a completely new instance of the page, finds the freshly created StatelessAjaxLink in it, executes its callback method, eventually uses AjaxRequestTarget to add components/javascript for the Ajax response and discards the newly created page instance (it is garbage collected). Next Ajax request does the same with a completely new page instance.

One would ask "Why Jolira's code is not in Wicket core ?" - because it gives partial solution. For example: clicking on statelessAjaxLink1 creates a new Page, executes onClick() on the new instance of the StatelessAjaxLink where PanelA is replaced with PanelB, and adds this panel (PanelB) to AjaxRequestTarget. In brief: clicking this link replaces the body of a panel in the page. If PanelB has a StatelessAjaxLink2 inside itself then this link is unfindable. Why ? Because clicking on it will create a new instance of the Page and this new instance will have PanelA, not PanelB, and thus there is no way to find StatelessAjaxLink2 to execute its onClick() method.

If your scenario is simple enough and Jolira's components cover your cases then use them. Just be aware that more complex scenario may fail.

Konopka answered 14/5, 2012 at 19:26 Comment(4)
I actually do replace panels, but I register that within the session, so when instantiating the page I can instantiate the correct "replaced" panels. This moves state from the page to the session.Cither
Jolira's StatelessAjaxFallbackLink solved the problem I stated in my original question. Right now I'm encountering problems with Stateless inside repeaters, where Wicket can't figure out what component to target. This might result in a new question :-)Cither
Is there anything for Wicket 6.10 ?Alcazar
@HendyIrawan See central.maven.org/maven2/org/wicketstuff/wicketstuff-statelessKonopka
C
3

There is code for a stateless AjaxFallbackLink referenced on the wicket wiki, and a related github project that you can get to following links from there. Not sure this will completely solve your problem, but it may at least be instructive.

A similar approach has been tried for wicket 6, but the author warns it's experimental. The code is here. I haven't tried to use it and thus can't vouch for it.

Crybaby answered 14/5, 2012 at 11:56 Comment(1)
Is there anything for Wicket 6.10 ?Alcazar

© 2022 - 2024 — McMap. All rights reserved.