26. Wicket Internals
26.1. Page storing
During request handling, Wicket manages page instances through interface org.apache.wicket.request.handler.IPageProvider. This interface creates a new page instance or loads a previously serialized page instance if we provide the corrisponding page id. IPageProvider delegates page creation and retrieval to interface org.apache.wicket.request.mapper.IPageSource. When page class is provided IPageSource delegates page creation to interface org.apache.wicket.IPageFactory, while when page id is provided it uses interface org.apache.wicket.page.IPageManager to load the previously serialized page.
The following workflow diagram summarizes the mechanism seen so far:
26.1.1. IPageManager
org.apache.wicket.page.IPageManager‘s task is to manage which pages have been used in a request and store their last state in the backing stores, namely IPageStore. The default implementation org.apache.wicket.page.PageStoreManager collects all stateful pages which have been used in the request cycle (more than one page can be used in a single request if for example setResponsePage() or RestartResponseException is used). At the end of the request all collected page instances are being stored in the first level cache - http session. They are stored in http session attribute named “wicket:persistentPageManagerData-APPLICATION_NAME“ and passed to the underlying IPageStore. When the next http request comes IPageProvider will ask for page with specific id and PageStoreManager will look first in the http session and if no match is found then it will delegate to the IPageStore. At the end of the second request the http session based cache is being overwritten completely with the newly used page instances.
To setup another IPageManager implementation use org.apache.wicket.Application.setPageManagerProvider(IPageManagerProvider). The custom IPageManager implementation may or may not use IPageStore/IDataStore.
26.1.2. IPageStore
org.apache.wicket.pageStore.IPageStore‘s role is to mediate the storing and loading of pages done by the underlying IDataStore. The default implementation org.apache.wicket.pageStore.DefaultPageStore pre-processes the pages before passing them to IDataStore.storeData(String, int, byte) and to post-processes them after IDataStore.getData(String, int). The processing consists of transforming the page instance to org.apache.wicket.pageStore.DefaultPageStore.SerializedPage. This is a struct of:
{
sessionId: String,
pageId : int,
data : byte[]
}
i.e. this is the serialized page instance (data) plus additional information needed to be able to easily find it later (sessionId, pageId).
When a SerializedPage has to be stored DefaultPageStore stores it in a application scoped cache ({sessionId, pageId} → SerializedPage) and additionally gives it to the underlying IDataStore.storeData(sessionId, pageId, data). The application scoped cache is used as second level cache. Getting a page from it is slower than the http session based cache in PageStoreManager because the page has to be deserialized, but is faster than the underlying IDataStore which stores the page bytes in some persistent store.
The size of the application scoped cache is configurable via org.apache.wicket.settings.StoreSettings.setInmemoryCacheSize(int).
26.1.3. IDataStore
org.apache.wicket.pageStore.IDataStore is used to persist Wicket pages (as bytes) to a persistent store like e.g. files or databases. The default implementation is org.apache.wicket.pageStore.DiskDataStore which as its name says stores the pages in files. The location of the folder where the files are stored is configurable via org.apache.wicket.settings.StoreSettings.setFileStoreFolder(File), by default the web container’s work folder is used (ServletContext attribute ‘javax.servlet.context.tempdir’). In this folder a sub-folder is created named ‘applicationName-filestore’. This folder contains a sub-folder for each active http session. This session folder contains a single file named ‘data’ which contains the bytes for the pages. The size of this ‘data’ file is configurable via org.apache.wicket.settings.StoreSettings.setMaxSizePerSession(Bytes). When this size is exceeded the newly stored files overwrite the oldest ones.
26.1.4. AsynchronousDataStore
By default Wicket wraps DiskDataStore with org.apache.wicket.pageStore.AsynchronousDataStore. The role of AsynchronousDataStore is to detach the http worker thread from waiting for the write of the page bytes to the disk. To disable it use: org.apache.wicket.settings.StoreSettings.setAsynchronous(false). AsynchronousDataStore can delay the storage of page’s bytes for at most org.apache.wicket.settings.StoreSettings.setAsynchronousQueueCapacity(int) pages. If this capacity is exceeded then the page’s bytes are written synchronously to the backing IDataStore.
26.1.5. DebugDiskDataStore
Wicket provides an extension of DiskDataStore that can be used to browse the content of the ‘data’ files created by DiskDataStore. This extension can be found in wicket-devutils.jar and needs to be enabled in the init-method of your application via
DebugDiskDataStore.register(this);
The debug information can be seen at http://host:port/context/wicket/internal/debug/diskDataStore
26.1.6. HttpSessionDataStore
In some environments like Google AppEngine it is not allowed to write to the file system and thus DiskDataStore cannot be used. In this case org.apache.wicket.pageStore.memory.HttpSessionDataStore can be used as replacement. This implementation of IDataStore is not persistent and puts all the data in the http session. Wicket comes with 2 default eviction strategies to keep the size of the http session reasonable:
org.apache.wicket.pageStore.memory.PageNumberEvictionStrategy - specifies how many pages can be hold
org.apache.wicket.pageStore.memory.MemorySizeEvictionStrategy - specifies the maximum amount of memory for pages per http session.
To configure it:
MyApp#init()
{
super.init();
setPageManagerProvider(new DefaultPageManagerProvider(this)
{
protected IDataStore newDataStore()
{
return new HttpSessionDataStore(getPageManagerContext(), new PageNumberEvictionStrategy(20));
}
}
}
26.1.7. DebugBar
Further insights which can be valueable during debugging can be retrieved using the org.apache.wicket.devutils.debugbar.DebugBar from wicket-devutils.jar. It’s a panel which you simply add:
Java:
add(new DebugBar("debug"));
HTML:
<span wicket:id="debug"/>
26.2. Markup parsing and Autocomponents
26.2.1. Markup loading and parsing
Before rendering any component Wicket must retrieve its markup calling method getMarkup() of class org.apache.wicket.Component. This markup is an instance of interface org.apache.wicket.markup.IMarkupFragment. Markup is lazy loaded the first time we render the relative component and is cached at application level. The internal class that actually loads the markup is org.apache.wicket.markup.MarkupFactory and is part of application’s markup settings:
//get current markup factory
Application.get().getMarkupSettings().getMarkupFactory()
After the markup has been loaded by MarkupFactory, it’s parsed with class org.apache.wicket.markup.MarkupParser. MarkupFactory creates a new MarkupParser with method newMarkupParser(MarkupResourceStream resource). The effective markup parsing is performed with a chain of entities implementing interface org.apache.wicket.markup.parser.IMarkupFilter. The default set of IMarkupFilters used by MarkupParser takes care of different tasks such as HTML validation, comments removing, Wicket tags handling, etc…
To customize the set of IMarkupFiltersS used in our application we can create a subclass of MarkupFactory overriding method newMarkupParser(MarkupResourceStream resource):
public MyMarkupFactory
{
...
public MarkupParser newMarkupParser(final MarkupResourceStream resource)
{
MarkupParser parser = super.newMarkupParser(resource);
parser.add(new MyFilter());
return parser;
}
}
This custom class must be registered in the markup settings during application’s initialization:
@Override
public void init()
{
super.init();
getMarkupSettings().setMarkupFactory(myMarkupFactory)
}
Usually we won’t need to change the default configuration of IMarkupFiltersS, but it’s important to be aware of this internal mechanism before we talk about another advanced feature, which is building auto components resolvers.
26.2.2. Auto components resolvers
Even if Wicket encourages developers to use just standard HTML in their markup code, in this guide we have seen a number of “special” tags (those starting with wicket:) that help us for specific tasks (e.g. wicket:enclosure tag). Wicket handles most of these tags creating a corresponding special component called auto component. This kind of components are resolved in two steps:
first their tag is identified by a IMarkupFilters which also takes care of assigning a unique tag id.
then during rendering phase when an auto-component is found a new component is created for it using one of the registered org.apache.wicket.markup.resolver.IComponentResolver:
public interface IComponentResolver extends IClusterable
{
/**
* Try to resolve a component.
*
* @param container
* The container parsing its markup
* @param markupStream
* The current markupStream
* @param tag
* The current component tag while parsing the markup
* @return component or {@code null} if not found
*/
public Component resolve(final MarkupContainer container, final MarkupStream markupStream,
final ComponentTag tag);
}
Registered IComponentResolverS can be retrieved through Application’s settings:
Application.get()
.getPageSettings()
.getComponentResolvers()
An internal utility class named org.apache.wicket.markup.resolver.ComponentResolvers is also available to resolve autocomponents for the current markup tag. |