You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by sv...@apache.org on 2019/03/19 13:23:07 UTC

[wicket] branch master updated (5efd88b -> 3d82589)

This is an automated email from the ASF dual-hosted git repository.

svenmeier pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/wicket.git.


    from 5efd88b  Fix maven-javadoc-plugin link to Java 11 APIs
     new ca03c25  WICKET-6563 new IPageStore implementation
     new 4c9d89d  WICKET-6559 new encrypting page store
     new 3d82589  tag ConnectionsTest SLOW

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../main/java/org/apache/wicket/Application.java   |  33 +-
 .../apache/wicket/DefaultPageManagerProvider.java  | 181 +++++--
 .../org/apache/wicket/IPageManagerProvider.java    |  10 +-
 .../src/main/java/org/apache/wicket/Page.java      |  10 +-
 .../src/main/java/org/apache/wicket/Session.java   |   7 +-
 .../wicket/core/request/handler/PageProvider.java  |   1 -
 .../request/handler/RenderPageRequestHandler.java  |   2 +-
 .../wicket/core/util/string/ComponentRenderer.java |   2 +-
 .../org/apache/wicket/mock/MockApplication.java    |   2 +-
 .../org/apache/wicket/mock/MockPageContext.java    | 110 ++++
 .../org/apache/wicket/mock/MockPageManager.java    |  56 +-
 .../{MockPageManager.java => MockPageStore.java}   |  89 ++--
 .../apache/wicket/page/AbstractPageManager.java    | 121 -----
 .../wicket/page/DefaultPageManagerContext.java     | 101 ----
 .../java/org/apache/wicket/page/IPageManager.java  |  84 ++-
 .../apache/wicket/page/IPageManagerContext.java    |  66 ---
 .../apache/wicket/page/PageAccessSynchronizer.java |  48 +-
 .../java/org/apache/wicket/page/PageManager.java   | 103 ++++
 .../org/apache/wicket/page/PageStoreManager.java   | 520 ------------------
 .../org/apache/wicket/page/RequestAdapter.java     | 206 -------
 .../wicket/pageStore/AbstractCachingPageStore.java | 105 ----
 .../apache/wicket/pageStore/AbstractPageStore.java | 154 ------
 .../pageStore/AbstractPersistentPageStore.java     | 259 +++++++++
 .../wicket/pageStore/AsynchronousDataStore.java    | 344 ------------
 .../wicket/pageStore/AsynchronousPageStore.java    | 386 ++++++++------
 .../apache/wicket/pageStore/CryptingPageStore.java | 151 ++++++
 .../wicket/pageStore/DefaultPageContext.java       | 109 ++++
 .../apache/wicket/pageStore/DefaultPageStore.java  | 469 ----------------
 .../DelegatingPageStore.java}                      | 183 +++----
 .../org/apache/wicket/pageStore/DiskDataStore.java | 589 ---------------------
 .../org/apache/wicket/pageStore/DiskPageStore.java | 561 ++++++++++++++++++++
 .../org/apache/wicket/pageStore/FilePageStore.java | 379 +++++++++++++
 .../apache/wicket/pageStore/GroupingPageStore.java | 285 ++++++++++
 .../org/apache/wicket/pageStore/IDataStore.java    |  83 ---
 .../org/apache/wicket/pageStore/IPageContext.java  |  84 +++
 .../org/apache/wicket/pageStore/IPageStore.java    | 108 ++--
 .../apache/wicket/pageStore/IPersistedPage.java}   |  33 +-
 .../wicket/pageStore/IPersistentPageStore.java     |  68 +++
 .../apache/wicket/pageStore/InMemoryPageStore.java | 338 ++++++++++++
 .../wicket/pageStore/InSessionPageStore.java       | 377 +++++++++++++
 .../apache/wicket/pageStore/NoopPageStore.java}    |  33 +-
 .../wicket/pageStore/PerSessionPageStore.java      | 332 ------------
 .../apache/wicket/pageStore/RequestPageStore.java  | 153 ++++++
 .../apache/wicket/pageStore/SerializedPage.java    | 105 ++++
 .../wicket/pageStore/SerializingPageStore.java     |  84 +++
 .../wicket/pageStore/crypt/DefaultCrypter.java     | 108 ++++
 .../crypt/ICrypter.java}                           |  54 +-
 .../wicket/pageStore/disk/NestedFolders.java       | 140 +++++
 .../pageStore/{ => disk}/PageWindowManager.java    | 150 +++---
 .../pageStore/memory/HttpSessionDataStore.java     | 186 -------
 .../memory/MemorySizeEvictionStrategy.java         |  64 ---
 .../memory/PageNumberEvictionStrategy.java         |  62 ---
 .../apache/wicket/pageStore/memory/PageTable.java  | 128 -----
 .../wicket/pageStore/memory/PageTableCleaner.java  |  46 --
 .../org/apache/wicket/settings/StoreSettings.java  |  89 ++--
 .../wicket/util/tester/BaseWicketTester.java       |   8 +-
 .../wicket/DefaultPageManagerProviderTest.java     |  52 ++
 .../core/request/mapper/TestMapperContext.java     |  48 +-
 .../DontStoreNotRenderedPageTestCase.java          |  33 +-
 .../html/TransparentWebMarkupContainerTest.java    |   8 +-
 .../wicket/page/AsyncPageStoreManagerTest.java     | 172 ++----
 .../wicket/page/PageAccessSynchronizerTest.java    |   2 +-
 .../wicket/page/PersistentPageManagerTest.java     |  77 ++-
 ...t.java => AbstractConcurrentPageStoreTest.java} | 217 +++-----
 .../wicket/pageStore/AbstractPageStoreTest.java    | 166 ++++--
 .../pageStore/AsynchronousDataStoreTest.java       |  46 +-
 .../pageStore/AsynchronousPageStoreTest.java       | 323 +++++++----
 .../wicket/pageStore/CryptingPageStoreTest.java    |  88 +++
 .../apache/wicket/pageStore/DiskPageStoreTest.java |  41 +-
 .../wicket/pageStore/FilePageStoreTest.java}       |  46 +-
 .../wicket/pageStore/GroupingPageStoreTest.java    | 114 ++++
 ...oreTest.java => InMemoryPageStoreSizeTest.java} |  10 +-
 .../wicket/pageStore/InMemoryPageStoreTest.java}   |  17 +-
 .../pageStore/InSessionPageStoreSizeTest.java}     |  21 +-
 ...eStoreTest.java => InSessionPageStoreTest.java} |  11 +-
 .../wicket/pageStore/RequestPageStoreTest.java     |  65 +++
 ...toreTest.java => SerializingPageStoreTest.java} |  48 +-
 .../wicket/pageStore/disk/NestedFoldersTest.java   |  96 ++++
 .../disk/PageWindowManagerTest.java                |  71 +--
 .../pageStore/memory/DummyPageManagerContext.java  |  66 ---
 .../pageStore/memory/HttpSessionDataStoreTest.java | 114 ----
 .../memory/MemorySizeEvictionStrategyTest.java     |  64 ---
 .../memory/PageNumberEvictionStrategyTest.java     |  66 ---
 .../wicket/pageStore/memory/PageTableTest.java     |  58 --
 ...ageIdPoliticTest.java => PageIdPolicyTest.java} |  32 +-
 .../wicket/request/handler/PageProviderTest.java   |  20 +-
 .../wicket/versioning/InMemoryPageStore.java       | 126 -----
 .../wicket/versioning/PageVersioningTest.java      |  29 +-
 .../org/apache/wicket/devutils/DevUtilsPage.java   |   9 +-
 .../devutils/debugbar/DebugBarInitializer.java     |   2 +-
 ...izeDebugPanel.java => PageStoreDebugPanel.java} |  44 +-
 .../devutils/debugbar/StandardDebugPanel.java      |   4 +-
 .../devutils/diskstore/DebugDiskDataStore.java     | 100 ----
 .../diskstore/DebugPageManagerProvider.java        |  63 ---
 .../devutils/diskstore/DiskStoreBrowserPage.java   |  51 --
 .../devutils/diskstore/browser/BrowserPanel.java   | 133 -----
 .../devutils/diskstore/browser/BrowserTable.java   |  47 --
 .../diskstore/browser/DataStoreHelper.java         |  43 --
 .../diskstore/browser/PageWindowColumn.java        |  80 ---
 .../diskstore/browser/PageWindowDescription.java   |  57 --
 .../diskstore/browser/PageWindowProvider.java      | 108 ----
 .../diskstore/browser/SessionsProviderModel.java   |  57 --
 .../devutils/inspector/EnhancedPageView.java       |  37 +-
 .../wicket/devutils/inspector/InspectorPage.java   |  30 +-
 .../PageStorePage.html}                            |  10 +-
 .../wicket/devutils/pagestore/PageStorePage.java   |  73 +++
 .../pagestore/browser/PersistedPagesProvider.java  | 133 +++++
 .../browser/PersistedPanel.html}                   |   9 +-
 .../devutils/pagestore/browser/PersistedPanel.java | 200 +++++++
 .../pagestore/browser/SessionIdentifiersModel.java |  72 +++
 .../apache/wicket/examples/frames/BodyFrame.java   |   6 +-
 .../org/apache/wicket/examples/StartExamples.java  |  13 +-
 .../ajax/markup/html/modal/ModalWindow.java        |   5 +-
 .../org/apache/wicket/jmx/StoreSettingsMBean.java  |  15 +-
 .../apache/wicket/jmx/wrapper/StoreSettings.java   |   6 -
 .../ws/api/AbstractWebSocketProcessor.java         |   2 +-
 .../java/org/apache/wicket/util/file/Files.java    |  14 +
 .../org/apache/wicket/util/io/ConnectionsTest.java |   7 +-
 118 files changed, 5995 insertions(+), 6341 deletions(-)
 create mode 100644 wicket-core/src/main/java/org/apache/wicket/mock/MockPageContext.java
 copy wicket-core/src/main/java/org/apache/wicket/mock/{MockPageManager.java => MockPageStore.java} (51%)
 delete mode 100644 wicket-core/src/main/java/org/apache/wicket/page/AbstractPageManager.java
 delete mode 100644 wicket-core/src/main/java/org/apache/wicket/page/DefaultPageManagerContext.java
 delete mode 100644 wicket-core/src/main/java/org/apache/wicket/page/IPageManagerContext.java
 create mode 100644 wicket-core/src/main/java/org/apache/wicket/page/PageManager.java
 delete mode 100644 wicket-core/src/main/java/org/apache/wicket/page/PageStoreManager.java
 delete mode 100644 wicket-core/src/main/java/org/apache/wicket/page/RequestAdapter.java
 delete mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/AbstractCachingPageStore.java
 delete mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/AbstractPageStore.java
 create mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/AbstractPersistentPageStore.java
 delete mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/AsynchronousDataStore.java
 create mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/CryptingPageStore.java
 create mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/DefaultPageContext.java
 delete mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/DefaultPageStore.java
 rename wicket-core/src/main/java/org/apache/wicket/{page/PageManagerDecorator.java => pageStore/DelegatingPageStore.java} (52%)
 delete mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/DiskDataStore.java
 create mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/DiskPageStore.java
 create mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/FilePageStore.java
 create mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/GroupingPageStore.java
 delete mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/IDataStore.java
 create mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/IPageContext.java
 copy wicket-core/src/{test/java/org/apache/wicket/pageStore/DefaultPageStoreTest.java => main/java/org/apache/wicket/pageStore/IPersistedPage.java} (62%)
 create mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/IPersistentPageStore.java
 create mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/InMemoryPageStore.java
 create mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/InSessionPageStore.java
 rename wicket-core/src/{test/java/org/apache/wicket/pageStore/NoopDataStore.java => main/java/org/apache/wicket/pageStore/NoopPageStore.java} (67%)
 delete mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/PerSessionPageStore.java
 create mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/RequestPageStore.java
 create mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/SerializedPage.java
 create mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/SerializingPageStore.java
 create mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/crypt/DefaultCrypter.java
 copy wicket-core/src/main/java/org/apache/wicket/{IPageManagerProvider.java => pageStore/crypt/ICrypter.java} (75%)
 create mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/disk/NestedFolders.java
 rename wicket-core/src/main/java/org/apache/wicket/pageStore/{ => disk}/PageWindowManager.java (79%)
 delete mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/memory/HttpSessionDataStore.java
 delete mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/memory/MemorySizeEvictionStrategy.java
 delete mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/memory/PageNumberEvictionStrategy.java
 delete mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/memory/PageTable.java
 delete mode 100644 wicket-core/src/main/java/org/apache/wicket/pageStore/memory/PageTableCleaner.java
 create mode 100644 wicket-core/src/test/java/org/apache/wicket/DefaultPageManagerProviderTest.java
 rename wicket-core/src/test/java/org/apache/wicket/pageStore/{DiskDataStoreTest.java => AbstractConcurrentPageStoreTest.java} (56%)
 create mode 100644 wicket-core/src/test/java/org/apache/wicket/pageStore/CryptingPageStoreTest.java
 copy wicket-util/src/test/java/org/apache/wicket/util/io/ConnectionsTest.java => wicket-core/src/test/java/org/apache/wicket/pageStore/DiskPageStoreTest.java (52%)
 rename wicket-core/src/{main/java/org/apache/wicket/pageStore/SecondLevelPageCache.java => test/java/org/apache/wicket/pageStore/FilePageStoreTest.java} (51%)
 create mode 100644 wicket-core/src/test/java/org/apache/wicket/pageStore/GroupingPageStoreTest.java
 copy wicket-core/src/test/java/org/apache/wicket/pageStore/{DefaultPageStoreTest.java => InMemoryPageStoreSizeTest.java} (73%)
 copy wicket-core/src/{main/java/org/apache/wicket/IPageManagerProvider.java => test/java/org/apache/wicket/pageStore/InMemoryPageStoreTest.java} (74%)
 rename wicket-core/src/{main/java/org/apache/wicket/pageStore/memory/IDataStoreEvictionStrategy.java => test/java/org/apache/wicket/pageStore/InSessionPageStoreSizeTest.java} (63%)
 rename wicket-core/src/test/java/org/apache/wicket/pageStore/{DefaultPageStoreTest.java => InSessionPageStoreTest.java} (74%)
 create mode 100644 wicket-core/src/test/java/org/apache/wicket/pageStore/RequestPageStoreTest.java
 rename wicket-core/src/test/java/org/apache/wicket/pageStore/{PerSessionPageStoreTest.java => SerializingPageStoreTest.java} (50%)
 create mode 100644 wicket-core/src/test/java/org/apache/wicket/pageStore/disk/NestedFoldersTest.java
 rename wicket-core/src/test/java/org/apache/wicket/{page/persistent => pageStore}/disk/PageWindowManagerTest.java (78%)
 delete mode 100644 wicket-core/src/test/java/org/apache/wicket/pageStore/memory/DummyPageManagerContext.java
 delete mode 100644 wicket-core/src/test/java/org/apache/wicket/pageStore/memory/HttpSessionDataStoreTest.java
 delete mode 100644 wicket-core/src/test/java/org/apache/wicket/pageStore/memory/MemorySizeEvictionStrategyTest.java
 delete mode 100644 wicket-core/src/test/java/org/apache/wicket/pageStore/memory/PageNumberEvictionStrategyTest.java
 delete mode 100644 wicket-core/src/test/java/org/apache/wicket/pageStore/memory/PageTableTest.java
 rename wicket-core/src/test/java/org/apache/wicket/request/handler/{PageIdPoliticTest.java => PageIdPolicyTest.java} (80%)
 delete mode 100644 wicket-core/src/test/java/org/apache/wicket/versioning/InMemoryPageStore.java
 rename wicket-devutils/src/main/java/org/apache/wicket/devutils/debugbar/{PageSizeDebugPanel.java => PageStoreDebugPanel.java} (58%)
 delete mode 100644 wicket-devutils/src/main/java/org/apache/wicket/devutils/diskstore/DebugDiskDataStore.java
 delete mode 100644 wicket-devutils/src/main/java/org/apache/wicket/devutils/diskstore/DebugPageManagerProvider.java
 delete mode 100644 wicket-devutils/src/main/java/org/apache/wicket/devutils/diskstore/DiskStoreBrowserPage.java
 delete mode 100644 wicket-devutils/src/main/java/org/apache/wicket/devutils/diskstore/browser/BrowserPanel.java
 delete mode 100644 wicket-devutils/src/main/java/org/apache/wicket/devutils/diskstore/browser/BrowserTable.java
 delete mode 100644 wicket-devutils/src/main/java/org/apache/wicket/devutils/diskstore/browser/DataStoreHelper.java
 delete mode 100644 wicket-devutils/src/main/java/org/apache/wicket/devutils/diskstore/browser/PageWindowColumn.java
 delete mode 100644 wicket-devutils/src/main/java/org/apache/wicket/devutils/diskstore/browser/PageWindowDescription.java
 delete mode 100644 wicket-devutils/src/main/java/org/apache/wicket/devutils/diskstore/browser/PageWindowProvider.java
 delete mode 100644 wicket-devutils/src/main/java/org/apache/wicket/devutils/diskstore/browser/SessionsProviderModel.java
 rename wicket-devutils/src/main/java/org/apache/wicket/devutils/{diskstore/DiskStoreBrowserPage.html => pagestore/PageStorePage.html} (81%)
 create mode 100644 wicket-devutils/src/main/java/org/apache/wicket/devutils/pagestore/PageStorePage.java
 create mode 100644 wicket-devutils/src/main/java/org/apache/wicket/devutils/pagestore/browser/PersistedPagesProvider.java
 rename wicket-devutils/src/main/java/org/apache/wicket/devutils/{diskstore/browser/BrowserPanel.html => pagestore/browser/PersistedPanel.html} (80%)
 create mode 100644 wicket-devutils/src/main/java/org/apache/wicket/devutils/pagestore/browser/PersistedPanel.java
 create mode 100644 wicket-devutils/src/main/java/org/apache/wicket/devutils/pagestore/browser/SessionIdentifiersModel.java


[wicket] 03/03: tag ConnectionsTest SLOW

Posted by sv...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

svenmeier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/wicket.git

commit 3d825894b56b97b438c50795ab3bfecd21b41a94
Author: Sven Meier <sv...@apache.org>
AuthorDate: Tue Mar 19 14:21:28 2019 +0100

    tag ConnectionsTest SLOW
    
    since wicket.apache.org might be unreachable
---
 .../src/test/java/org/apache/wicket/util/io/ConnectionsTest.java   | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/wicket-util/src/test/java/org/apache/wicket/util/io/ConnectionsTest.java b/wicket-util/src/test/java/org/apache/wicket/util/io/ConnectionsTest.java
index 8608ad4..b0b0c0f 100644
--- a/wicket-util/src/test/java/org/apache/wicket/util/io/ConnectionsTest.java
+++ b/wicket-util/src/test/java/org/apache/wicket/util/io/ConnectionsTest.java
@@ -16,7 +16,9 @@
  */
 package org.apache.wicket.util.io;
 
+import org.apache.wicket.util.WicketTestTag;
 import org.apache.wicket.util.time.Time;
+import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 
 import java.net.URL;
@@ -24,7 +26,10 @@ import java.net.URL;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
-@SuppressWarnings("javadoc")
+/**
+ * Tests for {@link Connections}
+ */
+@Tag(WicketTestTag.SLOW)
 public class ConnectionsTest
 {
 	/**


[wicket] 02/03: WICKET-6559 new encrypting page store

Posted by sv...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

svenmeier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/wicket.git

commit 4c9d89d6af39250bb92069fed28aa3eedbd7276d
Author: Sven Meier <sv...@apache.org>
AuthorDate: Tue Oct 30 09:13:41 2018 +0100

    WICKET-6559 new encrypting page store
---
 .../apache/wicket/DefaultPageManagerProvider.java  |  21 +++
 .../apache/wicket/pageStore/CryptingPageStore.java | 151 +++++++++++++++++++++
 .../wicket/pageStore/crypt/DefaultCrypter.java     | 108 +++++++++++++++
 .../apache/wicket/pageStore/crypt/ICrypter.java    |  28 ++++
 .../org/apache/wicket/settings/StoreSettings.java  |  26 +++-
 .../wicket/pageStore/CryptingPageStoreTest.java    |  88 ++++++++++++
 .../devutils/pagestore/browser/PersistedPanel.java |   3 +-
 .../pagestore/browser/SessionIdentifiersModel.java |   1 -
 8 files changed, 422 insertions(+), 4 deletions(-)

diff --git a/wicket-core/src/main/java/org/apache/wicket/DefaultPageManagerProvider.java b/wicket-core/src/main/java/org/apache/wicket/DefaultPageManagerProvider.java
index 8825ec6..1e8ec6f 100644
--- a/wicket-core/src/main/java/org/apache/wicket/DefaultPageManagerProvider.java
+++ b/wicket-core/src/main/java/org/apache/wicket/DefaultPageManagerProvider.java
@@ -21,6 +21,7 @@ import java.io.File;
 import org.apache.wicket.page.IPageManager;
 import org.apache.wicket.page.PageManager;
 import org.apache.wicket.pageStore.AsynchronousPageStore;
+import org.apache.wicket.pageStore.CryptingPageStore;
 import org.apache.wicket.pageStore.DiskPageStore;
 import org.apache.wicket.pageStore.FilePageStore;
 import org.apache.wicket.pageStore.GroupingPageStore;
@@ -44,6 +45,7 @@ import org.apache.wicket.util.lang.Bytes;
  * <li>{@link InSessionPageStore} keeping the last accessed page in the session</li>
  * <li>{@link AsynchronousPageStore} moving storage of pages to an asynchronous worker thread (enabled by default with {@link StoreSettings#isAsynchronous()})</li>
  * <li>{@link SerializingPageStore} serializing all pages (so they are available for back-button)</li>
+ * <li>{@link CryptingPageStore} encrypting all pages (disabled by default in {@link StoreSettings#isEncrypted()})</li>
  * <li>{@link DiskPageStore} persisting all pages, configured according to {@link StoreSettings}</li>
  * </ol>
  * An alternative chain with all pages held in-memory could be:
@@ -95,6 +97,8 @@ public class DefaultPageManagerProvider implements IPageManagerProvider
 	{
 		IPageStore store = newPersistentStore();
 		
+		store = newCryptingStore(store);
+
 		store = newSerializingStore(store);
 		
 		store = newAsynchronousStore(store);
@@ -169,6 +173,23 @@ public class DefaultPageManagerProvider implements IPageManagerProvider
 	}
 
 	/**
+	 * Crypt all pages, if enabled in {@link StoreSettings#isEncrypted()}.
+	 * 
+	 * @see CryptingPageStore
+	 */
+	protected IPageStore newCryptingStore(IPageStore pageStore)
+	{
+		StoreSettings storeSettings = application.getStoreSettings();
+		
+		if (storeSettings.isEncrypted())
+		{
+			pageStore = new CryptingPageStore(pageStore);
+		}
+
+		return pageStore;
+	}
+
+	/**
 	 * Keep persistent copies of all pages on disk.
 	 * 
 	 * @see DiskPageStore
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/CryptingPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/CryptingPageStore.java
new file mode 100644
index 0000000..74cd289
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/CryptingPageStore.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.pageStore;
+
+import java.io.Serializable;
+
+import org.apache.wicket.MetaDataKey;
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.page.IManageablePage;
+import org.apache.wicket.pageStore.crypt.DefaultCrypter;
+import org.apache.wicket.pageStore.crypt.ICrypter;
+import org.apache.wicket.util.lang.Args;
+
+/**
+ * A store that encrypts all pages before delegating and vice versa.
+ * <p>
+ * All pages passing through this store are restricted to be {@link SerializedPage}s. You can
+ * achieve this with
+ * <ul>
+ * <li>a {@link SerializingPageStore} delegating to this store and</li>
+ * <li>delegating to a store that does not deserialize its pages, e.g. a {@link DiskPageStore}.</li>.
+ * </ul>
+ */
+public class CryptingPageStore extends DelegatingPageStore
+{
+	private static final MetaDataKey<SessionData> KEY = new MetaDataKey<SessionData>()
+	{
+	};
+
+	/**
+	 * @param delegate
+	 *            store to delegate to
+	 * @param applicationName
+	 *            name of application
+	 */
+	public CryptingPageStore(IPageStore delegate)
+	{
+		super(delegate);
+	}
+
+	/**
+	 * Pages are always serialized, so versioning is supported.
+	 */
+	@Override
+	public boolean supportsVersioning()
+	{
+		return true;
+	}
+
+	/**
+	 * Supports asynchronous add if the delegate supports it.
+	 */
+	@Override
+	public boolean canBeAsynchronous(IPageContext context)
+	{
+		// session data must be added here *before* any asynchronous calls
+		// when session is no longer available
+		getSessionData(context);
+
+		return getDelegate().canBeAsynchronous(context);
+	}
+
+	private SessionData getSessionData(IPageContext context)
+	{
+		return context.getSessionData(KEY, () -> new SessionData(newCrypter(context))); 
+	}
+
+	/**
+	 * Create a new {@link ICrypter} for the given context.
+	 */
+	protected ICrypter newCrypter(IPageContext context) {
+		return new DefaultCrypter();
+	}
+
+	@Override
+	public IManageablePage getPage(IPageContext context, int id)
+	{
+		IManageablePage page = getDelegate().getPage(context, id);
+
+		if (page != null)
+		{
+			if (page instanceof SerializedPage == false)
+			{
+				throw new WicketRuntimeException("CryptingPageStore expects serialized pages");
+			}
+			SerializedPage serializedPage = (SerializedPage)page;
+
+			byte[] encrypted = serializedPage.getData();
+			byte[] decrypted = getSessionData(context).decrypt(encrypted);
+
+			page = new SerializedPage(page.getPageId(), serializedPage.getPageType(), decrypted);
+		}
+
+		return page;
+	}
+
+	@Override
+	public void addPage(IPageContext context, IManageablePage page)
+	{
+		if (page instanceof SerializedPage == false)
+		{
+			throw new WicketRuntimeException("CryptingPageStore works with serialized pages only");
+		}
+
+		SerializedPage serializedPage = (SerializedPage)page;
+
+		byte[] decrypted = serializedPage.getData();
+		byte[] encrypted = getSessionData(context).encrypt(decrypted);
+
+		page = new SerializedPage(page.getPageId(), serializedPage.getPageType(), encrypted);
+
+		getDelegate().addPage(context, page);
+	}
+
+	private static class SessionData implements Serializable
+	{
+
+		private final ICrypter cypter;
+
+		public SessionData(ICrypter crypter)
+		{
+			Args.notNull(crypter, "crypter");
+			
+			this.cypter= crypter;
+		}
+
+		public byte[] encrypt(byte[] decrypted)
+		{
+			return cypter.encrypt(decrypted);
+		}
+
+		public byte[] decrypt(byte[] encrypted)
+		{
+			return cypter.decrypt(encrypted);
+		}
+	}
+}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/crypt/DefaultCrypter.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/crypt/DefaultCrypter.java
new file mode 100644
index 0000000..53c63be
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/crypt/DefaultCrypter.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.pageStore.crypt;
+
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+
+import org.apache.wicket.WicketRuntimeException;
+
+/**
+ * Default encryption and decryption implementation. 
+ */
+public class DefaultCrypter implements ICrypter
+{
+	private final SecureRandom random;
+
+	private final SecretKey key;
+
+	public DefaultCrypter()
+	{
+		try
+		{
+			random = SecureRandom.getInstance("SHA1PRNG", "SUN");
+
+			KeyGenerator generator = KeyGenerator.getInstance("AES");
+			generator.init(256, random);
+			key = generator.generateKey();
+		}
+		catch (GeneralSecurityException ex)
+		{
+			throw new WicketRuntimeException(ex);
+		}
+	}
+
+	protected Cipher getCipher() throws GeneralSecurityException
+	{
+		return Cipher.getInstance("AES/CBC/PKCS5Padding");
+	}
+
+	@Override
+	public byte[] encrypt(byte[] decrypted)
+	{
+		try
+		{
+			Cipher cipher = getCipher();
+			cipher.init(Cipher.ENCRYPT_MODE, key, random);
+
+			AlgorithmParameters params = cipher.getParameters();
+			byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
+
+			byte[] ciphertext = cipher.doFinal(decrypted);
+			
+			byte[] encrypted = Arrays.copyOf(iv, iv.length + ciphertext.length);
+			System.arraycopy(ciphertext, 0, encrypted, iv.length, ciphertext.length);
+			
+			return encrypted;
+		}
+		catch (GeneralSecurityException ex)
+		{
+			throw new WicketRuntimeException(ex);
+		}
+	}
+
+	@Override
+	public byte[] decrypt(byte[] encrypted)
+	{
+		try
+		{
+			byte[] iv = new byte[16];
+			byte[] ciphertext = new byte[encrypted.length - 16];
+			System.arraycopy(encrypted, 0, iv, 0, iv.length);
+			System.arraycopy(encrypted, 16, ciphertext, 0, ciphertext.length);
+	
+			Cipher cipher = getCipher();
+			cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
+			byte[] decrypted = cipher.doFinal(ciphertext);
+			
+			return decrypted;
+		}
+		catch (GeneralSecurityException ex)
+		{
+			throw new WicketRuntimeException(ex);
+		}
+	}
+}
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/crypt/ICrypter.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/crypt/ICrypter.java
new file mode 100644
index 0000000..1cdbeaf
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/crypt/ICrypter.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.pageStore.crypt;
+
+import java.io.Serializable;
+
+/**
+ * An encrypter and decrypter of pages.
+ */
+public interface ICrypter extends Serializable {
+	byte[] encrypt(byte[] bytes);
+	
+	byte[] decrypt(byte[] bytes);
+}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/settings/StoreSettings.java b/wicket-core/src/main/java/org/apache/wicket/settings/StoreSettings.java
index 7c32101..6576a7c 100644
--- a/wicket-core/src/main/java/org/apache/wicket/settings/StoreSettings.java
+++ b/wicket-core/src/main/java/org/apache/wicket/settings/StoreSettings.java
@@ -49,6 +49,8 @@ public class StoreSettings
 
 	private boolean asynchronous = true;
 	
+	private boolean encrypted = false;
+
 	/**
 	 * Construct.
 	 * 
@@ -179,4 +181,26 @@ public class StoreSettings
 	{
 		return asynchronous;
 	}
-}
\ No newline at end of file
+	
+	/**
+	 * Sets a flag whether to wrap the configured {@link org.apache.wicket.pageStore.IPageStore} with
+	 * {@link org.apache.wicket.pageStore.CryptingPageStore}.
+	 *
+	 * @param encrypted
+	 *            {@code true} to encrypt, {@code false} - otherwise
+	 * @return {@code this} object for chaining
+	 */
+	public StoreSettings setEncrypted(boolean encrypted)
+	{
+		this.encrypted = encrypted;
+		return this;
+	}
+	
+	/**
+	 * @return {@code true} if the storing of page is encrypted
+	 */
+	public boolean isEncrypted()
+	{
+		return encrypted;
+	}
+}
diff --git a/wicket-core/src/test/java/org/apache/wicket/pageStore/CryptingPageStoreTest.java b/wicket-core/src/test/java/org/apache/wicket/pageStore/CryptingPageStoreTest.java
new file mode 100644
index 0000000..3f12483
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/pageStore/CryptingPageStoreTest.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.pageStore;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.security.GeneralSecurityException;
+
+import org.apache.wicket.MockPage;
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.mock.MockPageContext;
+import org.apache.wicket.mock.MockPageStore;
+import org.apache.wicket.serialize.java.JavaSerializer;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test for {@link CryptingPageStore}.
+ * 
+ * @author svenmeier
+ */
+public class CryptingPageStoreTest
+{
+
+	@Test
+	void test()
+	{
+		CryptingPageStore store = new CryptingPageStore(new MockPageStore());
+		JavaSerializer serializer = new JavaSerializer("test");
+
+		IPageContext context = new MockPageContext();
+
+		for (int p = 0; p < 10; p++)
+		{
+			MockPage add = new MockPage(p);
+			SerializedPage serializedAdd = new SerializedPage(p, "foo", serializer.serialize(add));
+			store.addPage(context, serializedAdd);
+
+			SerializedPage serializedGot = (SerializedPage)store.getPage(context, p);
+			MockPage got = (MockPage)serializer.deserialize(serializedGot.getData());
+			assertEquals(p, got.getPageId());
+		}
+	}
+
+	@Test
+	void testFail()
+	{
+		CryptingPageStore store = new CryptingPageStore(new MockPageStore());
+		JavaSerializer serializer = new JavaSerializer("test");
+
+		MockPageContext context = new MockPageContext();
+
+		int p = 42;
+
+		MockPage add = new MockPage(p);
+		SerializedPage serializedAdd = new SerializedPage(p, "foo", serializer.serialize(add));
+		store.addPage(context, serializedAdd);
+
+		// remove key from session
+		context.clearSession();
+
+		try
+		{
+			SerializedPage serializedGot = (SerializedPage)store.getPage(context, p);
+
+			MockPage got = (MockPage)serializer.deserialize(serializedGot.getData());
+			assertEquals(p, got.getPageId());
+		}
+		catch (WicketRuntimeException ex)
+		{
+			assertTrue(ex.getCause() instanceof GeneralSecurityException, "unable to decrypt with new key");
+		}
+	}
+}
diff --git a/wicket-devutils/src/main/java/org/apache/wicket/devutils/pagestore/browser/PersistedPanel.java b/wicket-devutils/src/main/java/org/apache/wicket/devutils/pagestore/browser/PersistedPanel.java
index 0ae5ab4..38031df 100644
--- a/wicket-devutils/src/main/java/org/apache/wicket/devutils/pagestore/browser/PersistedPanel.java
+++ b/wicket-devutils/src/main/java/org/apache/wicket/devutils/pagestore/browser/PersistedPanel.java
@@ -21,7 +21,6 @@ import java.util.List;
 import java.util.Optional;
 
 import org.apache.wicket.PageReference;
-import org.apache.wicket.Session;
 import org.apache.wicket.ajax.AbstractAjaxTimerBehavior;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
@@ -149,7 +148,7 @@ public class PersistedPanel extends GenericPanel<IPersistentPageStore>
 			return null;
 		}
 
-		IPageContext context = new DefaultPageContext(Session.get());
+		IPageContext context = new DefaultPageContext();
 		
 		return store.getSessionIdentifier(context);
 	}
diff --git a/wicket-devutils/src/main/java/org/apache/wicket/devutils/pagestore/browser/SessionIdentifiersModel.java b/wicket-devutils/src/main/java/org/apache/wicket/devutils/pagestore/browser/SessionIdentifiersModel.java
index 5792484..7cd8b4c 100644
--- a/wicket-devutils/src/main/java/org/apache/wicket/devutils/pagestore/browser/SessionIdentifiersModel.java
+++ b/wicket-devutils/src/main/java/org/apache/wicket/devutils/pagestore/browser/SessionIdentifiersModel.java
@@ -20,7 +20,6 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
-import org.apache.wicket.Session;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.LoadableDetachableModel;
 import org.apache.wicket.pageStore.DefaultPageContext;


[wicket] 01/03: WICKET-6563 new IPageStore implementation

Posted by sv...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

svenmeier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/wicket.git

commit ca03c252b5745bdccfb7c8cdffcc705846593ec1
Author: Sven Meier <sv...@apache.org>
AuthorDate: Mon Jun 25 17:19:41 2018 +0200

    WICKET-6563 new IPageStore implementation
    
    this closes #283
