"Page map" concept was discarded in Wicket 1.5 and is no longer part of the future releases.

"Page" in this text means stateful page. Page maps do not store [stateless pages]. "Page map" means IPageMap interface and its implementations.

Page maps

Page map is a part of Session. Basically it is used as an interface for storing pages and their versions. Page map stores instances of pages (including component tree) which were visited by user during the current session. So every time user goes to another page or changes page state, the page instance is stored in a page map. There can be one or more page maps in one http session where each page map corresponds to a browser tab (or a modal window if it contains a page and has had a separate page map set). Page maps are identified by their names.

In a sense, page maps can be viewed as an implementation detail of Session, since they are used only through IPageMap interface and some of their the functionality should be accessed only through Session.

Simple example

Here is a simple example of how page map is used from framework user's point of view. Suppose there is a home page which has two links:

  • "gotoNewPage" link creates new home page instance and displays it to user;
  • "changeState" link changes page state creating new version of page.

The page code goes like this:

class MyHomePage extends WebPage {

    public MyHomePage() {
        add(new Link("changeState", new Model()) {
            public void onClick() {
                // this changes link state and therefore page state
                this.setModelObject(new Random().nextInt());
            }
        });

        add(new Link("gotoNewPage") {
            public void onClick() {
                setResponsePage(new MyHomePage());
            }
        });
    }
}

