http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/FilePageStore.java
----------------------------------------------------------------------
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..eecd9df
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/FilePageStore.java
@@ -0,0 +1,571 @@
+/*
+ * 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.io.Serializable;
+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 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.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>
+ * 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 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 static final String KEY = "wicket:FilePageStore";
+
+       /**
+        * A cache that holds all page stores.
+        */
+       private static final ConcurrentMap<String, FilePageStore> FILE_STORES = 
new ConcurrentHashMap<>();
+
+       private final String applicationName;
+
+       private final ISerializer serializer;
+
+       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 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 FilePageStore(String applicationName, File fileStoreFolder, 
Bytes maxSizePerSession,
+               ISerializer serializer)
+       {
+               this.applicationName = Args.notNull(applicationName, 
"applicationName");
+               this.folders = new NestedFolders(new File(fileStoreFolder, 
applicationName + "-filestore"));
+               this.maxSizePerSession = Args.notNull(maxSizePerSession, 
"maxSizePerSession");
+               this.serializer = serializer; // optional
+
+               if (FILE_STORES.containsKey(applicationName))
+               {
+                       throw new IllegalStateException(
+                               "Store for application with key '" + 
applicationName + "' already exists.");
+               }
+               FILE_STORES.put(applicationName, this);
+       }
+
+       /**
+        * Versioning is supported if a serializer was provided to the 
constructor.
+        */
+       @Override
+       public boolean supportsVersioning()
+       {
+               return serializer != null;
+       }
+
+       @Override
+       public void destroy()
+       {
+               log.debug("Destroying...");
+
+               FILE_STORES.remove(applicationName);
+
+               log.debug("Destroyed.");
+       }
+
+       private File getPageFile(String sessionId, int id, boolean create)
+       {
+               File folder = folders.get(sessionId, create);
+
+               return new File(folder, id + FILE_SUFFIX);
+       }
+
+       @Override
+       public boolean canBeAsynchronous(IPageContext context)
+       {
+               // session attribute must be added here *before* any 
asynchronous calls
+               // when session is no longer available
+               getSessionAttribute(context, true);
+
+               return true;
+       }
+
+       @Override
+       public IManageablePage getPage(IPageContext context, int id)
+       {
+               SessionAttribute attribute = getSessionAttribute(context, 
false);
+               if (attribute == null)
+               {
+                       return null;
+               }
+
+               byte[] data = readFile(attribute.identifier, id);
+               if (data == null) {
+                       return null;
+               }
+               
+               IManageablePage page;
+               if (serializer == null)
+               {
+                       page = new SerializedPage(id, "unknown", data);
+               }
+               else
+               {
+                       page = (IManageablePage)serializer.deserialize(data);
+               }
+
+               return page;
+       }
+
+       private byte[] readFile(String identifier, int id)
+       {
+               File file = getPageFile(identifier, 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 
{}", identifier, id, ex);
+               }
+
+               return data;
+       }
+
+       @Override
+       public void removePage(IPageContext context, IManageablePage page)
+       {
+               SessionAttribute attribute = getSessionAttribute(context, 
false);
+               if (attribute == null)
+               {
+                       return;
+               }
+
+               File file = getPageFile(attribute.identifier, page.getPageId(), 
false);
+               if (file.exists())
+               {
+                       if (!file.delete())
+                       {
+                               log.warn("cannot remove page data for session 
{} page {}", attribute.identifier,
+                                       page.getPageId());
+                       }
+               }
+       }
+
+       @Override
+       public void removeAllPages(IPageContext context)
+       {
+               SessionAttribute attribute = getSessionAttribute(context, 
false);
+               if (attribute == null)
+               {
+                       return;
+               }
+
+               removeFolder(attribute.identifier);
+       }
+
+       private void removeFolder(String identifier)
+       {
+               folders.remove(identifier);
+       }
+
+       /**
+        * Supports {@link SerializedPage}s too - for this to work the 
delegating {@link IPageStore}
+        * must use the same {@link ISerializer} as this one.
+        */
+       @Override
+       public void addPage(IPageContext context, IManageablePage page)
+       {
+               SessionAttribute attribute = getSessionAttribute(context, 
false);
+               if (attribute == null)
+               {
+                       return;
+               }
+
+               String type;
+               byte[] data;
+               if (page instanceof SerializedPage)
+               {
+                       type = ((SerializedPage)page).getPageType();
+                       data = ((SerializedPage)page).getData();
+               }
+               else
+               {
+                       if (serializer == null)
+                       {
+                               throw new WicketRuntimeException("DiskPageStore 
not configured for serialization");
+                       }
+                       type = page.getClass().getName();
+                       data = serializer.serialize(page);
+               }
+
+               writeFile(attribute.identifier, page.getPageId(), type, data);
+
+               checkMaxSize(attribute.identifier);
+       }
+
+       private void writeFile(String identifier, int pageId, String pageType, 
byte[] data)
+       {
+               File file = getPageFile(identifier, 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 
{}", identifier, pageId, ex);
+               }
+
+               setPageType(file, pageType);
+       }
+
+       private void checkMaxSize(String identifier)
+       {
+               File[] files = folders.get(identifier, 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 {}", identifier,
+                                               candidate.getName());
+                               }
+                       }
+               }
+       }
+
+       protected SessionAttribute getSessionAttribute(IPageContext context, 
boolean create)
+       {
+               context.bind();
+
+               SessionAttribute attribute = context.getSessionAttribute(KEY);
+               if (attribute == null && create)
+               {
+                       attribute = new SessionAttribute(applicationName, 
context.getSessionId());
+                       context.setSessionAttribute(KEY, attribute);
+               }
+               return attribute;
+       }
+
+       /**
+        * Attribute held in session.
+        */
+       static class SessionAttribute implements Serializable, 
HttpSessionBindingListener
+       {
+
+               private final String applicationName;
+
+               /**
+                * The identifier of the session, must not be equal to {@link 
Session#getId()}, e.g. when
+                * the container changes the id after authorization.
+                */
+               public final String identifier;
+
+               public SessionAttribute(String applicationName, String 
sessionIdentifier)
+               {
+                       this.applicationName = Args.notNull(applicationName, 
"applicationName");
+                       this.identifier = Args.notNull(sessionIdentifier, 
"sessionIdentifier");
+               }
+
+
+               @Override
+               public void valueBound(HttpSessionBindingEvent event)
+               {
+               }
+
+               @Override
+               public void valueUnbound(HttpSessionBindingEvent event)
+               {
+                       FilePageStore store = FILE_STORES.get(applicationName);
+                       if (store == null)
+                       {
+                               log.warn(
+                                       "Cannot remove data '{}' because disk 
store for application '{}' is no longer present.",
+                                       identifier, applicationName);
+                       }
+                       else
+                       {
+                               store.removeFolder(identifier);
+                       }
+               }
+       }
+
+       public class LastModifiedComparator implements Comparator<File>
+       {
+
+               @Override
+               public int compare(File f1, File f2)
+               {
+                       return (int)(f2.lastModified() - f1.lastModified());
+               }
+
+       }
+
+       @Override
+       public String getContextIdentifier(IPageContext context)
+       {
+               return getSessionAttribute(context, true).identifier;
+       }
+
+       @Override
+       public Set<String> getContextIdentifiers()
+       {
+               Set<String> identifiers = new HashSet<>();
+
+               for (File folder : folders.getAll())
+               {
+                       identifiers.add(folder.getName());
+               }
+
+               return identifiers;
+       }
+
+       @Override
+       public List<IPersistedPage> getPersistentPages(String identifier)
+       {
+               List<IPersistedPage> pages = new ArrayList<>();
+
+               File folder = folders.get(identifier, 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);
+       }
+
+       private 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