---
 .../main/java/org/apache/wicket/Application.java   |  33 +-
 .../apache/wicket/DefaultPageManagerProvider.java  | 162 ++++--
 .../org/apache/wicket/IPageManagerProvider.java    |  10 +-
 .../src/main/java/org/apache/wicket/Page.java      |  10 +-
 .../src/main/java/org/apache/wicket/Session.java   |   7 +-
 .../wicket/core/request/handler/PageProvider.java  |   1 -
 .../request/handler/RenderPageRequestHandler.java  |   2 +-
 .../wicket/core/util/string/ComponentRenderer.java |   2 +-
 .../org/apache/wicket/mock/MockApplication.java    |   2 +-
 .../org/apache/wicket/mock/MockPageContext.java    | 110 ++++
 .../org/apache/wicket/mock/MockPageManager.java    |  56 +-
 .../{MockPageManager.java => MockPageStore.java}   |  89 ++--
 .../apache/wicket/page/AbstractPageManager.java    | 121 -----
 .../wicket/page/DefaultPageManagerContext.java     | 101 ----
 .../java/org/apache/wicket/page/IPageManager.java  |  84 ++-
 .../apache/wicket/page/IPageManagerContext.java    |  66 ---
 .../apache/wicket/page/PageAccessSynchronizer.java |  48 +-
 .../java/org/apache/wicket/page/PageManager.java   | 103 ++++
 .../org/apache/wicket/page/PageStoreManager.java   | 520 ------------------
 .../org/apache/wicket/page/RequestAdapter.java     | 206 -------
 .../wicket/pageStore/AbstractCachingPageStore.java | 105 ----
 .../apache/wicket/pageStore/AbstractPageStore.java | 154 ------
 .../pageStore/AbstractPersistentPageStore.java     | 259 +++++++++
 .../wicket/pageStore/AsynchronousDataStore.java    | 344 ------------
 .../wicket/pageStore/AsynchronousPageStore.java    | 386 ++++++++------
 .../wicket/pageStore/DefaultPageContext.java       | 109 ++++
 .../apache/wicket/pageStore/DefaultPageStore.java  | 469 ----------------
 .../DelegatingPageStore.java}                      | 183 +++----
 .../org/apache/wicket/pageStore/DiskDataStore.java | 589 ---------------------
 .../org/apache/wicket/pageStore/DiskPageStore.java | 561 ++++++++++++++++++++
 .../org/apache/wicket/pageStore/FilePageStore.java | 379 +++++++++++++
 .../apache/wicket/pageStore/GroupingPageStore.java | 285 ++++++++++
 .../org/apache/wicket/pageStore/IDataStore.java    |  83 ---
 .../org/apache/wicket/pageStore/IPageContext.java  |  84 +++
 .../org/apache/wicket/pageStore/IPageStore.java    | 108 ++--
 .../apache/wicket/pageStore/IPersistedPage.java}   |  33 +-
 .../wicket/pageStore/IPersistentPageStore.java     |  68 +++
 .../apache/wicket/pageStore/InMemoryPageStore.java | 338 ++++++++++++
 .../wicket/pageStore/InSessionPageStore.java       | 377 +++++++++++++
 .../apache/wicket/pageStore/NoopPageStore.java}    |  33 +-
 .../wicket/pageStore/PerSessionPageStore.java      | 332 ------------
 .../apache/wicket/pageStore/RequestPageStore.java  | 153 ++++++
 .../apache/wicket/pageStore/SerializedPage.java    | 105 ++++
 .../wicket/pageStore/SerializingPageStore.java     |  84 +++
 .../wicket/pageStore/disk/NestedFolders.java       | 140 +++++
 .../pageStore/{ => disk}/PageWindowManager.java    | 150 +++---
 .../pageStore/memory/HttpSessionDataStore.java     | 186 -------
 .../memory/MemorySizeEvictionStrategy.java         |  64 ---
 .../memory/PageNumberEvictionStrategy.java         |  62 ---
 .../apache/wicket/pageStore/memory/PageTable.java  | 128 -----
 .../wicket/pageStore/memory/PageTableCleaner.java  |  46 --
 .../org/apache/wicket/settings/StoreSettings.java  |  69 +--
 .../wicket/util/tester/BaseWicketTester.java       |   8 +-
 .../wicket/DefaultPageManagerProviderTest.java     |  52 ++
 .../core/request/mapper/TestMapperContext.java     |  48 +-
 .../DontStoreNotRenderedPageTestCase.java          |  33 +-
 .../html/TransparentWebMarkupContainerTest.java    |   8 +-
 .../wicket/page/AsyncPageStoreManagerTest.java     | 172 ++----
 .../wicket/page/PageAccessSynchronizerTest.java    |   2 +-
 .../wicket/page/PersistentPageManagerTest.java     |  77 ++-
 ...t.java => AbstractConcurrentPageStoreTest.java} | 217 +++-----
 .../wicket/pageStore/AbstractPageStoreTest.java    | 166 ++++--
 .../pageStore/AsynchronousDataStoreTest.java       |  46 +-
 .../pageStore/AsynchronousPageStoreTest.java       | 323 +++++++----
 ...onPageStoreTest.java => DiskPageStoreTest.java} |  43 +-
 .../wicket/pageStore/FilePageStoreTest.java}       |  46 +-
 .../wicket/pageStore/GroupingPageStoreTest.java    | 114 ++++
 ...oreTest.java => InMemoryPageStoreSizeTest.java} |  10 +-
 .../wicket/pageStore/InMemoryPageStoreTest.java}   |  17 +-
 .../pageStore/InSessionPageStoreSizeTest.java}     |  21 +-
 ...eStoreTest.java => InSessionPageStoreTest.java} |  11 +-
 .../wicket/pageStore/RequestPageStoreTest.java     |  65 +++
 ...toreTest.java => SerializingPageStoreTest.java} |  48 +-
 .../wicket/pageStore/disk/NestedFoldersTest.java   |  96 ++++
 .../disk/PageWindowManagerTest.java                |  71 +--
 .../pageStore/memory/DummyPageManagerContext.java  |  66 ---
 .../pageStore/memory/HttpSessionDataStoreTest.java | 114 ----
 .../memory/MemorySizeEvictionStrategyTest.java     |  64 ---
 .../memory/PageNumberEvictionStrategyTest.java     |  66 ---
 .../wicket/pageStore/memory/PageTableTest.java     |  58 --
 ...ageIdPoliticTest.java => PageIdPolicyTest.java} |  32 +-
 .../wicket/request/handler/PageProviderTest.java   |  20 +-
 .../wicket/versioning/InMemoryPageStore.java       | 126 -----
 .../wicket/versioning/PageVersioningTest.java      |  29 +-
 .../org/apache/wicket/devutils/DevUtilsPage.java   |   9 +-
 .../devutils/debugbar/DebugBarInitializer.java     |   2 +-
 ...izeDebugPanel.java => PageStoreDebugPanel.java} |  44 +-
 .../devutils/debugbar/StandardDebugPanel.java      |   4 +-
 .../devutils/diskstore/DebugDiskDataStore.java     | 100 ----
 .../diskstore/DebugPageManagerProvider.java        |  63 ---
 .../devutils/diskstore/DiskStoreBrowserPage.java   |  51 --
 .../devutils/diskstore/browser/BrowserPanel.java   | 133 -----
 .../devutils/diskstore/browser/BrowserTable.java   |  47 --
 .../diskstore/browser/DataStoreHelper.java         |  43 --
 .../diskstore/browser/PageWindowColumn.java        |  80 ---
 .../diskstore/browser/PageWindowDescription.java   |  57 --
 .../diskstore/browser/PageWindowProvider.java      | 108 ----
 .../diskstore/browser/SessionsProviderModel.java   |  57 --
 .../devutils/inspector/EnhancedPageView.java       |  37 +-
 .../wicket/devutils/inspector/InspectorPage.java   |  30 +-
 .../PageStorePage.html}                            |  10 +-
 .../wicket/devutils/pagestore/PageStorePage.java   |  73 +++
 .../pagestore/browser/PersistedPagesProvider.java  | 133 +++++
 .../browser/PersistedPanel.html}                   |   9 +-
 .../devutils/pagestore/browser/PersistedPanel.java | 201 +++++++
 .../pagestore/browser/SessionIdentifiersModel.java |  73 +++
 .../apache/wicket/examples/frames/BodyFrame.java   |   6 +-
 .../org/apache/wicket/examples/StartExamples.java  |  13 +-
 .../ajax/markup/html/modal/ModalWindow.java        |   5 +-
 .../org/apache/wicket/jmx/StoreSettingsMBean.java  |  15 +-
 .../apache/wicket/jmx/wrapper/StoreSettings.java   |   6 -
 .../ws/api/AbstractWebSocketProcessor.java         |   2 +-
 .../java/org/apache/wicket/util/file/Files.java    |  14 +
 113 files changed, 5568 insertions(+), 6325 deletions(-)

diff --git a/wicket-core/src/main/java/org/apache/wicket/Application.java b/wicket-core/src/main/java/org/apache/wicket/Application.java
index fec2343..035ee0b 100644
--- a/wicket-core/src/main/java/org/apache/wicket/Application.java
+++ b/wicket-core/src/main/java/org/apache/wicket/Application.java
@@ -55,10 +55,7 @@ import org.apache.wicket.markup.parser.filter.WicketMessageTagHandler;
 import org.apache.wicket.markup.resolver.HtmlHeaderResolver;
 import org.apache.wicket.markup.resolver.WicketContainerResolver;
 import org.apache.wicket.markup.resolver.WicketMessageResolver;
-import org.apache.wicket.page.DefaultPageManagerContext;
 import org.apache.wicket.page.IPageManager;
-import org.apache.wicket.page.IPageManagerContext;
-import org.apache.wicket.pageStore.IDataStore;
 import org.apache.wicket.pageStore.IPageStore;
 import org.apache.wicket.protocol.http.IRequestLogger;
 import org.apache.wicket.protocol.http.RequestLogger;
@@ -1019,7 +1016,7 @@ public abstract class Application implements UnboundListener, IEventSink, IMetad
 	/** The Security Settings */
 	private SecuritySettings securitySettings;
 
-	/** The settings for {@link IPageStore}, {@link IDataStore} and {@link IPageManager} */
+	/** The settings for {@link IPageStore} and {@link IPageManager} */
 	private StoreSettings storeSettings;
 
 	/** can the settings object be set/used. */
@@ -1337,8 +1334,11 @@ public abstract class Application implements UnboundListener, IEventSink, IMetad
 	}
 
 	/**
+	 * Set the provider of an {@link IPageManager}.
 	 * 
 	 * @param provider
+	 * 
+	 * @see DefaultPageManagerProvider
 	 */
 	public final Application setPageManagerProvider(final IPageManagerProvider provider)
 	{
@@ -1347,11 +1347,6 @@ public abstract class Application implements UnboundListener, IEventSink, IMetad
 	}
 
 	/**
-	 * Context for PageManager to interact with rest of Wicket
-	 */
-	private final IPageManagerContext pageManagerContext = new DefaultPageManagerContext();
-
-	/**
 	 * Returns an unsynchronized version of page manager
 	 * 
 	 * @return the page manager
@@ -1364,22 +1359,13 @@ public abstract class Application implements UnboundListener, IEventSink, IMetad
 			{
 				if (pageManager == null)
 				{
-					pageManager = pageManagerProvider.apply(getPageManagerContext());
+					pageManager = pageManagerProvider.get();
 				}
 			}
 		}
 		return pageManager;
 	}
 
-	/**
-	 * 
-	 * @return the page manager context
-	 */
-	protected IPageManagerContext getPageManagerContext()
-	{
-		return pageManagerContext;
-	}
-
 	// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 	// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 	//
@@ -1537,7 +1523,7 @@ public abstract class Application implements UnboundListener, IEventSink, IMetad
 		{
 			session = newSession(requestCycle.getRequest(), requestCycle.getResponse());
 			ThreadContext.setSession(session);
-			internalGetPageManager().newSessionCreated();
+			internalGetPageManager().removeAllPages();
 			sessionListeners.onCreated(session);
 		}
 		else
@@ -1584,10 +1570,15 @@ public abstract class Application implements UnboundListener, IEventSink, IMetad
 			@Override
 			public void onDetach(final RequestCycle requestCycle)
 			{
+				IPageManager pageManager;
+				
 				if (Session.exists())
 				{
-					Session.get().getPageManager().commitRequest();
+					pageManager = Session.get().getPageManager();
+				} else {
+					pageManager = internalGetPageManager();
 				}
+				pageManager.detach();
 
 				if (Application.exists())
 				{
diff --git a/wicket-core/src/main/java/org/apache/wicket/DefaultPageManagerProvider.java b/wicket-core/src/main/java/org/apache/wicket/DefaultPageManagerProvider.java
index f109472..8825ec6 100644
--- a/wicket-core/src/main/java/org/apache/wicket/DefaultPageManagerProvider.java
+++ b/wicket-core/src/main/java/org/apache/wicket/DefaultPageManagerProvider.java
@@ -19,22 +19,61 @@ package org.apache.wicket;
 import java.io.File;
 
 import org.apache.wicket.page.IPageManager;
-import org.apache.wicket.page.IPageManagerContext;
-import org.apache.wicket.page.PageStoreManager;
-import org.apache.wicket.pageStore.AsynchronousDataStore;
+import org.apache.wicket.page.PageManager;
 import org.apache.wicket.pageStore.AsynchronousPageStore;
-import org.apache.wicket.pageStore.DefaultPageStore;
-import org.apache.wicket.pageStore.DiskDataStore;
-import org.apache.wicket.pageStore.IDataStore;
+import org.apache.wicket.pageStore.DiskPageStore;
+import org.apache.wicket.pageStore.FilePageStore;
+import org.apache.wicket.pageStore.GroupingPageStore;
 import org.apache.wicket.pageStore.IPageStore;
+import org.apache.wicket.pageStore.InMemoryPageStore;
+import org.apache.wicket.pageStore.InSessionPageStore;
+import org.apache.wicket.pageStore.NoopPageStore;
+import org.apache.wicket.pageStore.RequestPageStore;
+import org.apache.wicket.pageStore.SerializedPage;
+import org.apache.wicket.pageStore.SerializingPageStore;
 import org.apache.wicket.serialize.ISerializer;
+import org.apache.wicket.settings.FrameworkSettings;
 import org.apache.wicket.settings.StoreSettings;
 import org.apache.wicket.util.lang.Args;
 import org.apache.wicket.util.lang.Bytes;
 
 /**
- * {@link IPageManagerProvider} implementation that creates new instance of {@link IPageManager}
- * that persists the pages in {@link DiskDataStore}
+ * A provider of a {@link PageManager} managing @link IManageablePage}s with a default chain of {@link IPageStore}s:
+ * <ol>
+ * <li>{@link RequestPageStore} caching pages until end of the request</li>
+ * <li>{@link InSessionPageStore} keeping the last accessed page in the session</li>
+ * <li>{@link AsynchronousPageStore} moving storage of pages to an asynchronous worker thread (enabled by default with {@link StoreSettings#isAsynchronous()})</li>
+ * <li>{@link SerializingPageStore} serializing all pages (so they are available for back-button)</li>
+ * <li>{@link DiskPageStore} persisting all pages, configured according to {@link StoreSettings}</li>
+ * </ol>
+ * An alternative chain with all pages held in-memory could be:
+ * <ol>
+ * <li>{@link RequestPageStore} caching pages until end of the request</li>
+ * <li>{@link InSessionPageStore} keeping the last accessed page in the session</li>
+ * <li>{@link AsynchronousPageStore} moving storage of pages to a worker thread</li>
+ * <li>{@link SerializingPageStore} serializing all pages (so they are available for back-button)</li>
+ * <li>{@link InMemoryPageStore} keeping all pages in memory</li>
+ * </ol>
+ * ... or if all pages should be kept in the session only, without any serialization (no back-button
+ * support though):
+ * <ul>
+ * <li>{@link RequestPageStore} caching pages until end of the request</li>
+ * <li>{@link InSessionPageStore} keeping a limited count of pages in the session, e.g. 10</li>
+ * <li>{@link NoopPageStore} discarding all exceeding pages</li>
+ * </ul>
+ * The chain's initial store should always be a {@link RequestPageStore}, buffering all adding of pages until the end of the request.
+ * Several stores accept {@link SerializedPage} only, these have to be preceded by a {@link SerializingPageStore}.
+ * <p> 
+ * For back-button support <em>at least one</em> store in the chain must create copies of stored
+ * pages (usually through serialization), otherwise any following request will work on an identical
+ * page instance and the previous state of page is no longer accessible.
+ * <p>
+ * Other stores be may inserted ad libitum, e.g.
+ * <ul>
+ * <li>{@link GroupingPageStore} groups pages, e.g. to limit storage size on a per-group basis</li>
+ * <li>{@link FilePageStore} as an alternative to the trusted {@link DiskPageStore}</li>
+ * <li>other implementations from <a href="https://github.com/wicketstuff/core/tree/master/datastores-parent">wicketstuff-datastores</a></li>
+ * </ul>
  */
 public class DefaultPageManagerProvider implements IPageManagerProvider
 {
@@ -44,7 +83,7 @@ public class DefaultPageManagerProvider implements IPageManagerProvider
 	 * Constructor.
 	 *
 	 * @param application
-	 *          The application instance
+	 *            The application instance
 	 */
 	public DefaultPageManagerProvider(Application application)
 	{
@@ -52,53 +91,96 @@ public class DefaultPageManagerProvider implements IPageManagerProvider
 	}
 
 	@Override
-	public IPageManager apply(IPageManagerContext pageManagerContext)
+	public IPageManager get()
 	{
-		IDataStore dataStore = newDataStore();
+		IPageStore store = newPersistentStore();
+		
+		store = newSerializingStore(store);
+		
+		store = newAsynchronousStore(store);
 
-		StoreSettings storeSettings = getStoreSettings();
+		store = newSessionStore(store);
 
-		IPageStore pageStore;
+		store = newRequestStore(store);
 
-		if (dataStore.canBeAsynchronous() && storeSettings.isAsynchronous())
-		{
-			int capacity = storeSettings.getAsynchronousQueueCapacity();
-			dataStore = new AsynchronousDataStore(dataStore, capacity);
+		return new PageManager(store);
+	}
 
-			pageStore = newPageStore(dataStore);
+	/**
+	 * Get the {@link ISerializer} to use for serializing of pages.
+	 * <p>
+	 * By default the serializer of the applications {@link FrameworkSettings}.
+	 * 
+	 * @return how to serialize pages if needed for any {@link IPageStore}
+	 * 
+	 * @see FrameworkSettings#getSerializer()
+	 */
+	protected ISerializer getSerializer()
+	{
+		return application.getFrameworkSettings().getSerializer();
+	}
 
-			if (pageStore.canBeAsynchronous())
-			{
-				pageStore = new AsynchronousPageStore(pageStore, capacity);
-			}
-		}
-		else
+	/**
+	 * Cache pages in the request until it is finished.
+	 * 
+	 * @see RequestPageStore
+	 */
+	protected IPageStore newRequestStore(IPageStore pageStore)
+	{
+		return new RequestPageStore(pageStore);
+	}
+
+	/**
+	 * Cache last page in the session for fast access.
+	 * 
+	 * @see InSessionPageStore
+	 */
+	protected IPageStore newSessionStore(IPageStore pageStore)
+	{
+		return new InSessionPageStore(pageStore, 1, getSerializer());
+	}
+
+	/**
+	 * Store pages asynchronously into the persistent store, if enabled in {@link StoreSettings#isAsynchronous()}.
+	 * 
+	 * @see AsynchronousPageStore
+	 */
+	protected IPageStore newAsynchronousStore(IPageStore pageStore)
+	{
+		StoreSettings storeSettings = application.getStoreSettings();
+
+		if (storeSettings.isAsynchronous())
 		{
-			pageStore = newPageStore(dataStore);
+			int capacity = storeSettings.getAsynchronousQueueCapacity();
+			pageStore = new AsynchronousPageStore(pageStore, capacity);
 		}
 
-		return new PageStoreManager(application.getName(), pageStore, pageManagerContext);
-
+		return pageStore;
 	}
 
-	protected IPageStore newPageStore(IDataStore dataStore)
+	/**
+	 * Serialize pages.
+	 * 
+	 * @see SerializingPageStore
+	 */
+	protected IPageStore newSerializingStore(IPageStore pageStore)
 	{
-		int inmemoryCacheSize = getStoreSettings().getInmemoryCacheSize();
-		ISerializer pageSerializer = application.getFrameworkSettings().getSerializer();
-		return new DefaultPageStore(pageSerializer, dataStore, inmemoryCacheSize);
+		return new SerializingPageStore(pageStore, getSerializer());
 	}
 
-	protected IDataStore newDataStore()
+	/**
+	 * Keep persistent copies of all pages on disk.
+	 * 
+	 * @see DiskPageStore
+	 * @see StoreSettings#getMaxSizePerSession()
+	 * @see StoreSettings#getFileStoreFolder()
+	 */
+	protected IPageStore newPersistentStore()
 	{
-		StoreSettings storeSettings = getStoreSettings();
+		StoreSettings storeSettings = application.getStoreSettings();
 		Bytes maxSizePerSession = storeSettings.getMaxSizePerSession();
 		File fileStoreFolder = storeSettings.getFileStoreFolder();
 
-		return new DiskDataStore(application.getName(), fileStoreFolder, maxSizePerSession);
-	}
-
-	StoreSettings getStoreSettings()
-	{
-		return application.getStoreSettings();
+		return new DiskPageStore(application.getName(), fileStoreFolder, maxSizePerSession);
 	}
-}
+}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/IPageManagerProvider.java b/wicket-core/src/main/java/org/apache/wicket/IPageManagerProvider.java
index 557a7aa..d887af3 100644
--- a/wicket-core/src/main/java/org/apache/wicket/IPageManagerProvider.java
+++ b/wicket-core/src/main/java/org/apache/wicket/IPageManagerProvider.java
@@ -16,11 +16,15 @@
  */
 package org.apache.wicket;
 
-import java.util.function.Function;
+import java.util.function.Supplier;
 
 import org.apache.wicket.page.IPageManager;
-import org.apache.wicket.page.IPageManagerContext;
 
-public interface IPageManagerProvider extends Function<IPageManagerContext, IPageManager>
+/**
+ * Provider of an {@link IPageManager}.
+ * 
+ * @author svenmeier
+ */
+public interface IPageManagerProvider extends Supplier<IPageManager>
 {
 }
diff --git a/wicket-core/src/main/java/org/apache/wicket/Page.java b/wicket-core/src/main/java/org/apache/wicket/Page.java
index a1f3b29..7e74a4e 100644
--- a/wicket-core/src/main/java/org/apache/wicket/Page.java
+++ b/wicket-core/src/main/java/org/apache/wicket/Page.java
@@ -287,18 +287,18 @@ public abstract class Page extends MarkupContainer
 
 			if (isInitialization == false)
 			{
-				pageManager.touchPage(this);
+				pageManager.addPage(this);
 			}
 		}
 	}
-
+	
 	@Override
 	protected void onInitialize()
 	{
 		super.onInitialize();
 
 		final IPageManager pageManager = getSession().getPageManager();
-		pageManager.touchPage(this);
+		pageManager.addPage(this);
 	}
 
 	/**
@@ -825,7 +825,7 @@ public abstract class Page extends MarkupContainer
 			getSession().getSessionStore().getSessionId(RequestCycle.get().getRequest(), true);
 
 			// Add/touch the response page in the session.
-			getSession().getPageManager().touchPage(this);
+			getSession().getPageManager().addPage(this);
 		}
 
 		if (getApplication().getDebugSettings().isOutputMarkupContainerClassName())
@@ -936,7 +936,7 @@ public abstract class Page extends MarkupContainer
 		setStatelessHint(false);
 
 		// make sure the page will be available on following request
-		getSession().getPageManager().touchPage(this);
+		getSession().getPageManager().addPage(this);
 
 		return new PageReference(numericId);
 	}
diff --git a/wicket-core/src/main/java/org/apache/wicket/Session.java b/wicket-core/src/main/java/org/apache/wicket/Session.java
index 3a365e5..60f5676 100644
--- a/wicket-core/src/main/java/org/apache/wicket/Session.java
+++ b/wicket-core/src/main/java/org/apache/wicket/Session.java
@@ -38,6 +38,7 @@ import org.apache.wicket.feedback.FeedbackMessages;
 import org.apache.wicket.feedback.IFeedbackContributor;
 import org.apache.wicket.page.IPageManager;
 import org.apache.wicket.page.PageAccessSynchronizer;
+import org.apache.wicket.pageStore.IPageStore;
 import org.apache.wicket.request.Request;
 import org.apache.wicket.request.cycle.RequestCycle;
 import org.apache.wicket.session.ISessionStore;
@@ -291,7 +292,7 @@ public abstract class Session implements IClusterable, IEventSink, IMetadataCont
 	{
 		if (isTemporary() == false)
 		{
-			getPageManager().clear();
+			getPageManager().removeAllPages();
 		}
 	}
 
@@ -916,8 +917,8 @@ public abstract class Session implements IClusterable, IEventSink, IMetadataCont
 	 */
 	public final IPageManager getPageManager()
 	{
-		IPageManager pageManager = Application.get().internalGetPageManager();
-		return pageAccessSynchronizer.get().adapt(pageManager);
+		IPageManager manager = Application.get().internalGetPageManager();
+		return pageAccessSynchronizer.get().adapt(manager);
 	}
 
 	/** {@inheritDoc} */
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/request/handler/PageProvider.java b/wicket-core/src/main/java/org/apache/wicket/core/request/handler/PageProvider.java
index c08f0ad..33c616e 100644
--- a/wicket-core/src/main/java/org/apache/wicket/core/request/handler/PageProvider.java
+++ b/wicket-core/src/main/java/org/apache/wicket/core/request/handler/PageProvider.java
@@ -19,7 +19,6 @@ package org.apache.wicket.core.request.handler;
 import org.apache.wicket.Application;
 import org.apache.wicket.core.request.mapper.IPageSource;
 import org.apache.wicket.core.request.mapper.StalePageException;
-import org.apache.wicket.page.IPageManager;
 import org.apache.wicket.protocol.http.PageExpiredException;
 import org.apache.wicket.request.IRequestHandler;
 import org.apache.wicket.request.IRequestMapper;
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/request/handler/RenderPageRequestHandler.java b/wicket-core/src/main/java/org/apache/wicket/core/request/handler/RenderPageRequestHandler.java
index 156d420..642d40f 100644
--- a/wicket-core/src/main/java/org/apache/wicket/core/request/handler/RenderPageRequestHandler.java
+++ b/wicket-core/src/main/java/org/apache/wicket/core/request/handler/RenderPageRequestHandler.java
@@ -136,7 +136,7 @@ public class RenderPageRequestHandler
 			if (Session.exists())
 			{
 				// WICKET-5499
-				Session.get().getPageManager().touchPage(pageProvider.getPageInstance());
+				Session.get().getPageManager().addPage(pageProvider.getPageInstance());
 			}
 		}
 	}
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/string/ComponentRenderer.java b/wicket-core/src/main/java/org/apache/wicket/core/util/string/ComponentRenderer.java
index 9104eb9..2b3d303 100644
--- a/wicket-core/src/main/java/org/apache/wicket/core/util/string/ComponentRenderer.java
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/string/ComponentRenderer.java
@@ -452,7 +452,7 @@ public class ComponentRenderer
 
 		private RenderPage(Component component)
 		{
-			// do not store the page in IPageStore/IDataStore. WICKET-5422
+			// WICKET-5422 do not store the page in IPageStore 
 			setStatelessHint(true);
 
 			String componentMarkup;
diff --git a/wicket-core/src/main/java/org/apache/wicket/mock/MockApplication.java b/wicket-core/src/main/java/org/apache/wicket/mock/MockApplication.java
index 1c0a99d..4ae6f41 100644
--- a/wicket-core/src/main/java/org/apache/wicket/mock/MockApplication.java
+++ b/wicket-core/src/main/java/org/apache/wicket/mock/MockApplication.java
@@ -67,7 +67,7 @@ public class MockApplication extends WebApplication
 
 		// set page and session store providers
 		setSessionStoreProvider(MockSessionStore::new);
-		setPageManagerProvider((pageManagerContext) -> new MockPageManager());
+		setPageManagerProvider(() -> new MockPageManager());
 
 		// for test cases we usually want stable resource names
 		getResourceSettings().setCachingStrategy(NoOpResourceCachingStrategy.INSTANCE);
diff --git a/wicket-core/src/main/java/org/apache/wicket/mock/MockPageContext.java b/wicket-core/src/main/java/org/apache/wicket/mock/MockPageContext.java
new file mode 100644
index 0000000..061b377
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/mock/MockPageContext.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.mock;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Supplier;
+
+import org.apache.wicket.MetaDataEntry;
+import org.apache.wicket.MetaDataKey;
+import org.apache.wicket.pageStore.IPageContext;
+
+/**
+ * Mock implementation of a page context - suitable for a single session only.
+ */
+public class MockPageContext implements IPageContext
+{
+
+	final String sessionId;
+
+	MetaDataEntry<?>[] requestData;
+	
+	MetaDataEntry<?>[] sessionData;
+
+	Map<String, Object> sessionAttributes = new HashMap<>();
+
+	public MockPageContext()
+	{
+		this("dummy_id");
+	}
+
+	public MockPageContext(String sessionId)
+	{
+		this.sessionId = sessionId;
+	}
+
+	@Override
+	public <T> T getRequestData(MetaDataKey<T> key, Supplier<T> defaultValue)
+	{
+		T value = key.get(requestData);
+		if (value == null) {
+			value = defaultValue.get();
+			if (value != null) {
+				requestData = key.set(requestData, value);
+			}
+		}
+
+		return value;
+	}
+
+	@Override
+	public <T extends Serializable> T getSessionAttribute(String key, Supplier<T> defaultValue)
+	{
+		@SuppressWarnings("unchecked")
+		T value = (T)sessionAttributes.get(key);
+		if (value == null) {
+			value = defaultValue.get();
+			if (value != null) {
+				sessionAttributes.put(key, value);
+			}
+		}
+		
+		return value;
+	}
+	
+	@Override
+	public <T extends Serializable> T getSessionData(MetaDataKey<T> key, Supplier<T> defaultValue)
+	{
+		T value = key.get(sessionData);
+		if (value == null) {
+			value = defaultValue.get();
+			if (value != null) {
+				sessionData = key.set(sessionData, value);
+			}
+		}
+		
+		return value;
+	}
+
+	@Override
+	public String getSessionId(boolean bind)
+	{
+		return sessionId;
+	}
+
+	public void clearRequest()
+	{
+		requestData = null;
+	}
+	
+	public void clearSession() {
+		sessionAttributes.clear();
+		sessionData = null;
+	}
+}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/mock/MockPageManager.java b/wicket-core/src/main/java/org/apache/wicket/mock/MockPageManager.java
index b27dc7e..799f7b0 100644
--- a/wicket-core/src/main/java/org/apache/wicket/mock/MockPageManager.java
+++ b/wicket-core/src/main/java/org/apache/wicket/mock/MockPageManager.java
@@ -21,7 +21,7 @@ import java.util.Map;
 
 import org.apache.wicket.page.IManageablePage;
 import org.apache.wicket.page.IPageManager;
-import org.apache.wicket.page.IPageManagerContext;
+import org.apache.wicket.pageStore.IPageStore;
 
 /**
  * Simple {@link IPageManager} used for testing.
@@ -32,19 +32,12 @@ public class MockPageManager implements IPageManager
 {
 	private final Map<Integer, IManageablePage> pages = new HashMap<>();
 
-	/**
-	 * Construct.
-	 * 
-	 */
-	public MockPageManager()
-	{
-	}
-
 	@Override
-	public void commitRequest()
+	public boolean supportsVersioning()
 	{
+		return false;
 	}
-
+	
 	@Override
 	public void destroy()
 	{
@@ -59,54 +52,29 @@ public class MockPageManager implements IPageManager
 
 	@Override
 	public void removePage(final IManageablePage page) {
-		if (page != null) {
-			pages.remove(page.getPageId());
-		}
+		pages.remove(page.getPageId());
 	}
 
 	@Override
-	public void newSessionCreated()
+	public void addPage(IManageablePage page)
 	{
-		pages.clear();
+		pages.put(page.getPageId(), page);
 	}
 
 	@Override
-	public void clear()
+	public void removeAllPages()
 	{
 		pages.clear();
 	}
 
-	/**
-	 * @param context
-	 */
-	public void setContext(IPageManagerContext context)
-	{
-	}
-
 	@Override
-	public boolean supportsVersioning()
-	{
-		return true;
-	}
-
-	@Override
-	public void touchPage(IManageablePage page)
+	public void detach()
 	{
-		if (true || page.isPageStateless() == false)
-		{
-			pages.put(page.getPageId(), page);
-		}
-	}
-
-	@Override
-	public void untouchPage(IManageablePage page)
-	{
-		pages.remove(page.getPageId());
 	}
 
 	@Override
-	public IPageManagerContext getContext()
+	public IPageStore getPageStore()
 	{
-		return null;
+		throw new UnsupportedOperationException();
 	}
-}
+}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/mock/MockPageManager.java b/wicket-core/src/main/java/org/apache/wicket/mock/MockPageStore.java
similarity index 51%
copy from wicket-core/src/main/java/org/apache/wicket/mock/MockPageManager.java
copy to wicket-core/src/main/java/org/apache/wicket/mock/MockPageStore.java
index b27dc7e..ec9c52c 100644
--- a/wicket-core/src/main/java/org/apache/wicket/mock/MockPageManager.java
+++ b/wicket-core/src/main/java/org/apache/wicket/mock/MockPageStore.java
@@ -16,97 +16,74 @@
  */
 package org.apache.wicket.mock;
 
-import java.util.HashMap;
-import java.util.Map;
+import java.util.LinkedList;
+import java.util.List;
 
 import org.apache.wicket.page.IManageablePage;
-import org.apache.wicket.page.IPageManager;
-import org.apache.wicket.page.IPageManagerContext;
+import org.apache.wicket.pageStore.IPageContext;
+import org.apache.wicket.pageStore.IPageStore;
 
