7 Page versioning and caching - Reference Documentation
Authors: Andrea Del Bene, Carsten Hufe, Christian Kroemer, Daniel Bartl
Version: 1.0.0.BUILD-SNAPSHOT
7 Page versioning and caching
This chapter explains how Wicket manages page instances, underlining the difference between stateful and stateless pages. The chapter also introduces some advanced topics like Java Serialization and multi-level cache. However, to understand what you will read you are not required to be familiar with these concepts.7.1 Stateful pages vs stateless
Wicket pages can be divided into two categories: stateful and stateless pages. Stateful pages are those which rely on user session to store they internal state and to keep track of user interaction. On the contrary stateless pages are those which don't change their internal state during their lifecycle and they don't need to occupy space into user session.From Wicket's point of view the biggest difference between these two types of page is that stateful pages are versioned, meaning that they will be saved into user session every time their internal state has changed. Wicket automatically assigns a session to the user the first time a stateful page is requested. Page versions are stored into user session using Java Serialization mechanism. Stateless pages are never versioned and that's why they don't require a valid user session. If we want to know whether a page is stateless or not, we can call the isPageStateless() method of class Page.In order to build a stateless page we must comply with some rules to ensure that the page won't need to use user session. These rules are illustrated in paragraph 6.3 but before talking about stateless pages we must first understand how stateful pages are handled and why they are versioned.7.2 Stateful pages
Stateful pages are versioned in order to support browser's back button: when this button is pressed Wicket must respond by rendering the same page instance previously used.A new page version is created when a stateful page is requested for the first time or when an existing instance is modified (for example changing its component hierarchy). To identify each page version Wicket uses a session-relative identifier called page id. This is a unique number and it is increased every time a new page version is created.In the final example of the previous chapter (project LifeCycleStages), you may have noticed the number appended at the and of URL. This number is the page id we are talking about:In this chapter we will use a revised version of this example project where the component hierarchy is modified inside the Link's onClick()method. This is necessary because Wicket creates a new page version only if the page is modified before its method onBeforeRender() is invoked. The code of the new home page is the following:public class HomePage extends WebPage { private static final long serialVersionUID = 1L; private Label firstLabel; private Label secondLabel; public HomePage(){ firstLabel = new Label("label", "First label"); secondLabel = new Label("label", "Second label"); add(firstLabel); add(new Link("reload"){ @Override public void onClick() { if(getPage().contains(firstLabel, true)) getPage().replace(secondLabel); else getPage().replace(firstLabel); } }); } }
For more details about page storing you can visit the wiki page at https://cwiki.apache.org/confluence/display/WICKET/Page+Storage . On this page you can find which classes are involved into page storing mechanism and how they work together.As we have stated at the beginning of this chapter, page versions are stored using Java serialization, therefore every object referenced inside a page must be serializable1. In paragraph 9.6 we will see how to overcome this limit and work with non-serializable objects in our components using detachable Wicket models.
Using a specific page version with PageReference
To retrieve a specific page version in our code we can use class org.apache.wicket. PageReference by providing its constructor with the corresponding page id://load page version with page id = 3
PageReference pageReference = new PageReference(3);
//load the related page instance
Page page = pageReference.getPage();
Turning off page versioning
If for any reason we need to switch off versioning for a given page, we can call its method setVersioned(false).Pluggable serialization
Starting from version 1.5 it is possible to choose which implementation of Java serialization will be used by Wicket to store page versions. Wicket serializes pages using an implementation of interface org.apache.wicket.serialize.ISerializer. The default implementation is org.apache.wicket.serialize.java.JavaSerializer and it uses the standard Java serialization mechanism based on classes ObjectOutputStream and ObjectInputStream. However on Internet we can find other interesting serialization libraries like Kryo1 which performs faster then the standard implementation.The serializer in use can be customized with the setSerializer(ISerializer) method defined by setting interface org.apache.wicket.settings.IFrameworkSettings.We can access this interface inside the method init of the class Application using the getFrameworkSettings() method :@Override public void init() { super.init(); getFrameworkSettings().setSerializer(yourSerializer); }
Page caching
By default Wicket persists versions of pages into a session-relative file on disk, but it uses a two-levels cache to speed up this process. The first level of the cache uses a http session attribute called “wicket:persistentPageManagerData-<APPLICATION_NAME>” to store pages. The second level cache stores pages into application-scoped variables which are identified by a session id and a page id.The following picture is an overview of these two caching levels:The session-scoped cache is faster then the other memory levels but it contains only the pages used to serve the last request. Wicket allows us to set the maximum amount of memory allowed for the application-scoped cache and for the page store file. Both parameters can be configured via setting interface org.apache .wicket.settings.IStoreSettings.This interface provides the setMaxSizePerSession(Bytes bytes) method to set the size for page store file. The Bytes parameter is the maximum size allowed for this file:@Override public void init() { super.init(); getStoreSettings().setMaxSizePerSession(Bytes.kilobytes(500)); }
@Override public void init() { super.init(); getStoreSettings().setInmemoryCacheSize(50); }
Page expiration
Page instances are not kept in the user session forever. They can be discarded when the limit set with the setMaxSizePerSession method is reached or (more often) when user session expires. When we ask Wicket for a page id corresponding to a page instance removed from the session, we bump into a PageExpiredException and we get the following default error page:This error page can be customized with the setPageExpiredErrorPage method of the org.apache.wicket.settings.IApplicationSettings interface:@Override public void init() { super.init(); getApplicationSettings().setPageExpiredErrorPage(CustomExpiredErrorPage.class); }
7.3 Stateless pages
Wicket makes it very easy to build stateful pages, but sometimes we might want to use an “old school” stateless page that doesn't keep memory of its state in the user session. For example consider the public area of a site or a login page: in those cases a stateful page would be a waste of resources or even a security threat, as we will see in paragraph 10.9.In Wicket a page can be stateless only if it satisfies the following requirements:- it has been instantiated by Wicket (i.e. we don't create it with operator new) using a constructor with no argument or a constructor that takes as input a single PageParameters argument (class PageParameters will be covered in chapter 8).
- All its children components (and behaviors) are in turn stateless, which means that their method isStateless must return true.
@Override protected void onInitialize() { super.onInitialize(); visitChildren(new IVisitor<Component, Void>() { @Override public void component(Component component, IVisit<Void> arg1) { if(!component.isStateless()) System.out.println("Component " + component.getId() + " is not stateless"); } }); }
@Override public void init() { super.init(); getComponentPostOnBeforeRenderListeners().add(new StatelessChecker()); }
Most of the Wicket's built-in components are stateful, hence they can not be used with a stateless page. However some of them have also a stateless version which can be adopted when we need to keep a page stateless. In the rest of the guide we will point out when a built-in component comes also with a stateless version.A page can be also explicitly declared as stateless setting the appropriate flag to true with the setStatelessHint(true) method. This method will not prevent us from violating the requirements for a stateless page, but if we do so we will get the following warning log message:
Page '<page class>' is not stateless because of component with path '<component path>'