http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/GroupingPageStore.java
----------------------------------------------------------------------
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..9343935
--- /dev/null
+++ 
b/wicket-core/src/main/java/org/apache/wicket/pageStore/GroupingPageStore.java
@@ -0,0 +1,288 @@
+/*
+ * 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 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.
+ * 
+ * @see #getGroup(IManageablePage)
+ */
+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);
+
+               sessionData.addPage(context, page, getGroupInternal(page), 
maxGroups, stableGroups, getDelegate());
+       }
+
+       @Override
+       public void removePage(IPageContext context, IManageablePage page)
+       {
+               SessionData sessionData = getSessionData(context);
+               
+               sessionData.removePage(context, page, getDelegate());
+       }
+
+       @Override
+       public void removeAllPages(IPageContext context)
+       {
+               SessionData sessionData = getSessionData(context);
+
+               sessionData.removeAllPages(context, getDelegate());
+       }
+
+       @Override
+       public IManageablePage getPage(IPageContext context, int id)
+       {
+               SessionData sessionData = getSessionData(context);
+               
+               return sessionData.getPage(context, id, getDelegate());
+       }
+
+       private SessionData getSessionData(IPageContext context)
+       {
+               SessionData data = context.getSessionData(KEY);
+               if (data == null)
+               {
+                       context.bind();
+
+                       data = context.setSessionData(KEY, new SessionData());
+               }
+
+               return data;
+       }
+
+       /**
+        * 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()
+               {
+                       return context.getSessionId() + "_" + group;
+               }
+
+               @Override
+               public <T extends Serializable> T setSessionData(MetaDataKey<T> 
key, T value)
+               {
+                       sessionData.setMetaData(group, key, value);
+                       
+                       return value;
+               }
+
+               @Override
+               public <T extends Serializable> T getSessionData(MetaDataKey<T> 
key)
+               {
+                       return sessionData.getMetaData(group, key);
+               }
+
+               @Override
+               public <T extends Serializable> void setSessionAttribute(String 
key, T value)
+               {
+                       context.setSessionAttribute(key + "_" + group, value);
+               }
+
+               @Override
+               public <T extends Serializable> T getSessionAttribute(String 
key)
+               {
+                       return context.getSessionAttribute(key + "_" + group);
+               }
+
+               @Override
+               public <T> void setRequestData(MetaDataKey<T> key, T data)
+               {
+                       throw new WicketRuntimeException("no request available 
for group");
+               }
+
+               @Override
+               public <T> T getRequestData(MetaDataKey<T> key)
+               {
+                       throw new WicketRuntimeException("no request available 
for group");
+               }
+
+
+               @Override
+               public void bind()
+               {
+                       context.bind();
+               }
+       }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/IDataStore.java
----------------------------------------------------------------------
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();
-}

http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageContext.java
----------------------------------------------------------------------
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..7dff1a5
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageContext.java
@@ -0,0 +1,114 @@
+/*
+ * 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.page.IManageablePage;
+
+/**
+ * Context of a {@link IManageablePage} when it is store in a {@link 
IPageStore}, decoupling it from
+ * request cycle and session.
+ * 
+ * @author Matej Knopp
+ * @author svenmeier
+ */
+public interface IPageContext
+{
+       /**
+        * Set data into the current request.
+        * 
+        * @param key
+        *            key
+        * @param value
+        *            value
+        */
+       <T> void setRequestData(MetaDataKey<T> key, T value);
+
+       /**
+        * Get data from the current request.
+        * 
+        * @param key
+        *            key
+        * @return value
+        */
+       <T> T getRequestData(MetaDataKey<T> key);
+
+       /**
+        * Set an attribute in the session.
+        * 
+        * @param key
+        *            key
+        * @param value
+        *            value
+        */
+       <T extends Serializable> void setSessionAttribute(String key, T value);
+
+       /**
+        * Get an attribute from the session.
+        * 
+        * @param key
+        *            key
+        * @param value
+        *            value
+        */
+       <T extends Serializable> T getSessionAttribute(String key);
+
+       /**
+        * Set data into the session - only if it is not set already.
+        * <p>
+        * Recommended usage:
+        * <pre>
+        * <code>
+        * SessionData data = context.getSessionData(KEY);
+        * if (data == null)
+        * {
+        *     data = context.setSessionData(KEY, new SessionData());
+        * }
+        * </code>
+        * </pre>
+        *
+        * @param key
+        *            key
+        * @param value
+        *            value
+        * @return the old value if already present, or the new value
+        */
+       <T extends Serializable> T setSessionData(MetaDataKey<T> key, T value);
+
+       /**
+        * Get data from the session.
+        * 
+        * @param key
+        *            key
+        * @return value
+        */
+       <T extends Serializable> T getSessionData(MetaDataKey<T> key);
+
+       /**
+        * Bind the current session. This make a temporary session become 
persistent across requests.
+        */
+       void bind();
+
+       /**
+        * Get the identifier of the session.
+        * 
+        * @return session id
+        */
+       String getSessionId();
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageStore.java
----------------------------------------------------------------------
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()
+       {
+       }
 }