-/**
- * Simple {@link IPageManager} used for testing.
- * 
- * @author Matej Knopp
- */
-public class MockPageManager implements IPageManager
+public class MockPageStore implements IPageStore
 {
-	private final Map<Integer, IManageablePage> pages = new HashMap<>();
-
-	/**
-	 * Construct.
-	 * 
-	 */
-	public MockPageManager()
-	{
-	}
+	private final LinkedList<IManageablePage> pages = new LinkedList<>();
 
 	@Override
-	public void commitRequest()
+	public boolean supportsVersioning()
 	{
+		// pretend versioning support, although pages are not actually serialized
+		return true;
 	}
-
+	
 	@Override
 	public void destroy()
 	{
 		pages.clear();
 	}
 
-	@Override
-	public IManageablePage getPage(int id)
+	public List<IManageablePage> getPages()
 	{
-		return pages.get(id);
+		return pages;
 	}
 
 	@Override
-	public void removePage(final IManageablePage page) {
-		if (page != null) {
-			pages.remove(page.getPageId());
+	public IManageablePage getPage(IPageContext context, int id)
+	{
+		for (IManageablePage page : pages) {
+			if (page.getPageId() == id) {
+				return page;
+			}
 		}
+		return null;
 	}
 
 	@Override
-	public void newSessionCreated()
+	public void removePage(IPageContext context, final IManageablePage page)
 	{
-		pages.clear();
+		for (IManageablePage candidate : pages) {
+			if (candidate.getPageId() == page.getPageId()) {
+				pages.remove(candidate);
+				return;
+			}
+		}
 	}
 
 	@Override
-	public void clear()
+	public void removeAllPages(IPageContext context)
 	{
 		pages.clear();
 	}
 
-	/**
-	 * @param context
-	 */
-	public void setContext(IPageManagerContext context)
-	{
-	}
-
 	@Override
-	public boolean supportsVersioning()
+	public boolean canBeAsynchronous(IPageContext context)
 	{
 		return true;
 	}
 
 	@Override
-	public void touchPage(IManageablePage page)
+	public void addPage(IPageContext context, IManageablePage page)
 	{
-		if (true || page.isPageStateless() == false)
-		{
-			pages.put(page.getPageId(), page);
-		}
-	}
-
-	@Override
-	public void untouchPage(IManageablePage page)
-	{
-		pages.remove(page.getPageId());
-	}
-
-	@Override
-	public IPageManagerContext getContext()
-	{
-		return null;
+		removePage(context, page);
+		
+		pages.addLast(page);
 	}
-}
+}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/page/AbstractPageManager.java b/wicket-core/src/main/java/org/apache/wicket/page/AbstractPageManager.java
deleted file mode 100644
index 03b26aa..0000000
--- a/wicket-core/src/main/java/org/apache/wicket/page/AbstractPageManager.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.wicket.page;
-
-import org.apache.wicket.util.lang.Args;
-
-/**
- * Convenience class for {@link IPageManager} implementations. Subclass should extend
- * {@link RequestAdapter} and override {@link #newRequestAdapter(IPageManagerContext)} method to return it's
- * {@link RequestAdapter} implementation.
- * 
- * @author Matej Knopp
- */
-public abstract class AbstractPageManager implements IPageManager
-{
-	private final IPageManagerContext context;
-
-	/**
-	 * Construct.
-	 * 
-	 * @param context
-	 */
-	public AbstractPageManager(IPageManagerContext context)
-	{
-		this.context = Args.notNull(context, "context");
-	}
-
-	/**
-	 * 
-	 * @param context
-	 * @return a new request adapter
-	 */
-	protected abstract RequestAdapter newRequestAdapter(IPageManagerContext context);
-
-	/**
-	 * @return The page manager context
-	 */
-	@Override
-	public IPageManagerContext getContext()
-	{
-		return context;
-	}
-
-	/**
-	 * @see #newRequestAdapter(IPageManagerContext)
-	 * @return the request adapter
-	 */
-	protected RequestAdapter getRequestAdapter()
-	{
-		RequestAdapter adapter = (RequestAdapter)getContext().getRequestData();
-		if (adapter == null)
-		{
-			adapter = newRequestAdapter(getContext());
-			getContext().setRequestData(adapter);
-		}
-		return adapter;
-	}
-
-	@Override
-	public void commitRequest()
-	{
-		getRequestAdapter().commitRequest();
-	}
-
-	@Override
-	public IManageablePage getPage(int id)
-	{
-		IManageablePage page = getRequestAdapter().getPage(id);
-		if (page != null)
-		{
-			touchPage(page);
-		}
-		return page;
-	}
-
-	@Override
-	public void removePage(final IManageablePage page) {
-		if (page != null)
-		{
-			getRequestAdapter().removePage(page);
-		}
-	}
-
-	@Override
-	public void newSessionCreated()
-	{
-		getRequestAdapter().newSessionCreated();
-	}
-
-	@Override
-	public void touchPage(IManageablePage page)
-	{
-		if (page != null)
-		{
-			getRequestAdapter().touch(page);
-		}
-	}
-
-	@Override
-	public void untouchPage(IManageablePage page)
-	{
-		if (page != null)
-		{
-			getRequestAdapter().untouch(page);
-		}
-	}
-}
diff --git a/wicket-core/src/main/java/org/apache/wicket/page/DefaultPageManagerContext.java b/wicket-core/src/main/java/org/apache/wicket/page/DefaultPageManagerContext.java
deleted file mode 100644
index 8607cfc..0000000
--- a/wicket-core/src/main/java/org/apache/wicket/page/DefaultPageManagerContext.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.wicket.page;
-
-import java.io.Serializable;
-
-import org.apache.wicket.MetaDataKey;
-import org.apache.wicket.Session;
-import org.apache.wicket.request.cycle.RequestCycle;
-
-/**
- * Wicket's default page manager context
- * 
- * @author Juergen Donnerstag
- */
-public class DefaultPageManagerContext implements IPageManagerContext
-{
-	private static final MetaDataKey<Object> requestCycleMetaDataKey = new MetaDataKey<>()
-	{
-		private static final long serialVersionUID = 1L;
-	};
-
-	/**
-	 * @see org.apache.wicket.page.IPageManagerContext#bind()
-	 */
-	@Override
-	public void bind()
-	{
-		Session.get().bind();
-	}
-
-	/**
-	 * @see org.apache.wicket.page.IPageManagerContext#getRequestData()
-	 */
-	@Override
-	public Object getRequestData()
-	{
-		RequestCycle requestCycle = RequestCycle.get();
-		if (requestCycle == null)
-		{
-			throw new IllegalStateException("Not a request thread.");
-		}
-		return requestCycle.getMetaData(requestCycleMetaDataKey);
-	}
-
-	/**
-	 * @see org.apache.wicket.page.IPageManagerContext#getSessionAttribute(java.lang.String)
-	 */
-	@Override
-	public Serializable getSessionAttribute(final String key)
-	{
-		return Session.get().getAttribute(key);
-	}
-
-	/**
-	 * @see org.apache.wicket.page.IPageManagerContext#getSessionId()
-	 */
-	@Override
-	public String getSessionId()
-	{
-		return Session.get().getId();
-	}
-
-	/**
-	 * @see org.apache.wicket.page.IPageManagerContext#setRequestData(Object)
-	 */
-	@Override
-	public void setRequestData(final Object data)
-	{
-		RequestCycle requestCycle = RequestCycle.get();
-		if (requestCycle == null)
-		{
-			throw new IllegalStateException("Not a request thread.");
-		}
-		requestCycle.setMetaData(requestCycleMetaDataKey, data);
-	}
-
-	/**
-	 * @see org.apache.wicket.page.IPageManagerContext#setSessionAttribute(java.lang.String,
-	 *      java.io.Serializable)
-	 */
-	@Override
-	public void setSessionAttribute(String key, Serializable value)
-	{
-		Session.get().setAttribute(key, value);
-	}
-}
diff --git a/wicket-core/src/main/java/org/apache/wicket/page/IPageManager.java b/wicket-core/src/main/java/org/apache/wicket/page/IPageManager.java
index a19a0e3..8f4cd0e 100644
--- a/wicket-core/src/main/java/org/apache/wicket/page/IPageManager.java
+++ b/wicket-core/src/main/java/org/apache/wicket/page/IPageManager.java
@@ -16,89 +16,81 @@
  */
 package org.apache.wicket.page;
 
+import org.apache.wicket.Page;
+import org.apache.wicket.pageStore.IPageStore;
 
 /**
- * Page manager.
+ * A manager of pages - facade between {@link Page}s and {@link IPageStore}s they are stored in.
  * 
- * @author Matej Knopp
+ * @see PageManager
  */
 public interface IPageManager
 {
 	/**
+	 * Is versionining of pages supported, see {@link IPageStore#supportsVersioning()}.
 	 * 
-	 * @return the page manager context
+	 * @return {@code true} if versioning is supported
 	 */
-	IPageManagerContext getContext();
+	boolean supportsVersioning();
 
 	/**
-	 * Retrieve page instance with given id.
+	 * Get a page
 	 * 
-	 * @param id
-	 *      the id of the page to load
-	 * @return page instance or <code>null</code>
-	 * @throws CouldNotLockPageException if the page is already locked by another thread
-	 * and the lock cannot be acquired for some timeout
+	 * @param pageId
+	 *            id of page
+	 * @return page, may be <code>null</code>
 	 */
-	IManageablePage getPage(int id) throws CouldNotLockPageException;
+	IManageablePage getPage(int pageId);
 
 	/**
-	 * Removes a page from the {@link org.apache.wicket.pageStore.IPageStore} and
-	 * {@link org.apache.wicket.pageStore.IDataStore}. Any attempt to access it later
-	 * will lead to {@link org.apache.wicket.protocol.http.PageExpiredException}
-	 *
-	 * @param page The page instance to remove from the stores
+	 * Remove a page
+	 * 
+	 * @param page
+	 *            page to remove
 	 */
 	void removePage(IManageablePage page);
 
 	/**
-	 * Marks page as changed.
-	 * <p><strong>Note:</strong>Only stateful pages are stored.</p>
+	 * Add a page.
 	 * 
 	 * @param page
-	 *      the page that should be stored in the page stores at the end of the request.
-	 * @throws CouldNotLockPageException if the page is already locked by another thread
-	 * and the lock cannot be acquired for some timeout
+	 *            page to add
 	 */
-	void touchPage(IManageablePage page);
-
+	void addPage(IManageablePage page);
+	
 	/**
-	 * Marks page as non-changed.
-	 * Could be used in Ajax requests to avoid storing the page if no changes have happened.
-	 *
-	 * @param page
-	 *      the page that should <strong>not</strong> be stored in the page stores at the end of the request.
-	 * @throws CouldNotLockPageException if the page is already locked by another thread
-	 * and the lock cannot be acquired for some timeout
+	 * Remove all pages.
 	 */
-	void untouchPage(IManageablePage page);
+	void removeAllPages();
 
 	/**
-	 * Returns whether this manager supports versioning. Managers that support versioning must store
-	 * page snapshots.
-	 * 
-	 * @return whether this page manager supports versioning
+	 * Detach at end of request.
 	 */
-	boolean supportsVersioning();
+	void detach();
 
 	/**
-	 * Commits the changes to external storage if the manager uses it.
-	 * 
-	 * Should also detach all pages that were touched during this request.
+	 * Destroy when application is destroyed.
 	 */
-	void commitRequest();
+	void destroy();
 
 	/**
-	 * Invoked when new session has been created.
+	 * Get the storage of pages, optional.
+	 *  
+	 * @return store or <code>null</code>
 	 */
-	void newSessionCreated();
+	IPageStore getPageStore();
 
 	/**
-	 * Clears all data for the current session.
+	 * @deprecated will be removed in Wicket 10
 	 */
-	void clear();
+	default void touchPage(IManageablePage page) {
+		addPage(page);
+	}
 
 	/**
-	 * Destroy the page manager.
+	 * @deprecated will be removed in Wicket 10
 	 */
-	void destroy();
+	default void clear() {
+		removeAllPages();
+	}
 }
diff --git a/wicket-core/src/main/java/org/apache/wicket/page/IPageManagerContext.java b/wicket-core/src/main/java/org/apache/wicket/page/IPageManagerContext.java
deleted file mode 100644
index d928606..0000000
--- a/wicket-core/src/main/java/org/apache/wicket/page/IPageManagerContext.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.wicket.page;
-
-import java.io.Serializable;
-
-/**
- * Context object for {@link IPageManager}. This decouples the {@link IPageManager} from request
- * cycle and session.
- * 
- * @author Matej Knopp
- */
-public interface IPageManagerContext
-{
-	/**
-	 * 
-	 * @param data
-	 */
-	void setRequestData(Object data);
-
-	/**
-	 * 
-	 * @return request data
-	 */
-	Object getRequestData();
-
-	/**
-	 * 
-	 * @param key
-	 * @param value
-	 */
-	void setSessionAttribute(String key, Serializable value);
-
-	/**
-	 * 
-	 * @param key
-	 * @return The session attribute associate with the key
-	 */
-	Serializable getSessionAttribute(String key);
-
-	/**
-	 * Tells Wicket to bind the current session. This make a temporary session become persistent
-	 * across requests.
-	 */
-	void bind();
-
-	/**
-	 * 
-	 * @return session id
-	 */
-	String getSessionId();
-}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/page/PageAccessSynchronizer.java b/wicket-core/src/main/java/org/apache/wicket/page/PageAccessSynchronizer.java
index d44b86a..8d69fda 100644
--- a/wicket-core/src/main/java/org/apache/wicket/page/PageAccessSynchronizer.java
+++ b/wicket-core/src/main/java/org/apache/wicket/page/PageAccessSynchronizer.java
@@ -23,6 +23,7 @@ import java.util.concurrent.ConcurrentMap;
 import java.util.function.Supplier;
 
 import org.apache.wicket.Application;
+import org.apache.wicket.pageStore.IPageStore;
 import org.apache.wicket.settings.ExceptionSettings.ThreadDumpStrategy;
 import org.apache.wicket.util.LazyInitializer;
 import org.apache.wicket.util.lang.Threads;
@@ -230,21 +231,27 @@ public class PageAccessSynchronizer implements Serializable
 	/**
 	 * Wraps a page manager with this synchronizer
 	 * 
-	 * @param pagemanager
+	 * @param manager
 	 * @return wrapped page manager
 	 */
-	public IPageManager adapt(IPageManager pagemanager)
+	public IPageManager adapt(final IPageManager manager)
 	{
-		return new PageManagerDecorator(pagemanager)
+		return new IPageManager()
 		{
 			@Override
+			public boolean supportsVersioning()
+			{
+				return manager.supportsVersioning();
+			}
+			
+			@Override
 			public IManageablePage getPage(int pageId)
 			{
 				IManageablePage page = null;
 				try
 				{
 					lockPage(pageId);
-					page = super.getPage(pageId);
+					page = manager.getPage(pageId);
 				}
 				finally
 				{
@@ -257,13 +264,13 @@ public class PageAccessSynchronizer implements Serializable
 			}
 
 			@Override
-			public void removePage(final IManageablePage page) {
+			public void removePage(IManageablePage page)
+			{
 				if (page != null)
 				{
 					try
 					{
-						super.removePage(page);
-						untouchPage(page);
+						manager.removePage(page);
 					}
 					finally
 					{
@@ -273,24 +280,43 @@ public class PageAccessSynchronizer implements Serializable
 			}
 
 			@Override
-			public void touchPage(IManageablePage page)
+			public void addPage(IManageablePage page)
 			{
 				lockPage(page.getPageId());
-				super.touchPage(page);
+				
+				manager.addPage(page);
 			}
 
 			@Override
-			public void commitRequest()
+			public void removeAllPages()
+			{
+				manager.removeAllPages();
+			}
+			
+			@Override
+			public void detach()
 			{
 				try
 				{
-					super.commitRequest();
+					manager.detach();
 				}
 				finally
 				{
 					unlockAllPages();
 				}
 			}
+			
+			@Override
+			public IPageStore getPageStore()
+			{
+				return manager.getPageStore();
+			}
+			
+			@Override
+			public void destroy()
+			{
+				manager.destroy();
+			}
 		};
 	}
 
diff --git a/wicket-core/src/main/java/org/apache/wicket/page/PageManager.java b/wicket-core/src/main/java/org/apache/wicket/page/PageManager.java
new file mode 100644
index 0000000..52604cc
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/page/PageManager.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.page;
+
+import org.apache.wicket.pageStore.DefaultPageContext;
+import org.apache.wicket.pageStore.IPageContext;
+import org.apache.wicket.pageStore.IPageStore;
+import org.apache.wicket.util.lang.Args;
+
+/**
+ * Default implementation of a page manager.
+ * 
+ * @see IPageStore
+ * @see IPageContext
+ */
+public class PageManager implements IPageManager
+{
+	private final IPageStore store;
+	
+	public PageManager(IPageStore store) {
+		this.store = Args.notNull(store, "store");
+	}
+
+	@Override
+	public boolean supportsVersioning()
+	{
+		return store.supportsVersioning();
+	}
+	
+	@Override
+	public IManageablePage getPage(int pageId)
+	{
+		IPageContext context = createPageContext();
+		
+		IManageablePage page = store.getPage(context, pageId);
+		
+		if (page != null) {
+			store.addPage(context, page);
+		}
+		
+		return page;
+	}
+
+	@Override
+	public void removePage(IManageablePage page)
+	{
+		store.removePage(createPageContext(), page);
+	}
+
+	@Override
+	public void addPage(IManageablePage page)
+	{
+		store.addPage(createPageContext(), page);
+	}
+
+	@Override
+	public void removeAllPages()
+	{
+		store.removeAllPages(createPageContext());
+	}
+
+	@Override
+	public void detach()
+	{
+		store.detach(createPageContext());
+	}
+
+	/**
+	 * Factory method for an {@link IPageContext}, returns a {@link DefaultPageContext} by default.
+	 * 
+	 * @return context of a stored page
+	 */
+	protected IPageContext createPageContext()
+	{
+		return new DefaultPageContext();
+	}
+
+	@Override
+	public void destroy()
+	{
+		store.destroy();
+	}
+	
+	@Override
+	public IPageStore getPageStore()
+	{
+		return store;
+	}
+}
diff --git a/wicket-core/src/main/java/org/apache/wicket/page/PageStoreManager.java b/wicket-core/src/main/java/org/apache/wicket/page/PageStoreManager.java
deleted file mode 100644
index 3e8daee..0000000
--- a/wicket-core/src/main/java/org/apache/wicket/page/PageStoreManager.java
+++ /dev/null
@@ -1,520 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.wicket.page;
-
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-import javax.servlet.http.HttpSessionBindingEvent;
-import javax.servlet.http.HttpSessionBindingListener;
-
-import org.apache.wicket.pageStore.IPageStore;
-
-/**
- * 
- */
-public class PageStoreManager extends AbstractPageManager
-{
-	/**
-	 * A cache that holds all registered page managers. <br/>
-	 * applicationName -> page manager
-	 */
-	private static final ConcurrentMap<String, PageStoreManager> MANAGERS = new ConcurrentHashMap<>();
-
-	private static final String ATTRIBUTE_NAME = "wicket:persistentPageManagerData";
-
-	/**
-	 * A flag indicating whether this session entry is being re-set in the Session.
-	 * <p>
-	 * Web containers intercept
-	 * {@link javax.servlet.http.HttpSession#setAttribute(String, Object)} to detect changes and
-	 * replicate the session. If the attribute has been already bound in the session then
-	 * {@link SessionEntry#valueUnbound(HttpSessionBindingEvent)} might get called - this flag
-	 * helps us to ignore the invocation in that case.
-	 * 
-	 * @see SessionEntry#valueUnbound(HttpSessionBindingEvent)
-	 */
-	private static final ThreadLocal<Boolean> STORING_TOUCHED_PAGES = new ThreadLocal<Boolean>()
-	{
-		@Override
-		protected Boolean initialValue()
-		{
-			return Boolean.FALSE;
-		};
-	};
-
-	private final IPageStore pageStore;
-	
-	private final String applicationName;
-	
-	/**
-	 * Construct.
-	 * 
-	 * @param applicationName
-	 * @param pageStore
-	 * @param context
-	 */
-	public PageStoreManager(final String applicationName, final IPageStore pageStore,
-		final IPageManagerContext context)
-	{
-		super(context);
-
-		this.applicationName = applicationName;
-		this.pageStore = pageStore;
-
-		if (MANAGERS.containsKey(applicationName))
-		{
-			throw new IllegalStateException(
-				"Manager for application with key '" + applicationName + "' already exists.");
-		}
-		MANAGERS.put(applicationName, this);
-	}
-
-	/**
-	 * Represents entry for single session. This is stored as session attribute and caches pages
-	 * between requests.
-	 * 
-	 * @author Matej Knopp
-	 */
-	private static class SessionEntry implements Serializable, HttpSessionBindingListener
-	{
-		private static final long serialVersionUID = 1L;
-
-		private final String applicationName;
-
-		/**
-		 * The id handed to the {@link IPageStore} to identify the session.
-		 * <p>
-		 * Note: If the container changes a session's id, this field remains unchanged on its
-		 * initial value.
-		 */
-		private final String sessionId;
-
-		private transient List<IManageablePage> sessionCache;
-		private transient List<Object> afterReadObject;
-
-
-		/**
-		 * Construct.
-		 * 
-		 * @param applicationName
-		 * @param sessionId
-		 */
-		public SessionEntry(String applicationName, String sessionId)
-		{
-			this.applicationName = applicationName;
-			this.sessionId = sessionId;
-		}
-
-		/**
-		 * 
-		 * @return page store
-		 */
-		private IPageStore getPageStore()
-		{
-			PageStoreManager manager = MANAGERS.get(applicationName);
-
-			if (manager == null)
-			{
-				return null;
-			}
-
-			return manager.pageStore;
-		}
-
-		/**
-		 * 
-		 * @param id
-		 * @return null, if not found
-		 */
-		private IManageablePage findPage(int id)
-		{
-			if (sessionCache != null)
-			{
-				for (IManageablePage p : sessionCache)
-				{
-					if (p.getPageId() == id)
-					{
-						return p;
-					}
-				}
-			}
-			return null;
-		}
-
-		/**
-		 * Add the page to cached pages if page with same id is not already there
-		 * 
-		 * @param page
-		 */
-		private void addPage(IManageablePage page)
-		{
-			if (page != null)
-			{
-				if (findPage(page.getPageId()) != null)
-				{
-					return;
-				}
-
-				if (sessionCache == null)
-				{
-					sessionCache = new ArrayList<>();
-				}
-
-				sessionCache.add(page);
-			}
-		}
-
-		private synchronized void removePage(IManageablePage page)
-		{
-			if (page != null)
-			{
-				if (sessionCache != null)
-				{
-					sessionCache.remove(page);
-				}
-
-				final IPageStore pageStore = getPageStore();
-				if (pageStore != null)
-				{
-					pageStore.removePage(sessionId, page.getPageId());
-				}
-			}
-		}
-
-		/**
-		 * If the pages are stored in temporary state (after deserialization) this method convert
-		 * them to list of "real" pages
-		 */
-		private void convertAfterReadObjects()
-		{
-			if (sessionCache == null)
-			{
-				sessionCache = new ArrayList<>();
-			}
-
-			final IPageStore pageStore = getPageStore();
-			if (pageStore != null)
-			{
-				for (Object o : afterReadObject)
-				{
-					IManageablePage page = pageStore.convertToPage(o);
-					addPage(page);
-				}
-			}
-
-			afterReadObject = null;
-		}
-
-		/**
-		 * 
-		 * @param id
-		 * @return manageable page
-		 */
-		public synchronized IManageablePage getPage(int id)
-		{
-			// check if pages are in deserialized state
-			if (afterReadObject != null && afterReadObject.isEmpty() == false)
-			{
-				convertAfterReadObjects();
-			}
-
-			IManageablePage page = null;
-			// try to find page with same id
-			if (sessionCache != null)
-			{
-				page = findPage(id);
-				if (page != null)
-				{
-					return page;
-				}
-			}
-
-			// not found, ask pagestore for the page
-			final IPageStore pageStore = getPageStore();
-			if (pageStore != null)
-			{
-				page = pageStore.getPage(sessionId, id);
-			}
-			return page;
-		}
-
-		/**
-		 * set the list of pages to remember after the request
-		 * 
-		 * @param pages
-		 */
-		public synchronized void setSessionCache(final List<IManageablePage> pages)
-		{
-			sessionCache = new ArrayList<>(pages);
-			afterReadObject = null;
-		}
-
-		/**
-		 * Serializes all pages in this {@link SessionEntry}. If this is http worker thread then
-		 * there is available {@link IPageStore} which will be asked to prepare the page for
-		 * serialization (see DefaultPageStore$SerializePage). If there is no {@link IPageStore}
-		 * available (session loading/persisting in application initialization/destruction thread)
-		 * then the pages are serialized without any pre-processing
-		 * 
-		 * @param s
-		 * @throws IOException
-		 */
-		private void writeObject(final ObjectOutputStream s) throws IOException
-		{
-			s.defaultWriteObject();
-
-			// prepare for serialization and store the pages
-			List<Serializable> serializedPages = new ArrayList<Serializable>();
-			if (sessionCache != null)
-			{
-				IPageStore pageStore = getPageStore();
-				for (IManageablePage p : sessionCache)
-				{
-					Serializable preparedPage;
-					if (pageStore != null)
-					{
-						preparedPage = pageStore.prepareForSerialization(sessionId, p);
-					}
-					else
-					{
-						preparedPage = p;
-					}
-
-					if (preparedPage != null)
-					{
-						serializedPages.add(preparedPage);
-					}
-				}
-			}
-			s.writeObject(serializedPages);
-		}
-
-		/**
-		 * Deserializes the pages in this {@link SessionEntry}. If this is http worker thread then
-		 * there is available {@link IPageStore} which will be asked to restore the page from its
-		 * optimized state (see DefaultPageStore$SerializePage). If there is no {@link IPageStore}
-		 * available (session loading/persisting in application initialization/destruction thread)
-		 * then the pages are deserialized without any post-processing
-		 * 
-		 * @param s
-		 * @throws IOException
-		 * @throws ClassNotFoundException
-		 */
-		@SuppressWarnings("unchecked")
-		private void readObject(final ObjectInputStream s)
-			throws IOException, ClassNotFoundException
-		{
-			s.defaultReadObject();
-
-			afterReadObject = new ArrayList<>();
-
-			List<Serializable> l = (List<Serializable>)s.readObject();
-
-			// convert to temporary state after deserialization (will need to be processed
-			// by convertAfterReadObject before the pages can be accessed)
-			IPageStore pageStore = getPageStore();
-			for (Serializable ser : l)
-			{
-				Object page;
-				if (pageStore != null)
-				{
-					page = pageStore.restoreAfterSerialization(ser);
-				}
-				else
-				{
-					page = ser;
-				}
-				afterReadObject.add(page);
-			}
-		}
-
-		@Override
-		public void valueBound(HttpSessionBindingEvent event)
-		{
-		}
-
-		@Override
-		public void valueUnbound(HttpSessionBindingEvent event)
-		{
-			if (STORING_TOUCHED_PAGES.get())
-			{
-				// triggered by #storeTouchedPages(), so do not remove the data
-				return;
-			}
-
-			// WICKET-5164 use the original sessionId
-			IPageStore store = getPageStore();
-			// store might be null if destroyed already
-			if (store != null)
-			{
-				store.unbind(sessionId);
-			}
-		}
-
-		@Override
-		public boolean equals(Object o)
-		{
-			// see https://issues.apache.org/jira/browse/WICKET-5390
-			return false;
-		}
-	}
-
-	private String getAttributeName()
-	{
-		return ATTRIBUTE_NAME + " - " + applicationName;
-	}
-
-	/**
-	 * {@link RequestAdapter} for {@link PageStoreManager}
-	 * 
-	 * @author Matej Knopp
-	 */
-	protected class PersistentRequestAdapter extends RequestAdapter
-	{
-		/**
-		 * Construct.
-		 * 
-		 * @param context
-		 */
-		public PersistentRequestAdapter(IPageManagerContext context)
-		{
-			super(context);
-		}
-
-		@Override
-		protected IManageablePage getPage(int id)
-		{
-			IManageablePage touchedPage = findPage(id);
-			if (touchedPage != null)
-			{
-				return touchedPage;
-			}
-
-			// try to get session entry for this session
-			SessionEntry entry = getSessionEntry(false);
-
-			if (entry != null)
-			{
-				return entry.getPage(id);
-			}
-			else
-			{
-				return null;
-			}
-		}
-
-		@Override
-		protected void removePage(final IManageablePage page)
-		{
-			final SessionEntry sessionEntry = getSessionEntry(false);
-			if (sessionEntry != null)
-			{
-				sessionEntry.removePage(page);
-			}
-		}
-
-		/**
-		 * 
-		 * @param create
-		 * @return Session Entry
-		 */
-		private SessionEntry getSessionEntry(boolean create)
-		{
-			SessionEntry entry = (SessionEntry)getSessionAttribute(getAttributeName());
-			if (entry == null && create)
-			{
-				bind();
-				entry = new SessionEntry(applicationName, getSessionId());
-			}
-			return entry;
-		}
-
-		@Override
-		protected void newSessionCreated()
-		{
-			// if the session is not temporary bind a session entry to it
-			if (getSessionId() != null)
-			{
-				getSessionEntry(true);
-			}
-		}
-
-		@Override
-		protected void storeTouchedPages(final List<IManageablePage> touchedPages)
-		{
-			if (!touchedPages.isEmpty())
-			{
-				SessionEntry entry = getSessionEntry(true);
-				entry.setSessionCache(touchedPages);
-				for (IManageablePage page : touchedPages)
-				{
-					// WICKET-5103 use the same sessionId as used in
-					// SessionEntry#getPage()
-					pageStore.storePage(entry.sessionId, page);
-				}
-
-				STORING_TOUCHED_PAGES.set(true);
-				try
-				{
-					setSessionAttribute(getAttributeName(), entry);
-				}
-				finally
-				{
-					STORING_TOUCHED_PAGES.remove();
-				}
-			}
-		}
-	}
-
-	@Override
-	protected RequestAdapter newRequestAdapter(IPageManagerContext context)
-	{
-		return new PersistentRequestAdapter(context);
-	}
-
-	@Override
-	public boolean supportsVersioning()
-	{
-		return true;
-	}
-
-	@Override
-	public void clear()
-	{
-		RequestAdapter requestAdapter = getRequestAdapter();
-		String sessionEntryAttributeName = getAttributeName();
-		Serializable sessionEntry = requestAdapter.getSessionAttribute(sessionEntryAttributeName);
-		if (sessionEntry instanceof SessionEntry)
-		{
-			((SessionEntry)sessionEntry).valueUnbound(null);
-		}
-	}
-
-	@Override
-	public void destroy()
-	{
-		MANAGERS.remove(applicationName);
-		pageStore.destroy();
-	}
-}
diff --git a/wicket-core/src/main/java/org/apache/wicket/page/RequestAdapter.java b/wicket-core/src/main/java/org/apache/wicket/page/RequestAdapter.java
deleted file mode 100644
index 7b8b7b9..0000000
--- a/wicket-core/src/main/java/org/apache/wicket/page/RequestAdapter.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.wicket.page;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Request scoped helper class for {@link IPageManager}.
- * 
- * @author Matej Knopp
- */
-public abstract class RequestAdapter
-{
-	private static final Logger log = LoggerFactory.getLogger(RequestAdapter.class);
-
-	private final IPageManagerContext context;
-
-	private final List<IManageablePage> touchedPages = new ArrayList<IManageablePage>();
-
-	/**
-	 * Construct.
-	 * 
-	 * @param context
-	 *            The page manager context
-	 */
-	public RequestAdapter(final IPageManagerContext context)
-	{
-		this.context = context;
-	}
-
-	/**
-	 * Returns the page with specified id. The page is then cached by {@link RequestAdapter} during
-	 * the rest of request processing.
-	 * 
-	 * @param id
-	 * @return page instance or <code>null</code> if the page does not exist.
-	 */
-	protected abstract IManageablePage getPage(int id);
-
-	/**
-	 * Removes a page from the cache and the stores ({@link org.apache.wicket.pageStore.IPageStore} and
-	 * {@link org.apache.wicket.pageStore.IDataStore}). Any attempt to access it later
-	 * will lead to {@link org.apache.wicket.protocol.http.PageExpiredException}
-	 *
-	 * @param page The page instance to remove
-	 */
-	protected abstract void removePage(final IManageablePage page);
-
-	/**
-	 * Store the list of stateful pages.
-	 * 
-	 * @param touchedPages
-	 */
-	protected abstract void storeTouchedPages(List<IManageablePage> touchedPages);
-
-	/**
-	 * Notification on new session being created.
-	 */
-	protected abstract void newSessionCreated();
-
-	/**
-	 * Bind the session
-	 * 
-	 * @see IPageManagerContext#bind()
-	 */
-	protected void bind()
-	{
-		context.bind();
-	}
-
-	/**
-	 * @see IPageManagerContext#setSessionAttribute(String, Serializable)
-	 * 
-	 * @param key
-	 * @param value
-	 */
-	public void setSessionAttribute(String key, Serializable value)
-	{
-		context.setSessionAttribute(key, value);
-	}
-
-	/**
-	 * @see IPageManagerContext#getSessionAttribute(String)
-	 * 
-	 * @param key
-	 * @return the session attribute
-	 */
-	public Serializable getSessionAttribute(final String key)
-	{
-		return context.getSessionAttribute(key);
-	}
-
-	/**
-	 * @see IPageManagerContext#getSessionId()
-	 * 
-	 * @return session id
-	 */
-	public String getSessionId()
-	{
-		return context.getSessionId();
-	}
-
-	/**
-	 * 
-	 * @param id
-	 * @return null, if not found
-	 */
-	protected IManageablePage findPage(final int id)
-	{
-		for (IManageablePage page : touchedPages)
-		{
-			if (page.getPageId() == id)
-			{
-				return page;
-			}
-		}
-		return null;
-	}
-
-	/**
-	 * Touches a page, so it will be stored in the page stores
-	 * at the end of the request cycle
-	 *
-	 * @param page The page to mark as dirty
-	 */
-	protected void touch(final IManageablePage page)
-	{
-		if (findPage(page.getPageId()) == null)
-		{
-			touchedPages.add(page);
-		}
-	}
-
-	/**
-	 * @param page The page to unmark as dirty, so it won't be stored
-	 *                at the end of the request cycle
-	 */
-	protected void untouch(final IManageablePage page)
-	{
-		Iterator<IManageablePage> iterator = touchedPages.iterator();
-		while (iterator.hasNext())
-		{
-			IManageablePage touchedPage = iterator.next();
-			if (touchedPage.getPageId() == page.getPageId())
-			{
-				iterator.remove();
-				break;
-			}
-		}
-	}
-
-	/**
-	 * 
-	 */
-	protected void commitRequest()
-	{
-		// store pages that are not stateless
-		if (touchedPages.isEmpty() == false)
-		{
-			List<IManageablePage> statefulPages = new ArrayList<IManageablePage>(
-				touchedPages.size());
-			for (IManageablePage page : touchedPages)
-			{
-				boolean isPageStateless;
-				try
-				{
-					isPageStateless = page.isPageStateless();
-				}
-				catch (Exception x)
-				{
-					log.warn("An error occurred while checking whether a page is stateless. Assuming it is stateful.", x);
-					isPageStateless = false;
-				}
-				if (isPageStateless == false)
-				{
-					statefulPages.add(page);
-				}
-			}
-
-			if (statefulPages.isEmpty() == false)
-			{
-				storeTouchedPages(statefulPages);
-			}
-		}
-	}
-}
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/AbstractCachingPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/AbstractCachingPageStore.java
deleted file mode 100644
index 86b8d82..0000000
--- a/wicket-core/src/main/java/org/apache/wicket/pageStore/AbstractCachingPageStore.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.wicket.pageStore;
-
-import org.apache.wicket.page.IManageablePage;
-import org.apache.wicket.serialize.ISerializer;
-import org.apache.wicket.util.lang.Args;
-
-/**
- * An abstract {@link org.apache.wicket.pageStore.IPageStore} that uses
- * {@link org.apache.wicket.pageStore.SecondLevelPageCache} to cache the stored pages in memory
- *
- * @param <P>
- *          The type of the page to be stored
- */
-public abstract class AbstractCachingPageStore<P> extends AbstractPageStore
-{
-	/**
-	 * The cache implementation
-	 */
-	protected final SecondLevelPageCache<String, Integer, P> pagesCache;
-
-	/**
-	 * Constructor.
-	 *
-	 * @param pageSerializer
-	 *          The serializer that will convert pages to/from byte[]
-	 * @param dataStore
-	 *          The third level page cache
-	 * @param pagesCache
-	 *          The cache to use as a second level store
-	 */
-	protected AbstractCachingPageStore(ISerializer pageSerializer, IDataStore dataStore,
-	                                   SecondLevelPageCache<String, Integer, P> pagesCache)
-	{
-		super(pageSerializer, dataStore);
-
-		this.pagesCache = Args.notNull(pagesCache, "pagesCache");
-	}
-
-	@Override
-	public IManageablePage getPage(final String sessionId, final int pageId)
-	{
-		P fromCache = pagesCache.getPage(sessionId, pageId);
-		if (fromCache != null)
-		{
-			return convertToPage(fromCache);
-		}
-
-		byte[] data = getPageData(sessionId, pageId);
-		if (data != null)
-		{
-			return deserializePage(data);
-		}
-		return null;
-	}
-
-	@Override
-	public void removePage(final String sessionId, final int pageId)
-	{
-		pagesCache.removePage(sessionId, pageId);
-		removePageData(sessionId, pageId);
-	}
-
-	@SuppressWarnings("unchecked")
-	@Override
-	public void storePage(final String sessionId, final IManageablePage page)
-	{
-		byte[] data = serializePage(page);
-		if (data != null)
-		{
-			int pageId = page.getPageId();
-			pagesCache.storePage(sessionId, pageId,  (P) page);
-			storePageData(sessionId, pageId, data);
-		}
-	}
-
-	@Override
-	public void unbind(final String sessionId)
-	{
-		removePageData(sessionId);
-		pagesCache.removePages(sessionId);
-	}
-
-	@Override
-	public void destroy()
-	{
-		super.destroy();
-		pagesCache.destroy();
-	}
-}
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/AbstractPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/AbstractPageStore.java
deleted file mode 100644
index 07f97d5..0000000
--- a/wicket-core/src/main/java/org/apache/wicket/pageStore/AbstractPageStore.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.wicket.pageStore;
-
-import java.io.Serializable;
-
-import org.apache.wicket.page.IManageablePage;
-import org.apache.wicket.serialize.ISerializer;
-import org.apache.wicket.util.lang.Args;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- *
- */
-public abstract class AbstractPageStore implements IPageStore
-{
-	private static final Logger LOG = LoggerFactory.getLogger(AbstractPageStore.class);
-
-	protected final IDataStore dataStore;
-
-	/**
-	 * The {@link org.apache.wicket.serialize.ISerializer} that will be used to convert pages from/to byte arrays
-	 */
-	protected final ISerializer pageSerializer;
-
-	protected AbstractPageStore(final ISerializer pageSerializer, final IDataStore dataStore)
-	{
-		Args.notNull(pageSerializer, "pageSerializer");
-		Args.notNull(dataStore, "dataStore");
-
-		this.pageSerializer = pageSerializer;
-		this.dataStore = dataStore;
-	}
-
-	@Override
-	public void destroy()
-	{
-		dataStore.destroy();
-	}
-
-	@Override
-	public Serializable prepareForSerialization(final String sessionId, final Serializable page)
-	{
-		if (dataStore.isReplicated())
-		{
-			return null;
-		}
-
-		return page;
-	}
-
-	@Override
-	public Object restoreAfterSerialization(final Serializable serializable)
-	{
-		return serializable;
-	}
-
-	/**
-	 * @param sessionId
-	 *          The id of the http session
-	 * @param pageId
-	 *          The id of page which serialized data should be got
-	 * @return page data
-	 * @see org.apache.wicket.pageStore.IDataStore#getData(String, int)
-	 */
-	protected byte[] getPageData(final String sessionId, final int pageId)
-	{
-		return dataStore.getData(sessionId, pageId);
-	}
-
-	/**
-	 * @param sessionId
-	 *          The id of the http session
-	 * @param pageId
-	 *          The id of page which serialized data should be removed
-	 * @see org.apache.wicket.pageStore.IDataStore#removeData(String, int)
-	 */
-	protected void removePageData(final String sessionId, final int pageId)
-	{
-		dataStore.removeData(sessionId, pageId);
-	}
-
-	/**
-	 * @param sessionId
-	 *          The id of the http session for which all data should be removed
-	 * @see org.apache.wicket.pageStore.IDataStore#removeData(String)
-	 */
-	protected void removePageData(final String sessionId)
-	{
-		dataStore.removeData(sessionId);
-	}
-
-	/**
-	 * @param sessionId
-	 *          The id of the http session
-	 * @param pageId
-	 *          The id of the page to store
-	 * @param data
-	 *          The serialized view of the page
-	 * @see org.apache.wicket.pageStore.IDataStore#storeData(String, int, byte[])
-	 */
-	protected void storePageData(final String sessionId, final int pageId, final byte[] data)
-	{
-		dataStore.storeData(sessionId, pageId, data);
-	}
-
-	/**
-	 * Serializes the passed page to byte[]
-	 *
-	 * @param page
-	 *          The page to serialize
-	 * @return the serialized view of the passed page
-	 */
-	protected byte[] serializePage(final IManageablePage page)
-	{
-		Args.notNull(page, "page");
-
-		byte[] data = pageSerializer.serialize(page);
-
-		if (data == null && LOG.isWarnEnabled())
-		{
-			LOG.warn("Page {} cannot be serialized. See previous logs for possible reasons.", page);
-		}
-		return data;
-	}
-
-	/**
-	 *
-	 * @param data
-	 *          The serialized view of the page
-	 * @return page data deserialized
-	 */
-	protected IManageablePage deserializePage(final byte[] data)
-	{
-		Args.notNull(data, "data");
-
-		return (IManageablePage) pageSerializer.deserialize(data);
-	}
-}
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/AbstractPersistentPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/AbstractPersistentPageStore.java
new file mode 100644
index 0000000..17541ee
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/AbstractPersistentPageStore.java
@@ -0,0 +1,259 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.pageStore;
+
+import java.io.Serializable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+
+import org.apache.wicket.Session;
+import org.apache.wicket.page.IManageablePage;
+import org.apache.wicket.util.lang.Args;
+import org.apache.wicket.util.lang.Bytes;
+import org.apache.wicket.util.lang.Classes;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract base class for stores that keep an identifier in the session only, while holding the actual pages
+ * in a secondary persistent storage.
+ * 
+ * @see #getSessionIdentifier(IPageContext, boolean)
+ */
+public abstract class AbstractPersistentPageStore implements IPageStore
+{
+	private static final String KEY_PREFIX = "wicket:";
+
+	private static final Logger log = LoggerFactory.getLogger(AbstractPersistentPageStore.class);
+
+	/**
+	 * A cache holding all store, the key is the application name suffixed with the page store implementation class.
+	 */
+	private static final ConcurrentMap<String, AbstractPersistentPageStore> STORES = new ConcurrentHashMap<>();
+
+	private final String storeKey;
+
+	protected AbstractPersistentPageStore(String applicationName)
+	{
+		this.storeKey = Args.notNull(applicationName, "applicationName") + ":" + getClass().getSimpleName();
+
+		if (STORES.containsKey(storeKey))
+		{
+			throw new IllegalStateException(
+				"Store with key '" + storeKey + "' already exists.");
+		}
+		STORES.put(storeKey, this);
+	}
+
+	@Override
+	public void destroy()
+	{
+		STORES.remove(storeKey);
+	}
+
+	@Override
+	public boolean canBeAsynchronous(IPageContext context)
+	{
+		// session attribute must be added here *before* any asynchronous calls
+		// when session is no longer available
+		getSessionIdentifier(context, true);
+
+		return true;
+	}
+
+	@Override
+	public IManageablePage getPage(IPageContext context, int id)
+	{
+		String sessionIdentifier = getSessionIdentifier(context, false);
+		if (sessionIdentifier == null)
+		{
+			return null;
+		}
+
+		return getPersistedPage(sessionIdentifier, id);
+	}
+
+	protected abstract IManageablePage getPersistedPage(String sessionIdentifier, int id);
+	
+	@Override
+	public void removePage(IPageContext context, IManageablePage page)
+	{
+		String sessionIdentifier = getSessionIdentifier(context, false);
+		if (sessionIdentifier == null)
+		{
+			return;
+		}
+		
+		removePersistedPage(sessionIdentifier, page);
+	}
+
+	protected abstract void removePersistedPage(String sessionIdentifier, IManageablePage page);
+
+	@Override
+	public void removeAllPages(IPageContext context)
+	{
+		String sessionIdentifier = getSessionIdentifier(context, false);
+		if (sessionIdentifier == null)
+		{
+			return;
+		}
+		
+		removeAllPersistedPages(sessionIdentifier);
+	}
+
+	protected abstract void removeAllPersistedPages(String sessionIdentifier);
+
+	@Override
+	public void addPage(IPageContext context, IManageablePage page)
+	{
+		String sessionIdentifier = getSessionIdentifier(context, true);
+		
+		addPersistedPage(sessionIdentifier, page);
+	}
+
+	/**
+	 * Add a page.
+	 * 
+	 * @param sessionIdentifier identifier of session
+	 * @param page page to add
+	 */
+	protected abstract void addPersistedPage(String sessionIdentifier, IManageablePage page);
+
+	/**
+	 * Get the distinct and stable identifier for the given context.
+	 * 
+	 * @param context the context to identify
+	 * @param create should a new identifier be created if not there already
+	 */
+	private String getSessionIdentifier(IPageContext context, boolean create)
+	{
+		String key = KEY_PREFIX + Classes.simpleName(getClass());
+		
+		SessionAttribute attribute = context.getSessionAttribute(key, () -> {
+			if (create)
+			{
+				return new SessionAttribute(storeKey, createSessionIdentifier(context));
+			}
+			
+			return null;
+		});
+		
+		if (attribute == null)
+		{
+			return null;
+		}
+		return attribute.sessionIdentifier;
+	}
+
+	/**
+	 * Create an identifier for the given context.
+	 * <p>
+	 * Default implementation uses {@link IPageContext#getSessionId()}.
+	 * 
+	 * @param context context
+	 * @return identifier for the session
+	 */
+	protected String createSessionIdentifier(IPageContext context)
+	{
+		return context.getSessionId(true);
+	}
+
+	/**
+	 * Attribute held in session.
+	 */
+	private static class SessionAttribute implements Serializable, HttpSessionBindingListener
+	{
+
+		private final String storeKey;
+
+		/**
+		 * The identifier of the session, may not be equal to {@link Session#getId()}, e.g. when
+		 * the container changes the id after authorization.
+		 */
+		public final String sessionIdentifier;
+
+		public SessionAttribute(String storeKey, String sessionIdentifier)
+		{
+			this.storeKey = Args.notNull(storeKey, "storeKey");
+			this.sessionIdentifier = Args.notNull(sessionIdentifier, "sessionIdentifier");
+		}
+
+
+		@Override
+		public void valueBound(HttpSessionBindingEvent event)
+		{
+		}
+
+		@Override
+		public void valueUnbound(HttpSessionBindingEvent event)
+		{
+			AbstractPersistentPageStore store = STORES.get(storeKey);
+			if (store == null)
+			{
+				log.warn(
+					"Cannot remove data '{}' because disk store '{}' is no longer present.", sessionIdentifier, storeKey);
+			}
+			else
+			{
+				store.removeAllPersistedPages(sessionIdentifier);
+			}
+		}
+	}
+
+	public String getSessionIdentifier(IPageContext context)
+	{
+		return getSessionIdentifier(context, true);
+	}
+
+	protected static class PersistedPage implements IPersistedPage
+	{
+		private int pageId;
+
+		private String pageType;
+
+		private long pageSize;
+
+		public PersistedPage(int pageId, String pageType, long pageSize)
+		{
+			this.pageId = pageId;
+			this.pageType = pageType;
+			this.pageSize = pageSize;
+		}
+
+		@Override
+		public int getPageId()
+		{
+			return pageId;
+		}
+
+		@Override
+		public Bytes getPageSize()
+		{
+			return Bytes.bytes(pageSize);
+		}
+
+		@Override
+		public String getPageType()
+		{
+			return pageType;
+		}
+
+	}
+}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/AsynchronousDataStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/AsynchronousDataStore.java
deleted file mode 100644
index f749354..0000000
--- a/wicket-core/src/main/java/org/apache/wicket/pageStore/AsynchronousDataStore.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.wicket.pageStore;
-
-import java.util.Iterator;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.wicket.util.lang.Args;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Facade for {@link IDataStore} that does the actual saving in worker thread.
- * <p>
- * Creates an {@link Entry} for each triple (sessionId, pageId, data) and puts it in
- * {@link #entries} queue if there is room. Acts as producer.<br/>
- * Later {@link PageSavingRunnable} reads in blocking manner from {@link #entries} and saves each
- * entry. Acts as consumer.
- * </p>
- * It starts only one instance of {@link PageSavingRunnable} because all we need is to make the page
- * storing asynchronous. We don't want to write concurrently in the wrapped {@link IDataStore},
- * though it may happen in the extreme case when the queue is full. These cases should be avoided.
- *
- * @author Matej Knopp
- */
-public class AsynchronousDataStore implements IDataStore
-{
-	/** Log for reporting. */
-	private static final Logger log = LoggerFactory.getLogger(AsynchronousDataStore.class);
-
-	/**
-	 * The time to wait when adding an {@link Entry} into the entries. In millis.
-	 */
-	private static final long OFFER_WAIT = 30L;
-
-	/**
-	 * The time to wait for an entry to save with the wrapped {@link IDataStore}. In millis.
-	 */
-	private static final long POLL_WAIT = 1000L;
-
-	/**
-	 * The page saving thread.
-	 */
-	private Thread pageSavingThread;
-
-	/**
-	 * The wrapped {@link IDataStore} that actually stores that pages
-	 */
-	private final IDataStore dataStore;
-
-	/**
-	 * The queue where the entries which have to be saved are temporary stored
-	 */
-	private final BlockingQueue<Entry> entries;
-
-	/**
-	 * A map 'sessionId:::pageId' -> {@link Entry}. Used for fast retrieval of {@link Entry}s which
-	 * are not yet stored by the wrapped {@link IDataStore}
-	 */
-	private final ConcurrentMap<String, Entry> entryMap;
-
-	/**
-	 * Construct.
-	 *
-	 * @param dataStore
-	 *            the wrapped {@link IDataStore} that actually saved the data
-	 * @param capacity
-	 *            the capacity of the queue that delays the saving
-	 */
-	public AsynchronousDataStore(final IDataStore dataStore, final int capacity)
-	{
-		this.dataStore = dataStore;
-		entries = new LinkedBlockingQueue<>(capacity);
-		entryMap = new ConcurrentHashMap<>();
-
-		pageSavingThread = new Thread(new PageSavingRunnable(), "Wicket-AsyncDataStore-PageSavingThread");
-		pageSavingThread.setDaemon(true);
-		pageSavingThread.start();
-	}
-
-	@Override
-	public void destroy()
-	{
-		final Thread thread = pageSavingThread;
-		pageSavingThread = null;
-		if (thread != null && thread.isAlive())
-		{
-			try
-			{
-				thread.join();
-			} catch (InterruptedException e)
-			{
-				log.error(e.getMessage(), e);
-			}
-		}
-
-		dataStore.destroy();
-	}
-
-	/**
-	 * Little helper
-	 *
-	 * @param sessionId
-	 * @param id
-	 * @return Entry
-	 */
-	private Entry getEntry(final String sessionId, final int id)
-	{
-		return entryMap.get(getKey(sessionId, id));
-	}
-
-	@Override
-	public byte[] getData(final String sessionId, final int id)
-	{
-		Entry entry = getEntry(sessionId, id);
-		if (entry != null)
-		{
-			log.debug(
-				"Returning the data of a non-stored entry with sessionId '{}' and pageId '{}'",
-				sessionId, id);
-			return entry.data;
-		}
-		byte[] data = dataStore.getData(sessionId, id);
-
-		log.debug("Returning the data of a stored entry with sessionId '{}' and pageId '{}'",
-			sessionId, id);
-
-		return data;
-	}
-
-	@Override
-	public boolean isReplicated()
-	{
-		return dataStore.isReplicated();
-	}
-
-	@Override
-	public void removeData(final String sessionId, final int id)
-	{
-		String key = getKey(sessionId, id);
-		if (key != null)
-		{
-			Entry entry = entryMap.remove(key);
-			if (entry != null)
-			{
-				entries.remove(entry);
-			}
-		}
-
-		dataStore.removeData(sessionId, id);
-	}
-
-	@Override
-	public void removeData(final String sessionId)
-	{
-		for (Iterator<Entry> itor = entries.iterator(); itor.hasNext();)
-		{
-			Entry entry = itor.next();
-			if (entry != null) // this check is not needed in JDK6
-			{
-				String entrySessionId = entry.sessionId;
-
-				if (sessionId.equals(entrySessionId))
-				{
-					entryMap.remove(getKey(entry));
-					itor.remove();
-				}
-			}
-		}
-
-		dataStore.removeData(sessionId);
-	}
-
-	/**
-	 * Save the entry in the queue if there is a room or directly pass it to the wrapped
-	 * {@link IDataStore} if there is no such
-	 *
-	 * @see org.apache.wicket.pageStore.IDataStore#storeData(java.lang.String, int, byte[])
-	 */
-	@Override
-	public void storeData(final String sessionId, final int id, final byte[] data)
-	{
-		if (pageSavingThread == null)
-		{
-			return;
-		}
-		Entry entry = new Entry(sessionId, id, data);
-		String key = getKey(entry);
-		entryMap.put(key, entry);
-
-		try
-		{
-			boolean added = entries.offer(entry, OFFER_WAIT, TimeUnit.MILLISECONDS);
-
-			if (added == false)
-			{
-				log.debug("Storing synchronously data with id '{}' in session '{}'", id, sessionId);
-				entryMap.remove(key);
-				dataStore.storeData(sessionId, id, data);
-			}
-		}
-		catch (InterruptedException e)
-		{
-			log.error(e.getMessage(), e);
-			if (pageSavingThread != null)
-			{
-				entryMap.remove(key);
-				dataStore.storeData(sessionId, id, data);
-			}
-		}
-	}
-
-	/**
-	 *
-	 * @param pageId
-	 * @param sessionId
-	 * @return generated key
-	 */
-	private static String getKey(final String sessionId, final int pageId)
-	{
-		return pageId + ":::" + sessionId;
-	}
-
-	/**
-	 *
-	 * @param entry
-	 * @return generated key
-	 */
-	private static String getKey(final Entry entry)
-	{
-		return getKey(entry.sessionId, entry.pageId);
-	}
-
-	/**
-	 * The structure used for an entry in the queue
-	 */
-	private static class Entry
-	{
-		private final String sessionId;
-		private final int pageId;
-		private final byte data[];
-
-		public Entry(final String sessionId, final int pageId, final byte data[])
-		{
-			this.sessionId = Args.notNull(sessionId, "sessionId");
-			this.pageId = pageId;
-			this.data = Args.notNull(data, "data");
-		}
-
-		@Override
-		public int hashCode()
-		{
-			final int prime = 31;
-			int result = 1;
-			result = prime * result + pageId;
-			result = prime * result + ((sessionId == null) ? 0 : sessionId.hashCode());
-			return result;
-		}
-
-		@Override
-		public boolean equals(Object obj)
-		{
-			if (this == obj)
-				return true;
-			if (obj == null)
-				return false;
-			if (getClass() != obj.getClass())
-				return false;
-			Entry other = (Entry)obj;
-			if (pageId != other.pageId)
-				return false;
-			if (sessionId == null)
-			{
-				if (other.sessionId != null)
-					return false;
-			}
-			else if (!sessionId.equals(other.sessionId))
-				return false;
-			return true;
-		}
-
-		@Override
-		public String toString()
-		{
-			return "Entry [sessionId=" + sessionId + ", pageId=" + pageId + "]";
-		}
-
-	}
-
-	/**
-	 * The thread that acts as consumer of {@link Entry}ies
-	 */
-	private class PageSavingRunnable implements Runnable
-	{
-		@Override
-		public void run()
-		{
-			while (pageSavingThread != null)
-			{
-				Entry entry = null;
-				try
-				{
-					entry = entries.poll(POLL_WAIT, TimeUnit.MILLISECONDS);
-				}
-				catch (InterruptedException e)
-				{
-					log.debug("PageSavingRunnable:: Interrupted...");
-				}
-
-				if (entry != null && pageSavingThread != null)
-				{
-					log.debug("PageSavingRunnable:: Saving asynchronously: {}...", entry);
-					dataStore.storeData(entry.sessionId, entry.pageId, entry.data);
-					entryMap.remove(getKey(entry));
-				}
-			}
-		}
-	}
-
-	@Override
-	public final boolean canBeAsynchronous()
-	{
-		// should not wrap in another AsynchronousDataStore
-		return false;
-	}
-}
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/AsynchronousPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/AsynchronousPageStore.java
index 13bed6e..caf17ce 100644
--- a/wicket-core/src/main/java/org/apache/wicket/pageStore/AsynchronousPageStore.java
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/AsynchronousPageStore.java
@@ -17,41 +17,44 @@
 package org.apache.wicket.pageStore;
 
 import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
 
+import org.apache.wicket.MetaDataKey;
+import org.apache.wicket.WicketRuntimeException;
 import org.apache.wicket.page.IManageablePage;
 import org.apache.wicket.util.lang.Args;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * Facade for {@link IPageStore} that does the actual saving in worker thread.
+ * Facade for {@link IPageStore} moving {@link #addPage(IPageContext, IManageablePage)} to a worker thread.
  * <p>
- * Creates an {@link Entry} for each double (sessionId, page) and puts it in {@link #entries} queue
- * if there is room. Acts as producer.<br/>
- * Later {@link PageSavingRunnable} reads in blocking manner from {@link #entries} and saves each
- * entry. Acts as consumer.
- * </p>
- * It starts only one instance of {@link PageSavingRunnable} because all we need is to make the page
+ * Creates an {@link PendingAdd} for {@link #addPage(IPageContext, IManageablePage)} and puts ito a {@link #queue}.
+ * Later {@link PageAddingRunnable} reads in blocking manner from {@link #queue} and performs the add.
+ * <p>
+ * It starts only one instance of {@link PageAddingRunnable} because all we need is to make the page
  * storing asynchronous. We don't want to write concurrently in the wrapped {@link IPageStore},
  * though it may happen in the extreme case when the queue is full. These cases should be avoided.
- *
- * Based on AsynchronousDataStore (@author Matej Knopp).
- *
+ * 
+ * @author Matej Knopp
  * @author manuelbarzi
  */
-public class AsynchronousPageStore implements IPageStore
+public class AsynchronousPageStore extends DelegatingPageStore
 {
 
 	/** Log for reporting. */
 	private static final Logger log = LoggerFactory.getLogger(AsynchronousPageStore.class);
 
 	/**
-	 * The time to wait when adding an {@link Entry} into the entries. In millis.
+	 * The time to wait when adding an {@link PendingAdd} into the entries. In millis.
 	 */
 	private static final long OFFER_WAIT = 30L;
 
@@ -63,27 +66,22 @@ public class AsynchronousPageStore implements IPageStore
 	/**
 	 * The page saving thread.
 	 */
-	private Thread pageSavingThread;
-
-	/**
-	 * The wrapped {@link IPageStore} that actually stores that pages
-	 */
-	private final IPageStore delegate;
+	private final Thread pageSavingThread;
 
 	/**
 	 * The queue where the entries which have to be saved are temporary stored
 	 */
-	private final BlockingQueue<Entry> entries;
+	private final BlockingQueue<PendingAdd> queue;
 
 	/**
-	 * A map 'sessionId:::pageId' -> {@link Entry}. Used for fast retrieval of {@link Entry}s which
+	 * A map 'sessionId:::pageId' -> {@link PendingAdd}. Used for fast retrieval of {@link PendingAdd}s which
 	 * are not yet stored by the wrapped {@link IPageStore}
 	 */
-	private final ConcurrentMap<String, Entry> entryMap;
+	private final ConcurrentMap<String, PendingAdd> queueMap;
 
 	/**
 	 * Construct.
-	 *
+	 * 
 	 * @param delegate
 	 *            the wrapped {@link IPageStore} that actually saved the page
 	 * @param capacity
@@ -91,29 +89,19 @@ public class AsynchronousPageStore implements IPageStore
 	 */
 	public AsynchronousPageStore(final IPageStore delegate, final int capacity)
 	{
-		this.delegate = Args.notNull(delegate, "delegate");
-		entries = new LinkedBlockingQueue<>(capacity);
-		entryMap = new ConcurrentHashMap<>();
+		super(delegate);
+		
+		queue = new LinkedBlockingQueue<>(capacity);
+		queueMap = new ConcurrentHashMap<>();
 
-		pageSavingThread = new Thread(new PageSavingRunnable(), "Wicket-AsyncPageStore-PageSavingThread");
+		PageAddingRunnable savingRunnable = new PageAddingRunnable(delegate, queue, queueMap);
+		pageSavingThread = new Thread(savingRunnable, "Wicket-AsyncPageStore-PageSavingThread");
 		pageSavingThread.setDaemon(true);
 		pageSavingThread.start();
 	}
 
 	/**
-	 * Little helper
-	 *
-	 * @param sessionId
-	 * @param pageId
-	 * @return Entry
-	 */
-	private Entry getEntry(final String sessionId, final int pageId)
-	{
-		return entryMap.get(getKey(sessionId, pageId));
-	}
-
-	/**
-	 *
+	 * 
 	 * @param pageId
 	 * @param sessionId
 	 * @return generated key
@@ -124,89 +112,180 @@ public class AsynchronousPageStore implements IPageStore
 	}
 
 	/**
-	 *
-	 * @param entry
-	 * @return generated key
+	 * An add of a page that is pending its asynchronous execution.
+	 * <p>
+	 * Used as an isolating {@link IPageContext} for the delegation to 
+	 * {@link IPageStore#addPage(IPageContext, IManageablePage)}.
 	 */
-	private static String getKey(final Entry entry)
+	private static class PendingAdd implements IPageContext
 	{
-		return getKey(entry.sessionId, entry.page.getPageId());
-	}
+		private final IPageContext context;
+		
+		private final IManageablePage page;
 
-	/**
-	 * The structure used for an entry in the queue
-	 */
-	private static class Entry
-	{
 		private final String sessionId;
-		private final IManageablePage page;
 
-		public Entry(final String sessionId, final IManageablePage page)
+		/**
+		 * Is this context passed to an asynchronously called {@link IPageStore#addPage(IPageContext, IManageablePage)}.
+		 */
+		private boolean asynchronous = false;
+
+		/**
+		 * Cache of session attributes which may filled in {@link IPageStore#canBeAsynchronous(IPageContext)},
+		 * so these are available asynchronously later on.
+		 */
+		private final Map<String, Serializable> attributeCache = new HashMap<>();
+
+		public PendingAdd(final IPageContext context, final IManageablePage page)
 		{
-			this.sessionId = Args.notNull(sessionId, "sessionId");
+			this.context = Args.notNull(context, "context");
 			this.page = Args.notNull(page, "page");
+			
+			this.sessionId = context.getSessionId(true);
+		}
+
+		/**
+		 * 
+		 * @param entry
+		 * @return generated key
+		 */
+		private String getKey()
+		{
+			return AsynchronousPageStore.getKey(sessionId, page.getPageId());
 		}
 
 		@Override
-		public int hashCode()
+		public String toString()
 		{
-			final int prime = 31;
-			int result = 1;
-			result = prime * result + page.getPageId();
-			result = prime * result + sessionId.hashCode();
-			return result;
+			return "PendingAdd [sessionId=" + sessionId + ", pageId=" + page.getPageId() + "]";
 		}
 
+		/**
+		 * Prevents access to request when called asynchronously.
+		 */
 		@Override
-		public boolean equals(Object obj)
+		public <T> T getRequestData(MetaDataKey<T> key, Supplier<T> value)
 		{
-			if (this == obj)
-				return true;
-			if (obj == null)
-				return false;
-			if (getClass() != obj.getClass())
-				return false;
-			Entry other = (Entry)obj;
-			if (page.getPageId() != other.page.getPageId())
-				return false;
-			if (!sessionId.equals(other.sessionId))
-				return false;
-			return true;
+			if (asynchronous)
+			{
+				throw new WicketRuntimeException("request data not available asynchronuously");
+			}
+			
+			return context.getRequestData(key, value);
 		}
 
+		/**
+		 * Prevents changing of session attributes when called asynchronously.
+		 * <p>
+		 * All values accessed from {@link IPageStore#canBeAsynchronous(IPageContext)} are still
+		 * available.
+		 */
+		@SuppressWarnings("unchecked")
 		@Override
-		public String toString()
+		public <T extends Serializable> T getSessionAttribute(String key, Supplier<T> defaultValue)
+		{
+			T value;
+			
+			if (asynchronous)
+			{
+				value = (T)attributeCache.get(key);
+				if (value == null )
+				{
+					if (defaultValue.get() != null)
+					{
+						throw new WicketRuntimeException("session attribute can not be changed asynchronuously");
+					}
+				}
+			} else {
+				value = context.getSessionAttribute(key, defaultValue);
+				if (value != null)
+				{
+					attributeCache.put(key, value);
+				}
+			}
+			
+			return value;
+		}
+		
+		/**
+		 * Prevents changing of session data when called asynchronously.
+		 */
+		@Override
+		public <T extends Serializable> T getSessionData(MetaDataKey<T> key, Supplier<T> defaultValue)
 		{
-			return "Entry [sessionId=" + sessionId + ", pageId=" + page.getPageId() + "]";
+			T value;
+			
+			if (asynchronous)
+			{
+				value = context.getSessionData(key, () -> null);
+				if (value == null )
+				{
+					if (defaultValue.get() != null)
+					{
+						throw new WicketRuntimeException("session data can not be changed asynchronuously");
+					}
+				}
+			}
+			else
+			{
+				value = context.getSessionData(key, defaultValue);
+			}
+			
+			return value;
 		}
 
+		/**
+		 * Returns id of session.
+		 */
+		@Override
+		public String getSessionId(boolean bind)
+		{
+			return sessionId;
+		}
 	}
 
 	/**
-	 * The thread that acts as consumer of {@link Entry}ies
+	 * The consumer of {@link PendingAdd}s.
 	 */
-	private class PageSavingRunnable implements Runnable
+	private static class PageAddingRunnable implements Runnable
 	{
+		private static final Logger log = LoggerFactory.getLogger(PageAddingRunnable.class);
+
+		private final BlockingQueue<PendingAdd> entries;
+
+		private final ConcurrentMap<String, PendingAdd> addQueue;
+
+		private final IPageStore delegate;
+
+		private PageAddingRunnable(IPageStore delegate, BlockingQueue<PendingAdd> entries,
+		                           ConcurrentMap<String, PendingAdd> entryMap)
+		{
+			this.delegate = delegate;
+			this.entries = entries;
+			this.addQueue = entryMap;
+		}
+
 		@Override
 		public void run()
 		{
-			while (pageSavingThread != null)
+			while (!Thread.interrupted())
 			{
-				Entry entry = null;
+				PendingAdd add = null;
 				try
 				{
-					entry = entries.poll(POLL_WAIT, TimeUnit.MILLISECONDS);
+					add = entries.poll(POLL_WAIT, TimeUnit.MILLISECONDS);
 				}
 				catch (InterruptedException e)
 				{
-					log.debug("PageSavingRunnable:: Interrupted...");
+					Thread.currentThread().interrupt();
 				}
 
-				if (entry != null && pageSavingThread != null)
+				if (add != null)
 				{
-					log.debug("PageSavingRunnable:: Saving asynchronously: {}...", entry);
-					delegate.storePage(entry.sessionId, entry.page);
-					entryMap.remove(getKey(entry));
+					log.debug("Saving asynchronously: {}...", add);
+					add.asynchronous = true;					
+					delegate.addPage(add, add.page);
+					addQueue.remove(add.getKey());
 				}
 			}
 		}
@@ -215,123 +294,118 @@ public class AsynchronousPageStore implements IPageStore
 	@Override
 	public void destroy()
 	{
-		final Thread thread = pageSavingThread;
-		pageSavingThread = null;
-		if (thread != null && thread.isAlive())
+		if (pageSavingThread.isAlive())
 		{
+			pageSavingThread.interrupt();
 			try
 			{
-				thread.join();
+				pageSavingThread.join();
 			}
 			catch (InterruptedException e)
 			{
 				log.error(e.getMessage(), e);
 			}
 		}
-		delegate.destroy();
+
+		super.destroy();
 	}
 
 	@Override
-	public IManageablePage getPage(String sessionId, int pageId)
+	public IManageablePage getPage(IPageContext context, int pageId)
 	{
-		Entry entry = getEntry(sessionId, pageId);
+		String sessionId = context.getSessionId(false);
+		if (sessionId == null) {
+			return null;
+		}
+		
+		PendingAdd entry = queueMap.get(getKey(sessionId, pageId));
 		if (entry != null)
 		{
-			log.debug(
-				"Returning the page of a non-stored entry with session id '{}' and page id '{}'",
-				sessionId, pageId);
+			log.debug("Returning the page of a non-stored entry with page id '{}'", pageId);
 			return entry.page;
 		}
-		IManageablePage page = delegate.getPage(sessionId, pageId);
+		IManageablePage page = getDelegate().getPage(context, pageId);
 
-		log.debug("Returning the page of a stored entry with session id '{}' and page id '{}'",
-			sessionId, pageId);
+		log.debug("Returning the page of a stored entry with page id '{}'", pageId);
 
 		return page;
 	}
 
 	@Override
-	public void removePage(String sessionId, int pageId)
+	public void removePage(IPageContext context, IManageablePage page)
 	{
-		String key = getKey(sessionId, pageId);
+		String sessionId = context.getSessionId(false);
+		if (sessionId == null) {
+			return;
+		}
+
+		String key = getKey(sessionId, page.getPageId());
 		if (key != null)
 		{
-			Entry entry = entryMap.remove(key);
+			PendingAdd entry = queueMap.remove(key);
 			if (entry != null)
 			{
-				entries.remove(entry);
+				queue.remove(entry);
 			}
 		}
 
-		delegate.removePage(sessionId, pageId);
+		getDelegate().removePage(context, page);
 	}
 
 	@Override
-	public void storePage(String sessionId, IManageablePage page)
+	public void addPage(IPageContext context, IManageablePage page)
 	{
-		if (pageSavingThread == null)
+		PendingAdd add = new PendingAdd(context, page);
+		if (getDelegate().canBeAsynchronous(add))
 		{
-			return;
-		}
-		Entry entry = new Entry(sessionId, page);
-		String key = getKey(entry);
-		entryMap.put(key, entry);
-
-		try
-		{
-			if (entries.offer(entry, OFFER_WAIT, TimeUnit.MILLISECONDS))
+			String key = add.getKey();
+			queueMap.put(key, add);
+			try
 			{
-				log.debug("Offered for storing asynchronously page with id '{}' in session '{}'",
-					page.getPageId(), sessionId);
+				if (queue.offer(add, OFFER_WAIT, TimeUnit.MILLISECONDS))
+				{
+					log.debug("Offered for storing asynchronously page with id '{}'", page.getPageId());
+					return;
+				}
+				else
+				{
+					log.debug("Storing synchronously page with id '{}'", page.getPageId());
+					queueMap.remove(key);
+				}
 			}
-			else
+			catch (InterruptedException e)
 			{
-				log.debug("Storing synchronously page with id '{}' in session '{}'",
-					page.getPageId(), sessionId);
-				entryMap.remove(key);
-				delegate.storePage(sessionId, page);
+				log.error(e.getMessage(), e);
+				queueMap.remove(key);
 			}
 		}
-		catch (InterruptedException e)
+		else
 		{
-			log.error(e.getMessage(), e);
-			if (pageSavingThread != null)
-			{
-				entryMap.remove(key);
-				delegate.storePage(sessionId, page);
-			}
+			log.warn("Delegated page store '{}' can not be asynchronous", getDelegate().getClass().getName());
 		}
+		
+		getDelegate().addPage(context, page);
 	}
 
 	@Override
-	public void unbind(String sessionId)
-	{
-		entries.removeIf(entry -> entry.sessionId.equals(sessionId));
-		delegate.unbind(sessionId);
-	}
-
-	@Override
-	public Serializable prepareForSerialization(String sessionId, Serializable page)
-	{
-		return delegate.prepareForSerialization(sessionId, page);
-	}
-
-	@Override
-	public Object restoreAfterSerialization(Serializable serializable)
-	{
-		return delegate.restoreAfterSerialization(serializable);
-	}
-
-	@Override
-	public IManageablePage convertToPage(Object page)
-	{
-		return delegate.convertToPage(page);
-	}
-
-	@Override
-	public boolean canBeAsynchronous()
+	public void removeAllPages(IPageContext context)
 	{
-		// should not wrap in another AsynchronousPageStore
-		return false;
+		String sessionId = context.getSessionId(false);
+		if (sessionId == null) {
+			return;
+		}
+		
+		Iterator<PendingAdd> iterator = queue.iterator();
+		while (iterator.hasNext())
+		{
+			PendingAdd add = iterator.next(); 
+		
+			if (add.sessionId.equals(sessionId))
+			{
+				iterator.remove();
+			}
+		}
+		
+		getDelegate().removeAllPages(context);
 	}
-}
+}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/DefaultPageContext.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/DefaultPageContext.java
new file mode 100644
index 0000000..57e7e3c
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/DefaultPageContext.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.pageStore;
+
+import java.io.Serializable;
+import java.util.function.Supplier;
+
+import org.apache.wicket.MetaDataKey;
+import org.apache.wicket.Session;
+import org.apache.wicket.request.cycle.RequestCycle;
+
+/**
+ * Default page context using {@link RequestCycle#getRequest()} and {@link Session#get()}.
+ * 
+ * @author Juergen Donnerstag
+ * @author svenmeier
+ */
+public class DefaultPageContext implements IPageContext
+{
+	
+	/**
+	 * @see org.apache.wicket.pageStore.IPageContext#getSessionId()
+	 */
+	@Override
+	public String getSessionId(boolean bind)
+	{
+		Session session = Session.get();
+
+		if (bind) {
+			session.bind();
+		}
+		
+		return session.getId();
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public <T extends Serializable> T getSessionAttribute(String key, Supplier<T> defaultValue)
+	{
+		Session session = Session.get();
+
+		synchronized (session)
+		{
+			T value = (T)session.getAttribute(key);
+			if (value == null) {
+				value = defaultValue.get();
+				if (value != null) {
+					session.bind();
+					session.setAttribute(key, value);
+				}
+			}
+			
+			return value;
+		}
+	}
+	
+	@Override
+	public <T extends Serializable> T getSessionData(MetaDataKey<T> key, Supplier<T> defaultValue)
+	{
+		Session session = Session.get();
+
+		synchronized (session)
+		{
+			T value = session.getMetaData(key);
+			if (value != null) {
+				return value;
+			}
+			
+			value = defaultValue.get();
+			if (value != null) {
+				session.bind();
+				session.setMetaData(key, value);
+			}
+			return value;
+		}
+	}
+
+	@Override
+	public <T> T getRequestData(MetaDataKey<T> key, Supplier<T> defaultValue)
+	{
+		RequestCycle requestCycle = RequestCycle.get();
+		if (requestCycle == null)
+		{
+			throw new IllegalStateException("Not a request thread.");
+		}
+		T value = requestCycle.getMetaData(key);
+		if (value == null) {
+			value = defaultValue.get();
+			if (value != null) {
+				requestCycle.setMetaData(key, value);
+			}
+		}
+		return value;
+	}
+}
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/DefaultPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/DefaultPageStore.java
deleted file mode 100644
index f324b85..0000000
--- a/wicket-core/src/main/java/org/apache/wicket/pageStore/DefaultPageStore.java
+++ /dev/null
@@ -1,469 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.wicket.pageStore;
-
-import java.io.Serializable;
-import java.lang.ref.SoftReference;
-import java.util.Iterator;
-import java.util.concurrent.ConcurrentLinkedDeque;
-
-import org.apache.wicket.page.IManageablePage;
-import org.apache.wicket.serialize.ISerializer;
-import org.apache.wicket.util.lang.Args;
-import org.apache.wicket.util.lang.Objects;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link IPageStore} that converts {@link IManageablePage} instances to {@link SerializedPage}s
- * before passing them to the {@link IDataStore} to store them and the same in the opposite
- * direction when loading {@link SerializedPage} from the data store.
- * 
- */
-public class DefaultPageStore extends AbstractCachingPageStore<DefaultPageStore.SerializedPage>
-{
-	private static final Logger LOG = LoggerFactory.getLogger(DefaultPageStore.class);
-
-	/**
-	 * Construct.
-	 * 
-	 * @param pageSerializer
-	 *            the {@link ISerializer} that will be used to convert pages from/to byte arrays
-	 * @param dataStore
-	 *            the {@link IDataStore} that actually stores the pages
-	 * @param cacheSize
-	 *            the number of pages to cache in memory before passing them to
-	 *            {@link IDataStore#storeData(String, int, byte[])}
-	 */
-	public DefaultPageStore(final ISerializer pageSerializer, final IDataStore dataStore,
-		final int cacheSize)
-	{
-		super(pageSerializer, dataStore, new SerializedPagesCache(cacheSize));
-	}
-
-	@Override
-	public void storePage(final String sessionId, final IManageablePage page)
-	{
-		SerializedPage serialized = createSerializedPage(sessionId, page);
-		if (serialized != null)
-		{
-			int pageId = page.getPageId();
-			pagesCache.storePage(sessionId, pageId, serialized);
-			storePageData(sessionId, pageId, serialized.getData());
-		}
-	}
-
-	@Override
-	public IManageablePage convertToPage(final Object object)
-	{
-		if (object == null)
-		{
-			return null;
-		}
-		else if (object instanceof IManageablePage)
-		{
-			return (IManageablePage)object;
-		}
-		else if (object instanceof SerializedPage)
-		{
-			SerializedPage page = (SerializedPage)object;
-			byte data[] = page.getData();
-			if (data == null)
-			{
-				data = getPageData(page.getSessionId(), page.getPageId());
-			}
-			if (data != null)
-			{
-				return deserializePage(data);
-			}
-			return null;
-		}
-
-		String type = object.getClass().getName();
-		throw new IllegalArgumentException("Unknown object type " + type);
-	}
-
-	/**
-	 * Reloads the {@link SerializedPage} from the backing {@link IDataStore} if the
-	 * {@link SerializedPage#data} is stripped earlier
-	 * 
-	 * @param serializedPage
-	 *            the {@link SerializedPage} with empty {@link SerializedPage#data} slot
-	 * @return the fully functional {@link SerializedPage}
-	 */
-	private SerializedPage restoreStrippedSerializedPage(final SerializedPage serializedPage)
-	{
-		SerializedPage result = pagesCache.getPage(serializedPage.getSessionId(),
-			serializedPage.getPageId());
-		if (result != null)
-		{
-			return result;
-		}
-
-		byte data[] = getPageData(serializedPage.getSessionId(), serializedPage.getPageId());
-		return new SerializedPage(serializedPage.getSessionId(), serializedPage.getPageId(), data);
-	}
-
-	@Override
-	public Serializable prepareForSerialization(final String sessionId, final Serializable page)
-	{
-		if (dataStore.isReplicated())
-		{
-			return null;
-		}
-
-		SerializedPage result = null;
-
-		if (page instanceof IManageablePage)
-		{
-			IManageablePage _page = (IManageablePage)page;
-			result = pagesCache.getPage(sessionId, _page.getPageId());
-			if (result == null)
-			{
-				result = createSerializedPage(sessionId, _page);
-				if (result != null)
-				{
-					pagesCache.storePage(sessionId, _page.getPageId(), result);
-				}
-			}
-		}
-		else if (page instanceof SerializedPage)
-		{
-			SerializedPage _page = (SerializedPage)page;
-			if (_page.getData() == null)
-			{
-				result = restoreStrippedSerializedPage(_page);
-			}
-			else
-			{
-				result = _page;
-			}
-		}
-
-		if (result != null)
-		{
-			return result;
-		}
-		return page;
-	}
-
-	/**
-	 * 
-	 * @return Always true for this implementation
-	 */
-	protected boolean storeAfterSessionReplication()
-	{
-		return true;
-	}
-
-	@Override
-	public Object restoreAfterSerialization(final Serializable serializable)
-	{
-		if (serializable == null)
-		{
-			return null;
-		}
-		else if (!storeAfterSessionReplication() || serializable instanceof IManageablePage)
-		{
-			return serializable;
-		}
-		else if (serializable instanceof SerializedPage)
-		{
-			SerializedPage page = (SerializedPage)serializable;
-			if (page.getData() != null)
-			{
-				storePageData(page.getSessionId(), page.getPageId(), page.getData());
-				return new SerializedPage(page.getSessionId(), page.getPageId(), null);
-			}
-			return page;
-		}
-
-		String type = serializable.getClass().getName();
-		throw new IllegalArgumentException("Unknown object type " + type);
-	}
-
-	/**
-	 * A representation of {@link IManageablePage} that knows additionally the id of the http
-	 * session in which this {@link IManageablePage} instance is used. The {@link #sessionId} and
-	 * {@link #pageId} are used for better clustering in the {@link IDataStore} structures.
-	 */
-	protected static class SerializedPage implements Serializable
-	{
-		private static final long serialVersionUID = 1L;
-
-		/**
-		 * The id of the serialized {@link IManageablePage}
-		 */
-		private final int pageId;
-
-		/**
-		 * The id of the http session in which the serialized {@link IManageablePage} is used.
-		 */
-		private final String sessionId;
-
-		/**
-		 * The serialized {@link IManageablePage}
-		 */
-		private final byte[] data;
-
-		public SerializedPage(String sessionId, int pageId, byte[] data)
-		{
-			this.pageId = pageId;
-			this.sessionId = sessionId;
-			this.data = data;
-		}
-
-		public byte[] getData()
-		{
-			return data;
-		}
-
-		public int getPageId()
-		{
-			return pageId;
-		}
-
-		public String getSessionId()
-		{
-			return sessionId;
-		}
-
-		@Override
-		public boolean equals(Object obj)
-		{
-			if (this == obj)
-			{
-				return true;
-			}
-			if ((obj instanceof SerializedPage) == false)
-			{
-				return false;
-			}
-			SerializedPage rhs = (SerializedPage)obj;
-			return Objects.equal(getPageId(), rhs.getPageId()) &&
-				Objects.equal(getSessionId(), rhs.getSessionId());
-		}
-
-		@Override
-		public int hashCode()
-		{
-			return Objects.hashCode(getPageId(), getSessionId());
-		}
-	}
-
-	/**
-	 * 
-	 * @param sessionId
-	 * @param page
-	 * @return the serialized page information
-	 */
-	protected SerializedPage createSerializedPage(final String sessionId, final IManageablePage page)
-	{
-		Args.notNull(sessionId, "sessionId");
-		Args.notNull(page, "page");
-
-		SerializedPage serializedPage = null;
-
-		byte[] data = serializePage(page);
-
-		if (data != null)
-		{
-			serializedPage = new SerializedPage(sessionId, page.getPageId(), data);
-		}
-		else if (LOG.isWarnEnabled())
-		{
-			LOG.warn("Page {} cannot be serialized. See previous logs for possible reasons.", page);
-		}
-		return serializedPage;
-	}
-
-	/**
-	 * Cache that stores serialized pages. This is important to make sure that a single page is not
-	 * serialized twice or more when not necessary.
-	 * <p>
-	 * For example a page is serialized during request, but it might be also later serialized on
-	 * session replication. The purpose of this cache is to make sure that the data obtained from
-	 * first serialization is reused on second serialization.
-	 * 
-	 * @author Matej Knopp
-	 */
-	static class SerializedPagesCache implements SecondLevelPageCache<String, Integer, SerializedPage>
-	{
-		private final int maxSize;
-
-		private final ConcurrentLinkedDeque<SoftReference<SerializedPage>> cache;
-
-		/**
-		 * Constructor.
-		 * 
-		 * @param maxSize
-		 *          The maximum number of entries to cache
-		 */
-		public SerializedPagesCache(final int maxSize)
-		{
-			this.maxSize = maxSize;
-			cache = new ConcurrentLinkedDeque<>();
-		}
-
-		/**
-		 * 
-		 * @param sessionId
-		 * @param pageId
-		 * @return the removed {@link SerializedPage} or <code>null</code> - otherwise
-		 */
-		@Override
-		public SerializedPage removePage(final String sessionId, final Integer pageId)
-		{
-			if (maxSize > 0)
-			{
-				Args.notNull(sessionId, "sessionId");
-				Args.notNull(pageId, "pageId");
-
-				SerializedPage sample = new SerializedPage(sessionId, pageId, null);
-
-				for (Iterator<SoftReference<SerializedPage>> i = cache.iterator(); i.hasNext();)
-				{
-					SoftReference<SerializedPage> ref = i.next();
-					SerializedPage entry = ref.get();
-					if (sample.equals(entry))
-					{
-						i.remove();
-						return entry;
-					}
-				}
-			}
-			return null;
-		}
-
-		/**
-		 * Removes all {@link SerializedPage}s for the session with <code>sessionId</code> from the
-		 * cache.
-		 * 
-		 * @param sessionId
-		 */
-		@Override
-		public void removePages(String sessionId)
-		{
-			if (maxSize > 0)
-			{
-				Args.notNull(sessionId, "sessionId");
-
-				for (Iterator<SoftReference<SerializedPage>> i = cache.iterator(); i.hasNext();)
-				{
-					SoftReference<SerializedPage> ref = i.next();
-					SerializedPage entry = ref.get();
-					if (entry != null && entry.getSessionId().equals(sessionId))
-					{
-						i.remove();
-					}
-				}
-			}
-		}
-
-		/**
-		 * Returns a {@link SerializedPage} by looking it up by <code>sessionId</code> and
-		 * <code>pageId</code>. If there is a match then it is <i>touched</i>, i.e. it is moved at
-		 * the top of the cache.
-		 * 
-		 * @param sessionId
-		 * @param pageId
-		 * @return the found serialized page or <code>null</code> when not found
-		 */
-		@Override
-		public SerializedPage getPage(String sessionId, Integer pageId)
-		{
-			SerializedPage result = null;
-			if (maxSize > 0)
-			{
-				Args.notNull(sessionId, "sessionId");
-				Args.notNull(pageId, "pageId");
-
-				SerializedPage sample = new SerializedPage(sessionId, pageId, null);
-
-				for (Iterator<SoftReference<SerializedPage>> i = cache.iterator(); i.hasNext();)
-				{
-					SoftReference<SerializedPage> ref = i.next();
-					SerializedPage entry = ref.get();
-					if (sample.equals(entry))
-					{
-						i.remove();
-						result = entry;
-						break;
-					}
-				}
-
-				if (result != null)
-				{
-					// move to top
-					internalStore(result);
-				}
-			}
-			return result;
-		}
-
-		/**
-		 * Store the serialized page in cache
-		 * 
-		 * @param page
-		 *      the data to serialize (page id, session id, bytes)
-		 */
-		@Override
-		public void storePage(String sessionId, Integer pageId, SerializedPage page)
-		{
-			if (maxSize > 0)
-			{
-				Args.notNull(sessionId, "sessionId");
-				Args.notNull(pageId, "pageId");
-				Args.notNull(page, "page");
-
-				for (Iterator<SoftReference<SerializedPage>> i = cache.iterator(); i.hasNext();)
-				{
-					SoftReference<SerializedPage> r = i.next();
-					SerializedPage entry = r.get();
-					if (entry != null && entry.equals(page))
-					{
-						i.remove();
-						break;
-					}
-				}
-
-				internalStore(page);
-			}
-		}
-
-		private void internalStore(SerializedPage page)
-		{
-			cache.push(new SoftReference<>(page));
-			while (cache.size() > maxSize)
-			{
-				cache.pollLast();
-			}
-		}
-
-		@Override
-		public void destroy()
-		{
-			cache.clear();
-		}
-	}
-
-	@Override
-	public boolean canBeAsynchronous()
-	{
-		return true;
-	}
-}
diff --git a/wicket-core/src/main/java/org/apache/wicket/page/PageManagerDecorator.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/DelegatingPageStore.java
similarity index 52%
rename from wicket-core/src/main/java/org/apache/wicket/page/PageManagerDecorator.java
rename to wicket-core/src/main/java/org/apache/wicket/pageStore/DelegatingPageStore.java
index 8f719f8..ba256a0 100644
--- a/wicket-core/src/main/java/org/apache/wicket/page/PageManagerDecorator.java
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/DelegatingPageStore.java
@@ -1,100 +1,83 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.wicket.page;
-
-import org.apache.wicket.util.lang.Args;
-
-/**
- * Decorator for {@link IPageManager}
- * 
- * @author igor
- */
-public class PageManagerDecorator implements IPageManager
-{
-	private final IPageManager delegate;
-
-	/**
-	 * Constructor
-	 * 
-	 * @param delegate
-	 */
-	public PageManagerDecorator(IPageManager delegate)
-	{
-		Args.notNull(delegate, "delegate");
-		this.delegate = delegate;
-	}
-
-	@Override
-	public IPageManagerContext getContext()
-	{
-		return delegate.getContext();
-	}
-
-	@Override
-	public IManageablePage getPage(int id)
-	{
-		return delegate.getPage(id);
-	}
-
-	@Override
-	public void removePage(final IManageablePage page) {
-		delegate.removePage(page);
-	}
-
-	@Override
-	public void touchPage(IManageablePage page)
-	{
-		delegate.touchPage(page);
-	}
-
-	@Override
-	public void untouchPage(IManageablePage page)
-	{
-		delegate.untouchPage(page);
-	}
-
-	@Override
-	public boolean supportsVersioning()
-	{
-		return delegate.supportsVersioning();
-	}
-
-	@Override
-	public void commitRequest()
-	{
-		delegate.commitRequest();
-	}
-
-	@Override
-	public void newSessionCreated()
-	{
-		delegate.newSessionCreated();
-	}
-
-	@Override
-	public void clear()
-	{
-		delegate.clear();
-	}
-
-	@Override
-	public void destroy()
-	{
-		delegate.destroy();
-	}
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.pageStore;
+
+import org.apache.wicket.page.IManageablePage;
+import org.apache.wicket.util.lang.Args;
+
+/**
+ * An {@link IPageStore} that delegates to another storage.
+ */
+public abstract class DelegatingPageStore implements IPageStore
+{
+	private final IPageStore delegate;
+	
+	protected DelegatingPageStore(IPageStore delegate)
+	{
+		this.delegate = Args.notNull(delegate, "delegate");
+	}
+
+	public IPageStore getDelegate()
+	{
+		return delegate;
+	}
+	
+	/**
+	 * Versioning is supported depending on the delegate.
+	 */
+	@Override
+	public boolean supportsVersioning()
+	{
+		return delegate.supportsVersioning();
+	}
+	
+	@Override
+	public void addPage(IPageContext context, IManageablePage page)
+	{
+		delegate.addPage(context, page);
+	}
+
+	@Override
+	public void removePage(IPageContext context, IManageablePage page)
+	{
+		delegate.removePage(context, page);
+	}
+
+	@Override
+	public void removeAllPages(IPageContext context)
+	{
+		delegate.removeAllPages(context);
+	}
+	
+	@Override
+	public IManageablePage getPage(IPageContext context, int id)
+	{
+		return delegate.getPage(context, id);
+	}
+
+	@Override
+	public void detach(IPageContext context)
+	{
+		delegate.detach(context);
+	}
+
+	@Override
+	public void destroy()
+	{
+		delegate.destroy();
+	}
+}
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/DiskDataStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/DiskDataStore.java
deleted file mode 100644
index 2dd95e4..0000000
--- a/wicket-core/src/main/java/org/apache/wicket/pageStore/DiskDataStore.java
+++ /dev/null
@@ -1,589 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.wicket.pageStore;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.OutputStream;
-import java.io.RandomAccessFile;
-import java.io.Serializable;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-import org.apache.wicket.WicketRuntimeException;
-import org.apache.wicket.pageStore.PageWindowManager.PageWindow;
-import org.apache.wicket.util.file.Files;
-import org.apache.wicket.util.io.IOUtils;
-import org.apache.wicket.util.lang.Args;
-import org.apache.wicket.util.lang.Bytes;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * A data store implementation which stores the data on disk (in a file system)
- */
-public class DiskDataStore implements IDataStore
-{
-	private static final Logger log = LoggerFactory.getLogger(DiskDataStore.class);
-
-	private static final String INDEX_FILE_NAME = "DiskDataStoreIndex";
-
-	private final String applicationName;
-
-	private final Bytes maxSizePerPageSession;
-
-	private final File fileStoreFolder;
-
-	private final ConcurrentMap<String, SessionEntry> sessionEntryMap;
-
-	/**
-	 * Construct.
-	 * 
-	 * @param applicationName
-	 * @param fileStoreFolder
-	 * @param maxSizePerSession
-	 */
-	public DiskDataStore(final String applicationName, final File fileStoreFolder,
-		final Bytes maxSizePerSession)
-	{
-		this.applicationName = applicationName;
-		this.fileStoreFolder = fileStoreFolder;
-		maxSizePerPageSession = Args.notNull(maxSizePerSession, "maxSizePerSession");
-		sessionEntryMap = new ConcurrentHashMap<>();
-
-		try
-		{
-			if (this.fileStoreFolder.exists() || this.fileStoreFolder.mkdirs())
-			{
-				loadIndex();
-			}
-			else
-			{
-				log.warn("Cannot create file store folder for some reason.");
-			}
-		}
-		catch (SecurityException e)
-		{
-			throw new WicketRuntimeException(
-				"SecurityException occurred while creating DiskDataStore. Consider using a non-disk based IDataStore implementation. "
-					+ "See org.apache.wicket.Application.setPageManagerProvider(IPageManagerProvider)",
-				e);
-		}
-	}
-
-	/**
-	 * @see org.apache.wicket.pageStore.IDataStore#destroy()
-	 */
-	@Override
-	public void destroy()
-	{
-		log.debug("Destroying...");
-		saveIndex();
-		log.debug("Destroyed.");
-	}
-
-	/**
-	 * @see org.apache.wicket.pageStore.IDataStore#getData(java.lang.String, int)
-	 */
-	@Override
-	public byte[] getData(final String sessionId, final int id)
-	{
-		byte[] pageData = null;
-		SessionEntry sessionEntry = getSessionEntry(sessionId, false);
-		if (sessionEntry != null)
-		{
-			pageData = sessionEntry.loadPage(id);
-		}
-
-		if (log.isDebugEnabled())
-		{
-			log.debug("Returning data{} for page with id '{}' in session with id '{}'",
-					pageData != null ? "" : "(null)", id, sessionId);
-		}
-		return pageData;
-	}
-
-	/**
-	 * @see org.apache.wicket.pageStore.IDataStore#isReplicated()
-	 */
-	@Override
-	public boolean isReplicated()
-	{
-		return false;
-	}
-
-	/**
-	 * @see org.apache.wicket.pageStore.IDataStore#removeData(java.lang.String, int)
-	 */
-	@Override
-	public void removeData(final String sessionId, final int id)
-	{
-		SessionEntry sessionEntry = getSessionEntry(sessionId, false);
-		if (sessionEntry != null)
-		{
-			if (log.isDebugEnabled())
-			{
-				log.debug("Removing data for page with id '{}' in session with id '{}'", id, sessionId);
-			}
-			sessionEntry.removePage(id);
-		}
-	}
-
-	/**
-	 * @see org.apache.wicket.pageStore.IDataStore#removeData(java.lang.String)
-	 */
-	@Override
-	public void removeData(final String sessionId)
-	{
-		SessionEntry sessionEntry = getSessionEntry(sessionId, false);
-		if (sessionEntry != null)
-		{
-			log.debug("Removing data for pages in session with id '{}'", sessionId);
-			synchronized (sessionEntry)
-			{
-				sessionEntryMap.remove(sessionEntry.sessionId);
-				sessionEntry.unbind();
-			}
-		}
-	}
-
-	/**
-	 * @see org.apache.wicket.pageStore.IDataStore#storeData(java.lang.String, int, byte[])
-	 */
-	@Override
-	public void storeData(final String sessionId, final int id, final byte[] data)
-	{
-		SessionEntry sessionEntry = getSessionEntry(sessionId, true);
-		if (sessionEntry != null)
-		{
-			log.debug("Storing data for page with id '{}' in session with id '{}'", id, sessionId);
-			sessionEntry.savePage(id, data);
-		}
-	}
-
-	/**
-	 * 
-	 * @param sessionId
-	 * @param create
-	 * @return the session entry
-	 */
-	protected SessionEntry getSessionEntry(final String sessionId, final boolean create)
-	{
-		if (!create)
-		{
-			return sessionEntryMap.get(sessionId);
-		}
-
-		SessionEntry entry = new SessionEntry(this, sessionId);
-		SessionEntry existing = sessionEntryMap.putIfAbsent(sessionId, entry);
-		return existing != null ? existing : entry;
-	}
-
-	/**
-	 * Load the index
-	 */
-	@SuppressWarnings("unchecked")
-	private void loadIndex()
-	{
-		File storeFolder = getStoreFolder();
-		File index = new File(storeFolder, INDEX_FILE_NAME);
-		if (index.exists() && index.length() > 0)
-		{
-			try
-			{
-				InputStream stream = new FileInputStream(index);
-				ObjectInputStream ois = new ObjectInputStream(stream);
-				try
-				{
-					Map<String, SessionEntry> map = (Map<String, SessionEntry>)ois.readObject();
-					sessionEntryMap.clear();
-					sessionEntryMap.putAll(map);
-
-					for (Entry<String, SessionEntry> entry : sessionEntryMap.entrySet())
-					{
-						// initialize the diskPageStore reference
-						SessionEntry sessionEntry = entry.getValue();
-						sessionEntry.diskDataStore = this;
-					}
-				} finally {
-					stream.close();
-					ois.close();
-				}
-			}
-			catch (Exception e)
-			{
-				log.error("Couldn't load DiskDataStore index from file " + index + ".", e);
-			}
-		}
-		Files.remove(index);
-	}
-
-	/**
-	 * 
-	 */
-	private void saveIndex()
-	{
-		File storeFolder = getStoreFolder();
-		if (storeFolder.exists())
-		{
-			File index = new File(storeFolder, INDEX_FILE_NAME);
-			Files.remove(index);
-			try
-			{
-				OutputStream stream = new FileOutputStream(index);
-				ObjectOutputStream oos = new ObjectOutputStream(stream);
-				try
-				{
-					Map<String, SessionEntry> map = new HashMap<>(sessionEntryMap.size());
-					for (Entry<String, SessionEntry> e : sessionEntryMap.entrySet())
-					{
-						if (e.getValue().unbound == false)
-						{
-							map.put(e.getKey(), e.getValue());
-						}
-					}
-					oos.writeObject(map);
-				} finally {
-					stream.close();
-					oos.close();
-				}
-			}
-			catch (Exception e)
-			{
-				log.error("Couldn't write DiskDataStore index to file " + index + ".", e);
-			}
-		}
-	}
-
-	/**
-	 * 
-	 */
-	protected static class SessionEntry implements Serializable
-	{
-		private static final long serialVersionUID = 1L;
-
-		private final String sessionId;
-		private transient DiskDataStore diskDataStore;
-		private String fileName;
-		private PageWindowManager manager;
-		private boolean unbound = false;
-
-		protected SessionEntry(DiskDataStore diskDataStore, String sessionId)
-		{
-			this.diskDataStore = diskDataStore;
-			this.sessionId = sessionId;
-		}
-
-		public PageWindowManager getManager()
-		{
-			if (manager == null)
-			{
-				manager = new PageWindowManager(diskDataStore.maxSizePerPageSession.bytes());
-			}
-			return manager;
-		}
-
-		private String getFileName()
-		{
-			if (fileName == null)
-			{
-				fileName = diskDataStore.getSessionFileName(sessionId, true);
-			}
-			return fileName;
-		}
-
-		/**
-		 * @return session id
-		 */
-		public String getSessionId()
-		{
-			return sessionId;
-		}
-
-		/**
-		 * Saves the serialized page to appropriate file.
-		 * 
-		 * @param pageId
-		 * @param data
-		 */
-		public synchronized void savePage(int pageId, byte data[])
-		{
-			if (unbound)
-			{
-				return;
-			}
-			// only save page that has some data
-			if (data != null)
-			{
-				// allocate window for page
-				PageWindow window = getManager().createPageWindow(pageId, data.length);
-
-				FileChannel channel = getFileChannel(true);
-				if (channel != null)
-				{
-					try
-					{
-						// write the content
-						channel.write(ByteBuffer.wrap(data), window.getFilePartOffset());
-					}
-					catch (IOException e)
-					{
-						log.error("Error writing to a channel " + channel, e);
-					}
-					finally
-					{
-						IOUtils.closeQuietly(channel);
-					}
-				}
-				else
-				{
-					log.warn(
-						"Cannot save page with id '{}' because the data file cannot be opened.",
-						pageId);
-				}
-			}
-		}
-
-		/**
-		 * Removes the page from pagemap file.
-		 * 
-		 * @param pageId
-		 */
-		public synchronized void removePage(int pageId)
-		{
-			if (unbound)
-			{
-				return;
-			}
-			getManager().removePage(pageId);
-		}
-
-		/**
-		 * Loads the part of pagemap file specified by the given PageWindow.
-		 * 
-		 * @param window
-		 * @return serialized page data
-		 */
-		public byte[] loadPage(PageWindow window)
-		{
-			byte[] result = null;
-			FileChannel channel = getFileChannel(false);
-			if (channel != null)
-			{
-				ByteBuffer buffer = ByteBuffer.allocate(window.getFilePartSize());
-				try
-				{
-					channel.read(buffer, window.getFilePartOffset());
-					if (buffer.hasArray())
-					{
-						result = buffer.array();
-					}
-				}
-				catch (IOException e)
-				{
-					log.error("Error reading from file channel " + channel, e);
-				}
-				finally
-				{
-					IOUtils.closeQuietly(channel);
-				}
-			}
-			return result;
-		}
-
-		private FileChannel getFileChannel(boolean create)
-		{
-			FileChannel channel = null;
-			File file = new File(getFileName());
-			if (create || file.exists())
-			{
-				String mode = create ? "rw" : "r";
-				try
-				{
-					RandomAccessFile randomAccessFile = new RandomAccessFile(file, mode);
-					channel = randomAccessFile.getChannel();
-				}
-				catch (FileNotFoundException fnfx)
-				{
-					// can happen if the file is locked. WICKET-4176
-					log.error(fnfx.getMessage(), fnfx);
-				}
-			}
-			return channel;
-		}
-
-		/**
-		 * Loads the specified page data.
-		 * 
-		 * @param id
-		 * @return page data or null if the page is no longer in pagemap file
-		 */
-		public synchronized byte[] loadPage(int id)
-		{
-			if (unbound)
-			{
-				return null;
-			}
-			byte[] result = null;
-			PageWindow window = getManager().getPageWindow(id);
-			if (window != null)
-			{
-				result = loadPage(window);
-			}
-			return result;
-		}
-
-		/**
-		 * Deletes all files for this session.
-		 */
-		public synchronized void unbind()
-		{
-			File sessionFolder = diskDataStore.getSessionFolder(sessionId, false);
-			if (sessionFolder.exists())
-			{
-				Files.removeFolder(sessionFolder);
-				cleanup(sessionFolder);
-			}
-			unbound = true;
-		}
-
-		/**
-		 * deletes the sessionFolder's parent and grandparent, if (and only if) they are empty.
-		 *
-		 * @see #createPathFrom(String sessionId)
-		 * @param sessionFolder
-		 *            must not be null
-		 */
-		private void cleanup(final File sessionFolder)
-		{
-			File high = sessionFolder.getParentFile();
-			if (high != null && high.list().length == 0)
-			{
-				if (Files.removeFolder(high))
-				{
-					File low = high.getParentFile();
-					if (low != null && low.list().length == 0)
-					{
-						Files.removeFolder(low);
-					}
-				}
-			}
-		}
-	}
-
-	/**
-	 * Returns the file name for specified session. If the session folder (folder that contains the
-	 * file) does not exist and createSessionFolder is true, the folder will be created.
-	 * 
-	 * @param sessionId
-	 * @param createSessionFolder
-	 * @return file name for pagemap
-	 */
-	private String getSessionFileName(String sessionId, boolean createSessionFolder)
-	{
-		File sessionFolder = getSessionFolder(sessionId, createSessionFolder);
-		return new File(sessionFolder, "data").getAbsolutePath();
-	}
-
-	/**
-	 * This folder contains sub-folders named as the session id for which they hold the data.
-	 * 
-	 * @return the folder where the pages are stored
-	 */
-	protected File getStoreFolder()
-	{
-		return new File(fileStoreFolder, applicationName + "-filestore");
-	}
-
-	/**
-	 * Returns the folder for the specified sessions. If the folder doesn't exist and the create
-	 * flag is set, the folder will be created.
-	 * 
-	 * @param sessionId
-	 * @param create
-	 * @return folder used to store session data
-	 */
-	protected File getSessionFolder(String sessionId, final boolean create)
-	{
-		File storeFolder = getStoreFolder();
-
-		sessionId = sessionId.replace('*', '_');
-		sessionId = sessionId.replace('/', '_');
-		sessionId = sessionId.replace(':', '_');
-
-		sessionId = createPathFrom(sessionId);
-
-		File sessionFolder = new File(storeFolder, sessionId);
-		if (create && sessionFolder.exists() == false)
-		{
-			Files.mkdirs(sessionFolder);
-		}
-		return sessionFolder;
-	}
-
-	/**
-	 * creates a three-level path from the sessionId in the format 0000/0000/<sessionId>. The two
-	 * prefixing directories are created from the sessionId's hashcode and thus, should be well
-	 * distributed.
-	 *
-	 * This is used to avoid problems with Filesystems allowing no more than 32k entries in a
-	 * directory.
-	 *
-	 * Note that the prefix paths are created from Integers and not guaranteed to be four chars
-	 * long.
-	 *
-	 * @param sessionId
-	 *      must not be null
-	 * @return path in the form 0000/0000/sessionId
-	 */
-	private String createPathFrom(final String sessionId)
-	{
-		int sessionIdHashCode = sessionId.hashCode();
-		if (sessionIdHashCode == Integer.MIN_VALUE) {
-			// Math.abs(MIN_VALUE) == MIN_VALUE, so avoid it
-			sessionIdHashCode += 1;
-		}
-		int hash = Math.abs(sessionIdHashCode);
-		String low = String.valueOf(hash % 9973);
-		String high = String.valueOf((hash / 9973) % 9973);
-		StringBuilder bs = new StringBuilder(sessionId.length() + 10);
-		bs.append(low);
-		bs.append(File.separator);
-		bs.append(high);
-		bs.append(File.separator);
-		bs.append(sessionId);
-
-		return bs.toString();
-	}
-
-	@Override
-	public boolean canBeAsynchronous()
-	{
-		return true;
-	}
-}
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/DiskPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/DiskPageStore.java
new file mode 100644
index 0000000..033e1cc
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/DiskPageStore.java
@@ -0,0 +1,561 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.pageStore;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.io.Serializable;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.page.IManageablePage;
+import org.apache.wicket.pageStore.disk.NestedFolders;
+import org.apache.wicket.pageStore.disk.PageWindowManager;
+import org.apache.wicket.pageStore.disk.PageWindowManager.FileWindow;
+import org.apache.wicket.protocol.http.PageExpiredException;
+import org.apache.wicket.serialize.ISerializer;
+import org.apache.wicket.util.file.Files;
+import org.apache.wicket.util.io.IOUtils;
+import org.apache.wicket.util.lang.Args;
+import org.apache.wicket.util.lang.Bytes;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A storage of pages on disk.
+ * <p>
+ * All pages passed into this store are restricted to be {@link SerializedPage}s.
+ * <p>
+ * Implementation note: {@link DiskPageStore} writes pages into a single file, appending new pages while overwriting the oldest pages.
+ * Since Ajax requests do not change the id of a page, {@link DiskPageStore} offers an optimization to overwrite the most recently written
+ * page, if it has the same id as a new page to write.<p>
+ * However this does not help in case of alternating requests between multiple browser windows: In this case requests are processed for
+ * different page ids and the oldests pages are constantly overwritten (this can easily happen with Ajax timers on one or more pages).
+ * This leads to pages with identical id superfluously kept in the file, while older pages are prematurely expelled.
+ * Any following request to these older pages will then fail with {@link PageExpiredException}.   
+ */
+public class DiskPageStore extends AbstractPersistentPageStore implements IPersistentPageStore
+{
+	private static final Logger log = LoggerFactory.getLogger(DiskPageStore.class);
+
+	/**
+	 * Name of the file where the page index is stored.
+	 */
+	private static final String INDEX_FILE_NAME = "DiskPageStoreIndex";
+
+	private final Bytes maxSizePerSession;
+
+	private final NestedFolders folders;
+
+	private final ConcurrentMap<String, DiskData> diskDatas;
+
+	/**
+	 * Create a store that supports {@link SerializedPage}s only.
+	 * 
+	 * @param applicationName
+	 *            name of application
+	 * @param fileStoreFolder
+	 *            folder to store to
+	 * @param maxSizePerSession
+	 *            maximum size per session
+	 * 
+	 * @see SerializingPageStore
+	 */
+	public DiskPageStore(String applicationName, File fileStoreFolder, Bytes maxSizePerSession)
+	{
+		this(applicationName, fileStoreFolder, maxSizePerSession, null);
+	}
+
+	/**
+	 * Create a store to disk.
+	 * 
+	 * @param applicationName
+	 *            name of application
+	 * @param fileStoreFolder
+	 *            folder to store to
+	 * @param maxSizePerSession
+	 *            maximum size per session
+	 * @param serializer
+	 *            for serialization of pages
+	 */
+	public DiskPageStore(String applicationName, File fileStoreFolder, Bytes maxSizePerSession,
+		ISerializer serializer)
+	{
+		super(applicationName);
+		
+		this.folders = new NestedFolders(new File(fileStoreFolder, applicationName + "-filestore"));
+		this.maxSizePerSession = Args.notNull(maxSizePerSession, "maxSizePerSession");
+
+		this.diskDatas = new ConcurrentHashMap<>();
+
+		try
+		{
+			if (folders.getBase().exists() || folders.getBase().mkdirs())
+			{
+				loadIndex();
+			}
+			else
+			{
+				log.warn("Cannot create file store folder for some reason.");
+			}
+		}
+		catch (SecurityException e)
+		{
+			throw new WicketRuntimeException(
+				"SecurityException occurred while creating DiskPageStore. Consider using a non-disk based IPageStore implementation. "
+					+ "See org.apache.wicket.Application.setPageManagerProvider(IPageManagerProvider)",
+				e);
+		}
+	}
+
+	/**
+	 * Pages are already serialized.
+	 */
+	@Override
+	public boolean supportsVersioning()
+	{
+		return true;
+	}
+	
+	@Override
+	public void destroy()
+	{
+		log.debug("Destroying...");
+		saveIndex();
+
+		super.destroy();
+		log.debug("Destroyed.");
+	}
+
+	@Override
+	protected IManageablePage getPersistedPage(String sessionIdentifier, int id)
+	{
+		DiskData diskData = getDiskData(sessionIdentifier, false);
+		if (diskData != null)
+		{
+			byte[] data = diskData.loadPage(id);
+			if (data != null)
+			{
+				if (log.isDebugEnabled())
+				{
+					log.debug("Returning page with id '{}' in session with id '{}'", id, sessionIdentifier);
+				}
+				
+				return new SerializedPage(id, "unknown", data);
+			}
+		}
+		
+		return null;
+	}
+
+	@Override
+	protected void removePersistedPage(String sessionIdentifier, IManageablePage page)
+	{
+		DiskData diskData = getDiskData(sessionIdentifier, false);
+		if (diskData != null)
+		{
+			if (log.isDebugEnabled())
+			{
+				log.debug("Removing page with id '{}' in session with id '{}'", page.getPageId(), sessionIdentifier);
+			}
+			
+			diskData.removeData(page.getPageId());
+		}
+	}
+
+	@Override
+	protected void removeAllPersistedPages(String sessionIdentifier)
+	{
+		DiskData diskData = getDiskData(sessionIdentifier, false);
+		if (diskData != null)
+		{
+			synchronized (diskDatas)
+			{
+				diskDatas.remove(diskData.sessionIdentifier);
+				diskData.unbind();
+			}
+		}
+	}
+
+	@Override
+	protected void addPersistedPage(String sessionIdentifier, IManageablePage page)
+	{
+		if (page instanceof SerializedPage == false)
+		{
+			throw new WicketRuntimeException("DiskPageStore works with serialized pages only");
+		}
+		SerializedPage serializedPage = (SerializedPage) page;
+
+		DiskData diskData = getDiskData(sessionIdentifier, true);
+
+		log.debug("Storing data for page with id '{}' in session with id '{}'", serializedPage.getPageId(), sessionIdentifier);
+
+		byte[] data = serializedPage.getData();
+		String type = serializedPage.getPageType();
+
+		diskData.savePage(serializedPage.getPageId(), type, data);
+	}
+
+	/**
+	 * Get the data on disk for the given session identifier.
+	 * 
+	 * @param sessionIdentifier identifier of session
+	 * @return matching data
+	 */
+	protected DiskData getDiskData(String sessionIdentifier, boolean create)
+	{
+		if (!create)
+		{
+			return diskDatas.get(sessionIdentifier);
+		}
+
+		DiskData data = new DiskData(this, sessionIdentifier);
+		DiskData existing = diskDatas.putIfAbsent(sessionIdentifier, data);
+		return existing != null ? existing : data;
+	}
+
+	/**
+	 * Load the index
+	 */
+	@SuppressWarnings("unchecked")
+	private void loadIndex()
+	{
+		File storeFolder = folders.getBase();
+
+		File index = new File(storeFolder, INDEX_FILE_NAME);
+		if (index.exists() && index.length() > 0)
+		{
+			try (InputStream stream = new FileInputStream(index))
+			{
+				ObjectInputStream ois = new ObjectInputStream(stream);
+
+				diskDatas.clear();
+
+				for (DiskData diskData : (List<DiskData>)ois.readObject())
+				{
+					diskData.pageStore = this;
+					diskDatas.put(diskData.sessionIdentifier, diskData);
+				}
+			}
+			catch (Exception e)
+			{
+				log.error("Couldn't load DiskPageStore index from file " + index + ".", e);
+			}
+		}
+		Files.remove(index);
+	}
+
+	private void saveIndex()
+	{
+		File storeFolder = folders.getBase();
+		if (storeFolder.exists())
+		{
+			File index = new File(storeFolder, INDEX_FILE_NAME);
+			Files.remove(index);
+			try (OutputStream stream = new FileOutputStream(index))
+			{
+				ObjectOutputStream oos = new ObjectOutputStream(stream);
+				
+				List<DiskData> list = new ArrayList<>(diskDatas.size());
+				for (DiskData diskData : diskDatas.values())
+				{
+					if (diskData.sessionIdentifier != null)
+					{
+						list.add(diskData);
+					}
+				}
+				oos.writeObject(list);
+			}
+			catch (Exception e)
+			{
+				log.error("Couldn't write DiskPageStore index to file " + index + ".", e);
+			}
+		}
+	}
+
+	@Override
+	public Set<String> getSessionIdentifiers()
+	{
+		return Collections.unmodifiableSet(diskDatas.keySet());
+	}
+
+	/**
+	 * 
+	 * @param session
+	 *            key
+	 * @return a list of the last N page windows
+	 */
+	@Override
+	public List<IPersistedPage> getPersistedPages(String sessionIdentifier)
+	{
+		List<IPersistedPage> pages = new ArrayList<>();
+
+		DiskData diskData = getDiskData(sessionIdentifier, false);
+		if (diskData != null)
+		{
+			PageWindowManager windowManager = diskData.getManager();
+
+			pages.addAll(windowManager.getFileWindows());
+		}
+		return pages;
+	}
+
+	@Override
+	public Bytes getTotalSize()
+	{
+		long size = 0;
+
+		synchronized (diskDatas)
+		{
+			for (DiskData diskData : diskDatas.values())
+			{
+				size = size + diskData.size();
+			}
+		}
+
+		return Bytes.bytes(size);
+	}
+
+	/**
+	 * Data held on disk.
+	 */
+	protected static class DiskData implements Serializable
+	{
+		private static final long serialVersionUID = 1L;
+
+		private transient DiskPageStore pageStore;
+
+		private transient String fileName;
+
+		private String sessionIdentifier;
+
+		private PageWindowManager manager;
+
+		protected DiskData(DiskPageStore pageStore, String sessionIdentifier)
+		{
+			this.pageStore = pageStore;
+
+			this.sessionIdentifier = sessionIdentifier;
+		}
+
+		public long size()
+		{
+			return manager.getTotalSize();
+		}
+
+		public PageWindowManager getManager()
+		{
+			if (manager == null)
+			{
+				manager = new PageWindowManager(pageStore.maxSizePerSession.bytes());
+			}
+			return manager;
+		}
+
+		private String getFileName()
+		{
+			if (fileName == null)
+			{
+				fileName = pageStore.getSessionFileName(sessionIdentifier);
+			}
+			return fileName;
+		}
+
+		/**
+		 * @return session id
+		 */
+		public String getKey()
+		{
+			return sessionIdentifier;
+		}
+
+		/**
+		 * Saves the serialized page to appropriate file.
+		 * 
+		 * @param pageId
+		 * @param pageType
+		 * @param data
+		 */
+		public synchronized void savePage(int pageId, String pageType, byte data[])
+		{
+			if (sessionIdentifier == null)
+			{
+				return;
+			}
+
+			// only save page that has some data
+			if (data != null)
+			{
+				// allocate window for page
+				FileWindow window = getManager().createPageWindow(pageId, pageType, data.length);
+
+				FileChannel channel = getFileChannel(true);
+				if (channel != null)
+				{
+					try
+					{
+						// write the content
+						channel.write(ByteBuffer.wrap(data), window.getFilePartOffset());
+					}
+					catch (IOException e)
+					{
+						log.error("Error writing to a channel " + channel, e);
+					}
+					finally
+					{
+						IOUtils.closeQuietly(channel);
+					}
+				}
+				else
+				{
+					log.warn(
+						"Cannot save page with id '{}' because the data file cannot be opened.",
+						pageId);
+				}
+			}
+		}
+
+		/**
+		 * Removes the page from disk.
+		 * 
+		 * @param pageId
+		 */
+		public synchronized void removeData(int pageId)
+		{
+			if (sessionIdentifier == null)
+			{
+				return;
+			}
+
+			getManager().removePage(pageId);
+		}
+
+		/**
+		 * Loads the part of pagemap file specified by the given PageWindow.
+		 * 
+		 * @param window
+		 * @return serialized page data
+		 */
+		public byte[] loadData(FileWindow window)
+		{
+			byte[] result = null;
+			FileChannel channel = getFileChannel(false);
+			if (channel != null)
+			{
+				ByteBuffer buffer = ByteBuffer.allocate(window.getFilePartSize());
+				try
+				{
+					channel.read(buffer, window.getFilePartOffset());
+					if (buffer.hasArray())
+					{
+						result = buffer.array();
+					}
+				}
+				catch (IOException e)
+				{
+					log.error("Error reading from file channel " + channel, e);
+				}
+				finally
+				{
+					IOUtils.closeQuietly(channel);
+				}
+			}
+			return result;
+		}
+
+		private FileChannel getFileChannel(boolean create)
+		{
+			FileChannel channel = null;
+			File file = new File(getFileName());
+			if (create || file.exists())
+			{
+				String mode = create ? "rw" : "r";
+				try
+				{
+					RandomAccessFile randomAccessFile = new RandomAccessFile(file, mode);
+					channel = randomAccessFile.getChannel();
+				}
+				catch (FileNotFoundException fnfx)
+				{
+					// can happen if the file is locked. WICKET-4176
+					log.error(fnfx.getMessage(), fnfx);
+				}
+			}
+			return channel;
+		}
+
+		/**
+		 * Loads the specified page data.
+		 * 
+		 * @param id
+		 * @return page data or null if the page is no longer in pagemap file
+		 */
+		public synchronized byte[] loadPage(int id)
+		{
+			if (sessionIdentifier == null)
+			{
+				return null;
+			}
+
+			FileWindow window = getManager().getPageWindow(id);
+			if (window == null)
+			{
+				return null;
+			}
+
+			return loadData(window);
+		}
+
+		/**
+		 * Deletes all files for this session.
+		 */
+		public synchronized void unbind()
+		{
+			pageStore.folders.remove(sessionIdentifier);
+
+			sessionIdentifier = null;
+		}
+	}
+
+	/**
+	 * Returns the file name for specified session. If the session folder (folder that contains the
+	 * file) does not exist, the folder will be created.
+	 * 
+	 * @param sessionIdentifier
+	 * @return file name for pagemap
+	 */
+	private String getSessionFileName(String sessionIdentifier)
+	{
+		File sessionFolder = folders.get(sessionIdentifier, true);
+		return new File(sessionFolder, "data").getAbsolutePath();
+	}
+}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/FilePageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/FilePageStore.java
new file mode 100644
index 0000000..2fb968d
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/FilePageStore.java
@@ -0,0 +1,379 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.pageStore;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileChannel.MapMode;
+import java.nio.charset.Charset;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.UserDefinedFileAttributeView;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.page.IManageablePage;
+import org.apache.wicket.pageStore.disk.NestedFolders;
+import org.apache.wicket.serialize.ISerializer;
+import org.apache.wicket.util.file.Files;
+import org.apache.wicket.util.io.IOUtils;
+import org.apache.wicket.util.lang.Args;
+import org.apache.wicket.util.lang.Bytes;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A storage of pages in files.
+ * <p>
+ * All pages passed into this store are restricted to be {@link SerializedPage}s.
+ * <p>
+ * While {@link DiskPageStore} uses a single file per session, this implementation stores each page
+ * in its own file. This improves on a {@link DiskPageStore disadvantage of DiskPageStore} surfacing
+ * with alternating Ajax requests from different browser tabs.  
+ */
+public class FilePageStore extends AbstractPersistentPageStore implements IPersistentPageStore
+{
+	private static final String ATTRIBUTE_PAGE_TYPE = "user.wicket_page_type";
+
+	private static final String FILE_SUFFIX = ".data";
+
+	private static final Logger log = LoggerFactory.getLogger(FilePageStore.class);
+
+	private final Bytes maxSizePerSession;
+
+	private final NestedFolders folders;
+
+	/**
+	 * Create a store that supports {@link SerializedPage}s only.
+	 * 
+	 * @param applicationName
+	 *            name of application
+	 * @param fileStoreFolder
+	 *            folder to store to
+	 * @param maxSizePerSession
+	 *            maximum size per session
+	 * 
+	 * @see SerializingPageStore
+	 */
+	public FilePageStore(String applicationName, File fileStoreFolder, Bytes maxSizePerSession)
+	{
+		this(applicationName, fileStoreFolder, maxSizePerSession, null);
+	}
+
+	/**
+	 * Create a store to files.
+	 * 
+	 * @param applicationName
+	 *            name of application
+	 * @param fileStoreFolder
+	 *            folder to store to
+	 * @param maxSizePerSession
+	 *            maximum size per session
+	 * @param serializer
+	 *            for serialization of pages
+	 */
+	public FilePageStore(String applicationName, File fileStoreFolder, Bytes maxSizePerSession, ISerializer serializer)
+	{
+		super(applicationName);
+		
+		this.folders = new NestedFolders(new File(fileStoreFolder, applicationName + "-filestore"));
+		this.maxSizePerSession = Args.notNull(maxSizePerSession, "maxSizePerSession");
+	}
+
+	/**
+	 * Pages are always serialized, so versioning is supported.
+	 */
+	@Override
+	public boolean supportsVersioning()
+	{
+		return true;
+	}
+
+	private File getPageFile(String sessionId, int id, boolean create)
+	{
+		File folder = folders.get(sessionId, create);
+
+		return new File(folder, id + FILE_SUFFIX);
+	}
+
+	@Override
+	protected IManageablePage getPersistedPage(String sessionIdentifier, int id)
+	{
+		byte[] data = readFile(sessionIdentifier, id);
+		if (data == null)
+		{
+			return null;
+		}
+		
+		return new SerializedPage(id, "unknown", data);
+	}
+
+	private byte[] readFile(String sessionIdentifier, int id)
+	{
+		File file = getPageFile(sessionIdentifier, id, false);
+		if (file.exists() == false)
+		{
+			return null;
+		}
+
+		byte[] data = null;
+
+		try
+		{
+			FileChannel channel = FileChannel.open(file.toPath());
+			try
+			{
+				int size = (int)channel.size();
+				MappedByteBuffer buf = channel.map(MapMode.READ_ONLY, 0, size);
+				data = new byte[size];
+				buf.get(data);
+			}
+			finally
+			{
+				IOUtils.closeQuietly(channel);
+			}
+		}
+		catch (IOException ex)
+		{
+			log.warn("cannot read page data for session {} page {}", sessionIdentifier, id, ex);
+		}
+
+		return data;
+	}
+
+	@Override
+	protected void removePersistedPage(String sessionIdentifier, IManageablePage page)
+	{
+		File file = getPageFile(sessionIdentifier, page.getPageId(), false);
+		if (file.exists())
+		{
+			if (!file.delete())
+			{
+				log.warn("cannot remove page data for session {} page {}", sessionIdentifier, page.getPageId());
+			}
+		}
+	}
+
+	@Override
+	protected void removeAllPersistedPages(String sessionIdentifier)
+	{
+		folders.remove(sessionIdentifier);
+	}
+
+	@Override
+	protected void addPersistedPage(String sessionIdentifier, IManageablePage page)
+	{
+		if (page instanceof SerializedPage == false)
+		{
+			throw new WicketRuntimeException("FilePageStore works with serialized pages only");
+		}
+		SerializedPage serializedPage = (SerializedPage) page;
+
+		String type = serializedPage.getPageType();
+		byte[] data = serializedPage.getData();
+
+		writeFile(sessionIdentifier, serializedPage.getPageId(), type, data);
+
+		checkMaxSize(sessionIdentifier);
+	}
+
+	private void writeFile(String sessionIdentifier, int pageId, String pageType, byte[] data)
+	{
+		File file = getPageFile(sessionIdentifier, pageId, true);
+		try
+		{
+			FileChannel channel = FileChannel.open(file.toPath(), StandardOpenOption.CREATE,
+				StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
+			try
+			{
+				ByteBuffer buffer = ByteBuffer.wrap(data);
+				channel.write(buffer);
+			}
+			finally
+			{
+				IOUtils.closeQuietly(channel);
+			}
+		}
+		catch (IOException ex)
+		{
+			log.warn("cannot store page data for session {} page {}", sessionIdentifier, pageId, ex);
+		}
+
+		setPageType(file, pageType);
+	}
+
+	private void checkMaxSize(String sessionIdentifier)
+	{
+		File[] files = folders.get(sessionIdentifier, true).listFiles();
+		Arrays.sort(files, new LastModifiedComparator());
+
+		long total = 0;
+		for (int f = 0; f < files.length; f++)
+		{
+			File candidate = files[f];
+
+			total += candidate.length();
+
+			if (total > maxSizePerSession.bytes())
+			{
+				if (!Files.remove(candidate))
+				{
+					log.warn("cannot remove page data for session {} page {}", sessionIdentifier,
+						candidate.getName());
+				}
+			}
+		}
+	}
+
+	public class LastModifiedComparator implements Comparator<File>
+	{
+
+		@Override
+		public int compare(File f1, File f2)
+		{
+			return Long.compare(f2.lastModified(), f1.lastModified());
+		}
+
+	}
+
+	@Override
+	public Set<String> getSessionIdentifiers()
+	{
+		Set<String> sessionIdentifiers = new HashSet<>();
+
+		for (File folder : folders.getAll())
+		{
+			sessionIdentifiers.add(folder.getName());
+		}
+
+		return sessionIdentifiers;
+	}
+
+	@Override
+	public List<IPersistedPage> getPersistedPages(String sessionIdentifier)
+	{
+		List<IPersistedPage> pages = new ArrayList<>();
+
+		File folder = folders.get(sessionIdentifier, false);
+		if (folder.exists())
+		{
+			File[] files = folder.listFiles();
+			Arrays.sort(files, new LastModifiedComparator());
+			for (File file : files)
+			{
+				String name = file.getName();
+				if (name.endsWith(FILE_SUFFIX))
+				{
+					int pageId;
+					try
+					{
+						pageId = Integer
+							.valueOf(name.substring(0, name.length() - FILE_SUFFIX.length()));
+					}
+					catch (Exception ex)
+					{
+						log.debug("unexpected file {}", file.getAbsolutePath());
+						continue;
+					}
+
+					String pageType = getPageType(file);
+
+					pages.add(new PersistedPage(pageId, pageType, file.length()));
+				}
+			}
+		}
+
+		return pages;
+	}
+
+	/**
+	 * Get the type of page from the given file.
+	 * <p>
+	 * This is an optional operation that returns <code>null</code> in case of any error. 
+	 * 
+	 * @param file
+	 * @return pageType
+	 */
+	protected String getPageType(File file)
+	{
+		String pageType = null;
+		try
+		{
+			UserDefinedFileAttributeView view = java.nio.file.Files
+				.getFileAttributeView(file.toPath(), UserDefinedFileAttributeView.class);
+			ByteBuffer buffer = ByteBuffer.allocate(view.size(ATTRIBUTE_PAGE_TYPE));
+			view.read(ATTRIBUTE_PAGE_TYPE, buffer);
+			buffer.flip();
+			pageType = Charset.defaultCharset().decode(buffer).toString();
+		}
+		catch (IOException ex)
+		{
+			log.debug("cannot get pageType for {}", file);
+		}
+
+		return pageType;
+	}
+
+	/**
+	 * Set the type of page on the given file.
+	 * <p>
+	 * This is an optional operation that silently fails in case of an error. 
+	 * 
+	 * @param file
+	 * @param pageType
+	 */
+	protected void setPageType(File file, String pageType)
+	{
+		try
+		{
+			UserDefinedFileAttributeView view = java.nio.file.Files
+				.getFileAttributeView(file.toPath(), UserDefinedFileAttributeView.class);
+			view.write(ATTRIBUTE_PAGE_TYPE, Charset.defaultCharset().encode(pageType));
+		}
+		catch (IOException ex)
+		{
+			log.debug("cannot set pageType for {}", file, ex);
+		}
+	}
+
+	@Override
+	public Bytes getTotalSize()
+	{
+		long total = 0;
+
+		for (File folder : folders.getAll())
+		{
+			for (File file : folder.listFiles())
+			{
+				String name = file.getName();
+				if (name.endsWith(FILE_SUFFIX))
+				{
+					total += file.length();
+				}
+			}
+		}
+
+		return Bytes.bytes(total);
+	}
+}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/GroupingPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/GroupingPageStore.java
new file mode 100644
index 0000000..b588df7
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/GroupingPageStore.java
@@ -0,0 +1,285 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.pageStore;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.function.Supplier;
+
+import org.apache.wicket.MetaDataEntry;
+import org.apache.wicket.MetaDataKey;
+import org.apache.wicket.Session;
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.page.IManageablePage;
+import org.apache.wicket.util.string.Strings;
+
+/**
+ * An {@link IPageStore} that groups pages.
+ * <p>
+ * By default all pages are stored in a single group, you'll have to override {@link #getGroup(IManageablePage)} to provide the actual group
+ * for a stored page, e.g. using a single group for all pages inside a single browser tab.
+ */
+public abstract class GroupingPageStore extends DelegatingPageStore
+{
+
+	private static final String DEFAULT_GROUP = "default";
+
+	private static final MetaDataKey<SessionData> KEY = new MetaDataKey<SessionData>()
+	{
+		private static final long serialVersionUID = 1L;
+	};
+
+	private int maxGroups;
+	
+	/**
+	 * Is a group of a page stable.
+	 */
+	private boolean stableGroups = false;
+
+	/**
+	 * @param delegate
+	 *            store to delegate to
+	 * @param maxGroups
+	 *            maximum groups to keep
+	 */
+	public GroupingPageStore(IPageStore delegate, int maxGroups)
+	{
+		super(delegate);
+
+		this.maxGroups = maxGroups;
+	}
+
+	/**
+	 * Indicate that groups are stable, i.e. the group of a page never changes.
+	 */
+	public GroupingPageStore withStableGroups()
+	{
+		stableGroups = true;
+
+		return this;
+	}
+
+	/**
+	 * Get the group of a page, default is <code>"default"</code>
+	 * 
+	 * @return group of page, must not be empty
+	 */
+	protected String getGroup(IManageablePage page)
+	{
+		return DEFAULT_GROUP;
+	}
+
+	private String getGroupInternal(IManageablePage page)
+	{
+		String group = getGroup(page);
+
+		if (Strings.isEmpty(group))
+		{
+			throw new WicketRuntimeException("group must not be empy");
+		}
+
+		return group;
+	}
+
+	@Override
+	public void addPage(IPageContext context, IManageablePage page)
+	{
+		SessionData sessionData = getSessionData(context, true);
+
+		sessionData.addPage(context, page, getGroupInternal(page), maxGroups, stableGroups, getDelegate());
+	}
+
+	@Override
+	public void removePage(IPageContext context, IManageablePage page)
+	{
+		SessionData sessionData = getSessionData(context, false);
+		if (sessionData == null) {
+			return;
+		}
+		
+		sessionData.removePage(context, page, getDelegate());
+	}
+
+	@Override
+	public void removeAllPages(IPageContext context)
+	{
+		SessionData sessionData = getSessionData(context, false);
+		if (sessionData == null) {
+			return;
+		}
+
+		sessionData.removeAllPages(context, getDelegate());
+	}
+
+	@Override
+	public IManageablePage getPage(IPageContext context, int id)
+	{
+		SessionData sessionData = getSessionData(context, false);
+		if (sessionData == null) {
+			return null;
+		}
+		
+		return sessionData.getPage(context, id, getDelegate());
+	}
+
+	private SessionData getSessionData(IPageContext context, boolean create)
+	{
+		return context.getSessionData(KEY, () -> {
+			if (create)
+			{
+				return new SessionData();
+			}
+			else
+			{
+				return null;
+			}
+		});
+	}
+
+	/**
+	 * Data kept in the {@link Session}.
+	 */
+	static class SessionData implements Serializable
+	{
+		private LinkedList<String> groups = new LinkedList<>();
+
+		private Map<String, MetaDataEntry<?>[]> metaData = new HashMap<>();
+
+		public synchronized <T> void setMetaData(String group, MetaDataKey<T> key, T value)
+		{
+			metaData.put(group, key.set(metaData.get(group), value));
+		}
+
+		public synchronized <T> T getMetaData(String group, MetaDataKey<T> key)
+		{
+			return key.get(metaData.get(group));
+		}
+		
+		public synchronized void addPage(IPageContext context, IManageablePage page, String group, int maxGroups, boolean stableGroups, IPageStore delegate)
+		{
+			if (stableGroups == false)
+			{
+				// group might have changed, so remove page first from all groups
+				for (String other : groups)
+				{
+					delegate.removePage(new GroupContext(context, this, other), page);
+				}
+			}
+
+			// add as last
+			groups.remove(group);
+			groups.addLast(group);
+			
+			// delegate
+			delegate.addPage(new GroupContext(context, this, group), page);
+
+			while (groups.size() > maxGroups)
+			{
+				String first = groups.removeFirst();
+				
+				delegate.removeAllPages(new GroupContext(context, this, first));
+			}
+		}
+		
+		public IManageablePage getPage(IPageContext context, int id, IPageStore delegate)
+		{
+			for (String group : groups)
+			{
+				IManageablePage page = delegate.getPage(new GroupContext(context, this, group), id);
+				if (page != null)
+				{
+					return page;
+				}
+			}
+			return null;
+		}
+
+		public synchronized void removePage(IPageContext context, IManageablePage page, IPageStore delegate)
+		{
+			for (String group : groups)
+			{
+				delegate.removePage(new GroupContext(context, this, group), page);
+			}
+		}
+
+		public synchronized void removeAllPages(IPageContext context, IPageStore delegate)
+		{
+			for (String group : groups)
+			{
+				delegate.removeAllPages(new GroupContext(context, this, group));
+			}
+		}
+	}
+
+	/**
+	 * Context passed to the delegate store to group data and attributes.
+	 */
+	static class GroupContext implements IPageContext
+	{
+
+		private final IPageContext context;
+
+		private final SessionData sessionData;
+
+		private final String group;
+
+		public GroupContext(IPageContext context, SessionData sessionData, String group)
+		{
+			this.context = context;
+			this.sessionData = sessionData;
+			this.group = group;
+		}
+
+		@Override
+		public String getSessionId(boolean bind)
+		{
+			return context.getSessionId(true) + "_" + group;
+		}
+
+		@Override
+		public <T extends Serializable> T getSessionData(MetaDataKey<T> key, Supplier<T> defaultValue)
+		{
+			synchronized (sessionData)
+			{
+				T data = sessionData.getMetaData(group, key);
+				if (data == null) {
+					data = defaultValue.get();
+					
+					if (data != null) {
+						sessionData.setMetaData(group, key, data);
+					}
+				}
+				
+				return data;
+			}
+		}
+
+		@Override
+		public <T extends Serializable> T getSessionAttribute(String key, Supplier<T> defaultValue)
+		{
+			return context.getSessionAttribute(key + "_" + group, defaultValue);
+		}
+
+		@Override
+		public <T> T getRequestData(MetaDataKey<T> key, Supplier<T> defaultValue)
+		{
+			throw new WicketRuntimeException("no request available for group");
+		}
+	}
+}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/IDataStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/IDataStore.java
deleted file mode 100644
index b59176a..0000000
--- a/wicket-core/src/main/java/org/apache/wicket/pageStore/IDataStore.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.wicket.pageStore;
-
-/**
- * Data stores are used to persist (read & write) Wicket page data to a persistent store like e.g.
- * files or databases.
- * 
- * @see IPageStore
- */
-public interface IDataStore
-{
-	/**
-	 * Get data associated with the session id and page id.
-	 * 
-	 * @param sessionId
-	 *            Session ID
-	 * @param id
-	 *            Page ID
-	 * @return All the page data persisted
-	 */
-	byte[] getData(String sessionId, int id);
-
-	/**
-	 * Remove all persisted data related to the session id and page id
-	 * 
-	 * @param sessionId
-	 *            Session ID
-	 * @param id
-	 *            Page ID
-	 */
-	void removeData(String sessionId, int id);
-
-	/**
-	 * Remove all page data for the session id
-	 * 
-	 * @param sessionId
-	 *            Session ID
-	 */
-	void removeData(String sessionId);
-
-	/**
-	 * Store the page data
-	 * 
-	 * @param sessionId
-	 *            Session ID
-	 * @param id
-	 *            Page ID
-	 * @param data
-	 *            Page data
-	 */
-	void storeData(String sessionId, int id, byte[] data);
-
-	/**
-	 * Properly close the data store and possibly open resource handles
-	 */
-	void destroy();
-
-	/**
-	 * 
-	 * @return whether the data store is replicated
-	 */
-	boolean isReplicated();
-
-	/**
-	 * @return whether the implementation can be wrapped in {@link AsynchronousDataStore}
-	 */
-	boolean canBeAsynchronous();
-}
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageContext.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageContext.java
new file mode 100644
index 0000000..0cdcd48
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageContext.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.pageStore;
+
+import java.io.Serializable;
+import java.util.function.Supplier;
+
+import org.apache.wicket.MetaDataKey;
+import org.apache.wicket.Session;
+import org.apache.wicket.page.IManageablePage;
+import org.apache.wicket.request.cycle.RequestCycle;
+
+/**
+ * Context of a {@link IManageablePage} when it is stored in an {@link IPageStore}, decoupling it
+ * from request cycle and session.
+ * 
+ * @author Matej Knopp
+ * @author svenmeier
+ */
+public interface IPageContext
+{
+	/**
+	 * Get data from the current request cycle.
+	 * 
+	 * @param key
+	 *            key
+	 * @param defaultValue
+	 *            default value to use if not present
+	 * 
+	 * @see RequestCycle#getMetaData(MetaDataKey)
+	 */
+	<T> T getRequestData(MetaDataKey<T> key, Supplier<T> defaultValue);
+
+	/**
+	 * Get an attribute from the session. <br>
+	 * Binds the session if not already set and supplied default value is not <code>null</code>.
+	 * 
+	 * @param key
+	 *            key
+	 * @param defaultValue
+	 *            default value to use if not present
+	 * @return value
+	 * 
+	 * @see Session#getAttribute(String)
+	 */
+	<T extends Serializable> T getSessionAttribute(String key, Supplier<T> defaultValue);
+
+	/**
+	 * Get metadata from the session. <br>
+	 * Binds the session if not already set and supplied default value is not <code>null</code>.
+	 *
+	 * @param key
+	 *            key
+	 * @param defaultValue
+	 *            default value to use if not present
+	 * @return value
+	 * 
+	 * @see Session#getMetaData(MetaDataKey)
+	 */
+	<T extends Serializable> T getSessionData(MetaDataKey<T> key, Supplier<T> defaultValue);
+
+	/**
+	 * Get the identifier of the session.
+	 * 
+	 * @param bind
+	 *            should the session be bound
+	 * @return session id, might be <code>null</code> if not bound yet
+	 */
+	String getSessionId(boolean bind);
+}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageStore.java
index ed1b9d3..bf2c798 100644
--- a/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageStore.java
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageStore.java
@@ -16,96 +16,88 @@
  */
 package org.apache.wicket.pageStore;
 
-import java.io.Serializable;
-
 import org.apache.wicket.page.IManageablePage;
 
 /**
- * {@link IPageStore} role is to mediate the storing and loading of pages done by {@link IDataStore}
- * s. {@link IPageStore} may pre-process the pages before passing them to
- * {@link IDataStore#storeData(String, int, byte[])} and to post-process them after
- * {@link IDataStore#getData(String, int)}.
- * 
- * @see IDataStore
+ * A store of pages
  */
 public interface IPageStore
 {
 	/**
-	 * Destroy the store.
+	 * Is versioning of pages supported, i.e. when an altered page instance has been stored in this store, can the previous state of that page
+	 * still be retrieved under its former page id. Most implementations achieve this by keeping a copy of the page instance, e.g. by means of serialization.   
 	 */
-	void destroy();
+	boolean supportsVersioning();
 
 	/**
-	 * Restores a page from the persistent layer.
+	 * This method is called by {@link AsynchronousPageStore} before any attempt to call
+	 * {@link #addPage(IPageContext, IManageablePage)} asynchronously.
+	 * <p>
+	 * A page store returning <code>true</code> must immediately access all required values from the context, 
+	 * since no additional values can be accessed when {@link #addPage(IPageContext, IManageablePage)} is called
+	 * asynchronously afterwards.
 	 * 
-	 * @param sessionId
-	 *            The session of the page that must be removed
-	 * @param pageId
-	 *            The id of the page.
-	 * @return The page
+	 * @return whether {@link #addPage(IPageContext, IManageablePage)} may be called asynchronously,
+	 *         default is <code>false</code>
 	 */
-	IManageablePage getPage(String sessionId, int pageId);
-
+	default boolean canBeAsynchronous(IPageContext context)
+	{
+		return false;
+	}
+	
 	/**
-	 * Removes a page from the persistent layer.
+	 * Stores the page-
 	 * 
-	 * @param sessionId
-	 *            The session of the page that must be removed
-	 * @param pageId
-	 *            The id of the page.
+	 * @param context
+	 *            the context of the page
+	 * @param id
+	 *            the id of the page.
 	 */
-	void removePage(String sessionId, int pageId);
+	void addPage(IPageContext context, IManageablePage page);
 
 	/**
-	 * Stores the page to a persistent layer. The page should be stored under the id and the version
-	 * number.
+	 * Removes a page from storage.
 	 * 
-	 * @param sessionId
-	 *            The session of the page that must be removed
-	 * @param page
-	 *            The page to store
+	 * @param context
+	 *            the context of the page
+	 * @param id
+	 *            the id of the page.
 	 */
-	void storePage(String sessionId, IManageablePage page);
+	void removePage(IPageContext context, IManageablePage page);
 
 	/**
-	 * The page store should cleanup all the pages for that sessionid.
+	 * All pages should be removed from storage for the given context.
 	 * 
-	 * @param sessionId
-	 *            The session of the page that must be removed
+	 * @param context
+	 *            the context of the pages
 	 */
-	void unbind(String sessionId);
+	void removeAllPages(IPageContext context);
 
 	/**
-	 * Process the page before the it gets serialized. The page can be either real page instance or
-	 * object returned by {@link #restoreAfterSerialization(Serializable)}.
+	 * Restores a page from storage.
 	 * 
-	 * @param sessionId
-	 *            The session of the page that must be removed
-	 * @param page
-	 * @return The Page itself or a SerializedContainer for that page
+	 * @param context
+	 *            the context of the page
+	 * @param id
+	 *            the id of the page.
+	 * @return the page
 	 */
-	Serializable prepareForSerialization(String sessionId, Serializable page);
+	IManageablePage getPage(IPageContext context, int id);
 
 	/**
-	 * This method should restore the serialized page to intermediate object that can be converted
-	 * to real page instance using {@link #convertToPage(Object)}.
+	 * Detach from the current context.
 	 * 
-	 * @param serializable
-	 * @return Page
+	 * @param context
+	 *            the context of the pages
 	 */
-	Object restoreAfterSerialization(Serializable serializable);
+	default void detach(IPageContext context)
+	{
+	}
 
 	/**
-	 * Converts a page representation to an instance of {@link IManageablePage}
-	 * 
-	 * @param page
-	 *            some kind of page representation
-	 * @return page
-	 */
-	IManageablePage convertToPage(Object page);
-	
-	/**
-	 * @return whether the implementation can be wrapped in {@link AsynchronousPageStore}
+	 * Destroy the store.
 	 */
-	boolean canBeAsynchronous();
+	default void destroy()
+	{
+	}
 }
diff --git a/wicket-core/src/test/java/org/apache/wicket/pageStore/DefaultPageStoreTest.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/IPersistedPage.java
similarity index 62%
copy from wicket-core/src/test/java/org/apache/wicket/pageStore/DefaultPageStoreTest.java
copy to wicket-core/src/main/java/org/apache/wicket/pageStore/IPersistedPage.java
index 2ffa376..c8a126d 100644
--- a/wicket-core/src/test/java/org/apache/wicket/pageStore/DefaultPageStoreTest.java
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/IPersistedPage.java
@@ -16,16 +16,31 @@
  */
 package org.apache.wicket.pageStore;
 
-import org.apache.wicket.serialize.ISerializer;
+import java.io.Serializable;
+
+import org.apache.wicket.util.lang.Bytes;
 
 /**
- * Tests for DefaultPageStore
+ * Information about a page persisted in an {@link IPersistentPageStore}.
+ * <p>
+ * Note: This is not an actual page but a representation with essential information only.
+ * 
+ * @see IPersistentPageStore#getPersistedPages(String)
  */
-public class DefaultPageStoreTest extends AbstractPageStoreTest
+public interface IPersistedPage extends Serializable
 {
-	@Override
-	protected IPageStore createPageStore(ISerializer serializer, IDataStore dataStore, int maxEntries)
-	{
-		return new DefaultPageStore(serializer, dataStore, maxEntries);
-	}
-}
+	/**
+	 * Id of page.
+	 */
+	int getPageId();
+
+	/**
+	 * Size of page.
+	 */
+	Bytes getPageSize();
+
+	/**
+	 * Type of page.
+	 */
+	String getPageType();
+}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/IPersistentPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/IPersistentPageStore.java
new file mode 100644
index 0000000..9a4226d
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/IPersistentPageStore.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.pageStore;
+
+import java.util.List;
+import java.util.Set;
+
+import org.apache.wicket.util.lang.Bytes;
+
+/**
+ * A store that can provide information about stored pages.
+ * <p>
+ * This is an optional interface to a store that is not involved during normal page processing.
+ * Rather, it is used for analysis of application memory footprint.
+ */
+public interface IPersistentPageStore extends IPageStore
+{
+
+	/**
+	 * Get the session identifier for pages stored for the given context.
+	 * 
+	 * @param context
+	 *            a context of pages
+	 * @return the identifier of the session.
+	 * 
+	 * @see #getPersistedPages(String)
+	 */
+	String getSessionIdentifier(IPageContext context);
+
+	/**
+	 * Get the identifiers for all stored sessions.
+	 * 
+	 * @return the identifiers of all session.
+	 */
+	Set<String> getSessionIdentifiers();
+
+	/**
+	 * Get information about all persisted pages with the given session identifier.
+	 * 
+	 * @param the
+	 *            identifier of the session.
+	 * @return all persisted pages
+	 */
+	List<IPersistedPage> getPersistedPages(String sessionIdentifier);
+
+	/**
+	 * Get total size of all pages stored in all contexts.
+	 * <p>
+	 * Optional operation, may return <code>null</code>.
+	 * 
+	 * @return total size or <code>null</code>
+	 */
+	Bytes getTotalSize();
+}
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/InMemoryPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/InMemoryPageStore.java
new file mode 100644
index 0000000..481f717
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/InMemoryPageStore.java
@@ -0,0 +1,338 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.pageStore;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.core.util.lang.WicketObjects;
+import org.apache.wicket.page.IManageablePage;
+import org.apache.wicket.util.lang.Args;
+import org.apache.wicket.util.lang.Bytes;
+import org.apache.wicket.util.lang.Classes;
+
+/**
+ * A storage of pages in memory.
+ */
+public class InMemoryPageStore extends AbstractPersistentPageStore implements IPersistentPageStore
+{
+
+	private final Map<String, MemoryData> datas = new ConcurrentHashMap<>();
+
+	private final Supplier<MemoryData> dataCreator;
+
+	/**
+	 * Keep {@code maxPages} for each session.
+	 *    
+	 * @param applicationName
+	 *            {@link Application#getName()}
+	 * @param maxPages
+	 *            max pages per session
+	 */
+	public InMemoryPageStore(String applicationName, int maxPages)
+	{
+		this(applicationName, () -> new CountLimitedData(maxPages));
+	}
+
+	/**
+	 * Keep page up to {@code maxBytes} for each session.
+	 * <p>
+	 * All pages added to this store <em>must</em> be {@code SerializedPage}s. You can achieve this by letting
+	 * a {@link SerializingPageStore} delegate to this store.
+	 * 
+	 * @param applicationName
+	 *            {@link Application#getName()}
+	 * @param maxBytes
+	 *            maximum bytes to keep in session
+	 */
+	public InMemoryPageStore(String applicationName, Bytes maxBytes)
+	{
+		this(applicationName, () -> new SizeLimitedData(maxBytes));
+	}
+
+	InMemoryPageStore(String applicationName, Supplier<MemoryData> dataCreator)
+	{
+		super(applicationName);
+		
+		this.dataCreator = dataCreator;
+	}
+
+	/**
+	 * Versioning is not supported.	
+	 */
+	@Override
+	public boolean supportsVersioning()
+	{
+		return false;
+	}
+	
+	@Override
+	protected IManageablePage getPersistedPage(String sessionIdentifier, int id)
+	{
+		MemoryData data = getMemoryData(sessionIdentifier, false);
+		if (data != null)
+		{
+			return data.get(id);
+		}
+
+		return null;
+	}
+
+	@Override
+	protected void removePersistedPage(String sessionIdentifier, IManageablePage page)
+	{
+		MemoryData data = getMemoryData(sessionIdentifier, false);
+		if (data != null)
+		{
+			synchronized (data)
+			{
+				data.remove(page.getPageId());
+			}
+		}
+	}
+
+	@Override
+	protected void removeAllPersistedPages(String sessionIdentifier)
+	{
+		datas.remove(sessionIdentifier);
+	}
+
+	@Override
+	protected void addPersistedPage(String sessionIdentifier, IManageablePage page)
+	{
+		MemoryData data = getMemoryData(sessionIdentifier, true);
+
+		data.add(page);
+	}
+
+	@Override
+	public Set<String> getSessionIdentifiers()
+	{
+		return datas.keySet();
+	}
+
+	@Override
+	public List<IPersistedPage> getPersistedPages(String sessionIdentifier)
+	{
+		MemoryData data = datas.get(sessionIdentifier);
+		if (data == null)
+		{
+			return new ArrayList<>();
+		}
+
+		synchronized (data)
+		{
+			return StreamSupport.stream(data.spliterator(), false)
+				.map(page -> {
+					String pageType = page instanceof SerializedPage ? ((SerializedPage)page).getPageType() : Classes.name(page.getClass());
+					
+					return new PersistedPage(page.getPageId(), pageType, getSize(page));
+				})
+				.collect(Collectors.toList());
+		}
+	}
+
+	@Override
+	public Bytes getTotalSize()
+	{
+		int size = 0;
+
+		for (MemoryData data : datas.values())
+		{
+			synchronized (data)
+			{
+				for (IManageablePage page : data)
+				{
+					size += getSize(page);
+				}
+			}
+		}
+
+		return Bytes.bytes(size);
+	}
+
+	/**
+	 * Get the size of the given page.
+	 */
+	protected long getSize(IManageablePage page)
+	{
+		if (page instanceof SerializedPage)
+		{
+			return ((SerializedPage)page).getData().length;
+		}
+		else
+		{
+			return WicketObjects.sizeof(page);
+		}
+	}
+
+	private MemoryData getMemoryData(String sessionIdentifier, boolean create)
+	{
+		if (!create)
+		{
+			return datas.get(sessionIdentifier);
+		}
+
+		MemoryData data = dataCreator.get();
+		MemoryData existing = datas.putIfAbsent(sessionIdentifier, data);
+		return existing != null ? existing : data;
+	}
+
+	/**
+	 * Data kept in memory.
+	 */
+	static abstract class MemoryData implements Iterable<IManageablePage>
+	{
+		/**
+		 * Kept in list instead of map, since non-serialized pages might change their id during a request.
+		 */
+		List<IManageablePage> pages = new LinkedList<>();
+
+		@Override
+		public Iterator<IManageablePage> iterator()
+		{
+			return pages.iterator();
+		}
+
+		public synchronized void add(IManageablePage page)
+		{
+			remove(page.getPageId());
+			
+			pages.add(page);
+		}
+
+		public synchronized IManageablePage remove(int pageId)
+		{
+			Iterator<IManageablePage> iterator = pages.iterator();
+			while (iterator.hasNext())
+			{
+				IManageablePage page = iterator.next();
+				
+				if (page.getPageId() == pageId)
+				{
+					iterator.remove();
+					return page;
+				}
+			}
+			return null;
+		}
+
+		public synchronized IManageablePage get(int pageId)
+		{
+			Iterator<IManageablePage> iterator = pages.iterator();
+			while (iterator.hasNext())
+			{
+				IManageablePage page = iterator.next();
+				
+				if (page.getPageId() == pageId)
+				{
+					return page;
+				}
+			}
+			
+			return null;
+		}
+		
+		protected void removeOldest() {
+			IManageablePage page = pages.iterator().next();
+			
+			remove(page.getPageId());
+		}
+	}
+	
+	/**
+	 * Limit pages by count.
+	 */
+	static class CountLimitedData extends MemoryData
+	{
+
+		private int maxPages;
+
+		public CountLimitedData(int maxPages)
+		{
+			this.maxPages = Args.withinRange(1, Integer.MAX_VALUE, maxPages, "maxPages");
+		}
+	
+		@Override
+		public synchronized void add(IManageablePage page)
+		{
+			super.add(page);
+			
+			while (pages.size() > maxPages)
+			{
+				removeOldest();
+			}
+		}
+	}
+	
+	/**
+	 * Limit pages by size.
+	 */
+	static class SizeLimitedData extends MemoryData
+	{
+		
+		private Bytes maxBytes;
+		
+		private long size;
+
+		public SizeLimitedData(Bytes maxBytes)
+		{
+			Args.notNull(maxBytes, "maxBytes");
+			
+			this.maxBytes = Args.withinRange(Bytes.bytes(1), Bytes.MAX, maxBytes, "maxBytes");
+		}
+		
+		@Override
+		public synchronized void add(IManageablePage page)
+		{
+			if (page instanceof SerializedPage == false)
+			{
+				throw new WicketRuntimeException("InMemoryPageStore limited by size works with serialized pages only");
+			}
+			
+			super.add(page);
+			
+			size += ((SerializedPage) page).getData().length;
+
+			while (size > maxBytes.bytes())
+			{
+				removeOldest();
+			}
+		}
+		
+		@Override
+		public synchronized IManageablePage remove(int pageId)
+		{
+			SerializedPage page = (SerializedPage) super.remove(pageId);
+			if (page != null)
+			{
+				size -= ((SerializedPage) page).getData().length;
+			}
+			return page;
+		}
+	}
+}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/InSessionPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/InSessionPageStore.java
new file mode 100644
index 0000000..3f1275e
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/InSessionPageStore.java
@@ -0,0 +1,377 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.pageStore;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.function.Supplier;
+
+import javax.servlet.http.HttpSession;
+
+import org.apache.wicket.DefaultPageManagerProvider;
+import org.apache.wicket.MetaDataKey;
+import org.apache.wicket.Session;
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.page.IManageablePage;
+import org.apache.wicket.serialize.ISerializer;
+import org.apache.wicket.util.lang.Args;
+import org.apache.wicket.util.lang.Bytes;
+import org.apache.wicket.util.lang.Classes;
+
+/**
+ * A store keeping a configurable maximum of pages in the session.
+ * <p>
+ * This store is used by {@link DefaultPageManagerProvider} as a cache in front 
+ * of a persistent store.
+ */
+public class InSessionPageStore extends DelegatingPageStore
+{
+
+	private static final MetaDataKey<SessionData> KEY = new MetaDataKey<SessionData>()
+	{
+		private static final long serialVersionUID = 1L;
+	};
+
+	private final ISerializer serializer;
+
+	private final Supplier<SessionData> dataCreator;
+	
+	/**
+	 * Keep {@code maxPages} in each session.
+	 * <p>
+	 * If the container serializes sessions to disk, any non-{@code SerializedPage} added to this store
+	 * will be dropped.   
+	 * 
+	 * @param delegate
+	 *            store to delegate to
+	 * @param maxPages
+	 *            maximum pages to keep in session
+	 */
+	public InSessionPageStore(IPageStore delegate, int maxPages)
+	{
+		this(delegate, maxPages, null);
+	}
+	
+	/**
+	 * Keep {@code maxPages} in each session.
+	 * <p>
+	 * If the container serializes sessions to disk, any non-{@code SerializedPage} added to this store
+	 * will be automatically serialized.   
+	 * 
+	 * @param delegate
+	 *            store to delegate to
+	 * @param maxPages
+	 *            maximum pages to keep in session
+	 * @param serializer
+	 *            optional serializer of pages in the session
+	 */
+	public InSessionPageStore(IPageStore delegate, int maxPages, ISerializer serializer)
+	{
+		this(delegate, serializer, () -> new CountLimitedData(maxPages));
+	}
+
+	/**
+	 * Keep page up to {@code maxBytes} in each session.
+	 * <p>
+	 * All pages added to this store <em>must</em> be {@code SerializedPage}s. You can achieve this by letting
+	 * a {@link SerializingPageStore} delegate to this store.
+	 * 
+	 * @param delegate
+	 *            store to delegate to
+	 * @param maxBytes
+	 *            maximum bytes to keep in session
+	 */
+	public InSessionPageStore(IPageStore delegate, Bytes maxBytes)
+	{
+		this(delegate, null, () -> new SizeLimitedData(maxBytes));
+	}
+
+	private InSessionPageStore(IPageStore delegate, ISerializer serializer, Supplier<SessionData> dataCreator)
+	{
+		super(delegate);
+
+		this.serializer = serializer;
+		
+		this.dataCreator = dataCreator;
+	}
+
+	@Override
+	public IManageablePage getPage(IPageContext context, int id)
+	{
+		SessionData data = getSessionData(context, false);
+		if (data != null)
+		{
+			IManageablePage page = data.get(id);
+			if (page != null)
+			{
+				return page;
+			}
+		}
+
+		return getDelegate().getPage(context, id);
+	}
+
+	@Override
+	public void addPage(IPageContext context, IManageablePage page)
+	{
+		SessionData data = getSessionData(context, true);
+
+		data.add(page);
+		
+		getDelegate().addPage(context, page);
+	}
+
+	@Override
+	public void removePage(IPageContext context, IManageablePage page)
+	{
+		SessionData data = getSessionData(context, false);
+		if (data != null)
+		{
+			data.remove(page.getPageId());
+		}
+
+		getDelegate().removePage(context, page);
+	}
+
+	@Override
+	public void removeAllPages(IPageContext context)
+	{
+		SessionData data = getSessionData(context, false);
+		if (data != null)
+		{
+			data.removeAll();
+		}
+
+		getDelegate().removeAllPages(context);
+	}
+
+	private SessionData getSessionData(IPageContext context, boolean create)
+	{
+		SessionData data = context.getSessionData(KEY, () -> {
+			if (create)
+			{
+				return dataCreator.get();
+			}
+			else
+			{
+				return null;
+			}
+		});
+
+		if (data != null && serializer != null)
+		{
+			// data might be deserialized so initialize again
+			data.supportSessionSerialization(serializer);
+		}
+
+		return data;
+	}
+
+	/**
+	 * Data kept in the {@link Session}, might get serialized along with its containing
+	 * {@link HttpSession}.
+	 */
+	static abstract class SessionData implements Serializable
+	{
+
+		transient ISerializer serializer;
+
+		/**
+		 * Pages, may partly be serialized.
+		 * <p>
+		 * Kept in list instead of map, since non-serialized pages might change their id during a request.
+		 */
+		List<IManageablePage> pages = new LinkedList<>();
+
+		/**
+		 * Call this method if session serialization should be supported, i.e. all pages get serialized along with the session.
+		 */
+		public void supportSessionSerialization(ISerializer serializer)
+		{
+			this.serializer = Args.notNull(serializer, "serializer");
+		}
+
+		public synchronized void add(IManageablePage page)
+		{
+			// move to end
+			remove(page.getPageId());
+			
+			pages.add(page);
+		}
+
+		protected synchronized void removeOldest()
+		{
+			IManageablePage page = pages.get(0);
+			
+			remove(page.getPageId());
+		}
+		
+		public synchronized IManageablePage remove(int pageId)
+		{
+			Iterator<IManageablePage> iterator = pages.iterator();
+			while (iterator.hasNext())
+			{
+				IManageablePage page = iterator.next();
+				
+				if (page.getPageId() == pageId)
+				{
+					iterator.remove();
+					return page;
+				}
+			}
+			return null;
+		}
+
+		public synchronized void removeAll()
+		{
+			pages.clear();
+		}
+
+		public synchronized IManageablePage get(int id)
+		{
+			for (int p = 0; p < pages.size(); p++)
+			{
+				IManageablePage candidate = pages.get(p);
+
+				if (candidate.getPageId() == id)
+				{
+					if (candidate instanceof SerializedPage && serializer != null)
+					{
+						candidate = (IManageablePage)serializer.deserialize(((SerializedPage)candidate).getData());
+		
+						pages.set(p, candidate);
+					}
+					
+					return candidate;
+				}
+			}
+
+			return null;
+		}
+
+		/**
+		 * Serialize pages before writing to output.
+		 */
+		private void writeObject(final ObjectOutputStream output) throws IOException
+		{
+			// handle non-serialized pages
+			for (int p = 0; p < pages.size(); p++)
+			{
+				IManageablePage page = pages.get(p);
+				
+				if ((page instanceof SerializedPage) == false)
+				{
+					if (serializer == null)
+					{
+						pages.remove(p);
+						p--;
+					}
+					else
+					{
+						pages.set(p, new SerializedPage(page.getPageId(), Classes.name(page.getClass()), serializer.serialize(page)));
+					}
+				}
+			}
+
+			output.defaultWriteObject();
+		}
+	}
+	
+	/**
+	 * Limit pages by count.
+	 */
+	static class CountLimitedData extends SessionData
+	{
+		
+		private int maxPages;
+
+		public CountLimitedData(int maxPages)
+		{
+			this.maxPages = Args.withinRange(1, Integer.MAX_VALUE, maxPages, "maxPages");
+		}
+		
+		public synchronized void add(IManageablePage page)
+		{
+			super.add(page);
+			
+			while (pages.size() > maxPages)
+			{
+				removeOldest();
+			}
+		}
+	}
+	
+	/**
+	 * Limit pages by size.
+	 */
+	static class SizeLimitedData extends SessionData
+	{
+
+		private Bytes maxBytes;
+		
+		private long size;
+
+		public SizeLimitedData(Bytes maxBytes)
+		{
+			Args.notNull(maxBytes, "maxBytes");
+			
+			this.maxBytes = Args.withinRange(Bytes.bytes(1), Bytes.MAX, maxBytes, "maxBytes");
+		}
+		
+		@Override
+		public synchronized void add(IManageablePage page)
+		{
+			if (page instanceof SerializedPage == false)
+			{
+				throw new WicketRuntimeException("InSessionPageStore limited by size works with serialized pages only");
+			}
+			
+			super.add(page);
+			
+			size += ((SerializedPage) page).getData().length;
+
+			while (size > maxBytes.bytes())
+			{
+				removeOldest();
+			}
+		}
+		
+		@Override
+		public synchronized IManageablePage remove(int pageId)
+		{
+			SerializedPage page = (SerializedPage) super.remove(pageId);
+			if (page != null)
+			{
+				size -= ((SerializedPage) page).getData().length;
+			}
+			
+			return page;
+		}
+		
+		@Override
+		public synchronized void removeAll()
+		{
+			super.removeAll();
+			
+			size = 0;
+		}
+	}
+}
\ No newline at end of file
diff --git a/wicket-core/src/test/java/org/apache/wicket/pageStore/NoopDataStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/NoopPageStore.java
similarity index 67%
rename from wicket-core/src/test/java/org/apache/wicket/pageStore/NoopDataStore.java
rename to wicket-core/src/main/java/org/apache/wicket/pageStore/NoopPageStore.java
index 8242d4f..d31313f 100644
--- a/wicket-core/src/test/java/org/apache/wicket/pageStore/NoopDataStore.java
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/NoopPageStore.java
@@ -16,46 +16,43 @@
  */
 package org.apache.wicket.pageStore;
 
+import org.apache.wicket.page.IManageablePage;
+
 /**
- * An implementation of IDataStore that does nothing
+ * A non-storage of pages.
  */
-class NoopDataStore implements IDataStore
+public class NoopPageStore implements IPageStore
 {
 	@Override
-	public byte[] getData(String sessionId, int id)
-	{
-		return null;
-	}
-
-	@Override
-	public void removeData(String sessionId, int id)
+	public boolean supportsVersioning()
 	{
+		return false;
 	}
 
 	@Override
-	public void removeData(String sessionId)
+	public boolean canBeAsynchronous(IPageContext context)
 	{
+		return true;
 	}
-
+	
 	@Override
-	public void storeData(String sessionId, int id, byte[] data)
+	public void addPage(IPageContext context, IManageablePage page)
 	{
 	}
 
 	@Override
-	public void destroy()
+	public void removePage(IPageContext context, IManageablePage page)
 	{
 	}
 
 	@Override
-	public boolean isReplicated()
+	public void removeAllPages(IPageContext context)
 	{
-		return false;
 	}
 
 	@Override
-	public boolean canBeAsynchronous()
+	public IManageablePage getPage(IPageContext context, int id)
 	{
-		return false;
+		return null;
 	}
-}
+}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/PerSessionPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/PerSessionPageStore.java
deleted file mode 100644
index 4a21f4c..0000000
--- a/wicket-core/src/main/java/org/apache/wicket/pageStore/PerSessionPageStore.java
+++ /dev/null
@@ -1,332 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.wicket.pageStore;
-
-import java.lang.ref.SoftReference;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.ConcurrentSkipListMap;
-
-import org.apache.wicket.page.IManageablePage;
-import org.apache.wicket.serialize.ISerializer;
-import org.apache.wicket.util.lang.Args;
-
-/**
- * A page store that uses a SecondLevelPageCache with the last N used page instances
- * per session.
- *
- * <strong>Note</strong>: the size of the cache depends on the {@code cacheSize} constructor
- * parameter multiplied by the number of the active http sessions.
- *
- * It depends on the application use cases but usually a reasonable value of
- * {@code cacheSize} would be just a few pages (2-3). If the application don't expect many
- * active http sessions and the work flow involves usage of the browser/application history
- * then the {@code cacheSize} value may be increased to a bigger value.
- */
-public class PerSessionPageStore extends AbstractCachingPageStore<IManageablePage>
-{
-	/**
-	 * Constructor.
-	 *
-	 * @param pageSerializer
-	 *            the {@link org.apache.wicket.serialize.ISerializer} that will be used to convert pages from/to byte arrays
-	 * @param dataStore
-	 *            the {@link org.apache.wicket.pageStore.IDataStore} that actually stores the pages
-	 * @param cacheSize
-	 *            the number of pages to cache in memory before passing them to
-	 *            {@link org.apache.wicket.pageStore.IDataStore#storeData(String, int, byte[])}
-	 */
-	public PerSessionPageStore(final ISerializer pageSerializer, final IDataStore dataStore,
-	                           final int cacheSize)
-	{
-		super(pageSerializer, dataStore, new PagesCache(cacheSize));
-	}
-
-	@Override
-	public IManageablePage convertToPage(final Object object)
-	{
-		if (object == null)
-		{
-			return null;
-		}
-		else if (object instanceof IManageablePage)
-		{
-			return (IManageablePage)object;
-		}
-
-		String type = object.getClass().getName();
-		throw new IllegalArgumentException("Unknown object type: " + type);
-	}
-
-	/**
-	 * An implementation of SecondLevelPageCache that stores the last used N live page instances
-	 * per http session.
-	 */
-	protected static class PagesCache implements SecondLevelPageCache<String, Integer, IManageablePage>
-	{
-		/**
-		 * Helper class used to compare the page entries in the cache by their
-		 * access time
-		 */
-		private static class PageValue
-		{
-			/**
-			 * The id of the cached page
-			 */
-			private final int pageId;
-
-			/**
-			 * The last time this page has been used/accessed.
-			 */
-			private long accessTime;
-
-			private PageValue(IManageablePage page)
-			{
-				this(page.getPageId());
-			}
-
-			private PageValue(int pageId)
-			{
-				this.pageId = pageId;
-				touch();
-			}
-
-			/**
-			 * Updates the access time with the current time
-			 */
-			private void touch()
-			{
-				accessTime = System.nanoTime();
-			}
-
-			@Override
-			public boolean equals(Object o)
-			{
-				if (this == o) return true;
-				if (o == null || getClass() != o.getClass()) return false;
-
-				PageValue pageValue = (PageValue) o;
-
-				return pageId == pageValue.pageId;
-			}
-
-			@Override
-			public int hashCode()
-			{
-				return pageId;
-			}
-		}
-
-		private static class PageComparator implements Comparator<PageValue>
-		{
-			@Override
-			public int compare(PageValue p1, PageValue p2)
-			{
-				return Long.compare(p1.accessTime, p2.accessTime);
-			}
-		}
-
-		private final int maxEntriesPerSession;
-
-		private final ConcurrentMap<String, SoftReference<ConcurrentSkipListMap<PageValue, IManageablePage>>> cache;
-
-		/**
-		 * Constructor.
-		 *
-		 * @param maxEntriesPerSession
-		 *          The number of cache entries per session
-		 */
-		public PagesCache(final int maxEntriesPerSession)
-		{
-			this.maxEntriesPerSession = maxEntriesPerSession;
-			cache = new ConcurrentHashMap<>();
-		}
-
-		/**
-		 *
-		 * @param sessionId
-		 *          The id of the http session
-		 * @param pageId
-		 *          The id of the page to remove from the cache
-		 * @return the removed {@link org.apache.wicket.page.IManageablePage} or <code>null</code> - otherwise
-		 */
-		@Override
-		public IManageablePage removePage(final String sessionId, final Integer pageId)
-		{
-			IManageablePage result = null;
-
-			if (maxEntriesPerSession > 0)
-			{
-				Args.notNull(sessionId, "sessionId");
-				Args.notNull(pageId, "pageId");
-
-				SoftReference<ConcurrentSkipListMap<PageValue, IManageablePage>> pagesPerSession = cache.get(sessionId);
-				if (pagesPerSession != null)
-				{
-					ConcurrentMap<PageValue, IManageablePage> pages = pagesPerSession.get();
-					if (pages != null)
-					{
-						PageValue sample = new PageValue(pageId);
-						Iterator<Map.Entry<PageValue, IManageablePage>> iterator = pages.entrySet().iterator();
-						while (iterator.hasNext())
-						{
-							Map.Entry<PageValue, IManageablePage> entry = iterator.next();
-							if (sample.equals(entry.getKey()))
-							{
-								result = entry.getValue();
-								iterator.remove();
-								break;
-							}
-						}
-					}
-				}
-			}
-
-			return result;
-		}
-
-		/**
-		 * Removes all {@link org.apache.wicket.page.IManageablePage}s for the session
-		 * with <code>sessionId</code> from the cache.
-		 *
-		 * @param sessionId
-		 *          The id of the expired http session
-		 */
-		@Override
-		public void removePages(String sessionId)
-		{
-			Args.notNull(sessionId, "sessionId");
-
-			if (maxEntriesPerSession > 0)
-			{
-				cache.remove(sessionId);
-			}
-		}
-
-		/**
-		 * Returns a {@link org.apache.wicket.page.IManageablePage} by looking it up by <code>sessionId</code> and
-		 * <code>pageId</code>. If there is a match then it is <i>touched</i>, i.e. it is moved at
-		 * the top of the cache.
-		 * 
-		 * @param sessionId
-		 *          The id of the http session
-		 * @param pageId
-		 *          The id of the page to find
-		 * @return the found serialized page or <code>null</code> when not found
-		 */
-		@Override
-		public IManageablePage getPage(String sessionId, Integer pageId)
-		{
-			IManageablePage result = null;
-
-			if (maxEntriesPerSession > 0)
-			{
-				Args.notNull(sessionId, "sessionId");
-				Args.notNull(pageId, "pageId");
-
-				SoftReference<ConcurrentSkipListMap<PageValue, IManageablePage>> pagesPerSession = cache.get(sessionId);
-				if (pagesPerSession != null)
-				{
-					ConcurrentSkipListMap<PageValue, IManageablePage> pages = pagesPerSession.get();
-					if (pages != null)
-					{
-						PageValue sample = new PageValue(pageId);
-						for (Map.Entry<PageValue, IManageablePage> entry : pages.entrySet())
-						{
-							if (sample.equals(entry.getKey()))
-							{
-								// touch the entry
-								entry.getKey().touch();
-								result = entry.getValue();
-								break;
-							}
-						}
-					}
-				}
-			}
-			return result;
-		}
-
-		/**
-		 * Store the serialized page in cache
-		 * 
-		 * @param page
-		 *      the data to serialize (page id, session id, bytes)
-		 */
-		@Override
-		public void storePage(String sessionId, Integer pageId, IManageablePage page)
-		{
-			if (maxEntriesPerSession > 0)
-			{
-				Args.notNull(sessionId, "sessionId");
-				Args.notNull(pageId, "pageId");
-
-				SoftReference<ConcurrentSkipListMap<PageValue, IManageablePage>> pagesPerSession = cache.get(sessionId);
-				if (pagesPerSession == null)
-				{
-					ConcurrentSkipListMap<PageValue, IManageablePage> pages = new ConcurrentSkipListMap<>(new PageComparator());
-					pagesPerSession = new SoftReference<>(pages);
-					SoftReference<ConcurrentSkipListMap<PageValue, IManageablePage>> old = cache.putIfAbsent(sessionId, pagesPerSession);
-					if (old != null)
-					{
-						pagesPerSession = old;
-					}
-				}
-
-				ConcurrentSkipListMap<PageValue, IManageablePage> pages = pagesPerSession.get();
-				if (pages == null)
-				{
-					pages = new ConcurrentSkipListMap<>();
-					pagesPerSession = new SoftReference<>(pages);
-					SoftReference<ConcurrentSkipListMap<PageValue, IManageablePage>> old = cache.putIfAbsent(sessionId, pagesPerSession);
-					if (old != null)
-					{
-						pages = old.get();
-					}
-				}
-
-				if (pages != null)
-				{
-					removePage(sessionId, pageId);
-
-					PageValue pv = new PageValue(page);
-					pages.put(pv, page);
-
-					while (pages.size() > maxEntriesPerSession)
-					{
-						pages.pollFirstEntry();
-					}
-				}
-			}
-		}
-
-		@Override
-		public void destroy()
-		{
-			cache.clear();
-		}
-	}
-
-	@Override
-	public boolean canBeAsynchronous()
-	{
-		return false; // NOTE: not analyzed neither tested yet, this page store being wrapped by asynchronous one
-	}
-}
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/RequestPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/RequestPageStore.java
new file mode 100644
index 0000000..0355774
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/RequestPageStore.java
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.pageStore;
+
+import java.util.LinkedList;
+
+import org.apache.wicket.MetaDataKey;
+import org.apache.wicket.page.IManageablePage;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Buffers storage of added pages until the end of the request, when they are delegated to the next store in
+ * the identical order they where added.
+ */
+public class RequestPageStore extends DelegatingPageStore
+{
+
+	private static final Logger log = LoggerFactory.getLogger(RequestPageStore.class);
+
+	private static final MetaDataKey<RequestData> KEY = new MetaDataKey<>()
+	{
+		private static final long serialVersionUID = 1L;
+	};
+
+	public RequestPageStore(IPageStore delegate)
+	{
+		super(delegate);
+	}
+
+	@Override
+	public IManageablePage getPage(IPageContext context, int id)
+	{
+		IManageablePage page = getRequestData(context).get(id);
+		if (page != null)
+		{
+			return page;
+		}
+
+		return getDelegate().getPage(context, id);
+	}
+
+	@Override
+	public void addPage(IPageContext context, IManageablePage page)
+	{
+		getRequestData(context).add(page);
+	}
+
+	@Override
+	public void removePage(IPageContext context, IManageablePage page)
+	{
+		getRequestData(context).remove(page);
+
+		getDelegate().removePage(context, page);
+	}
+
+	@Override
+	public void removeAllPages(IPageContext context)
+	{
+		getRequestData(context).removeAll();
+
+		getDelegate().removeAllPages(context);
+	}
+
+	@Override
+	public void detach(IPageContext context)
+	{
+		RequestData requestData = getRequestData(context);
+		for (IManageablePage page : requestData.pages())
+		{
+			boolean isPageStateless;
+			try
+			{
+				isPageStateless = page.isPageStateless();
+			}
+			catch (Exception x)
+			{
+				log.warn("An error occurred while checking whether a page is stateless. Assuming it is stateful.", x);
+				isPageStateless = false;
+			}
+
+			if (isPageStateless == false)
+			{
+				getDelegate().addPage(context, page);
+			}
+		}
+		requestData.removeAll();
+
+		getDelegate().detach(context);
+	}
+
+	private RequestData getRequestData(IPageContext context)
+	{
+		return context.getRequestData(KEY, () -> new RequestData());
+	}
+	
+	/**
+	 * Data kept in the {@link RequestCycle}.
+	 */
+	static class RequestData
+	{
+		private LinkedList<IManageablePage> pages = new LinkedList<>();
+		
+		public void add(IManageablePage page)
+		{
+			// add as last
+			pages.remove(page);
+			pages.addLast(page);
+		}
+
+		public Iterable<IManageablePage> pages()
+		{
+			return pages;
+		}
+
+		public IManageablePage get(int id)
+		{
+			for (IManageablePage page : pages)
+			{
+				if (page.getPageId() == id)
+				{
+					return page;
+				}
+			}
+			return null;
+		}
+
+		public void remove(IManageablePage page)
+		{
+			pages.remove(page);
+		}
+
+		public void removeAll()
+		{
+			pages.clear();
+		}		
+	}
+}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/SerializedPage.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/SerializedPage.java
new file mode 100644
index 0000000..eccabe6
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/SerializedPage.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.pageStore;
+
+import org.apache.wicket.page.IManageablePage;
+import org.apache.wicket.util.lang.Args;
+
+/**
+ * A wrapper around a serialized page.
+ * <p>
+ * An {@link IPageStore} might use this representation of a page internally,
+ * accept it in {@link IPageStore#addPage(IPageContext, IManageablePage)} or delegate it
+ * to another store.
+ * <p>
+ * Stores might pose restrictions on what type of pages they work with, see {@link CryptingPageStore}
+ * for an example.
+ * 
+ * @see SerializingPageStore
+ */
+public final class SerializedPage implements IManageablePage
+{
+	private final int pageId;
+	
+	private final String pageType;
+
+	private final byte[] data;
+
+	/**
+	 * Create a serialized page.
+	 * 
+	 * @param pageId id of page
+	 * @param data
+	 */
+	public SerializedPage(int pageId, byte[] data)
+	{
+		this(pageId, null, data);
+	}
+
+	/**
+	 * Create a serialized page.
+	 * 
+	 * @param pageId id of page
+	 * @param pageType type of page, might be {@code null}
+	 * @param data
+	 */
+	public SerializedPage(int pageId, String pageType, byte[] data)
+	{
+		this.pageId = pageId;
+		this.pageType = pageType;
+		this.data = Args.notNull(data, "data");
+	}
+
+	@Override
+	public boolean isPageStateless()
+	{
+		return false;
+	}
+
+	@Override
+	public int getPageId()
+	{
+		return pageId;
+	}
+
+	public String getPageType()
+	{
+		return pageType;
+	}
+	
+	public byte[] getData()
+	{
+		return data;
+	}
+	
+	@Override
+	public void detach()
+	{
+	}
+
+	@Override
+	public boolean setFreezePageId(boolean freeze)
+	{
+		return false;
+	}
+	
+	@Override
+	public String toString()
+	{
+		return "[SerializedPage id = " + pageId + ", type=" + pageType + ", size=" + data.length + "]";
+	}
+}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/SerializingPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/SerializingPageStore.java
new file mode 100644
index 0000000..bd19872
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/SerializingPageStore.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.pageStore;
+
+import org.apache.wicket.page.IManageablePage;
+import org.apache.wicket.serialize.ISerializer;
+import org.apache.wicket.util.lang.Args;
+import org.apache.wicket.util.lang.Classes;
+
+/**
+ * A store that serializes all pages before delegating and vice versa.
+ */
+public class SerializingPageStore extends DelegatingPageStore
+{
+
+	private ISerializer serializer;
+
+	/**
+	 * @param delegate
+	 *            store to delegate to
+	 * @param serializer
+	 *            serializer to use if session gets persisted
+	 */
+	public SerializingPageStore(IPageStore delegate, ISerializer serializer)
+	{
+		super(delegate);
+
+		this.serializer = Args.notNull(serializer, "serializer");
+	}
+
+	/**
+	 * Versioning is supported, since pages are always serialized.
+	 */
+	@Override
+	public boolean supportsVersioning()
+	{
+		return true;
+	}
+
+	/**
+	 * Supports asynchronous add if the delegate supports it.
+	 */
+	@Override
+	public boolean canBeAsynchronous(IPageContext context)
+	{
+		return getDelegate().canBeAsynchronous(context);
+	}
+	
+	@Override
+	public IManageablePage getPage(IPageContext context, int id)
+	{
+		IManageablePage page = getDelegate().getPage(context, id);
+
+		if (page instanceof SerializedPage)
+		{
+			page = (IManageablePage)serializer.deserialize(((SerializedPage)page).getData());
+		}
+		return page;
+	}
+
+	@Override
+	public void addPage(IPageContext context, IManageablePage page)
+	{
+		if (page instanceof SerializedPage == false)
+		{
+			page = new SerializedPage(page.getPageId(), Classes.name(page.getClass()), serializer.serialize(page));
+		}
+		getDelegate().addPage(context, page);
+	}
+}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/disk/NestedFolders.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/disk/NestedFolders.java
new file mode 100644
index 0000000..7636d83
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/disk/NestedFolders.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.pageStore.disk;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.wicket.util.file.Files;
+import org.apache.wicket.util.lang.Args;
+
+/**
+ * Keep files in a nested filed structure to minimize amount of directory entries (inodes) in a single directory. 
+ * 
+ * @author svenmeier
+ */
+public class NestedFolders
+{
+	private final File base;
+	
+	/**
+	 * Create folders in the given base folder.
+	 * 
+	 * @param base base has to be a folder
+	 */
+	public NestedFolders(File base)
+	{
+		this.base = Args.notNull(base, "base");
+	}
+	
+	public File getBase()
+	{
+		return base;
+	}
+	
+	/**
+	 * Get a nested folder for the given name.
+	 * 
+	 * @param name name 
+	 * @param create
+	 * @return
+	 */
+	public File get(String name, final boolean create)
+	{
+		name = name.replace('*', '_');
+		name = name.replace('/', '_');
+		name = name.replace(':', '_');
+
+		String path = createPathFrom(name);
+
+		File folder = new File(base, path);
+		if (create && folder.exists() == false)
+		{
+			Files.mkdirs(folder);
+		}
+		return folder;
+	}
+
+	private String createPathFrom(final String name)
+	{
+		int hash = Math.abs(name.hashCode());
+		String low = String.valueOf(hash % 9973);
+		String high = String.valueOf((hash / 9973) % 9973);
+		StringBuilder bs = new StringBuilder(name.length() + 10);
+		bs.append(low);
+		bs.append(File.separator);
+		bs.append(high);
+		bs.append(File.separator);
+		bs.append(name);
+
+		return bs.toString();
+	}
+
+	/**
+	 * Remove a nested folder.
+	 * 
+	 * @param name name of folder
+	 */
+	public void remove(String name)
+	{
+		File folder = get(name, false);
+		if (folder.exists())
+		{
+			Files.removeFolder(folder);
+			
+			File high = folder.getParentFile();
+			if (high.list().length == 0)
+			{
+				if (Files.removeFolder(high))
+				{
+					File low = high.getParentFile();
+					if (low.list().length == 0)
+					{
+						Files.removeFolder(low);
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 * Get all files inside.
+	 * 
+	 * @return files
+	 */
+	public Set<File> getAll()
+	{
+		Set<File> files = new HashSet<>();
+		
+		if (base.exists())
+		{
+			for (File low : Files.list(base))
+			{
+				for (File high: Files.list(low))
+				{
+					for (File file : Files.list(high))
+					{
+						files.add(file);
+					}
+				}
+			}
+		}
+		
+		return files;
+	}
+}
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/PageWindowManager.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/disk/PageWindowManager.java
similarity index 79%
rename from wicket-core/src/main/java/org/apache/wicket/pageStore/PageWindowManager.java
rename to wicket-core/src/main/java/org/apache/wicket/pageStore/disk/PageWindowManager.java
index f194710..ad60dab 100644
--- a/wicket-core/src/main/java/org/apache/wicket/pageStore/PageWindowManager.java
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/disk/PageWindowManager.java
@@ -14,18 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.wicket.pageStore;
+package org.apache.wicket.pageStore.disk;
 
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.apache.wicket.pageStore.IPersistedPage;
 import org.apache.wicket.util.collections.IntHashMap;
+import org.apache.wicket.util.lang.Bytes;
 
 /**
- * Manages positions and size of serialized pages in the pagemap file.
+ * Manages positions and size of chunks of data in a file.
  * <p>
- * The pages are stored inside the file in a cyclic way. Newer pages are placed after older ones,
+ * The data is stored inside the file in a cyclic way. Newer pages are placed after older ones,
  * until the maximum file size is reached. After that, the next page is stored in the beginning of
  * the file.
  * 
@@ -40,22 +42,51 @@ public class PageWindowManager implements Serializable
 	 * 
 	 * @author Matej Knopp
 	 */
-	private static class PageWindowInternal implements Serializable
+	public static class FileWindow implements IPersistedPage, Serializable
 	{
 		private static final long serialVersionUID = 1L;
 
-		/** id of page or -1 if the window is empty */
-		private int pageId;
+		/** id of data or -1 if the window is empty */
+		private int id;
+
+		private String type;
 
 		/** offset in the file where the serialized page data begins */
 		private int filePartOffset;
 
 		/** size of serialized page data */
 		private int filePartSize;
+
+		@Override
+		public int getPageId()
+		{
+			return id;
+		}
+
+		@Override
+		public String getPageType()
+		{
+			return type;
+		}
+		
+		@Override
+		public Bytes getPageSize()
+		{
+			return Bytes.bytes(filePartSize);
+		}
+
+		public int getFilePartOffset()
+		{
+			return filePartOffset;
+		}
+		
+		public int getFilePartSize()
+		{
+			return filePartSize;
+		}
 	}
 
-	/** list of PageWindowInternal objects */
-	private final List<PageWindowInternal> windows = new ArrayList<PageWindowInternal>();
+	private final List<FileWindow> windows = new ArrayList<FileWindow>();
 
 	/**
 	 * map from page id to list of pagewindow indices (referring to the windows list) - to improve
@@ -123,8 +154,8 @@ public class PageWindowManager implements Serializable
 		windowIndexToPageId = new IntHashMap<Integer>();
 		for (int i = 0; i < windows.size(); ++i)
 		{
-			PageWindowInternal window = windows.get(i);
-			putWindowIndex(window.pageId, i);
+			FileWindow window = windows.get(i);
+			putWindowIndex(window.id, i);
 		}
 	}
 
@@ -175,7 +206,7 @@ public class PageWindowManager implements Serializable
 	{
 		if (index > 0)
 		{
-			PageWindowInternal window = windows.get(index - 1);
+			FileWindow window = windows.get(index - 1);
 			return window.filePartOffset + window.filePartSize;
 		}
 		return 0;
@@ -190,7 +221,7 @@ public class PageWindowManager implements Serializable
 	 */
 	private void splitWindow(int index, int size)
 	{
-		PageWindowInternal window = windows.get(index);
+		FileWindow window = windows.get(index);
 		int delta = window.filePartSize - size;
 
 		if (index == windows.size() - 1)
@@ -201,8 +232,8 @@ public class PageWindowManager implements Serializable
 		}
 		else if (window.filePartSize != size)
 		{
-			PageWindowInternal newWindow = new PageWindowInternal();
-			newWindow.pageId = -1;
+			FileWindow newWindow = new FileWindow();
+			newWindow.id = -1;
 			window.filePartSize = size;
 
 			windows.add(index + 1, newWindow);
@@ -225,8 +256,8 @@ public class PageWindowManager implements Serializable
 	{
 		if (index < windows.size() - 1)
 		{
-			PageWindowInternal window = windows.get(index);
-			PageWindowInternal next = windows.get(index + 1);
+			FileWindow window = windows.get(index);
+			FileWindow next = windows.get(index + 1);
 			window.filePartSize += next.filePartSize;
 
 			windows.remove(index + 1);
@@ -246,7 +277,7 @@ public class PageWindowManager implements Serializable
 	 */
 	private void adjustWindowSize(int index, int size)
 	{
-		PageWindowInternal window = windows.get(index);
+		FileWindow window = windows.get(index);
 
 		// last window, just adjust size
 		if (index == windows.size() - 1)
@@ -279,7 +310,7 @@ public class PageWindowManager implements Serializable
 			}
 		}
 
-		window.pageId = -1;
+		window.id = -1;
 	}
 
 	/**
@@ -291,15 +322,15 @@ public class PageWindowManager implements Serializable
 	 * @param size
 	 * @return page window
 	 */
-	private PageWindowInternal allocatePageWindow(int index, int size)
+	private FileWindow allocatePageWindow(int index, int size)
 	{
-		final PageWindowInternal window;
+		final FileWindow window;
 
 		// new window
 		if (index == windows.size())
 		{
 			// new page window
-			window = new PageWindowInternal();
+			window = new FileWindow();
 			window.filePartOffset = getWindowFileOffset(index);
 			totalSize += size;
 			window.filePartSize = size;
@@ -321,57 +352,14 @@ public class PageWindowManager implements Serializable
 	}
 
 	/**
-	 * Public (read only) version of page window.
-	 * 
-	 * @author Matej Knopp
-	 */
-	public static class PageWindow
-	{
-		private final PageWindowInternal pageWindowInternal;
-
-		/**
-		 * Construct.
-		 * 
-		 * @param pageWindowInternal
-		 */
-		private PageWindow(PageWindowInternal pageWindowInternal)
-		{
-			this.pageWindowInternal = pageWindowInternal;
-		}
-
-		/**
-		 * @return page Id
-		 */
-		public int getPageId()
-		{
-			return pageWindowInternal.pageId;
-		}
-
-		/**
-		 * @return offset in the pagemap file where the serialized page data starts
-		 */
-		public int getFilePartOffset()
-		{
-			return pageWindowInternal.filePartOffset;
-		}
-
-		/**
-		 * @return size of the serialized page data
-		 */
-		public int getFilePartSize()
-		{
-			return pageWindowInternal.filePartSize;
-		}
-	}
-
-	/**
 	 * Creates and returns a new page window for given page.
 	 * 
 	 * @param pageId
+	 * @param type 
 	 * @param size
 	 * @return page window
 	 */
-	public synchronized PageWindow createPageWindow(int pageId, int size)
+	public synchronized FileWindow createPageWindow(int pageId, String pageType, int size)
 	{
 		int index = getWindowIndex(pageId);
 
@@ -379,7 +367,7 @@ public class PageWindowManager implements Serializable
 		if (index != -1)
 		{
 			removeWindowIndex(pageId);
-			(windows.get(index)).pageId = -1;
+			(windows.get(index)).id = -1;
 		}
 
 		// if we are not going to reuse a page window (because it's not on
@@ -390,11 +378,12 @@ public class PageWindowManager implements Serializable
 			index = incrementIndexPointer();
 		}
 
-		PageWindowInternal window = allocatePageWindow(index, size);
-		window.pageId = pageId;
+		FileWindow window = allocatePageWindow(index, size);
+		window.id = pageId;
+		window.type = pageType;
 
 		putWindowIndex(pageId, index);
-		return new PageWindow(window);
+		return window;
 	}
 
 	/**
@@ -403,12 +392,12 @@ public class PageWindowManager implements Serializable
 	 * @param pageId
 	 * @return page window or null
 	 */
-	public synchronized PageWindow getPageWindow(int pageId)
+	public synchronized FileWindow getPageWindow(int pageId)
 	{
 		int index = getWindowIndex(pageId);
 		if (index != -1)
 		{
-			return new PageWindow(windows.get(index));
+			return windows.get(index);
 		}
 		return null;
 	}
@@ -423,7 +412,7 @@ public class PageWindowManager implements Serializable
 		int index = getWindowIndex(pageId);
 		if (index != -1)
 		{
-			PageWindowInternal window = windows.get(index);
+			FileWindow window = windows.get(index);
 			removeWindowIndex(pageId);
 			if (index == windows.size() - 1)
 			{
@@ -436,7 +425,7 @@ public class PageWindowManager implements Serializable
 			}
 			else
 			{
-				window.pageId = -1;
+				window.id = -1;
 			}
 		}
 	}
@@ -444,12 +433,11 @@ public class PageWindowManager implements Serializable
 	/**
 	 * Returns last n saved page windows.
 	 * 
-	 * @param count
 	 * @return list of page windows
 	 */
-	public synchronized List<PageWindow> getLastPageWindows(int count)
+	public synchronized List<FileWindow> getFileWindows()
 	{
-		List<PageWindow> result = new ArrayList<PageWindow>();
+		List<FileWindow> result = new ArrayList<FileWindow>();
 
 		// start from current index to 0
 		int currentIndex = indexPointer;
@@ -463,10 +451,10 @@ public class PageWindowManager implements Serializable
 
 			if (currentIndex < windows.size())
 			{
-				PageWindowInternal window = windows.get(currentIndex);
-				if (window.pageId != -1)
+				FileWindow window = windows.get(currentIndex);
+				if (window.id != -1)
 				{
-					result.add(new PageWindow(window));
+					result.add(window);
 				}
 			}
 
@@ -477,7 +465,7 @@ public class PageWindowManager implements Serializable
 				currentIndex = windows.size() - 1;
 			}
 		}
-		while (result.size() < count && currentIndex != indexPointer);
+		while (currentIndex != indexPointer);
 
 		return result;
 	}
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/memory/HttpSessionDataStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/memory/HttpSessionDataStore.java
deleted file mode 100644
index fcc4f7a..0000000
--- a/wicket-core/src/main/java/org/apache/wicket/pageStore/memory/HttpSessionDataStore.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.wicket.pageStore.memory;
-
-import javax.servlet.http.HttpSession;
-
-import org.apache.wicket.Session;
-import org.apache.wicket.page.IPageManagerContext;
-import org.apache.wicket.pageStore.IDataStore;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * A {@link IDataStore} which stores the pages in the {@link HttpSession}. Uses
- * {@link IDataStoreEvictionStrategy} to keep the memory footprint reasonable.
- *
- * <p>
- * Usage:
- *
- * <pre>
- * <!--@formatter:off-->
- * MyApp#init()
- * {
- *
- * 	setPageManagerProvider(new DefaultPageManagerProvider(this)
- * 	{
- * 		protected IDataStore newDataStore() 
- * 		{ 
- * 			return  new HttpSessionDataStore(pageManagerContext, new PageNumberEvictionStrategy(20));
- * 		}
- * 	}
- * }
- * <!--@formatter:on-->
- * </pre>
- */
-public class HttpSessionDataStore implements IDataStore
-{
-	private static final Logger LOG = LoggerFactory.getLogger(HttpSessionDataStore.class);
-
-	/** the session attribute key. auto-prefixed with application.getSessionAttributePrefix() */
-	private static final String PAGE_TABLE_KEY = "page:store:memory";
-
-	private final IPageManagerContext pageManagerContext;
-
-	private final IDataStoreEvictionStrategy evictionStrategy;
-
-	/**
-	 * Construct.
-	 * 
-	 * @param pageManagerContext
-	 * @param evictionStrategy
-	 */
-	public HttpSessionDataStore(IPageManagerContext pageManagerContext,
-		IDataStoreEvictionStrategy evictionStrategy)
-	{
-		this.pageManagerContext = pageManagerContext;
-		this.evictionStrategy = evictionStrategy;
-	}
-
-	/**
-	 * @param sessionId
-	 *            Ignored. Only pages from the current http session can be read
-	 * @see org.apache.wicket.pageStore.IDataStore#getData(java.lang.String, int)
-	 */
-	@Override
-	public byte[] getData(String sessionId, int pageId)
-	{
-		PageTable pageTable = getPageTable(false, false);
-		byte[] pageAsBytes = null;
-		if (pageTable != null)
-		{
-			pageAsBytes = pageTable.getPage(pageId);
-		}
-
-		if (LOG.isDebugEnabled())
-		{
-			int bytesLength = pageAsBytes != null ? pageAsBytes.length : -1;
-			LOG.debug("Loaded '{}' bytes for page with id '{}' in session '{}'",
-					bytesLength, pageId, sessionId);
-		}
-
-
-		return pageAsBytes;
-	}
-
-	@Override
-	public void removeData(String sessionId, int pageId)
-	{
-		PageTable pageTable = getPageTable(false, true);
-		if (pageTable != null)
-		{
-			byte[] bytes = pageTable.removePage(pageId);
-
-			if (LOG.isDebugEnabled() && bytes != null)
-			{
-				LOG.debug("Removed page '{}' in session '{}'", pageId, sessionId);
-			}
-		}
-	}
-
-	@Override
-	public void removeData(String sessionId)
-	{
-		PageTable pageTable = getPageTable(false, true);
-		if (pageTable != null)
-		{
-			pageTable.clear();
-			LOG.debug("Removed all pages in session '{}'", sessionId);
-		}
-	}
-
-	@Override
-	public void storeData(String sessionId, int pageId, byte[] pageAsBytes)
-	{
-		PageTable pageTable = getPageTable(true, true);
-		if (pageTable != null)
-		{
-			pageTable.storePage(pageId, pageAsBytes);
-			if (LOG.isDebugEnabled())
-			{
-				LOG.debug("Stored '{}' bytes for page '{}' in session '{}'",
-						pageAsBytes.length, pageId, sessionId);
-			}
-			evictionStrategy.evict(pageTable);
-		}
-		else
-		{
-			LOG.error("Cannot store the data for page with id '{}' in session with id '{}'",
-				pageId, sessionId);
-		}
-	}
-
-	@Override
-	public void destroy()
-	{
-		// do nothing
-		// this is application lifecycle thread (WicketFilter#destroy())
-		// so there is no reachable http session
-	}
-
-	@Override
-	public boolean isReplicated()
-	{
-		return true;
-	}
-
-	private PageTable getPageTable(boolean create, boolean rewriteToSession)
-	{
-		PageTable pageTable = null;
-		if (Session.exists())
-		{
-			pageTable = (PageTable)pageManagerContext.getSessionAttribute(PAGE_TABLE_KEY);
-			if (pageTable == null && create)
-			{
-				pageTable = new PageTable();
-				pageManagerContext.setSessionAttribute(PAGE_TABLE_KEY, pageTable);
-			} else if (rewriteToSession) {
-				pageManagerContext.setSessionAttribute(PAGE_TABLE_KEY, pageTable);
-			}
-		}
-		return pageTable;
-	}
-
-	@Override
-	public final boolean canBeAsynchronous()
-	{
-		// HttpSessionDataStore needs access to the current http session
-		// and this is not possible in AsychronousDataStore
-		return false;
-	}
-
-}
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/memory/MemorySizeEvictionStrategy.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/memory/MemorySizeEvictionStrategy.java
deleted file mode 100644
index 92f985b..0000000
--- a/wicket-core/src/main/java/org/apache/wicket/pageStore/memory/MemorySizeEvictionStrategy.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.wicket.pageStore.memory;
-
-import org.apache.wicket.util.lang.Args;
-import org.apache.wicket.util.lang.Bytes;
-import org.apache.wicket.core.util.lang.WicketObjects;
-
-/**
- * An eviction strategy that keeps the data store size up to configured bytes
- */
-public class MemorySizeEvictionStrategy implements IDataStoreEvictionStrategy
-{
-
-	private final Bytes maxBytes;
-
-	/**
-	 * Construct.
-	 * 
-	 * @param maxBytes
-	 *            the maximum size of the data store
-	 */
-	public MemorySizeEvictionStrategy(Bytes maxBytes)
-	{
-		Args.notNull(maxBytes, "maxBytes");
-
-		this.maxBytes = maxBytes;
-	}
-
-	/**
-	 * 
-	 * @see IDataStoreEvictionStrategy#evict(org.apache.wicket.pageStore.memory.PageTable)
-	 */
-	@Override
-	public void evict(PageTable pageTable)
-	{
-
-		long storeCurrentSize = WicketObjects.sizeof(pageTable);
-
-		if (storeCurrentSize > maxBytes.bytes())
-		{
-			PageTableCleaner cleaner = new PageTableCleaner();
-			cleaner.drop(pageTable, 1);
-
-			// recurse until enough space is cleaned
-			evict(pageTable);
-		}
-	}
-
-}
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/memory/PageNumberEvictionStrategy.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/memory/PageNumberEvictionStrategy.java
deleted file mode 100644
index 9857a6b..0000000
--- a/wicket-core/src/main/java/org/apache/wicket/pageStore/memory/PageNumberEvictionStrategy.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.wicket.pageStore.memory;
-
-
-/**
- * An eviction strategy which decides whether to evict entries from the in-memory data store
- * depending on the number of stored paged per session
- */
-public class PageNumberEvictionStrategy implements IDataStoreEvictionStrategy
-{
-
-	private final int pagesNumber;
-
-	/**
-	 * Construct.
-	 * 
-	 * @param pagesNumber
-	 *            the maximum number of pages the data store can hold
-	 */
-	public PageNumberEvictionStrategy(int pagesNumber)
-	{
-		if (pagesNumber < 1)
-		{
-			throw new IllegalArgumentException("'pagesNumber' must be greater than 0.");
-		}
-
-		this.pagesNumber = pagesNumber;
-	}
-
-	/**
-	 * 
-	 * @see IDataStoreEvictionStrategy#evict(org.apache.wicket.pageStore.memory.PageTable)
-	 */
-	@Override
-	public void evict(PageTable pageTable)
-	{
-		int size = pageTable.size();
-		int pagesToDrop = size - pagesNumber;
-
-		if (pagesToDrop > 0)
-		{
-			PageTableCleaner cleaner = new PageTableCleaner();
-			cleaner.drop(pageTable, pagesToDrop);
-		}
-	}
-
-}
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/memory/PageTable.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/memory/PageTable.java
deleted file mode 100644
index dfad4af..0000000
--- a/wicket-core/src/main/java/org/apache/wicket/pageStore/memory/PageTable.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.wicket.pageStore.memory;
-
-import java.util.Iterator;
-import java.util.Queue;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.ConcurrentMap;
-
-import org.apache.wicket.util.io.IClusterable;
-
-/**
- * A structure that holds page id => pageAsBytes.
- * 
- * <p>
- * Additionally it has an index of the least recently used pages
- */
-public class PageTable implements IClusterable
-{
-	private static final long serialVersionUID = 1L;
-
-	/**
-	 * Holds the index of last/least recently used page ids. The most recently used page id is in
-	 * the tail, the least recently used is in the head.
-	 */
-	/*
-	 * Can be replaced later with PriorityQueue to deal with lightweight (Ajax) and heavyweight
-	 * pages
-	 */
-	private final Queue<Integer> index;
-
-	/**
-	 * The actual container for the pages.
-	 * 
-	 * <p>
-	 * page id => page as bytes
-	 */
-	private final ConcurrentMap<Integer, byte[]> pages;
-
-	public PageTable()
-	{
-		pages = new ConcurrentHashMap<>();
-		index = new ConcurrentLinkedQueue<>();
-	}
-
-	public void storePage(Integer pageId, byte[] pageAsBytes)
-	{
-		synchronized (index)
-		{
-			pages.put(pageId, pageAsBytes);
-
-			updateIndex(pageId);
-		}
-	}
-
-	public byte[] getPage(final Integer pageId)
-	{
-		synchronized (index)
-		{
-			updateIndex(pageId);
-
-			return pages.get(pageId);
-		}
-	}
-
-	public byte[] removePage(Integer pageId)
-	{
-		synchronized (index)
-		{
-			index.remove(pageId);
-
-			return pages.remove(pageId);
-		}
-	}
-
-	public void clear()
-	{
-		synchronized (index)
-		{
-			index.clear();
-			pages.clear();
-		}
-	}
-
-	public int size()
-	{
-		return pages.size();
-	}
-
-	public Integer getOldest()
-	{
-		return index.peek();
-	}
-
-	public Iterator<Integer> indexIterator()
-	{
-		return index.iterator();
-	}
-
-	/**
-	 * Updates the index of last/least recently used pages by removing the page id from the index
-	 * (in case it is already in) and (re-)adding it at the head
-	 * 
-	 * @param pageId
-	 *            the id of a recently used page
-	 */
-	private void updateIndex(Integer pageId)
-	{
-		index.remove(pageId);
-		index.offer(pageId);
-	}
-
-}
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/memory/PageTableCleaner.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/memory/PageTableCleaner.java
deleted file mode 100644
index 2c9a9cb..0000000
--- a/wicket-core/src/main/java/org/apache/wicket/pageStore/memory/PageTableCleaner.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.wicket.pageStore.memory;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Helper class that knows how to remove the nth oldest pages from {@link PageTable}
- */
-public class PageTableCleaner
-{
-	private static final Logger LOG = LoggerFactory.getLogger(PageTableCleaner.class);
-
-	/**
-	 * Removes {@code pagesNumber} of pages from the {@link PageTable pageTable}
-	 * 
-	 * @param pageTable
-	 *            the {@link PageTable} to clean
-	 * @param pagesNumber
-	 *            the number of pages to remove
-	 */
-	public void drop(final PageTable pageTable, final int pagesNumber)
-	{
-		for (int i = 0; i < pagesNumber; i++)
-		{
-			Integer pageIdOfTheOldest = pageTable.getOldest();
-			pageTable.removePage(pageIdOfTheOldest);
-			LOG.debug("Evicted page with id '{}' from the HttpSessionDataStore");
-		}
-	}
-}
diff --git a/wicket-core/src/main/java/org/apache/wicket/settings/StoreSettings.java b/wicket-core/src/main/java/org/apache/wicket/settings/StoreSettings.java
index 8852208..7c32101 100644
--- a/wicket-core/src/main/java/org/apache/wicket/settings/StoreSettings.java
+++ b/wicket-core/src/main/java/org/apache/wicket/settings/StoreSettings.java
@@ -26,9 +26,8 @@ import org.apache.wicket.util.lang.Args;
 import org.apache.wicket.util.lang.Bytes;
 
 /**
- * A class for settings related to the the storages where page instances are persisted -
- * {@link org.apache.wicket.pageStore.IPageStore},
- * {@link org.apache.wicket.pageStore.IDataStore} and {@link org.apache.wicket.page.IPageManager}.
+ * A class for settings related to the the storages where page instances are persisted, used by
+ * {@link org.apache.wicket.pageStore.IPageStore} {@link org.apache.wicket.page.IPageManager}.
  * <p>
  * For more information about page storages read <a
  * href="https://cwiki.apache.org/confluence/x/qIaoAQ">Page Storage - Wiki page</a>
@@ -38,28 +37,18 @@ import org.apache.wicket.util.lang.Bytes;
  */
 public class StoreSettings
 {
-	/**
-	 * By default the second level cache is disabled.
-	 *
-	 * @see <a href="https://issues.apache.org/jira/browse/WICKET-5554">WICKET-5554</a>
-	 * @see <a href="https://cwiki.apache.org/confluence/x/qIaoAQ">Wicket Page storages</a>
-	 */
-	private static final int DEFAULT_CACHE_SIZE = 0;
-
 	private static final Bytes DEFAULT_MAX_SIZE_PER_SESSION = Bytes.megabytes(10);
 
 	private static final int DEFAULT_ASYNCHRONOUS_QUEUE_CAPACITY = 100;
 
-	private int inmemoryCacheSize = DEFAULT_CACHE_SIZE;
-
 	private Bytes maxSizePerSession = DEFAULT_MAX_SIZE_PER_SESSION;
 
 	private File fileStoreFolder = null;
 
 	private int asynchronousQueueCapacity = DEFAULT_ASYNCHRONOUS_QUEUE_CAPACITY;
 
-	private boolean isAsynchronous = true;
-
+	private boolean asynchronous = true;
+	
 	/**
 	 * Construct.
 	 * 
@@ -70,32 +59,8 @@ public class StoreSettings
 	}
 
 	/**
-	 * @return the number of page instances which will be stored in the application scoped cache for
-	 *         faster retrieval
-	 */
-	public int getInmemoryCacheSize()
-	{
-		return inmemoryCacheSize;
-	}
-
-	/**
-	 * Sets the maximum number of page instances which will be stored in the application scoped
-	 * second level cache for faster retrieval
-	 *
-	 * @param inmemoryCacheSize
-	 *            the maximum number of page instances which will be held in the application scoped
-	 *            cache
-	 * @return {@code this} object for chaining
-	 */
-	public StoreSettings setInmemoryCacheSize(int inmemoryCacheSize)
-	{
-		this.inmemoryCacheSize = inmemoryCacheSize;
-		return this;
-	}
-
-	/**
 	 * @return maximum page size. After this size is exceeded,
-	 * the {@link org.apache.wicket.pageStore.DiskDataStore} will start saving the
+	 * the {@link org.apache.wicket.pageStore.DiskPageStore} will start saving the
 	 * pages at the beginning of file.
 	 */
 	public Bytes getMaxSizePerSession()
@@ -105,7 +70,7 @@ public class StoreSettings
 
 	/**
 	 * Sets the maximum size of the {@link File} where page instances per session are stored. After
-	 * reaching this size the {@link org.apache.wicket.pageStore.DiskDataStore} will start overriding the
+	 * reaching this size the {@link org.apache.wicket.pageStore.DiskPageStore} will start overriding the
 	 * oldest pages at the beginning of the file.
 	 *
 	 * @param maxSizePerSession
@@ -120,7 +85,7 @@ public class StoreSettings
 	}
 
 	/**
-	 * @return the location of the folder where {@link org.apache.wicket.pageStore.DiskDataStore} will store the files with page
+	 * @return the location of the folder where {@link org.apache.wicket.pageStore.DiskPageStore} will store the files with page
 	 *         instances per session
 	 */
 	public File getFileStoreFolder()
@@ -151,7 +116,7 @@ public class StoreSettings
 	}
 
 	/**
-	 * Sets the folder where {@link org.apache.wicket.pageStore.DiskDataStore} will store the files with page instances per
+	 * Sets the folder where {@link org.apache.wicket.pageStore.DiskPageStore} will store the files with page instances per
 	 * session
 	 *
 	 * @param fileStoreFolder
@@ -166,7 +131,7 @@ public class StoreSettings
 
 	/**
 	 * @return the capacity of the queue used to store the pages which will be stored asynchronously
-	 * @see org.apache.wicket.pageStore.AsynchronousDataStore
+	 * @see org.apache.wicket.pageStore.AsynchronousPageStore
 	 */
 	public int getAsynchronousQueueCapacity()
 	{
@@ -178,7 +143,7 @@ public class StoreSettings
 	 *
 	 * @param queueCapacity
 	 *            the capacity of the queue
-	 * @see org.apache.wicket.pageStore.AsynchronousDataStore
+	 * @see org.apache.wicket.pageStore.AsynchronousPageStore
 	 * @return {@code this} object for chaining
 	 */
 	public StoreSettings setAsynchronousQueueCapacity(int queueCapacity)
@@ -193,9 +158,9 @@ public class StoreSettings
 	}
 
 	/**
-	 * Sets a flag whether to wrap the configured {@link org.apache.wicket.pageStore.IDataStore} with
-	 * {@link org.apache.wicket.pageStore.AsynchronousDataStore}. By doing this the HTTP worker thread will not wait for the
-	 * actual write of the page's bytes into the wrapped {@link org.apache.wicket.pageStore.IDataStore}.
+	 * Sets a flag whether to wrap the configured {@link org.apache.wicket.pageStore.IPageStore} with
+	 * {@link org.apache.wicket.pageStore.AsynchronousPageStore}. By doing this the HTTP worker thread will not wait for the
+	 * actual write of the page's bytes into the wrapped {@link org.apache.wicket.pageStore.IPageStore}.
 	 *
 	 * @param async
 	 *            {@code true} to make it asynchronous, {@code false} - otherwise
@@ -203,15 +168,15 @@ public class StoreSettings
 	 */
 	public StoreSettings setAsynchronous(boolean async)
 	{
-		isAsynchronous = async;
+		asynchronous = async;
 		return this;
 	}
 
 	/**
-	 * @return {@code true} if the storing of page's bytes is asynchronous
+	 * @return {@code true} if the storing of page is asynchronous
 	 */
 	public boolean isAsynchronous()
 	{
-		return isAsynchronous;
+		return asynchronous;
 	}
-}
+}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/util/tester/BaseWicketTester.java b/wicket-core/src/main/java/org/apache/wicket/util/tester/BaseWicketTester.java
index 4b3c6b8..03a3d08 100644
--- a/wicket-core/src/main/java/org/apache/wicket/util/tester/BaseWicketTester.java
+++ b/wicket-core/src/main/java/org/apache/wicket/util/tester/BaseWicketTester.java
@@ -91,11 +91,11 @@ import org.apache.wicket.markup.html.link.ResourceLink;
 import org.apache.wicket.markup.parser.XmlPullParser;
 import org.apache.wicket.markup.parser.XmlTag;
 import org.apache.wicket.mock.MockApplication;
-import org.apache.wicket.mock.MockPageManager;
+import org.apache.wicket.mock.MockPageStore;
 import org.apache.wicket.mock.MockRequestParameters;
 import org.apache.wicket.model.PropertyModel;
 import org.apache.wicket.page.IPageManager;
-import org.apache.wicket.page.IPageManagerContext;
+import org.apache.wicket.page.PageManager;
 import org.apache.wicket.protocol.http.AjaxEnclosureListener;
 import org.apache.wicket.protocol.http.IMetaDataBufferingWebResponse;
 import org.apache.wicket.protocol.http.WebApplication;
@@ -2784,9 +2784,9 @@ public class BaseWicketTester
 	private static class TestPageManagerProvider implements IPageManagerProvider
 	{
 		@Override
-		public IPageManager apply(IPageManagerContext pageManagerContext)
+		public IPageManager get()
 		{
-			return new MockPageManager();
+			return new PageManager(new MockPageStore());
 		}
 	}
 
diff --git a/wicket-core/src/test/java/org/apache/wicket/DefaultPageManagerProviderTest.java b/wicket-core/src/test/java/org/apache/wicket/DefaultPageManagerProviderTest.java
new file mode 100644
index 0000000..5adad4d
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/DefaultPageManagerProviderTest.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.apache.wicket.page.IPageManager;
+import org.apache.wicket.pageStore.AsynchronousPageStore;
+import org.apache.wicket.pageStore.DiskPageStore;
+import org.apache.wicket.pageStore.InSessionPageStore;
+import org.apache.wicket.pageStore.RequestPageStore;
+import org.apache.wicket.pageStore.SerializingPageStore;
+import org.apache.wicket.util.tester.WicketTestCase;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test for {@link DefaultPageManagerProvider}.
+ */
+class DefaultPageManagerProviderTest extends WicketTestCase
+{
+
+	/**
+	 * Test default chain of stores.
+	 */
+	@Test
+	void chain()
+	{
+		IPageManager manager = new DefaultPageManagerProvider(tester.getApplication()).get();
+
+		RequestPageStore request = (RequestPageStore)manager.getPageStore();
+		InSessionPageStore session = (InSessionPageStore)request.getDelegate();
+		AsynchronousPageStore asynchronous = (AsynchronousPageStore)session.getDelegate();
+		SerializingPageStore serializing = (SerializingPageStore)asynchronous.getDelegate();
+		DiskPageStore disk = (DiskPageStore)serializing.getDelegate();
+		
+		assertNotNull(disk);
+	}
+}
diff --git a/wicket-core/src/test/java/org/apache/wicket/core/request/mapper/TestMapperContext.java b/wicket-core/src/test/java/org/apache/wicket/core/request/mapper/TestMapperContext.java
index 2fff6cc..4129c66 100644
--- a/wicket-core/src/test/java/org/apache/wicket/core/request/mapper/TestMapperContext.java
+++ b/wicket-core/src/test/java/org/apache/wicket/core/request/mapper/TestMapperContext.java
@@ -20,19 +20,19 @@ import org.apache.wicket.MockPage;
 import org.apache.wicket.WicketRuntimeException;
 import org.apache.wicket.core.request.handler.PageProvider;
 import org.apache.wicket.markup.MarkupParser;
-import org.apache.wicket.page.IPageManagerContext;
-import org.apache.wicket.page.PageStoreManager;
-import org.apache.wicket.pageStore.DefaultPageStore;
-import org.apache.wicket.pageStore.IDataStore;
-import org.apache.wicket.pageStore.IPageStore;
-import org.apache.wicket.pageStore.memory.DummyPageManagerContext;
+import org.apache.wicket.mock.MockPageContext;
+import org.apache.wicket.page.IPageManager;
+import org.apache.wicket.page.PageManager;
+import org.apache.wicket.pageStore.IPageContext;
+import org.apache.wicket.pageStore.InMemoryPageStore;
+import org.apache.wicket.pageStore.InSessionPageStore;
+import org.apache.wicket.pageStore.RequestPageStore;
 import org.apache.wicket.request.component.IRequestablePage;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 import org.apache.wicket.request.resource.ResourceReference;
 import org.apache.wicket.request.resource.ResourceReference.Key;
 import org.apache.wicket.request.resource.ResourceReferenceRegistry;
 import org.apache.wicket.serialize.java.JavaSerializer;
-import org.apache.wicket.versioning.InMemoryPageStore;
 
 /**
  * Simple {@link IMapperContext} implementation for testing purposes
@@ -43,10 +43,10 @@ public class TestMapperContext implements IMapperContext
 {
 	private static final String APP_NAME = "test_app";
 	private static int count;
-	private IDataStore dataStore;
-	private IPageStore pageStore;
-	private IPageManagerContext pageManagerContext;
-	private PageStoreManager pageManager;
+
+	InSessionPageStore pageStore;
+	MockPageContext pageContext;
+	IPageManager pageManager;
 	private String appName;
 	private boolean createMockPageIfInstanceNotFound = true;
 
@@ -56,10 +56,18 @@ public class TestMapperContext implements IMapperContext
 	public TestMapperContext()
 	{
 		appName = APP_NAME + count++;
-		dataStore = new InMemoryPageStore();
-		pageStore = new DefaultPageStore(new JavaSerializer(appName), dataStore, 4);
-		pageManagerContext = new DummyPageManagerContext();
-		pageManager = new PageStoreManager(appName, pageStore, pageManagerContext);
+		
+		pageContext = new MockPageContext();
+		
+		InMemoryPageStore inMemoryPageStore = new InMemoryPageStore(appName, Integer.MAX_VALUE);
+		pageStore = new InSessionPageStore(inMemoryPageStore, 4, new JavaSerializer(appName));
+		pageManager = new PageManager(new RequestPageStore(pageStore)) {
+			@Override
+			protected IPageContext createPageContext()
+			{
+				return pageContext;
+			}
+		};
 	}
 
 	/**
@@ -67,17 +75,17 @@ public class TestMapperContext implements IMapperContext
 	 */
 	public void cleanSessionCache()
 	{
-		getPageManager().getContext().setRequestData(null);
+		pageContext.clearRequest();
 		MockPage other = new MockPage();
 		other.setPageId(Integer.MAX_VALUE);
-		getPageManager().touchPage(other);
-		getPageManager().commitRequest();
+		getPageManager().addPage(other);
+		pageManager.detach();
 	}
 
 	/**
 	 * @return pageManager
 	 */
-	public PageStoreManager getPageManager()
+	public IPageManager getPageManager()
 	{
 		return pageManager;
 	}
@@ -175,7 +183,7 @@ public class TestMapperContext implements IMapperContext
 
 	}
 
-	private int idCounter = 0;
+	int idCounter = 0;
 
 	@Override
 	public IRequestablePage newPageInstance(Class<? extends IRequestablePage> pageClass,
diff --git a/wicket-core/src/test/java/org/apache/wicket/dontstoreunrendered/DontStoreNotRenderedPageTestCase.java b/wicket-core/src/test/java/org/apache/wicket/dontstoreunrendered/DontStoreNotRenderedPageTestCase.java
index ceac5e3..857c715 100644
--- a/wicket-core/src/test/java/org/apache/wicket/dontstoreunrendered/DontStoreNotRenderedPageTestCase.java
+++ b/wicket-core/src/test/java/org/apache/wicket/dontstoreunrendered/DontStoreNotRenderedPageTestCase.java
@@ -24,8 +24,6 @@ import org.apache.wicket.application.IComponentInstantiationListener;
 import org.apache.wicket.behavior.Behavior;
 import org.apache.wicket.mock.MockPageManager;
 import org.apache.wicket.page.IManageablePage;
-import org.apache.wicket.page.IPageManager;
-import org.apache.wicket.page.IPageManagerContext;
 import org.apache.wicket.protocol.http.WebApplication;
 import org.apache.wicket.util.tester.WicketTestCase;
 import org.apache.wicket.util.tester.WicketTester;
@@ -34,7 +32,7 @@ import org.junit.jupiter.api.Test;
 /**
  * https://issues.apache.org/jira/browse/WICKET-5415
  */
-abstract class DontStoreNotRenderedPageTestCase extends WicketTestCase
+public abstract class DontStoreNotRenderedPageTestCase extends WicketTestCase
 {
 	@Override
 	protected WicketTester newWicketTester(WebApplication app)
@@ -56,30 +54,27 @@ abstract class DontStoreNotRenderedPageTestCase extends WicketTestCase
 			@Override
 			protected IPageManagerProvider newTestPageManagerProvider()
 			{
-				return new IPageManagerProvider()
-				{
-					@Override
-					public IPageManager apply(IPageManagerContext context)
+				return () -> {
+					return new MockPageManager()
 					{
-						return new MockPageManager()
+						@Override
+						public void addPage(IManageablePage page)
 						{
-							@Override
-							public void touchPage(IManageablePage page)
-							{
-								assertFalse(page instanceof PageB, "PageB should not be touched!");
-								super.touchPage(page);
-							}
-						};
-					}
+							assertFalse(page instanceof PageB, "PageB should not be touched!");
+							super.addPage(page);
+						}
+					};
 				};
 			}
 		};
 	}
 
 	/**
-	 * Start with PageA. Then click a link to go to PageB. PageB throws a
-	 * RestartResponseException(PageC) in its constructor, so it shouldn't be neither initialized
-	 * nor rendered. PageC is rendered.
+	 * Start with PageA.
+	 * Then click a link to go to PageB.
+	 * PageB throws a RestartResponseException(PageC) in its constructor, so
+	 * it shouldn't be neither initialized nor rendered.
+	 * PageC is rendered.
 	 *
 	 * Verifies that PageB is not initialized, rendered and stored by PageManager
 	 */
diff --git a/wicket-core/src/test/java/org/apache/wicket/markup/html/TransparentWebMarkupContainerTest.java b/wicket-core/src/test/java/org/apache/wicket/markup/html/TransparentWebMarkupContainerTest.java
index da853e4..39d81c3 100644
--- a/wicket-core/src/test/java/org/apache/wicket/markup/html/TransparentWebMarkupContainerTest.java
+++ b/wicket-core/src/test/java/org/apache/wicket/markup/html/TransparentWebMarkupContainerTest.java
@@ -38,7 +38,7 @@ import org.apache.wicket.markup.html.panel.Fragment;
 import org.apache.wicket.mock.MockPageManager;
 import org.apache.wicket.page.IManageablePage;
 import org.apache.wicket.page.IPageManager;
-import org.apache.wicket.page.IPageManagerContext;
+import org.apache.wicket.pageStore.IPageContext;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 import org.apache.wicket.util.resource.IResourceStream;
 import org.apache.wicket.util.resource.StringResourceStream;
@@ -120,15 +120,15 @@ public class TransparentWebMarkupContainerTest extends WicketTestCase
 				return new IPageManagerProvider()
 				{
 					@Override
-					public IPageManager apply(IPageManagerContext context)
+					public IPageManager get()
 					{
 						return new MockPageManager()
 						{
 							@Override
-							public void touchPage(IManageablePage page)
+							public void addPage(IManageablePage page)
 							{
 								page = WicketObjects.cloneObject(page);
-								super.touchPage(page);
+								super.addPage(page);
 							}
 						};
 					}
diff --git a/wicket-core/src/test/java/org/apache/wicket/page/AsyncPageStoreManagerTest.java b/wicket-core/src/test/java/org/apache/wicket/page/AsyncPageStoreManagerTest.java
index 38df077..e26d0dc 100644
--- a/wicket-core/src/test/java/org/apache/wicket/page/AsyncPageStoreManagerTest.java
+++ b/wicket-core/src/test/java/org/apache/wicket/page/AsyncPageStoreManagerTest.java
@@ -16,24 +16,20 @@
  */
 package org.apache.wicket.page;
 
-import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
 
+import java.util.concurrent.CountDownLatch;
+
+import org.apache.wicket.mock.MockPageContext;
+import org.apache.wicket.mock.MockPageStore;
 import org.apache.wicket.pageStore.AsynchronousPageStore;
-import org.apache.wicket.pageStore.DefaultPageStore;
-import org.apache.wicket.pageStore.DiskDataStore;
-import org.apache.wicket.pageStore.IDataStore;
+import org.apache.wicket.pageStore.IPageContext;
 import org.apache.wicket.pageStore.IPageStore;
-import org.apache.wicket.pageStore.memory.DummyPageManagerContext;
-import org.apache.wicket.serialize.ISerializer;
-import org.apache.wicket.serialize.java.DeflatedJavaSerializer;
+import org.apache.wicket.pageStore.SerializedPage;
 import org.apache.wicket.util.WicketTestTag;
-import org.apache.wicket.util.file.File;
-import org.apache.wicket.util.lang.Bytes;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
-import org.mockito.internal.util.reflection.FieldReader;
-import org.mockito.internal.util.reflection.FieldSetter;
 
 /**
  * https://issues.apache.org/jira/browse/WICKET-6629
@@ -41,8 +37,9 @@ import org.mockito.internal.util.reflection.FieldSetter;
 @Tag(WicketTestTag.SLOW)
 class AsyncPageStoreManagerTest
 {
-	private static final String APP_NAME = "test_app";
-
+	private CountDownLatch added = new CountDownLatch(1);
+	private CountDownLatch allowAdd = new CountDownLatch(1);
+	
 	/*
 	 * Symptoms:
 	 * 1) Out of memory on DiskDataStore.sessionEntryMap, map contains thousands of
@@ -60,114 +57,61 @@ class AsyncPageStoreManagerTest
 	@Test
 	void invalidateSessionBeforeSave() throws InterruptedException
 	{
-		ISerializer serializer = new DeflatedJavaSerializer("applicationKey");
-		IDataStore dataStore = new DiskDataStore("applicationName", new File("target"),
-			Bytes.bytes(10000l));
-		IPageStore pageStore = new DefaultPageStore(serializer, dataStore, 0);
-		IPageStore asyncPageStore = new AsynchronousPageStore(pageStore, 100);
-		IPageManagerContext pageManagerContext = new DummyPageManagerContext();
-		IPageManager newPageManager = new PageStoreManager(APP_NAME, asyncPageStore,
-			pageManagerContext);
-
-		// Commit a page to the pagemanager
-		TestPage toSerializePage = new TestPage(0);
-		newPageManager.touchPage(toSerializePage);
-		newPageManager.commitRequest();
-
-		// Allow some time for the PageSavingRunnable to save the page to disk
-		Thread.sleep(1000);
-
-		// Page should be stored on disk
-		assertNotNull(dataStore.getData("dummy_id", 0));
-
-		// "Stop" the PageSavingRunnable, so we can simulate a pending page
-		Thread t = null;
-		try
-		{
-			t = (Thread)new FieldReader(asyncPageStore,
-				AsynchronousPageStore.class.getDeclaredField("pageSavingThread")).read();
-			Runnable r = (Runnable)new FieldReader(t, Thread.class.getDeclaredField("target"))
-				.read();
-			FieldSetter.setField(asyncPageStore,
-				AsynchronousPageStore.class.getDeclaredField("pageSavingThread"), null);
-
-			// Allow some time for the original PageSavingRunnable to exit
-			Thread.sleep(1000);
-
-			t = new Thread(r, "Wicket-AsyncPageStore-PageSavingThread");
-			t.setDaemon(true);
-			FieldSetter.setField(asyncPageStore,
-				AsynchronousPageStore.class.getDeclaredField("pageSavingThread"), t);
-		}
-		catch (NoSuchFieldException | SecurityException e)
-		{
-			throw new RuntimeException(e);
-		}
-
-		// Commit a second page to this session
-		newPageManager.touchPage(new TestPage(1));
-		newPageManager.commitRequest();
+		MockPageStore testStore = new MockPageStore() {
+			
+			@Override
+			public void addPage(IPageContext context, IManageablePage page)
+			{
+				super.addPage(context, page);
+
+				added.countDown();
+				
+				try
+				{
+					allowAdd.await();
+				}
+				catch (InterruptedException interrupted)
+				{
+				}
+			}
+		};
+		
+		IPageStore asyncPageStore = new AsynchronousPageStore(testStore, 100);
+		
+		IPageContext contextA = new MockPageContext("A");
 
-		Thread.sleep(1000);
+		// add a page to the session
+		asyncPageStore.addPage(contextA, new SerializedPage(1, new byte[0]));
 
-		// PageSavingRunnable is not running so page should not be in the datastore
-		assertNull(dataStore.getData("dummy_id", 1));
+		// wait for page being added
+		added.await();
 
-		// Session is invalidated and a clear on the PageStoreManager is called
-		newPageManager.clear();
+		assertEquals(1, testStore.getPage(contextA, 1).getPageId());
 
-		// Datastore is indeed empty after the clear
-		assertNull(dataStore.getData("dummy_id", 0));
-		assertNull(dataStore.getData("dummy_id", 1));
+		// add a second page to this session
+		asyncPageStore.addPage(contextA, new SerializedPage(2, new byte[0]));
 
-		// "Restart" the PageSavingRunnable
-		t.start();
+		// Session is invalidated
+		asyncPageStore.removeAllPages(contextA);
 
-		// Allow some time for the PageSavingRunnable to save any pages left in the queue to disk
-		Thread.sleep(1000);
+		// indeed empty
+		assertNull(testStore.getPage(contextA, 1));
+		assertNull(testStore.getPage(contextA, 2));
 
-		// Session has been invalidated. The datastore should not contain any pages for this
-		// session, because they will never be cleaned! Not in the DiskDataStore.sessionEntryMap
-		// (OOM) and not on disk (slowly filling disk as this even survives application restarts.
-		assertNull(dataStore.getData("dummy_id", 0));
-		assertNull(dataStore.getData("dummy_id", 1));
+		// allow next page add
+		allowAdd.countDown();
+		
+		// wait for page being added
+		added = new CountDownLatch(1);
+		asyncPageStore.addPage(new MockPageContext("B"), new SerializedPage(3, new byte[0]));
+		added.await();
+
+		// Session has been invalidated. The pageStore should not contain any pages for this
+		// session, because they will never be cleaned!
+		assertNull(testStore.getPage(contextA, 1));
+		assertNull(testStore.getPage(contextA, 2));
 		
 		//destroy page manager to clean static variables 
-		newPageManager.destroy();
-	}
-
-	private static class TestPage implements IManageablePage
-	{
-		private static final long serialVersionUID = 1L;
-
-		private final int instanceID;
-
-		private TestPage(int id)
-		{
-			instanceID = id;
-		}
-
-		@Override
-		public boolean isPageStateless()
-		{
-			return false;
-		}
-
-		@Override
-		public int getPageId()
-		{
-			return instanceID;
-		}
-
-		@Override
-		public void detach()
-		{
-		}
-
-		@Override
-		public boolean setFreezePageId(boolean freeze)
-		{
-			return false;
-		}
+		testStore.destroy();
 	}
-}
+}
\ No newline at end of file
diff --git a/wicket-core/src/test/java/org/apache/wicket/page/PageAccessSynchronizerTest.java b/wicket-core/src/test/java/org/apache/wicket/page/PageAccessSynchronizerTest.java
index c686d6a..ad697ed 100644
... 4728 lines suppressed ...