Now if you go to home page, click once on "gotoNewPage" link, twice on "changeState" link and twice on "gotoNewPage" link again, the page map for the current session will look like this (it doesn't mean page map stores pages in a table or map):

version \ id

0

1

2

3

0

page

page

page

page

1


page



2


page



In the table "page" denotes page instance. Page id is a number unique within a page map or session (see ISessionSettings#setPageIdUniquePerSession()). All pages have id since they subclass Component class. Id is assigned to page automatically on creation.

The first instance of home page with id 0 and version 0 was created when you went to application home page (i.e. typed in browser a URL like http://localhost:8080/app). Then after clicking on "gotoNewPage" link, page with id 1 and version 0 was created and added to the page map. Next two clicks on "changeState" link added to the page map versions 1 and 2 of the home page. And finally two clicks on "gotoNewPage" link created and added another two home page instances to the page map.

There is another thing which is related to storing pages. It's that they can be accessed by page id and version using specific URL. In a simplified way this URL has the following form (see WebRequestCodingStrategy#addInterfaceParameters() javadoc for full description):

http://<application URL>/?wicket:interface=<page map name>:<page id>:<page version>:<some other parameters>

where:

  • application URL is something like "localhost:8080/app";
  • page map name is the name of the page map to be requested. Page map which is created by default has name "null" and can be omitted in the URL;
  • page id is id of the page to retrieve (must be a number);
  • page version is version of the page to retrieve (can be empty string which means version 0).

So for example to access second version of page instance with id 1 the following URL will be used: http://localhost:8080/app/?wicket:interface=:1:2:::. If there is no page with specified id and version, then "Page Expired" page will be shown.

Similarly components like Links and Buttons, which provide callback to user code, use URLs which point to the page instance in a page map. For example "changeState " link on the second version of page with id 1 will have URL like this http://localhost:8080/app/?wicket:interface=:1:changeState:2:ILinkListener:: When this link is clicked Wicket will call onClick() handler for this link on the page instance with id 1 and version 2.

In this way pages generated by Wicket point to specific page instance on server.

Page map life cycle

Every session in Wicket has at least one page map. This page map is called default and has null name (see PageMap#isDefault()). Normally default page map is lazily created while constructing the first accessed page in session. Additional page maps may be created ("may be" means here that it is not necessarily the case):

  • on opening tab in web-browser (see IPageSettings#getAutomaticMultiWindowSupport())
  • on creating modal/popup window
  • when using inline frames
  • anywhere in code using PageMap#forName() method
  • it's not normal usecase, but page map is also created whenever a URL is requested which contains name of not-existing page map. For example URL like http://localhost:8080/app/?wicket:interface=newPageMap:0:::: will create page map with name "newPageMap".

Page maps are created by ISessionStores. There are two ISessionStore implementations bundled with Wicket, each of them create different page map implementations. HttpSessionStore creates AccessStackPageMap; SecondLevelCacheSessionStore (this is default in Wicket 1.3) creates SecondLevelCachePageMap.

From the API point of view there are several ways to create page map. The wrong way is to obtain ISessionStore instance (for example through Application#getSessionStore()) and ask it to create page map. It's shouldn't be done this way since all page map creation is "encapsulated" in Session class. There are several methods in Session for creating/getting a page map:

  • pageMapForName(name, autoCreate),
  • newPageMap(name)
  • createAutoPageMap() (see WebPage#onNewBrowserWindow() method)
    There is also convenient static method in PageMap class PageMap#forName(pageMapName) which simply calls pageMapForName(name, autoCreate) with autoCreate parameter set to true. Probably the most common way to create/obtain page map in code, if you need one, is to do something like PageMap.forName("myPageMapName").

In this diagram Session#pageMapForName() first tries to get page map from ISessionStore and if the attempt fails, creates new one and puts it into the ISessionStore.

After creation page map must be stored somehow. In terms of java objects page maps don't contain page instances, but they know how to obtain pages. All page maps have names and using this name, page id and version they obtain page instances from ISessionStore. Since page maps don't contain pages, there are not big in size and they are stored in http session. How page maps are stored in ISessionStore depends on ISessionStore implementation (in current implementations it's http session).

Page map can be obtained using Session#pageMapForName() method. Process of obtaining page map is basically getting from ISessionStore specific attribute with the name containing page map name. Currently it is page map name prepended with "m:".

Page map can be removed from session using Session#removePageMap(pageMap) method (TODO currently it's not so; should use PageMap#remove() ?). Removing page map also means removing all page instances stored in it. There are three cases when page map is removed:

  • there are too many page maps in the current session and a page map is removed in order to create a new one (see Session#newPageMap() and ISessionSettings#getMaxPageMaps())
  • popup window is closed
  • modal window is closed
    If page map is not removed from session, it will remain in memory until session expires.

Page map and pages

Similarly to methods for creating/getting page maps, Session "encapsulates" access for putting/getting pages to/from page map. There are Session#getPage() and Session#touch() methods for that purpose. Normally these methods should not be called by framework user.

There is only one way to put page instance into page map, it is to "touch" this page in the current session. Note that "touching" page is different from making it "dirty" (see Page#dirty()). There are several cases when page may be touched. Perhaps the common one is touching page after it's been rendered. For Session "touching" means adding page to the list of touched pages. Later at the end of request cycle in the requestDetached() method Session actually adds all touched pages to the page maps they belong to (see #diagram). What page map does after it's asked to store page differs between page map implementations but in the end they delegate storing to corresponding ISessionStore implementation. AccessStackPageMap uses HttpSessionStore, which as its name implies stores pages in http session. SecondLevelCachePageMap uses SecondLevelCacheSessionStore (this is default in Wicket 1.3) which stores pages on disk except for the most recently accessed one which is stored in http session.

There is only one way to get page instance from page map, it is to use Session#getPage() method. Basically this method obtains page map and asks it for required page. The process of getting page differs between page map implementations but in the end ISessionStore is used.

There is no methods in Session for removing pages from page map. Pages may be removed from page map when the page map which contains them is removed. In other cases removing pages from page map is responsibility of ISessionStore (for example HttpSessionStore use LeastRecentlyAccessedEvictionStrategy).

This is simplification since PageMap don't use ISessionStore directly.

Page map and intercept pages

Besides storing page instances page map supports intercepting pages. PageMap#redirectToInterceptPage(...) methods store relative URL of the current page as string in the page map. PageMap#continueToOriginalDestination() retrieves URL from the current page map, creates new request target for it and restarts request cycle, thus "redirecting" to the previous page. There are convenient methods in the Component class with the same names which relay calls to page map.

There is also RestartResponseAtInterceptPageException which is "similar to calling redirectToInteceptPage(Page) with the difference that this exception will interrupt processing of the current request."

  • No labels