http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/IPersistedPage.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/pageStore/IPersistedPage.java 
b/wicket-core/src/main/java/org/apache/wicket/pageStore/IPersistedPage.java
new file mode 100644
index 0000000..8a03507
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/IPersistedPage.java
@@ -0,0 +1,44 @@
+/*
+ * 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.util.lang.Bytes;
+
+/**
+ * Information about a persisted page in an {@link IPersistentPageStore}.
+ * 
+ * @see IPersistentPageStore#getPersistentPages(String, int)
+ */
+public interface IPersistedPage extends Serializable
+{
+       /**
+        * Id of page.
+        */
+       int getPageId();
+
+       /**
+        * Size of page.
+        */
+       Bytes getPageSize();
+
+       /**
+        * Type of page.
+        */
+       String getPageType();
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/IPersistentPageStore.java
----------------------------------------------------------------------
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..ab0ed24
--- /dev/null
+++ 
b/wicket-core/src/main/java/org/apache/wicket/pageStore/IPersistentPageStore.java
@@ -0,0 +1,51 @@
+/*
+ * 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.  
+ */
+public interface IPersistentPageStore extends IPageStore
+{
+
+       /**
+        * Get the identifier for pages stored for the given context.
+        */
+       String getContextIdentifier(IPageContext context);
+
+       /**
+        * Get the identifiers for all pages stored in all contexts.
+        */
+       Set<String> getContextIdentifiers();
+
+       /**
+        * Get information about all persisted pages with the given context 
identifier.
+        */
+       List<IPersistedPage> getPersistentPages(String contextIdentifier);
+
+       /**
+        * Get total size of all pages stored in all contexts. 
+        *  
+        * @return
+        */
+       Bytes getTotalSize();
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/InMemoryPageStore.java
----------------------------------------------------------------------
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..d9d025a
--- /dev/null
+++ 
b/wicket-core/src/main/java/org/apache/wicket/pageStore/InMemoryPageStore.java
@@ -0,0 +1,368 @@
+/*
+ * 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.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.Session;
+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;
+
+/**
+ * A storage of pages in memory.
+ */
+public class InMemoryPageStore implements IPersistentPageStore
+{
+
+       /**
+        * A registry of all page instances.
+        */
+       private static final ConcurrentMap<String, InMemoryPageStore> 
IN_MEMORY_STORES = new ConcurrentHashMap<>();
+
+       private static final String KEY = "wicket:InMemoryPageStore";
+
+       private final Map<String, MemoryData> datas = new ConcurrentHashMap<>();
+
+       private String applicationName;
+
+       private int maxPages;
+
+       /**
+        * @param applicationName
+        *            {@link Application#getName()}
+        * @param maxPages
+        *            max pages per session
+        */
+       public InMemoryPageStore(String applicationName, int maxPages)
+       {
+               this.applicationName = Args.notNull(applicationName, 
"applicationName");
+               this.maxPages = maxPages;
+
+               IN_MEMORY_STORES.put(applicationName, this);
+       }
+
+       /**
+        * Versioning is not supported. 
+        */
+       @Override
+       public boolean supportsVersioning()
+       {
+               return false;
+       }
+       
+       /**
+        * 
+        * 
+        * @return <code>true</code> always
+        */
+       @Override
+       public boolean canBeAsynchronous(IPageContext context)
+       {
+               // session attribute must be added here *before* any 
asynchronous calls
+               // when session is no longer available
+               getSessionAttribute(context, true);
+
+               return true;
+       }
+
+       protected SessionAttribute getSessionAttribute(IPageContext context, 
boolean create)
+       {
+               SessionAttribute attribute = context.getSessionAttribute(KEY);
+               if (attribute == null && create)
+               {
+                       context.bind();
+                       
+                       attribute = new SessionAttribute(applicationName, 
context.getSessionId());
+                       context.setSessionAttribute(KEY, attribute);
+               }
+               return attribute;
+       }
+
+       @Override
+       public void destroy()
+       {
+               datas.clear();
+
+               IN_MEMORY_STORES.remove(applicationName);
+       }
+
+       @Override
+       public IManageablePage getPage(IPageContext context, int id)
+       {
+               MemoryData data = getMemoryData(context, false);
+               if (data == null)
+               {
+                       return null;
+               }
+
+               return data.get(id);
+       }
+
+       @Override
+       public void removePage(IPageContext context, final IManageablePage page)
+       {
+               MemoryData data = getMemoryData(context, false);
+
+               if (data != null)
+               {
+                       synchronized (data)
+                       {
+                               data.remove(page);
+                       }
+               }
+       }
+
+       @Override
+       public void removeAllPages(IPageContext context)
+       {
+               MemoryData data = getMemoryData(context, false);
+
+               if (data != null)
+               {
+                       synchronized (data)
+                       {
+                               data.removeAll();
+                       }
+               }
+       }
+
+       @Override
+       public void addPage(IPageContext context, IManageablePage page)
+       {
+               MemoryData data = getMemoryData(context, true);
+
+               data.add(page, maxPages);
+       }
+
+       @Override
+       public String getContextIdentifier(IPageContext context)
+       {
+               return context.getSessionId();
+       }
+
+       @Override
+       public Set<String> getContextIdentifiers()
+       {
+               return datas.keySet();
+       }
+
+       @Override
+       public List<IPersistedPage> getPersistentPages(String sessionIdentifier)
+       {
+               MemoryData data = datas.get(sessionIdentifier);
+               if (data == null)
+               {
+                       return new ArrayList<>();
+               }
+
+               synchronized (data)
+               {
+                       return StreamSupport.stream(data.spliterator(), false)
+                               .map(page -> new PersistedPage(page, 
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(IPageContext context, boolean create)
+       {
+               SessionAttribute attribute = getSessionAttribute(context, 
create);
+
+               if (!create)
+               {
+                       if (attribute == null) {
+                               return null;
+                       } else {
+                               return datas.get(attribute.identifier);
+                       }
+               }
+
+               MemoryData data = new MemoryData();
+               MemoryData existing = datas.putIfAbsent(attribute.identifier, 
data);
+               return existing != null ? existing : data;
+       }
+
+       private void removeMemoryData(String identifier)
+       {
+               datas.remove(identifier);
+       }
+       
+       /**
+        * Data kept in memory.
+        */
+       static class MemoryData implements Iterable<IManageablePage>
+       {
+               private LinkedHashMap<Integer, IManageablePage> pages = new 
LinkedHashMap<>();
+
+               @Override
+               public Iterator<IManageablePage> iterator()
+               {
+                       return pages.values().iterator();
+               }
+
+               public synchronized void add(IManageablePage page, int maxPages)
+               {
+                       pages.remove(page.getPageId());
+                       pages.put(page.getPageId(), page);
+
+                       Iterator<IManageablePage> iterator = 
pages.values().iterator();
+                       int size = pages.size();
+                       while (size > maxPages)
+                       {
+                               iterator.next();
+
+                               iterator.remove();
+                               size--;
+                       }
+               }
+
+               public void remove(IManageablePage page)
+               {
+                       pages.remove(page.getPageId());
+               }
+
+               public void removeAll()
+               {
+                       pages.clear();
+               }
+
+               public IManageablePage get(int id)
+               {
+                       IManageablePage page = pages.get(id);
+
+                       return page;
+               }
+       }
+
+       /**
+        * Attribute held in session.
+        */
+       static class SessionAttribute implements Serializable, 
HttpSessionBindingListener
+       {
+
+               private final String applicationName;
+
+               /**
+                * The identifier of the session, must not be equal to {@link 
Session#getId()}, e.g. when
+                * the container changes the id after authorization.
+                */
+               public final String identifier;
+
+               public SessionAttribute(String applicationName, String 
sessionIdentifier)
+               {
+                       this.applicationName = Args.notNull(applicationName, 
"applicationName");
+                       this.identifier = Args.notNull(sessionIdentifier, 
"sessionIdentifier");
+               }
+
+
+               @Override
+               public void valueBound(HttpSessionBindingEvent event)
+               {
+               }
+
+               @Override
+               public void valueUnbound(HttpSessionBindingEvent event)
+               {
+                       InMemoryPageStore store = 
IN_MEMORY_STORES.get(applicationName);
+                       if (store != null)
+                       {
+                               store.removeMemoryData(identifier);
+                       }
+               }
+       }
+       
+       private static class PersistedPage implements IPersistedPage
+       {
+
+               private final int id;
+
+               private final String type;
+
+               private final long size;
+
+               public PersistedPage(IManageablePage page, long size)
+               {
+                       this.id = page.getPageId();
+                       this.type = page instanceof SerializedPage ? 
((SerializedPage)page).getPageType() : page.getClass().getName();
+                       this.size = size;
+               }
+
+               @Override
+               public int getPageId()
+               {
+                       return id;
+               }
+
+               @Override
+               public String getPageType()
+               {
+                       return type;
+               }
+
+               @Override
+               public Bytes getPageSize()
+               {
+                       return Bytes.bytes(size);
+               }
+       }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/InSessionPageStore.java
----------------------------------------------------------------------
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..86b68ce
--- /dev/null
+++ 
b/wicket-core/src/main/java/org/apache/wicket/pageStore/InSessionPageStore.java
@@ -0,0 +1,267 @@
+/*
+ * 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 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;
+
+/**
+ * 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 int maxPages;
+       
+       /**
+        * Use this constructor, if sessions are never persisted by the 
container.
+        * 
+        * @param delegate
+        *            store to delegate to
+        * @param maxPages
+        *            maximum pages to keep in session
+        */
+       public InSessionPageStore(IPageStore delegate, int maxPages)
+       {
+               this(delegate, maxPages,  new ISerializer()
+               {
+                       @Override
+                       public byte[] serialize(Object object)
+                       {
+                               throw new 
WicketRuntimeException("InSessionPageStore not configured for serialization");
+                       }
+                       
+                       @Override
+                       public Object deserialize(byte[] data)
+                       {
+                               throw new 
WicketRuntimeException("InSessionPageStore not configured for serialization");
+                       }
+               });
+       }
+       
+       /**
+        * @param delegate
+        *            store to delegate to
+        * @param maxPages
+        *            maximum pages to keep in session
+        * @param serializer
+        *            for serialization of pages if session gets persisted
+        */
+       public InSessionPageStore(IPageStore delegate, int maxPages, 
ISerializer serializer)
+       {
+               super(delegate);
+
+               this.serializer = Args.notNull(serializer, "serializer");
+
+               this.maxPages = maxPages;
+       }
+
+       @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 super.getPage(context, id);
+       }
+
+       @Override
+       public void addPage(IPageContext context, IManageablePage page)
+       {
+               SessionData data = getSessionData(context, true);
+
+               data.add(context, page, maxPages);
+               
+               super.addPage(context, page);
+       }
+
+       @Override
+       public void removePage(IPageContext context, IManageablePage page)
+       {
+               SessionData data = getSessionData(context, false);
+               if (data != null) {
+                       data.remove(page);
+               }
+
+               super.removePage(context, page);
+       }
+
+       @Override
+       public void removeAllPages(IPageContext context)
+       {
+               SessionData data = getSessionData(context, false);
+               if (data != null) {
+                       data.removeAll();
+               }
+
+               super.removeAllPages(context);
+       }
+
+       private SessionData getSessionData(IPageContext context, boolean create)
+       {
+               SessionData data = context.getSessionData(KEY);
+               if (data == null && create)
+               {
+                       context.bind();
+
+                       data = context.setSessionData(KEY, new SessionData());
+               }
+
+               if (data != null) {
+                       // data might be deserialized so initialize again
+                       data.init(serializer);
+               }
+
+               return data;
+       }
+
+       /**
+        * Data kept in the {@link Session}, might get serialized along with 
its containing
+        * {@link HttpSession}.
+        */
+       static class SessionData implements Serializable
+       {
+
+               transient ISerializer serializer;
+
+               /**
+                * Pages, may partly be serialized.
+                * <p>
+                * Kept in list instead of map, since life pages might change 
their id during a request.
+                */
+               private List<IManageablePage> pages = new LinkedList<>();
+
+               /**
+                * This method <em>must</em> be called each time it is 
retrieved from the session: <br/>
+                * After deserializing from persisted session the serializer is 
no longer referenced and all
+                * contained pages are in a serialized state.
+                */
+               public void init(ISerializer serializer)
+               {
+                       this.serializer = Args.notNull(serializer, 
"serializer");
+               }
+
+               public synchronized void add(IPageContext context, 
IManageablePage page, int maxPages)
+               {
+                       // move to end
+                       remove(page);
+                       pages.add(page);
+
+                       while (pages.size() > maxPages)
+                       {
+                               pages.remove(0);
+                       }
+               }
+
+               public synchronized void remove(IManageablePage page)
+               {
+                       Iterator<IManageablePage> iterator = pages.iterator();
+                       while (iterator.hasNext()) {
+                               if (iterator.next().getPageId() == 
page.getPageId()) {
+                                       iterator.remove();
+                                       break;
+                               }
+                       }
+               }
+
+               public synchronized void removeAll()
+               {
+                       pages.clear();
+               }
+
+               public synchronized IManageablePage get(int id)
+               {
+                       IManageablePage page = null;
+                       
+                       for (int p = 0; p < pages.size(); p++)
+                       {
+                               IManageablePage candidate = pages.get(p);
+
+                               if (candidate.getPageId() == id) {
+                                       if (candidate instanceof SerializedPage)
+                                       {
+                                               if (serializer == null)
+                                               {
+                                                       throw new 
IllegalStateException("SessionData#init() was not called");
+                                               }
+                                               candidate = 
(IManageablePage)serializer.deserialize(((SerializedPage)candidate).getData());
+               
+                                               pages.set(p, candidate);
+                                       }
+                                       
+                                       page = candidate;
+                                       break;
+                               }
+                       }
+
+                       return page;
+               }
+
+               /**
+                * Serialize pages before writing to output.
+                */
+               private void writeObject(final ObjectOutputStream output) 
throws IOException
+               {
+                       // serialize pages if not already
+                       for (int p = 0; p < pages.size(); p++)
+                       {
+                               IManageablePage page = pages.get(p);
+                               
+                               if ((page instanceof SerializedPage) == false)
+                               {
+                                       if (serializer == null)
+                                       {
+                                               throw new 
IllegalStateException("SessionData#init() was not called");
+                                       }
+                                       pages.set(p,  new 
SerializedPage(page.getPageId(), page.getClass().getName(), 
serializer.serialize(page)));
+                               }
+                       }
+
+                       output.defaultWriteObject();
+               }
+       }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/NoopPageStore.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/pageStore/NoopPageStore.java 
b/wicket-core/src/main/java/org/apache/wicket/pageStore/NoopPageStore.java
new file mode 100644
index 0000000..d31313f
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/NoopPageStore.java
@@ -0,0 +1,58 @@
+/*
+ * 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;
+
+/**
+ * A non-storage of pages.
+ */
+public class NoopPageStore implements IPageStore
+{
+       @Override
+       public boolean supportsVersioning()
+       {
+               return false;
+       }
+
+       @Override
+       public boolean canBeAsynchronous(IPageContext context)
+       {
+               return true;
+       }
+       
+       @Override
+       public void addPage(IPageContext context, IManageablePage page)
+       {
+       }
+
+       @Override
+       public void removePage(IPageContext context, IManageablePage page)
+       {
+       }
+
+       @Override
+       public void removeAllPages(IPageContext context)
+       {
+       }
+
+       @Override
+       public IManageablePage getPage(IPageContext context, int id)
+       {
+               return null;
+       }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/PageWindowManager.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/pageStore/PageWindowManager.java 
b/wicket-core/src/main/java/org/apache/wicket/pageStore/PageWindowManager.java
deleted file mode 100644
index f194710..0000000
--- 
a/wicket-core/src/main/java/org/apache/wicket/pageStore/PageWindowManager.java
+++ /dev/null
@@ -1,506 +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.util.ArrayList;
-import java.util.List;
-
-import org.apache.wicket.util.collections.IntHashMap;
-
-/**
- * Manages positions and size of serialized pages in the pagemap file.
- * <p>
- * The pages are 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.
- * 
- * @author Matej Knopp
- */
-public class PageWindowManager implements Serializable
-{
-       private static final long serialVersionUID = 1L;
-
-       /**
-        * Contains information about a page inside the file.
-        * 
-        * @author Matej Knopp
-        */
-       private static class PageWindowInternal implements Serializable
-       {
-               private static final long serialVersionUID = 1L;
-
-               /** id of page or -1 if the window is empty */
-               private int pageId;
-
-               /** offset in the file where the serialized page data begins */
-               private int filePartOffset;
-
-               /** size of serialized page data */
-               private int filePartSize;
-       }
-
-       /** list of PageWindowInternal objects */
-       private final List<PageWindowInternal> windows = new 
ArrayList<PageWindowInternal>();
-
-       /**
-        * map from page id to list of pagewindow indices (referring to the 
windows list) - to improve
-        * searching speed the index must be cleaned when the instances in the 
windows list change their
-        * indexes (e.g. items are shifted on page window removal)
-        */
-       private IntHashMap<Integer> idToWindowIndex = null;
-
-       /**
-        * Inversed index of #idToWindowIndex
-        */
-       private IntHashMap<Integer> windowIndexToPageId = null;
-
-       /** index of last added page */
-       private int indexPointer = -1;
-
-       private int totalSize = 0;
-
-       /**
-        * Maximum page size. After this size is exceeded, the pages will be 
saved starting at the
-        * beginning of file.
-        */
-       private final long maxSize;
-
-       /**
-        * 
-        * @param pageId
-        * @param windowIndex
-        */
-       private void putWindowIndex(int pageId, int windowIndex)
-       {
-               if (idToWindowIndex != null && pageId != -1 && windowIndex != 
-1)
-               {
-                       Integer oldPageId = 
windowIndexToPageId.remove(windowIndex);
-                       if (oldPageId != null)
-                       {
-                               idToWindowIndex.remove(oldPageId);
-                       }
-                       idToWindowIndex.put(pageId, windowIndex);
-                       windowIndexToPageId.put(windowIndex, pageId);
-               }
-       }
-
-       /**
-        * 
-        * @param pageId
-        */
-       private void removeWindowIndex(int pageId)
-       {
-               Integer windowIndex = idToWindowIndex.remove(pageId);
-               if (windowIndex != null)
-               {
-                       windowIndexToPageId.remove(windowIndex);
-               }
-       }
-
-       /**
-        * 
-        */
-       private void rebuildIndices()
-       {
-               idToWindowIndex = null;
-               idToWindowIndex = new IntHashMap<Integer>();
-               windowIndexToPageId = null;
-               windowIndexToPageId = new IntHashMap<Integer>();
-               for (int i = 0; i < windows.size(); ++i)
-               {
-                       PageWindowInternal window = windows.get(i);
-                       putWindowIndex(window.pageId, i);
-               }
-       }
-
-       /**
-        * Returns the index of the given page in the {@link #windows} list.
-        * 
-        * @param pageId
-        * @return window index
-        */
-       private int getWindowIndex(int pageId)
-       {
-               if (idToWindowIndex == null)
-               {
-                       rebuildIndices();
-               }
-
-               Integer result = idToWindowIndex.get(pageId);
-               return result != null ? result : -1;
-       }
-
-       /**
-        * Increments the {@link #indexPointer}. If the maximum file size has 
been reached, the
-        * {@link #indexPointer} is set to 0.
-        * 
-        * @return new index pointer
-        */
-       private int incrementIndexPointer()
-       {
-               if ((maxSize > 0) && (totalSize >= maxSize) && (indexPointer == 
windows.size() - 1))
-               {
-                       indexPointer = 0;
-               }
-               else
-               {
-                       ++indexPointer;
-               }
-               return indexPointer;
-       }
-
-       /**
-        * Returns the offset in file of the window on given index. The offset 
is counted by getting the
-        * previous page offset and adding the previous page size to it.
-        * 
-        * @param index
-        * @return window file offset
-        */
-       private int getWindowFileOffset(int index)
-       {
-               if (index > 0)
-               {
-                       PageWindowInternal window = windows.get(index - 1);
-                       return window.filePartOffset + window.filePartSize;
-               }
-               return 0;
-       }
-
-       /**
-        * Splits the window with given index to two windows. First of those 
will have size specified by
-        * the argument, the other one will fill up the rest of the original 
window.
-        * 
-        * @param index
-        * @param size
-        */
-       private void splitWindow(int index, int size)
-       {
-               PageWindowInternal window = windows.get(index);
-               int delta = window.filePartSize - size;
-
-               if (index == windows.size() - 1)
-               {
-                       // if this is last window
-                       totalSize -= delta;
-                       window.filePartSize = size;
-               }
-               else if (window.filePartSize != size)
-               {
-                       PageWindowInternal newWindow = new PageWindowInternal();
-                       newWindow.pageId = -1;
-                       window.filePartSize = size;
-
-                       windows.add(index + 1, newWindow);
-
-                       newWindow.filePartOffset = getWindowFileOffset(index + 
1);
-                       newWindow.filePartSize = delta;
-               }
-
-               idToWindowIndex = null;
-               windowIndexToPageId = null;
-       }
-
-       /**
-        * Merges the window with given index with the next window. The 
resulting window will have size
-        * of the two windows summed together.
-        * 
-        * @param index
-        */
-       private void mergeWindowWithNext(int index)
-       {
-               if (index < windows.size() - 1)
-               {
-                       PageWindowInternal window = windows.get(index);
-                       PageWindowInternal next = windows.get(index + 1);
-                       window.filePartSize += next.filePartSize;
-
-                       windows.remove(index + 1);
-                       idToWindowIndex = null; // reset index
-                       windowIndexToPageId = null;
-               }
-       }
-
-       /**
-        * Adjusts the window on given index to the specified size. If the new 
size is smaller than the
-        * window size, the window will be split. Otherwise the window will be 
merged with as many
-        * subsequent window as necessary. In case the window is last window in 
the file, the size will
-        * be adjusted without splitting or merging.
-        * 
-        * @param index
-        * @param size
-        */
-       private void adjustWindowSize(int index, int size)
-       {
-               PageWindowInternal window = windows.get(index);
-
-               // last window, just adjust size
-               if (index == windows.size() - 1)
-               {
-                       int delta = size - window.filePartSize;
-                       totalSize += delta;
-                       window.filePartSize = size;
-               }
-               else
-               {
-                       // merge as many times as necessary
-                       while (window.filePartSize < size && index < 
windows.size() - 1)
-                       {
-                               mergeWindowWithNext(index);
-                       }
-
-                       // done merging - do we have enough room ?
-                       if (window.filePartSize < size)
-                       {
-                               // no, this is the last window
-                               int delta = size - window.filePartSize;
-                               totalSize += delta;
-                               window.filePartSize = size;
-                       }
-                       else
-                       {
-                               // yes, we might want to split the window, so 
that we don't lose
-                               // space when the created window was too big
-                               splitWindow(index, size);
-                       }
-               }
-
-               window.pageId = -1;
-       }
-
-       /**
-        * Allocates window on given index with to size. If the index is 
pointing to existing window,
-        * the window size will be adjusted. Otherwise a new window with 
appropriated size will be
-        * created.
-        * 
-        * @param index
-        * @param size
-        * @return page window
-        */
-       private PageWindowInternal allocatePageWindow(int index, int size)
-       {
-               final PageWindowInternal window;
-
-               // new window
-               if (index == windows.size())
-               {
-                       // new page window
-                       window = new PageWindowInternal();
-                       window.filePartOffset = getWindowFileOffset(index);
-                       totalSize += size;
-                       window.filePartSize = size;
-                       windows.add(window);
-               }
-               else
-               {
-                       // get the window
-                       window = windows.get(index);
-
-                       // adjust if necessary
-                       if (window.filePartSize != size)
-                       {
-                               adjustWindowSize(index, size);
-                       }
-               }
-
-               return window;
-       }
-
-       /**
-        * 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 size
-        * @return page window
-        */
-       public synchronized PageWindow createPageWindow(int pageId, int size)
-       {
-               int index = getWindowIndex(pageId);
-
-               // if we found the page window, mark it as invalid
-               if (index != -1)
-               {
-                       removeWindowIndex(pageId);
-                       (windows.get(index)).pageId = -1;
-               }
-
-               // if we are not going to reuse a page window (because it's not 
on
-               // indexPointer position or because we didn't find it), 
increment the
-               // indexPointer
-               if (index == -1 || index != indexPointer)
-               {
-                       index = incrementIndexPointer();
-               }
-
-               PageWindowInternal window = allocatePageWindow(index, size);
-               window.pageId = pageId;
-
-               putWindowIndex(pageId, index);
-               return new PageWindow(window);
-       }
-
-       /**
-        * Returns the page window for given page or null if no window was 
found.
-        * 
-        * @param pageId
-        * @return page window or null
-        */
-       public synchronized PageWindow getPageWindow(int pageId)
-       {
-               int index = getWindowIndex(pageId);
-               if (index != -1)
-               {
-                       return new PageWindow(windows.get(index));
-               }
-               return null;
-       }
-
-       /**
-        * Removes the page window for given page.
-        * 
-        * @param pageId
-        */
-       public synchronized void removePage(int pageId)
-       {
-               int index = getWindowIndex(pageId);
-               if (index != -1)
-               {
-                       PageWindowInternal window = windows.get(index);
-                       removeWindowIndex(pageId);
-                       if (index == windows.size() - 1)
-                       {
-                               windows.remove(index);
-                               totalSize -= window.filePartSize;
-                               if (indexPointer == index)
-                               {
-                                       --indexPointer;
-                               }
-                       }
-                       else
-                       {
-                               window.pageId = -1;
-                       }
-               }
-       }
-
-       /**
-        * Returns last n saved page windows.
-        * 
-        * @param count
-        * @return list of page windows
-        */
-       public synchronized List<PageWindow> getLastPageWindows(int count)
-       {
-               List<PageWindow> result = new ArrayList<PageWindow>();
-
-               // start from current index to 0
-               int currentIndex = indexPointer;
-
-               do
-               {
-                       if (currentIndex == -1)
-                       {
-                               break;
-                       }
-
-                       if (currentIndex < windows.size())
-                       {
-                               PageWindowInternal window = 
windows.get(currentIndex);
-                               if (window.pageId != -1)
-                               {
-                                       result.add(new PageWindow(window));
-                               }
-                       }
-
-                       --currentIndex;
-                       if (currentIndex == -1)
-                       {
-                               // rewind to the last entry and collect all 
entries until current index
-                               currentIndex = windows.size() - 1;
-                       }
-               }
-               while (result.size() < count && currentIndex != indexPointer);
-
-               return result;
-       }
-
-       /**
-        * Creates a new PageWindowManager.
-        * 
-        * @param maxSize
-        *            maximum page size. After this size is exceeded, the pages 
will be saved starting
-        *            at the beginning of file
-        */
-       public PageWindowManager(long maxSize)
-       {
-               this.maxSize = maxSize;
-       }
-
-       /**
-        * Returns the size of all saved pages
-        * 
-        * @return total size
-        */
-       public synchronized int getTotalSize()
-       {
-               return totalSize;
-       }
-}

http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/PerSessionPageStore.java
----------------------------------------------------------------------
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
-       }
-}

http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/RequestPageStore.java
----------------------------------------------------------------------
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..89618bc
--- /dev/null
+++ 
b/wicket-core/src/main/java/org/apache/wicket/pageStore/RequestPageStore.java
@@ -0,0 +1,157 @@
+/*
+ * 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 super.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);
+
+               super.removePage(context, page);
+       }
+
+       @Override
+       public void removeAllPages(IPageContext context)
+       {
+               getRequestData(context).removeAll();
+
+               super.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)
+                       {
+                               super.addPage(context, page);
+                       }
+               }
+               requestData.removeAll();
+
+               super.detach(context);
+       }
+
+       private RequestData getRequestData(IPageContext context)
+       {
+               RequestData requestData = context.getRequestData(KEY);
+               if (requestData == null)
+               {
+                       requestData = new RequestData();
+
+                       context.setRequestData(KEY, requestData);
+               }
+               return 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

http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/SecondLevelPageCache.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/pageStore/SecondLevelPageCache.java
 
b/wicket-core/src/main/java/org/apache/wicket/pageStore/SecondLevelPageCache.java
deleted file mode 100644
index 3d30857..0000000
--- 
a/wicket-core/src/main/java/org/apache/wicket/pageStore/SecondLevelPageCache.java
+++ /dev/null
@@ -1,42 +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;
-
-/**
- * An application scoped cache that holds the last N used pages in the 
application.
- * Acts as a second level cache between the Http Session (first level) and the
- * disk (third level cache).
- *
- * @param <S>
- *          The type of the session identifier
- * @param <PI>
- *          The type of the page identifier
- * @param <P>
- *          The type of the stored page
- */
-public interface SecondLevelPageCache<S, PI, P>
-{
-       P removePage(S session, PI pageId);
-
-       void removePages(S session);
-
-       P getPage(S session, PI pageId);
-
-       void storePage(S session, PI pageId, P page);
-
-       void destroy();
-}

http://git-wip-us.apache.org/repos/asf/wicket/blob/3f364e71/wicket-core/src/main/java/org/apache/wicket/pageStore/SerializedPage.java
----------------------------------------------------------------------
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..5d858b5
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/SerializedPage.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 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
+ */
+class SerializedPage implements IManageablePage
+{
+
+       private final int pageId;
+       
+       private final String pageType;
+
+       private final byte[] 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 + "]";
+       }
+}
\ No newline at end of file

Reply via email to