You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by kn...@apache.org on 2007/08/13 20:51:43 UTC

svn commit: r565474 [1/2] - in /wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket: ./ protocol/http/ protocol/http/pagestore/ request/target/coding/ settings/ util/tester/

Author: knopp
Date: Mon Aug 13 11:51:41 2007
New Revision: 565474

URL: http://svn.apache.org/viewvc?view=rev&rev=565474
Log:
WICKET-30 - made it possible to have unique page id per session

Modified:
    wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/AccessStackPageMap.java
    wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/IPageMap.java
    wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/Page.java
    wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/PageMap.java
    wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/Session.java
    wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/protocol/http/FilePageStore.java
    wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/protocol/http/SecondLevelCacheSessionStore.java
    wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/protocol/http/TestFilePageStore.java
    wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/protocol/http/WebApplication.java
    wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/protocol/http/pagestore/DiskPageStore.java
    wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/protocol/http/pagestore/SimpleSynchronousFilePageStore.java
    wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/request/target/coding/HybridUrlCodingStrategy.java
    wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/settings/ISessionSettings.java
    wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/settings/Settings.java
    wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/util/tester/WicketTester.java

Modified: wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/AccessStackPageMap.java
URL: http://svn.apache.org/viewvc/wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/AccessStackPageMap.java?view=diff&rev=565474&r1=565473&r2=565474
==============================================================================
--- wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/AccessStackPageMap.java (original)
+++ wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/AccessStackPageMap.java Mon Aug 13 11:51:41 2007
@@ -184,9 +184,10 @@
 	 */
 	public final void removeEntry(final IPageMapEntry entry)
 	{
-		if(entry == null)
+		if (entry == null)
 		{
-			// TODO this shouldn't happen but to many people are still getting this now and then/
+			// TODO this shouldn't happen but to many people are still getting
+			// this now and then/
 			// so first this "fix"
 			log.warn("PageMap.removeEntry called with an null entry");
 			return;
@@ -247,7 +248,6 @@
 			final Page version = page.getVersion(versionNumber);
 
 
-
 			// Is the requested version available?
 			if (version != null)
 			{
@@ -357,7 +357,7 @@
 							remove(topPage);
 						}
 					}
-					else if(top != null)
+					else if (top != null)
 					{
 						// Remove entry
 						removeEntry(top);
@@ -417,6 +417,19 @@
 		}
 		accessStack.push(access);
 		dirty();
+	}
+
+	public boolean containsPage(int id, int versionNumber)
+	{
+		for (Iterator i = accessStack.iterator(); i.hasNext();)
+		{
+			Access access = (Access)i.next();
+			if (access.id == id && access.version == versionNumber)
+			{
+				return true;
+			}
+		}
+		return false;
 	}
 
 	/**

Modified: wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/IPageMap.java
URL: http://svn.apache.org/viewvc/wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/IPageMap.java?view=diff&rev=565474&r1=565473&r2=565474
==============================================================================
--- wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/IPageMap.java (original)
+++ wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/IPageMap.java Mon Aug 13 11:51:41 2007
@@ -138,4 +138,13 @@
 	 *            The entry to remove
 	 */
 	void removeEntry(final IPageMapEntry entry);
+
+	/**
+	 * Returns true if the PageMap contains page with given id and versonNumber
+	 * 
+	 * @param id
+	 * @param versionNumber
+	 * @return
+	 */
+	public boolean containsPage(int id, int versionNumber);
 }

Modified: wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/Page.java
URL: http://svn.apache.org/viewvc/wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/Page.java?view=diff&rev=565474&r1=565473&r2=565474
==============================================================================
--- wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/Page.java (original)
+++ wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/Page.java Mon Aug 13 11:51:41 2007
@@ -1188,8 +1188,7 @@
 			throw new IllegalStateException("PageMap cannot be null");
 		}
 
-		// Set the numeric id on this page
-		setNumericId(getPageMap().nextId());
+		setNextAvailableId();
 
 		// Set versioning of page based on default
 		setVersioned(Application.get().getPageSettings().getVersionPagesByDefault());
@@ -1198,6 +1197,19 @@
 		dirty();
 	}
 
+	private void setNextAvailableId()
+	{
+		if (getApplication().getSessionSettings().isPageIdUniquePerSession())
+		{
+			setNumericId(getSession().nextPageId());
+		}
+		else
+		{
+			// Set the numeric id on this page
+			setNumericId(getPageMap().nextId());
+		}
+	}
+
 	/**
 	 * For the given component, whether we may record changes.
 	 * 
@@ -1372,7 +1384,8 @@
 		// This should be done if the page was not cloned first, but shouldn't
 		// be done if it was cloned..
 		setPageMap(map);
-		numericId = (short)map.nextId();
+
+		setNextAvailableId();
 	}
 
 	/**

Modified: wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/PageMap.java
URL: http://svn.apache.org/viewvc/wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/PageMap.java?view=diff&rev=565474&r1=565473&r2=565474
==============================================================================
--- wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/PageMap.java (original)
+++ wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/PageMap.java Mon Aug 13 11:51:41 2007
@@ -16,6 +16,7 @@
  */
 package org.apache.wicket;
 
+import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
@@ -206,7 +207,7 @@
 	public final int nextId()
 	{
 		dirty();
-		return this.pageId++;
+		return pageId++;
 	}
 
 	/**
@@ -341,6 +342,42 @@
 				visitor.entry((IPageMapEntry)session.getAttribute(attribute));
 			}
 		}
+	}
+
+	/**
+	 * MetaDataEntry array.
+	 */
+	private MetaDataEntry[] metaData;
+
+	/**
+	 * Sets the metadata for this PageMap using the given key. If the metadata
+	 * object is not of the correct type for the metadata key, an
+	 * IllegalArgumentException will be thrown. For information on creating
+	 * MetaDataKeys, see {@link MetaDataKey}.
+	 * 
+	 * @param key
+	 *            The singleton key for the metadata
+	 * @param object
+	 *            The metadata object
+	 * @throws IllegalArgumentException
+	 * @see MetaDataKey
+	 */
+	public final void setMetaData(final MetaDataKey key, final Serializable object)
+	{
+		metaData = key.set(metaData, object);
+	}
+
+	/**
+	 * Gets metadata for this PageMap using the given key.
+	 * 
+	 * @param key
+	 *            The key for the data
+	 * @return The metadata or null of no metadata was found for the given key
+	 * @see MetaDataKey
+	 */
+	public final Serializable getMetaData(final MetaDataKey key)
+	{
+		return key.get(metaData);
 	}
 
 	/**

Modified: wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/Session.java
URL: http://svn.apache.org/viewvc/wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/Session.java?view=diff&rev=565474&r1=565473&r2=565474
==============================================================================
--- wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/Session.java (original)
+++ wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/Session.java Mon Aug 13 11:51:41 2007
@@ -508,12 +508,14 @@
 	 */
 	public synchronized final String createAutoPageMapName()
 	{
-		String name = getAutoPageMapNamePrefix() + autoCreatePageMapCounter;
+		String name = getAutoPageMapNamePrefix() + autoCreatePageMapCounter +
+				getAutoPageMapNameSuffix();
 		IPageMap pm = pageMapForName(name, false);
 		while (pm != null)
 		{
 			autoCreatePageMapCounter++;
-			name = getAutoPageMapNamePrefix() + autoCreatePageMapCounter;
+			name = getAutoPageMapNamePrefix() + autoCreatePageMapCounter +
+					getAutoPageMapNameSuffix();
 			pm = pageMapForName(name, false);
 		}
 		return name;
@@ -528,6 +530,14 @@
 	}
 
 	/**
+	 * @return
+	 */
+	protected String getAutoPageMapNameSuffix()
+	{
+		return "";
+	}
+
+	/**
 	 * Registers an error feedback message for this session
 	 * 
 	 * @param message
@@ -662,6 +672,38 @@
 	/**
 	 * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
 	 * 
+	 * Returns the page with given id and versionNumber. It keeps asking
+	 * pageMaps for given page until it finds one that contains it.
+	 * 
+	 * @param pageId
+	 * @param versionNumber
+	 * @return
+	 */
+	public final Page getPage(final int pageId, final int versionNumber)
+	{
+		if (Application.get().getSessionSettings().isPageIdUniquePerSession() == false)
+		{
+			throw new IllegalStateException(
+					"To call this method ISessionSettings.setPageIdUniquePerSession must be set to true");
+		}
+
+		List pageMaps = getPageMaps();
+
+		for (Iterator i = pageMaps.iterator(); i.hasNext();)
+		{
+			IPageMap pm = (IPageMap)i.next();
+			if (pm.containsPage(pageId, versionNumber))
+			{
+				return getPage(pm.getName(), "" + pageId, versionNumber);
+			}
+		}
+
+		return null;
+	}
+
+	/**
+	 * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
+	 * 
 	 * Get the page for the given path.
 	 * 
 	 * @param pageMapName
@@ -1446,5 +1488,12 @@
 				pageMapsUsedInRequest.notifyAll();
 			}
 		}
+	}
+
+	private int pageIdCounter = 0;
+
+	synchronized int nextPageId()
+	{
+		return pageIdCounter++;
 	}
 }

Modified: wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/protocol/http/FilePageStore.java
URL: http://svn.apache.org/viewvc/wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/protocol/http/FilePageStore.java?view=diff&rev=565474&r1=565473&r2=565474
==============================================================================
--- wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/protocol/http/FilePageStore.java (original)
+++ wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/protocol/http/FilePageStore.java Mon Aug 13 11:51:41 2007
@@ -328,9 +328,9 @@
 			this.id = id;
 			this.versionNumber = versionNumber;
 			this.ajaxVersionNumber = ajaxVersionNumber;
-			this.pageClassName = pageClass == null ? null : pageClass.getName();
-			this.pageMap = pagemap;
-			this.data = page;
+			pageClassName = pageClass == null ? null : pageClass.getName();
+			pageMap = pagemap;
+			data = page;
 		}
 
 		SessionPageKey(String sessionId, Page page)
@@ -527,7 +527,8 @@
 	public Page getPage(String sessionId, String pagemapName, int id, int versionNumber,
 			int ajaxVersionNumber)
 	{
-		// first try to figure out correct versionNumber and ajaxVersionNumeber if necessary
+		// first try to figure out correct versionNumber and ajaxVersionNumeber
+		// if necessary
 		if (versionNumber == -1 || ajaxVersionNumber == -1)
 		{
 			PageVersions versions = findPageVersion(sessionId, pagemapName, id, versionNumber);
@@ -541,7 +542,7 @@
 				ajaxVersionNumber = versions.ajaxversionid;
 			}
 		}
-		
+
 		SessionPageKey currentKey = new SessionPageKey(sessionId, id, versionNumber,
 				ajaxVersionNumber, pagemapName, null);
 		long t1 = System.currentTimeMillis();
@@ -692,7 +693,8 @@
 			{
 				listFiles[i].delete();
 			}
-			if (id == -1) {
+			if (id == -1)
+			{
 				removePageVersions(sessionId, pageMap);
 			}
 		}
@@ -885,7 +887,7 @@
 		 */
 		public PageSerializer(SessionPageKey key)
 		{
-			this.current = key;
+			current = key;
 		}
 
 		/**
@@ -957,8 +959,8 @@
 
 		PageHolder(Page page)
 		{
-			this.pageid = page.getNumericId();
-			this.pagemap = page.getPageMapName();
+			pageid = page.getNumericId();
+			pagemap = page.getPageMapName();
 		}
 
 		protected Object readResolve() throws ObjectStreamException
@@ -1007,14 +1009,14 @@
 	}
 
 	// sessionId->(pageMapName->List<PageVersion>)
-	private Map /* <String, Map<String, List<PageVersions>> */pageVersionMap = new ConcurrentHashMap();
+	private final Map /* <String, Map<String, List<PageVersions>> */pageVersionMap = new ConcurrentHashMap();
 
 	private List /* <PageVersions> */getPageVersions(String sessionId, String pageMapName,
 			boolean createIfDoesNotExist)
 	{
 		if (pageMapName == null)
 			pageMapName = "";
-		
+
 		Map pageMapToList = (Map)pageVersionMap.get(sessionId);
 		if (pageMapToList == null && createIfDoesNotExist == false)
 		{
@@ -1039,7 +1041,7 @@
 	{
 		pageVersionMap.remove(sessionId);
 	}
-	
+
 	private void removePageVersions(String sessionId, String pageMapName)
 	{
 		if (pageMapName == null)
@@ -1121,5 +1123,25 @@
 				list.remove(0);
 			}
 		}
+	}
+
+	public boolean containsPage(String sessionId, String pageMapName, int pageId, int pageVersion)
+	{
+		List list = getPageVersions(sessionId, pageMapName, false);
+		if (list != null)
+		{
+			synchronized (list)
+			{
+				for (Iterator i = list.iterator(); i.hasNext();)
+				{
+					PageVersions v = (PageVersions)i.next();
+					if (v.pageid == pageId && v.versionid == pageVersion)
+					{
+						return true;
+					}
+				}
+			}
+		}
+		return false;
 	}
 }

Modified: wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/protocol/http/SecondLevelCacheSessionStore.java
URL: http://svn.apache.org/viewvc/wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/protocol/http/SecondLevelCacheSessionStore.java?view=diff&rev=565474&r1=565473&r2=565474
==============================================================================
--- wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/protocol/http/SecondLevelCacheSessionStore.java (original)
+++ wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/protocol/http/SecondLevelCacheSessionStore.java Mon Aug 13 11:51:41 2007
@@ -117,13 +117,24 @@
 		 * @param sessionId
 		 */
 		void unbind(String sessionId);
+
+		/**
+		 * Returns whether the PageStore contains given page.
+		 * 
+		 * @param sessionId
+		 * @param pageMapName
+		 * @param pageId
+		 * @param pageVersion
+		 * @return
+		 */
+		boolean containsPage(String sessionId, String pageMapName, int pageId, int pageVersion);
 	}
 
-	protected boolean isPageStoreClustered() 
+	protected boolean isPageStoreClustered()
 	{
 		return pageStore instanceof IClusteredPageStore;
 	}
-	
+
 	/**
 	 * Marker interface for PageStores that support replication of serialized
 	 * pages across cluster, which means that the lastPage attribute of
@@ -131,11 +142,11 @@
 	 * 
 	 * @author Matej Knopp
 	 */
-	public static interface IClusteredPageStore extends IPageStore 
+	public static interface IClusteredPageStore extends IPageStore
 	{
-		
+
 	};
-	
+
 	/**
 	 * Some PageStores might want to preprocess page before serialization. For
 	 * example if the PageStore serializes page, it might keep the serialized
@@ -145,23 +156,25 @@
 	 * 
 	 * @author Matej Knopp
 	 */
-	public static interface ISerializationAwarePageStore extends IPageStore 
+	public static interface ISerializationAwarePageStore extends IPageStore
 	{
 		/**
 		 * Process the page before the it gets serialized
+		 * 
 		 * @param page
 		 * @return
 		 */
 		public Serializable prepareForSerialization(Page page);
-		
+
 		/**
 		 * This method should restore the given object to the original page.
+		 * 
 		 * @param serializable
 		 * @return
 		 */
 		public Page restoreAfterSerialization(Serializable serializable);
 	};
-	
+
 	/**
 	 * Page map implementation for this session store.
 	 */
@@ -175,19 +188,20 @@
 
 		// whether the last page instance should be serialized together with the
 		// pagemap
-		private boolean serializeLastPage;
-		
+		private final boolean serializeLastPage;
+
 		// when the last page is deserialized, it's actually placed here
-		// then on first demand, it's postprocessed (if the session store implements
+		// then on first demand, it's postprocessed (if the session store
+		// implements
 		// ISerializationAwareSessionStore) and set as lastPage
 		private Serializable lastPageDeserialized;
-		
+
 		private transient SecondLevelCacheSessionStore sessionStore;
-		
+
 		private IPageStore getPageStore()
 		{
 			if (sessionStore == null)
-			{				
+			{
 				Application app = Application.exists() ? Application.get() : null;
 				if (app != null)
 				{
@@ -203,32 +217,34 @@
 				return null;
 			}
 		}
-		
+
 		private Page getLastPage()
 		{
 			if (lastPage == null && lastPageDeserialized != null)
 			{
 				IPageStore store = getPageStore();
-				// initialize lastPage if necessary (we intentionally delay this until the first demand)
+				// initialize lastPage if necessary (we intentionally delay this
+				// until the first demand)
 				if (store instanceof ISerializationAwarePageStore)
 				{
-					lastPage = ((ISerializationAwarePageStore)store).restoreAfterSerialization(lastPageDeserialized);
+					lastPage = ((ISerializationAwarePageStore)store)
+							.restoreAfterSerialization(lastPageDeserialized);
 				}
 				else if (lastPageDeserialized instanceof Page)
 				{
-					lastPage = (Page) lastPageDeserialized;
+					lastPage = (Page)lastPageDeserialized;
 				}
 				lastPageDeserialized = null;
 			}
 			return lastPage;
 		}
-		
+
 		private void setLastPage(Page lastPage)
 		{
 			this.lastPage = lastPage;
-			this.lastPageDeserialized = null;
+			lastPageDeserialized = null;
 		}
-		
+
 		/**
 		 * Construct.
 		 * 
@@ -240,9 +256,23 @@
 		{
 			super(name);
 			this.sessionStore = sessionStore;
-			this.serializeLastPage = sessionStore.isPageStoreClustered() == false;
+			serializeLastPage = sessionStore.isPageStoreClustered() == false;
 		}
-		
+
+		public boolean containsPage(int id, int versionNumber)
+		{
+			Page lastPage = this.lastPage;
+			if (lastPage != null && lastPage.getNumericId() == id &&
+					lastPage.getCurrentVersionNumber() == versionNumber)
+			{
+				return true;
+			}
+			else
+			{
+				return getStore().containsPage(getSession().getId(), getName(), id, versionNumber);
+			}
+		}
+
 		/**
 		 * @see org.apache.wicket.PageMap#get(int, int)
 		 */
@@ -274,7 +304,9 @@
 			String sessionId = getSession().getId();
 			if (getLastPage() != null && getLastPage().getNumericId() == id)
 			{
-				page = versionNumber != -1 ? getLastPage().getVersion(versionNumber) : getLastPage();
+				page = versionNumber != -1
+						? getLastPage().getVersion(versionNumber)
+						: getLastPage();
 				if (page != null)
 				{
 					// ask the page store if it is ready saving the page.
@@ -286,8 +318,7 @@
 			if (sessionId != null)
 			{
 				setLastPage(null);
-				page = getStore().getPage(sessionId, getName(), id, versionNumber,
-						-1);
+				page = getStore().getPage(sessionId, getName(), id, versionNumber, -1);
 				pages.put(id, page);
 				return page;
 
@@ -341,28 +372,29 @@
 		{
 			return ((SecondLevelCacheSessionStore)Application.get().getSessionStore()).getStore();
 		}
-		
-		private void writeObject(java.io.ObjectOutputStream s) throws IOException 
+
+		private void writeObject(java.io.ObjectOutputStream s) throws IOException
 		{
 			s.defaultWriteObject();
-			
+
 			// if the pagestore is not clustered, we need to serialize the
 			// lastPage instance
-			if (serializeLastPage) 
+			if (serializeLastPage)
 			{
 				Serializable page = lastPage;
-				
+
 				IPageStore store = getPageStore();
 				if (page != null && store instanceof ISerializationAwarePageStore)
 				{
 					page = ((ISerializationAwarePageStore)store).prepareForSerialization(lastPage);
 				}
-				
+
 				s.writeObject(page);
 			}
 		}
-		
-		private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException 
+
+		private void readObject(java.io.ObjectInputStream s) throws IOException,
+				ClassNotFoundException
 		{
 			s.defaultReadObject();
 
@@ -370,7 +402,7 @@
 			// instance
 			if (serializeLastPage)
 			{
-				Serializable page = (Serializable) s.readObject();
+				Serializable page = (Serializable)s.readObject();
 				if (page != null)
 				{
 					lastPageDeserialized = page;
@@ -405,7 +437,7 @@
 		{
 			this.page = page;
 		}
-		
+
 		/**
 		 * @see org.apache.wicket.version.IPageVersionManager#beginVersion(boolean)
 		 */
@@ -430,7 +462,7 @@
 			else
 			{
 				if (RequestCycle.get().getRequest() instanceof WebRequest &&
-					((WebRequest)RequestCycle.get().getRequest()).isAjax())
+						((WebRequest)RequestCycle.get().getRequest()).isAjax())
 				{
 					currentAjaxVersionNumber++;
 				}
@@ -593,11 +625,10 @@
 
 			// versionStarted = true;
 		}
-
 	}
 
 	static final ThreadLocal usedPages = new ThreadLocal();
-	
+
 	/**
 	 * @return
 	 */

Modified: wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/protocol/http/TestFilePageStore.java
URL: http://svn.apache.org/viewvc/wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/protocol/http/TestFilePageStore.java?view=diff&rev=565474&r1=565473&r2=565474
==============================================================================
--- wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/protocol/http/TestFilePageStore.java (original)
+++ wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/protocol/http/TestFilePageStore.java Mon Aug 13 11:51:41 2007
@@ -50,14 +50,14 @@
 
 	private final File defaultWorkDir;
 	private final String appName;
-	
+
 	volatile long totalSavingTime = 0;
 	volatile long totalSerializationTime = 0;
 
 	private volatile int saved;
 
 	private volatile int bytesSaved;
-	
+
 	/**
 	 * Construct.
 	 */
@@ -78,7 +78,7 @@
 		defaultWorkDir = dir;
 		appName = Application.get().getApplicationKey();
 	}
-	
+
 	private class SessionPageKey
 	{
 		private final String sessionId;
@@ -101,8 +101,8 @@
 			this.id = id;
 			this.versionNumber = versionNumber;
 			this.ajaxVersionNumber = ajaxVersionNumber;
-			this.pageClassName = pageClass.getName();
-			this.pageMap = pagemap;
+			pageClassName = pageClass.getName();
+			pageMap = pagemap;
 		}
 
 		/**
@@ -121,11 +121,11 @@
 			if (obj instanceof SessionPageKey)
 			{
 				SessionPageKey key = (SessionPageKey)obj;
-				return id == key.id
-						&& versionNumber == key.versionNumber
-						&& ajaxVersionNumber == key.ajaxVersionNumber
-						&& ((pageMap != null && pageMap.equals(key.pageMap)) || (pageMap == null && key.pageMap == null))
-						&& sessionId.equals(key.sessionId);
+				return id == key.id &&
+						versionNumber == key.versionNumber &&
+						ajaxVersionNumber == key.ajaxVersionNumber &&
+						((pageMap != null && pageMap.equals(key.pageMap)) || (pageMap == null && key.pageMap == null)) &&
+						sessionId.equals(key.sessionId);
 			}
 			return false;
 		}
@@ -135,14 +135,16 @@
 		 */
 		public String toString()
 		{
-			return "SessionPageKey[" + sessionId + "," + id + "," + versionNumber + ","
-					+ ajaxVersionNumber + ", " + pageMap + "]";
+			return "SessionPageKey[" + sessionId + "," + id + "," + versionNumber + "," +
+					ajaxVersionNumber + ", " + pageMap + "]";
 		}
 	}
-	
-	public Page getPage(String sessionId, String pagemap, int id, int versionNumber, int ajaxVersionNumber)
+
+	public Page getPage(String sessionId, String pagemap, int id, int versionNumber,
+			int ajaxVersionNumber)
 	{
-		SessionPageKey currentKey = new SessionPageKey(sessionId, id, versionNumber, ajaxVersionNumber, pagemap, null);
+		SessionPageKey currentKey = new SessionPageKey(sessionId, id, versionNumber,
+				ajaxVersionNumber, pagemap, null);
 		File sessionDir = new File(getWorkDir(), sessionId);
 		File pageFile = getPageFile(currentKey, sessionDir);
 		if (pageFile.exists())
@@ -171,11 +173,11 @@
 				if (page != null && log.isDebugEnabled())
 				{
 					long t3 = System.currentTimeMillis();
-					log.debug("restoring page " + page.getClass() + "[" + page.getNumericId()
-							+ "," + page.getCurrentVersionNumber() + "] size: "
-							+ pageData.length + " for session " + sessionId + " took "
-							+ (t2 - t1) + " miliseconds to read in and " + (t3 - t2)
-							+ " miliseconds to deserialize");
+					log.debug("restoring page " + page.getClass() + "[" + page.getNumericId() +
+							"," + page.getCurrentVersionNumber() + "] size: " + pageData.length +
+							" for session " + sessionId + " took " + (t2 - t1) +
+							" miliseconds to read in and " + (t3 - t2) +
+							" miliseconds to deserialize");
 				}
 				return page;
 			}
@@ -189,7 +191,7 @@
 
 	public void pageAccessed(String sessionId, Page page)
 	{
-		
+
 	}
 
 	public void removePage(String sessionId, String pageMapName, int pageId)
@@ -200,7 +202,7 @@
 	{
 		SessionPageKey key = new SessionPageKey(sessionId, page);
 		byte[] serialized = Objects.objectToByteArray(page);
-		//map.put(key, serialized);
+		// map.put(key, serialized);
 		savePage(key, serialized);
 	}
 
@@ -210,15 +212,15 @@
 
 	private File getPageFile(SessionPageKey key, File sessionDir)
 	{
-		return new File(sessionDir, appName + "-pm-" + key.pageMap + "-p-" + key.id + "-v-"
-				+ key.versionNumber + "-a-" + key.ajaxVersionNumber);
+		return new File(sessionDir, appName + "-pm-" + key.pageMap + "-p-" + key.id + "-v-" +
+				key.versionNumber + "-a-" + key.ajaxVersionNumber);
 	}
 
 	private File getWorkDir()
 	{
 		return defaultWorkDir;
 	}
-	
+
 	private void savePage(SessionPageKey key, byte[] bytes)
 	{
 		File sessionDir = new File(getWorkDir(), key.sessionId);
@@ -237,8 +239,8 @@
 		}
 		catch (Exception e)
 		{
-			log.error("Error saving page " + key.pageClassName + " [" + key.id + ","
-					+ key.versionNumber + "] for the sessionid " + key.sessionId);
+			log.error("Error saving page " + key.pageClassName + " [" + key.id + "," +
+					key.versionNumber + "] for the sessionid " + key.sessionId);
 		}
 		finally
 		{
@@ -257,12 +259,17 @@
 		long t3 = System.currentTimeMillis();
 		if (log.isDebugEnabled())
 		{
-			log.debug("storing page " + key.pageClassName + "[" + key.id + "," + key.versionNumber
-					+ "] size: " + length + " for session " + key.sessionId + " took " + (t3 - t1)
-					+ " miliseconds to save");
+			log.debug("storing page " + key.pageClassName + "[" + key.id + "," + key.versionNumber +
+					"] size: " + length + " for session " + key.sessionId + " took " + (t3 - t1) +
+					" miliseconds to save");
 		}
 		totalSavingTime += (t3 - t1);
 		saved++;
 		bytesSaved += length;
+	}
+
+	public boolean containsPage(String sessionId, String pageMapName, int pageId, int pageVersion)
+	{
+		return false;
 	}
 }

Modified: wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/protocol/http/WebApplication.java
URL: http://svn.apache.org/viewvc/wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/protocol/http/WebApplication.java?view=diff&rev=565474&r1=565473&r2=565474
==============================================================================
--- wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/protocol/http/WebApplication.java (original)
+++ wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/protocol/http/WebApplication.java Mon Aug 13 11:51:41 2007
@@ -34,6 +34,7 @@
 import org.apache.wicket.markup.html.pages.InternalErrorPage;
 import org.apache.wicket.markup.html.pages.PageExpiredErrorPage;
 import org.apache.wicket.markup.resolver.AutoLinkResolver;
+import org.apache.wicket.protocol.http.pagestore.DiskPageStore;
 import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
 import org.apache.wicket.request.IRequestCycleProcessor;
 import org.apache.wicket.request.target.coding.BookmarkablePageRequestTargetUrlCodingStrategy;
@@ -400,7 +401,7 @@
 	public final void setWicketFilter(final WicketFilter wicketFilter)
 	{
 		this.wicketFilter = wicketFilter;
-		this.applicationKey = wicketFilter.getFilterConfig().getFilterName();
+		applicationKey = wicketFilter.getFilterConfig().getFilterName();
 	}
 
 	/**
@@ -559,7 +560,7 @@
 	 */
 	protected ISessionStore newSessionStore()
 	{
-		return new SecondLevelCacheSessionStore(this, new FilePageStore());
+		return new SecondLevelCacheSessionStore(this, new DiskPageStore());
 	}
 
 	/**

Modified: wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/protocol/http/pagestore/DiskPageStore.java
URL: http://svn.apache.org/viewvc/wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/protocol/http/pagestore/DiskPageStore.java?view=diff&rev=565474&r1=565473&r2=565474
==============================================================================
--- wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/protocol/http/pagestore/DiskPageStore.java (original)
+++ wicket/trunk/jdk-1.4/wicket/src/main/java/org/apache/wicket/protocol/http/pagestore/DiskPageStore.java Mon Aug 13 11:51:41 2007
@@ -1,1041 +1,1089 @@
-/*
- * 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.protocol.http.pagestore;
-
-
-import java.io.File;
-import java.io.IOException;
-import java.io.Serializable;
-import java.lang.ref.WeakReference;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.wicket.Application;
-import org.apache.wicket.Page;
-import org.apache.wicket.protocol.http.FilePageStore;
-import org.apache.wicket.protocol.http.WebApplication;
-import org.apache.wicket.protocol.http.SecondLevelCacheSessionStore.IPageStore;
-import org.apache.wicket.protocol.http.SecondLevelCacheSessionStore.ISerializationAwarePageStore;
-import org.apache.wicket.protocol.http.pagestore.PageWindowManager.PageWindow;
-import org.apache.wicket.util.concurrent.ConcurrentHashMap;
-import org.apache.wicket.util.lang.Bytes;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * {@link IPageStore} implementation that stores the serialized pages grouped in
- * a single file per pagemap.
- * <p>
- * This store was designed to overcome the problems of {@link FilePageStore}
- * which stores the pages in separate files per page.
- * <p>
- * {@link DiskPageStore} allows to set maximum size for pagemap file and maximum
- * size for session. If the maximum size for session is exceeded, the last
- * recently used pagemap file is removed.
- * 
- * @author Matej Knopp
- */
-public class DiskPageStore extends AbstractPageStore implements ISerializationAwarePageStore
-{
-	/**
-	 * Each PageMap is represented by this class.
-	 * 
-	 * @author Matej Knopp
-	 */
-	protected static class PageMapEntry
-	{
-		private String pageMapName;
-		private String fileName;
-		private PageWindowManager manager;
-
-		/**
-		 * @return the name of pagemap
-		 */
-		public String getPageMapName()
-		{
-			return pageMapName;
-		}
-
-		/**
-		 * @return path of file that contains serialized pages in this pagemap
-		 */
-		public String getFileName()
-		{
-			return fileName;
-		}
-
-		/**
-		 * @return manager that maintains information about pages inside the
-		 *         file
-		 */
-		public PageWindowManager getManager()
-		{
-			return manager;
-		}
-	}
-
-	/**
-	 * Represents a session,
-	 * 
-	 * @author Matej Knopp
-	 */
-	protected class SessionEntry
-	{
-		private String sessionId;
-		private List pageMapEntryList = new ArrayList();
-
-		/**
-		 * @return session id
-		 */
-		public String getSessionId()
-		{
-			return sessionId;
-		}
-
-		/**
-		 * @return summed size of all pagemap files
-		 */
-		public int getTotalSize()
-		{
-			int result = 0;
-			for (Iterator i = pageMapEntryList.iterator(); i.hasNext();)
-			{
-				PageMapEntry entry = (PageMapEntry)i.next();
-				if (entry.manager != null)
-				{
-					result += entry.manager.getTotalSize();
-				}
-			}
-			return result;
-		}
-
-		/**
-		 * @return list of {@link PageMapEntry} for this session
-		 */
-		public List /* <PageMapEntry> */getPageMapEntryList()
-		{
-			return Collections.unmodifiableList(pageMapEntryList);
-		}
-
-		/**
-		 * Returns a {@link PageMapEntry} for specified pagemap. If the create
-		 * attribute is set and the pagemap does not exist, new
-		 * {@link PageMapEntry} will be created.
-		 * 
-		 * @param pageMapName
-		 * @param create
-		 * @return
-		 */
-		public PageMapEntry getPageMapEntry(String pageMapName, boolean create)
-		{
-			PageMapEntry result = null;
-			for (Iterator i = pageMapEntryList.iterator(); i.hasNext();)
-			{
-				PageMapEntry entry = (PageMapEntry)i.next();
-				if (entry.pageMapName == pageMapName ||
-						(entry.pageMapName != null && entry.pageMapName.equals(pageMapName)))
-				{
-					result = entry;
-				}
-
-			}
-			if (result == null && create)
-			{
-				result = new PageMapEntry();
-				result.pageMapName = pageMapName;
-				result.fileName = getPageMapFileName(sessionId, pageMapName, true);
-				result.manager = new PageWindowManager(getMaxSizePerPageMap());
-				pageMapEntryList.add(result);
-			}
-			return result;
-		}
-
-		/**
-		 * Removes the pagemap entry and deletes the file.
-		 * 
-		 * @param entry
-		 */
-		private void removePageMapEntry(PageMapEntry entry)
-		{
-			fileChannelPool.closeAndDeleteFileChannel(entry.fileName);
-			pageMapEntryList.remove(entry);
-		}
-
-		/**
-		 * Removes the specified pagemap and deletes the file.
-		 * 
-		 * @param pageMapName
-		 */
-		public synchronized void removePageMap(String pageMapName)
-		{
-			PageMapEntry entry = getPageMapEntry(pageMapName, false);
-			if (entry != null)
-			{
-				removePageMapEntry(entry);
-			}
-		}
-
-		/**
-		 * Saves the serialized page to appropriate pagemap file.
-		 * 
-		 * @param page
-		 */
-		public synchronized void savePage(SerializedPage page)
-		{
-			// only save page that has some data
-			if (page.getData() != null)
-			{
-				PageMapEntry entry = getPageMapEntry(page.getPageMapName(), true);
-
-				// allocate window for page
-				PageWindow window = entry.manager.createPageWindow(page.getPageId(), page
-						.getVersionNumber(), page.getAjaxVersionNumber(), page.getData().length);
-
-				// remove the entry and add it to the end of entry list (to mark
-				// it as last accessed(
-				pageMapEntryList.remove(entry);
-				pageMapEntryList.add(entry);
-
-				// if we exceeded maximum session size, try to remove as many
-				// pagemap as necessary and possible
-				while (getTotalSize() > getMaxSizePerSession() && pageMapEntryList.size() > 1)
-				{
-					removePageMapEntry((PageMapEntry)pageMapEntryList.get(0));
-				}
-
-				// take the filechannel from the pool
-				FileChannel channel = fileChannelPool.getFileChannel(entry.fileName, true);
-				try
-				{
-					// write the content
-					channel.write(ByteBuffer.wrap(page.getData()), window.getFilePartOffset());
-				}
-				catch (IOException e)
-				{
-					log.error("Error writing to a channel " + channel, e);
-				}
-				finally
-				{
-					// return the "borrowed" file channel
-					fileChannelPool.returnFileChannel(channel);
-				}
-			}
-		}
-
-		/**
-		 * Removes the page from pagemap file.
-		 * 
-		 * @param pageMapName
-		 * @param pageId
-		 */
-		public synchronized void removePage(String pageMapName, int pageId)
-		{
-			PageMapEntry entry = getPageMapEntry(pageMapName, false);
-			if (entry != null)
-			{
-				entry.manager.removePage(pageId);
-			}
-		}
-
-		/**
-		 * Loads the part of pagemap file specified by the given PageWindow.
-		 * 
-		 * @param window
-		 * @param pageMapFileName
-		 * @return
-		 */
-		public byte[] loadPage(PageWindow window, String pageMapFileName)
-		{
-			byte[] result = null;
-			FileChannel channel = fileChannelPool.getFileChannel(pageMapFileName, false);
-			if (channel != null)
-			{
-				ByteBuffer buffer = ByteBuffer.allocate(window.getFilePartSize());
-				try
-				{
-					channel.read(buffer, window.getFilePartOffset());
-					if (buffer.hasArray())
-					{
-						result = buffer.array();
-					}
-				}
-				catch (IOException e)
-				{
-					log.error("Error reading from file channel " + channel, e);
-				}
-				finally
-				{
-					fileChannelPool.returnFileChannel(channel);
-				}
-			}
-			return result;
-		}
-
-		/**
-		 * Loads the specified page data.
-		 * 
-		 * @param pageMapName
-		 * @param id
-		 * @param versionNumber
-		 * @param ajaxVersionNumber
-		 * @return page data or null if the page is no longer in pagemap file
-		 */
-		public synchronized byte[] loadPage(String pageMapName, int id, int versionNumber,
-				int ajaxVersionNumber)
-		{
-			byte[] result = null;
-			PageMapEntry entry = getPageMapEntry(pageMapName, false);
-			if (entry != null)
-			{
-				PageWindow window = entry.manager.getPageWindow(id, versionNumber,
-						ajaxVersionNumber);
-				if (window != null)
-				{
-					result = loadPage(window, entry.fileName);
-				}
-			}
-			return result;
-		}
-
-		/**
-		 * Deletes all files for this session.
-		 */
-		public synchronized void unbind()
-		{
-			while (pageMapEntryList.size() > 0)
-			{
-				removePageMapEntry((PageMapEntry)pageMapEntryList.get(pageMapEntryList.size() - 1));
-			}
-			File sessionFolder = getSessionFolder(sessionId, false);
-			if (sessionFolder.exists())
-			{
-				sessionFolder.delete();
-			}
-		}
-	}
-
-	/**
-	 * Returns the folder for the specified sessions. If the folder doesn't
-	 * exist and the create flag is set, the folder will be created.
-	 * 
-	 * @param sessionId
-	 * @param create
-	 * @return
-	 */
-	private File getSessionFolder(String sessionId, boolean create)
-	{
-		File storeFolder = new File(fileStoreFolder, appName + "-filestore");
-		File sessionFolder = new File(storeFolder, sessionId);
-		if (create && sessionFolder.exists() == false)
-		{
-			mkdirs(sessionFolder);
-		}
-		return sessionFolder;
-	}
-
-
-	/**
-	 * Utility method for creating a directory
-	 * 
-	 * @param file
-	 */
-	private void mkdirs(File file)
-	{
-		// for some reason, simple file.mkdirs sometimes fails under heavy load
-		for (int j = 0; j < 5; ++j)
-		{
-			for (int i = 0; i < 10; ++i)
-			{
-				if (file.mkdirs())
-				{
-					return;
-				}
-			}
-			try
-			{
-				Thread.sleep(100);
-			}
-			catch (InterruptedException ignore)
-			{
-			}
-		}
-		log.error("Failed to make directory " + file);
-	}
-
-	/**
-	 * Returns the file name for specified pagemap. If the session folder
-	 * (folder that contains the file) does not exist and createSessionFolder is
-	 * true, the folder will becreated.
-	 * 
-	 * @param sessionId
-	 * @param pageMapName
-	 * @param createSessionFolder
-	 * @return file name for pagemap
-	 */
-	private String getPageMapFileName(String sessionId, String pageMapName,
-			boolean createSessionFolder)
-	{
-		File sessionFolder = getSessionFolder(sessionId, createSessionFolder);
-		return new File(sessionFolder, "pm-" + pageMapName).getAbsolutePath();
-	}
-
-	private final int maxSizePerPageMap;
-
-	/**
-	 * Return maximum pagemap file size (in bytes).
-	 * 
-	 * @return
-	 */
-	protected int getMaxSizePerPageMap()
-	{
-		return maxSizePerPageMap;
-	}
-
-	private final int maxSizePerSession;
-
-	/**
-	 * Returns maximum size per session (in bytes). After the session exceeds
-	 * this size, appropriate number of last recently used pagemap files will be
-	 * removed.
-	 * 
-	 * @return
-	 */
-	protected int getMaxSizePerSession()
-	{
-		return maxSizePerSession;
-	}
-
-	private final FileChannelPool fileChannelPool;
-
-	private final File fileStoreFolder;
-
-	/**
-	 * Returns the "root" file store folder.
-	 * 
-	 * @return
-	 */
-	protected File getFileStoreFolder()
-	{
-		return fileStoreFolder;
-	}
-
-	private final String appName;
-
-	/**
-	 * Creates a new {@link DiskPageStore} instance.
-	 * 
-	 * @param fileStoreFolder
-	 *            folder in which the session folders containing pagemap files
-	 *            will be stored
-	 * @param maxSizePerPagemap
-	 *            the maximum size of pagemap file (in bytes)
-	 * @param maxSizePerSession
-	 *            the maximum size of session (in bytes)
-	 * @param fileChannelPoolCapacity
-	 *            the maximum number of concurrently opened files (higher number
-	 *            improves performance under heavy load).
-	 */
-	public DiskPageStore(File fileStoreFolder, int maxSizePerPagemap, int maxSizePerSession,
-			int fileChannelPoolCapacity)
-	{
-		this.maxSizePerPageMap = maxSizePerPagemap;
-		this.maxSizePerSession = maxSizePerSession;
-		this.fileChannelPool = new FileChannelPool(fileChannelPoolCapacity);
-		this.fileStoreFolder = fileStoreFolder;
-
-		if (maxSizePerSession < maxSizePerPageMap)
-		{
-			throw new IllegalArgumentException(
-					"Provided maximum session size must be bigger than maximum pagemap size");
-		}
-
-		this.fileStoreFolder.mkdirs();
-		appName = Application.get().getApplicationKey();
-
-		initPageSavingThread();
-	}
-
-	private static File getDefaultFileStoreFolder()
-	{
-		return (File)((WebApplication)Application.get()).getServletContext().getAttribute(
-				"javax.servlet.context.tempdir");
-	}
-
-	/**
-	 * Creates a new {@link DiskPageStore} instance.
-	 * 
-	 * @param maxSizePerPagemap
-	 *            the maximum size of pagemap file (in bytes)
-	 * @param maxSizePerSession
-	 *            the maximum size of session (in bytes)
-	 * @param fileChannelPoolCapacity
-	 *            the maximum number of concurrently opened files (higher number
-	 *            improves performance under heavy load).
-	 * 
-	 */
-	public DiskPageStore(int maxSizePerPagemap, int maxSizePerSession, int fileChannelPoolCapacity)
-	{
-		this(getDefaultFileStoreFolder(), maxSizePerPagemap, maxSizePerSession,
-				fileChannelPoolCapacity);
-	}
-
-	/**
-	 * Creates a new {@link DiskPageStore} instance.
-	 */
-	public DiskPageStore()
-	{
-		this((int)Bytes.megabytes(10).bytes(), (int)Bytes.megabytes(100).bytes(), 50);
-	}
-
-	/**
-	 * @see org.apache.wicket.protocol.http.SecondLevelCacheSessionStore.IPageStore#destroy()
-	 */
-	public void destroy()
-	{
-		fileChannelPool.destroy();
-		if (pageSavingThread != null)
-		{
-			pageSavingThread.stop();
-		}
-	}
-
-	private Map /* <String, SessionEntry> */sessionIdToEntryMap = new ConcurrentHashMap();
-
-	/**
-	 * Returns the SessionEntry for session with given id. If the entry does not
-	 * yet exist and the createIfDoesNotExist attribute is set, new SessionEntry
-	 * will be created.
-	 * 
-	 * @param sessionId
-	 * @param createIfDoesNotExist
-	 * @return
-	 */
-	protected SessionEntry getSessionEntry(String sessionId, boolean createIfDoesNotExist)
-	{
-		SessionEntry entry = (SessionEntry)sessionIdToEntryMap.get(sessionId);
-		if (entry == null && createIfDoesNotExist)
-		{
-			synchronized (sessionIdToEntryMap)
-			{
-				entry = (SessionEntry)sessionIdToEntryMap.get(sessionId);
-				if (entry == null)
-				{
-					entry = new SessionEntry();
-					entry.sessionId = sessionId;
-					sessionIdToEntryMap.put(sessionId, entry);
-				}
-			}
-		}
-		return entry;
-	}
-
-	/**
-	 * @see org.apache.wicket.protocol.http.SecondLevelCacheSessionStore.IPageStore#getPage(java.lang.String,
-	 *      java.lang.String, int, int, int)
-	 */
-	public Page getPage(String sessionId, String pagemap, int id, int versionNumber,
-			int ajaxVersionNumber)
-	{
-		SessionEntry entry = getSessionEntry(sessionId, false);
-		if (entry != null)
-		{
-			byte[] data;
-
-			if (isSynchronous())
-			{
-				data = entry.loadPage(pagemap, id, versionNumber, ajaxVersionNumber);
-			}
-			else
-			{
-				// we need to make sure that the there are no pending pages to
-				// be saved before loading a page
-				List pages = getPagesToSaveList(sessionId);
-				synchronized (pages)
-				{
-					flushPagesToSaveList(sessionId, pages);
-					data = entry.loadPage(pagemap, id, versionNumber, ajaxVersionNumber);
-				}
-			}
-
-			if (data != null)
-			{
-				return deserializePage(data, versionNumber);
-			}
-		}
-
-		return null;
-	}
-
-	/**
-	 * @see org.apache.wicket.protocol.http.SecondLevelCacheSessionStore.IPageStore#pageAccessed(java.lang.String,
-	 *      org.apache.wicket.Page)
-	 */
-	public void pageAccessed(String sessionId, Page page)
-	{
-	}
-
-	/**
-	 * Removes the page (or entire pagemap) from specified session.
-	 * 
-	 * @param entry
-	 * @param pageMap
-	 * @param id
-	 *            page id to remove or -1 if the whole pagemap should be removed
-	 */
-	private void removePage(SessionEntry entry, String pageMap, int id)
-	{
-		if (id != -1)
-		{
-			entry.removePage(pageMap, id);
-		}
-		else
-		{
-			entry.removePageMap(pageMap);
-		}
-	}
-
-	/**
-	 * @see org.apache.wicket.protocol.http.SecondLevelCacheSessionStore.IPageStore#removePage(java.lang.String,
-	 *      java.lang.String, int)
-	 */
-	public void removePage(String sessionId, String pageMap, int id)
-	{
-		SessionEntry entry = getSessionEntry(sessionId, false);
-		if (entry != null)
-		{
-			if (isSynchronous())
-			{
-				removePage(entry, pageMap, id);
-			}
-			else
-			{
-				// we need to make sure that the there are no pending pages to
-				// be saved before removing the page (or pagemap)
-				List pages = getPagesToSaveList(sessionId);
-				synchronized (pages)
-				{
-					flushPagesToSaveList(sessionId, pages);
-					removePage(entry, pageMap, id);
-				}
-			}
-		}
-	}
-
-	/**
-	 * Stores the serialized pages. The storing is done either immediately (in
-	 * synchronous mode) or it's scheduled to be stored by the worker thread.
-	 * 
-	 * @param sessionId
-	 * @param pages
-	 */
-	protected void storeSerializedPages(String sessionId, List /* <SerializedPage> */pages)
-	{
-		SessionEntry entry = getSessionEntry(sessionId, true);
-
-		if (isSynchronous())
-		{
-			for (Iterator i = pages.iterator(); i.hasNext();)
-			{
-				SerializedPage serializedPage = (SerializedPage)i.next();
-				entry.savePage(serializedPage);
-			}
-		}
-		else
-		{
-			schedulePagesSave(sessionId, pages);
-		}
-	}
-
-	/**
-	 * Hook for processing serialized pages (e.g. sending those across cluster)
-	 * 
-	 * @param sessionId
-	 * @param pages
-	 */
-	protected void onPagesSerialized(String sessionId, List /* <SerializedPage */pages)
-	{
-
-	}
-
-	/**
-	 * @see org.apache.wicket.protocol.http.SecondLevelCacheSessionStore.IPageStore#storePage(java.lang.String,
-	 *      org.apache.wicket.Page)
-	 */
-	public void storePage(String sessionId, Page page)
-	{
-		List pages = serializePage(page);
-
-		cacheSerializedPage(sessionId, page, pages);
-
-		onPagesSerialized(sessionId, pages);
-
-		storeSerializedPages(sessionId, pages);
-	}
-
-	/**
-	 * @see org.apache.wicket.protocol.http.SecondLevelCacheSessionStore.IPageStore#unbind(java.lang.String)
-	 */
-	public void unbind(String sessionId)
-	{
-		SessionEntry entry = (SessionEntry)sessionIdToEntryMap.get(sessionId);
-		if (entry != null)
-		{
-			if (isSynchronous())
-			{
-				entry.unbind();
-			}
-			else
-			{
-				List pages = getPagesToSaveList(sessionId);
-				synchronized (pages)
-				{
-					flushPagesToSaveList(sessionId, pages);
-					entry.unbind();
-				}
-				pagesToSaveAll.remove(sessionId);
-			}
-		}
-	}
-
-	// map from session id to serializedpage list
-	// this contains lists for all active sessions
-	private Map /* <String, List<SerializedPage>> */pagesToSaveAll = new ConcurrentHashMap();
-
-	// contains list of serialized pages to be saved - only non empty lists
-	private Map /* <String, List<SerializedPage>> */pagesToSaveActive = new ConcurrentHashMap();
-
-	/**
-	 * Returns the list of pages to be saved for the specified session id. If
-	 * the list is not found, new list is created.
-	 * 
-	 * @param sessionId
-	 * @return
-	 */
-	protected List getPagesToSaveList(String sessionId)
-	{
-		List list = (List)pagesToSaveAll.get(sessionId);
-		if (list == null)
-		{
-			synchronized (pagesToSaveAll)
-			{
-				list = (List)pagesToSaveAll.get(sessionId);
-				if (list == null)
-				{
-					list = new ArrayList();
-					pagesToSaveAll.put(sessionId, list);
-				}
-			}
-		}
-		return list;
-	}
-
-	/**
-	 * Saves all entries from the specified list.
-	 * 
-	 * @param sessionId
-	 * @param list
-	 */
-	protected void flushPagesToSaveList(String sessionId, List /* <SerializedPage> */list)
-	{
-		if (list != null)
-		{
-			for (Iterator i = list.iterator(); i.hasNext();)
-			{
-				SerializedPage page = (SerializedPage)i.next();
-				getSessionEntry(sessionId, true).savePage(page);
-			}
-			list.clear();
-		}
-	}
-
-	/**
-	 * Schedules the pages to be saved by the worker thread.
-	 * 
-	 * @param sessionId
-	 * @param pages
-	 */
-	private void schedulePagesSave(String sessionId, List/* <SerializedPage> */pages)
-	{
-		List list = getPagesToSaveList(sessionId);
-		synchronized (list)
-		{
-			list.addAll(pages);
-
-			if (list.size() > 0 && pagesToSaveActive.containsKey(sessionId) == false)
-			{
-				pagesToSaveActive.put(sessionId, list);
-			}
-		}
-	}
-
-	/**
-	 * Worker thread that saves the serialized pages. Saving pages in the
-	 * separate thread results in smoother performance under load.
-	 * 
-	 * @author Matej Knopp
-	 */
-	private class PageSavingThread implements Runnable
-	{
-		private volatile boolean stop = false;
-
-		public void run()
-		{
-			while (stop == false)
-			{
-				// wait until we have something to save
-				while (pagesToSaveActive.isEmpty())
-				{
-					try
-					{
-						Thread.sleep(getSavingThreadSleepTime());
-					}
-					catch (InterruptedException ignore)
-					{
-					}
-				}
-
-				// iterate through lists of pages to be saved
-				for (Iterator i = pagesToSaveActive.entrySet().iterator(); i.hasNext();)
-				{
-					Map.Entry entry = (Map.Entry)i.next();
-					String sessionId = (String)entry.getKey();
-					List pages = (List)entry.getValue();
-
-					synchronized (pages)
-					{
-						try
-						{
-							flushPagesToSaveList(sessionId, pages);
-						}
-						catch (Exception e)
-						{
-							log
-									.error(
-											"Error flushing serialized pages from worker thread for session " +
-													sessionId, e);
-						}
-						i.remove();
-					}
-				}
-			}
-		}
-
-		/**
-		 * Stops the worker thread.
-		 */
-		public void stop()
-		{
-			this.stop = true;
-		}
-	};
-
-	/**
-	 * Initializes the worker thread.
-	 */
-	private void initPageSavingThread()
-	{
-		if (isSynchronous() == false)
-		{
-			pageSavingThread = new PageSavingThread();
-			Thread t = new Thread(pageSavingThread, "PageSavingThread-" + appName);
-			t.setDaemon(true);
-			t.setPriority(Thread.MAX_PRIORITY);
-			t.start();
-		}
-	}
-
-	private PageSavingThread pageSavingThread = null;
-
-	/**
-	 * Returns the amount time in milliseconds for the saving thread to sleep
-	 * between checking whether there are pending serialized pages to be saved.
-	 * 
-	 * @return
-	 */
-	protected int getSavingThreadSleepTime()
-	{
-		return 100;
-	}
-
-	/**
-	 * Returns whether the {@link DiskPageStore} should work in synchronous or
-	 * asynchronous mode. Asynchronous mode uses a worker thread to save pages,
-	 * which results in smoother performance.
-	 * 
-	 * @return
-	 */
-	protected boolean isSynchronous()
-	{
-		return false;
-	}
-
-	private int lastRecentlySerializedPagesCacheSize = 50;
-
-	/**
-	 * Sets the number of last recently serialized pages kept in cache. The
-	 * cache is used to aid performance on session replication.
-	 * 
-	 * @param lastRecentlySerializedPagesCacheSize
-	 */
-	public void setLastRecentlySerializedPagesCacheSize(int lastRecentlySerializedPagesCacheSize)
-	{
-		this.lastRecentlySerializedPagesCacheSize = lastRecentlySerializedPagesCacheSize;
-	}
-
-	/**
-	 * @return
-	 */
-	public int getLastRecentlySerializedPagesCacheSize()
-	{
-		return lastRecentlySerializedPagesCacheSize;
-	}
-
-	private List /* SerializedPageWithSession */lastRecentlySerializedPagesCache = new ArrayList(
-			lastRecentlySerializedPagesCacheSize);
-
-	private SerializedPageWithSession removePageFromLastRecentlySerializedPagesCache(Page page)
-	{
-		for (Iterator i = lastRecentlySerializedPagesCache.iterator(); i.hasNext();)
-		{
-			SerializedPageWithSession entry = (SerializedPageWithSession)i.next();
-			if (entry != null && entry.page.get() == page)
-			{
-				i.remove();
-				return entry;
-			}
-		}
-		return null;
-	}
-
-	/**
-	 * Store the serialized page in the request metadata,.
-	 * 
-	 * @param sessionId
-	 * @param page
-	 * @param pagesList
-	 */
-	private void cacheSerializedPage(String sessionId, Page page,
-			List /* <SerializedPage> */pagesList)
-	{
-		if (getLastRecentlySerializedPagesCacheSize() > 0)
-		{
-			SerializedPageWithSession entry = new SerializedPageWithSession(sessionId, page,
-					pagesList);
-
-			synchronized (lastRecentlySerializedPagesCache)
-			{
-				removePageFromLastRecentlySerializedPagesCache(page);
-				lastRecentlySerializedPagesCache.add(entry);
-				if (lastRecentlySerializedPagesCache.size() > getLastRecentlySerializedPagesCacheSize())
-				{
-					lastRecentlySerializedPagesCache.remove(0);
-				}
-			}
-		}
-	}
-
-	/**
-	 * 
-	 * @author Matej Knopp
-	 */
-	private static class SerializedPageWithSession implements Serializable
-	{
-		private static final long serialVersionUID = 1L;
-
-		// this is used for lookup on pagemap serialization. We don't have the
-		// session id at that point, because it can happen outside the request
-		// thread. We only have the page instance and we need to use it as a key
-		private final transient WeakReference /* <Page> */page;
-
-		// list of serialized pages
-		private final List pages;
-
-		private final String sessionId;
-
-		// after deserialization, we need to be able to know which page to load
-		private final int pageId;
-		private final String pageMapName;
-		private final int versionNumber;
-		private final int ajaxVersionNumber;
-
-		private SerializedPageWithSession(String sessionId, Page page,
-				List /* <SerializablePage> */pages)
-		{
-			this.sessionId = sessionId;
-			this.pageId = page.getNumericId();
-			this.pageMapName = page.getPageMapName();
-			this.versionNumber = page.getCurrentVersionNumber();
-			this.ajaxVersionNumber = page.getAjaxVersionNumber();
-			this.pages = pages;
-			this.page = new WeakReference(page);
-		}
-	};
-
-	/**
-	 * @see org.apache.wicket.protocol.http.SecondLevelCacheSessionStore.ISerializationAwarePageStore#prepareForSerialization(org.apache.wicket.Page)
-	 */
-	public Serializable prepareForSerialization(Page page)
-	{
-		Serializable result = page;
-
-		if (getLastRecentlySerializedPagesCacheSize() > 0)
-		{
-			SerializedPageWithSession entry;
-			synchronized (lastRecentlySerializedPagesCache)
-			{				
-				entry = removePageFromLastRecentlySerializedPagesCache(page);
-			}
-
-			if (entry != null)
-			{
-				result = entry;
-			} 
-		}
-
-		return result;
-	}
-
-	/**
-	 * @see org.apache.wicket.protocol.http.SecondLevelCacheSessionStore.ISerializationAwarePageStore#restoreAfterSerialization(java.io.Serializable)
-	 */
-	public Page restoreAfterSerialization(Serializable serializable)
-	{
-		if (serializable instanceof Page)
-		{
-			return (Page)serializable;
-		}
-		else if (serializable instanceof SerializedPageWithSession)
-		{
-			SerializedPageWithSession page = (SerializedPageWithSession)serializable;
-			storeSerializedPages(page.sessionId, page.pages);
-			return getPage(page.sessionId, page.pageMapName, page.pageId, page.versionNumber,
-					page.ajaxVersionNumber);
-		}
-		else
-		{
-			throw new IllegalArgumentException("Unknown object type");
-		}
-	}
-
-	private static final Logger log = LoggerFactory.getLogger(DiskPageStore.class);
-
-}
+/*
+ * 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.protocol.http.pagestore;
+
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Serializable;
+import java.lang.ref.WeakReference;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.Page;
+import org.apache.wicket.protocol.http.FilePageStore;
+import org.apache.wicket.protocol.http.WebApplication;
+import org.apache.wicket.protocol.http.SecondLevelCacheSessionStore.IPageStore;
+import org.apache.wicket.protocol.http.SecondLevelCacheSessionStore.ISerializationAwarePageStore;
+import org.apache.wicket.protocol.http.pagestore.PageWindowManager.PageWindow;
+import org.apache.wicket.util.concurrent.ConcurrentHashMap;
+import org.apache.wicket.util.lang.Bytes;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link IPageStore} implementation that stores the serialized pages grouped in
+ * a single file per pagemap.
+ * <p>
+ * This store was designed to overcome the problems of {@link FilePageStore}
+ * which stores the pages in separate files per page.
+ * <p>
+ * {@link DiskPageStore} allows to set maximum size for pagemap file and maximum
+ * size for session. If the maximum size for session is exceeded, the last
+ * recently used pagemap file is removed.
+ * 
+ * @author Matej Knopp
+ */
+public class DiskPageStore extends AbstractPageStore implements ISerializationAwarePageStore
+{
+	/**
+	 * Each PageMap is represented by this class.
+	 * 
+	 * @author Matej Knopp
+	 */
+	protected static class PageMapEntry
+	{
+		private String pageMapName;
+		private String fileName;
+		private PageWindowManager manager;
+
+		/**
+		 * @return the name of pagemap
+		 */
+		public String getPageMapName()
+		{
+			return pageMapName;
+		}
+
+		/**
+		 * @return path of file that contains serialized pages in this pagemap
+		 */
+		public String getFileName()
+		{
+			return fileName;
+		}
+
+		/**
+		 * @return manager that maintains information about pages inside the
+		 *         file
+		 */
+		public PageWindowManager getManager()
+		{
+			return manager;
+		}
+	}
+
+	/**
+	 * Represents a session,
+	 * 
+	 * @author Matej Knopp
+	 */
+	protected class SessionEntry
+	{
+		private String sessionId;
+		private final List pageMapEntryList = new ArrayList();
+
+		/**
+		 * @return session id
+		 */
+		public String getSessionId()
+		{
+			return sessionId;
+		}
+
+		/**
+		 * @return summed size of all pagemap files
+		 */
+		public int getTotalSize()
+		{
+			int result = 0;
+			for (Iterator i = pageMapEntryList.iterator(); i.hasNext();)
+			{
+				PageMapEntry entry = (PageMapEntry)i.next();
+				if (entry.manager != null)
+				{
+					result += entry.manager.getTotalSize();
+				}
+			}
+			return result;
+		}
+
+		/**
+		 * @return list of {@link PageMapEntry} for this session
+		 */
+		public List /* <PageMapEntry> */getPageMapEntryList()
+		{
+			return Collections.unmodifiableList(pageMapEntryList);
+		}
+
+		/**
+		 * Returns a {@link PageMapEntry} for specified pagemap. If the create
+		 * attribute is set and the pagemap does not exist, new
+		 * {@link PageMapEntry} will be created.
+		 * 
+		 * @param pageMapName
+		 * @param create
+		 * @return
+		 */
+		public PageMapEntry getPageMapEntry(String pageMapName, boolean create)
+		{
+			PageMapEntry result = null;
+			for (Iterator i = pageMapEntryList.iterator(); i.hasNext();)
+			{
+				PageMapEntry entry = (PageMapEntry)i.next();
+				if (entry.pageMapName == pageMapName ||
+						(entry.pageMapName != null && entry.pageMapName.equals(pageMapName)))
+				{
+					result = entry;
+				}
+
+			}
+			if (result == null && create)
+			{
+				result = new PageMapEntry();
+				result.pageMapName = pageMapName;
+				result.fileName = getPageMapFileName(sessionId, pageMapName, true);
+				result.manager = new PageWindowManager(getMaxSizePerPageMap());
+				pageMapEntryList.add(result);
+			}
+			return result;
+		}
+
+		/**
+		 * Removes the pagemap entry and deletes the file.
+		 * 
+		 * @param entry
+		 */
+		private void removePageMapEntry(PageMapEntry entry)
+		{
+			fileChannelPool.closeAndDeleteFileChannel(entry.fileName);
+			pageMapEntryList.remove(entry);
+		}
+
+		/**
+		 * Removes the specified pagemap and deletes the file.
+		 * 
+		 * @param pageMapName
+		 */
+		public synchronized void removePageMap(String pageMapName)
+		{
+			PageMapEntry entry = getPageMapEntry(pageMapName, false);
+			if (entry != null)
+			{
+				removePageMapEntry(entry);
+			}
+		}
+
+		/**
+		 * Saves the serialized page to appropriate pagemap file.
+		 * 
+		 * @param page
+		 */
+		public synchronized void savePage(SerializedPage page)
+		{
+			// only save page that has some data
+			if (page.getData() != null)
+			{
+				PageMapEntry entry = getPageMapEntry(page.getPageMapName(), true);
+
+				// allocate window for page
+				PageWindow window = entry.manager.createPageWindow(page.getPageId(), page
+						.getVersionNumber(), page.getAjaxVersionNumber(), page.getData().length);
+
+				// remove the entry and add it to the end of entry list (to mark
+				// it as last accessed(
+				pageMapEntryList.remove(entry);
+				pageMapEntryList.add(entry);
+
+				// if we exceeded maximum session size, try to remove as many
+				// pagemap as necessary and possible
+				while (getTotalSize() > getMaxSizePerSession() && pageMapEntryList.size() > 1)
+				{
+					removePageMapEntry((PageMapEntry)pageMapEntryList.get(0));
+				}
+
+				// take the filechannel from the pool
+				FileChannel channel = fileChannelPool.getFileChannel(entry.fileName, true);
+				try
+				{
+					// write the content
+					channel.write(ByteBuffer.wrap(page.getData()), window.getFilePartOffset());
+				}
+				catch (IOException e)
+				{
+					log.error("Error writing to a channel " + channel, e);
+				}
+				finally
+				{
+					// return the "borrowed" file channel
+					fileChannelPool.returnFileChannel(channel);
+				}
+			}
+		}
+
+		/**
+		 * Removes the page from pagemap file.
+		 * 
+		 * @param pageMapName
+		 * @param pageId
+		 */
+		public synchronized void removePage(String pageMapName, int pageId)
+		{
+			PageMapEntry entry = getPageMapEntry(pageMapName, false);
+			if (entry != null)
+			{
+				entry.manager.removePage(pageId);
+			}
+		}
+
+		/**
+		 * Loads the part of pagemap file specified by the given PageWindow.
+		 * 
+		 * @param window
+		 * @param pageMapFileName
+		 * @return
+		 */
+		public byte[] loadPage(PageWindow window, String pageMapFileName)
+		{
+			byte[] result = null;
+			FileChannel channel = fileChannelPool.getFileChannel(pageMapFileName, false);
+			if (channel != null)
+			{
+				ByteBuffer buffer = ByteBuffer.allocate(window.getFilePartSize());
+				try
+				{
+					channel.read(buffer, window.getFilePartOffset());
+					if (buffer.hasArray())
+					{
+						result = buffer.array();
+					}
+				}
+				catch (IOException e)
+				{
+					log.error("Error reading from file channel " + channel, e);
+				}
+				finally
+				{
+					fileChannelPool.returnFileChannel(channel);
+				}
+			}
+			return result;
+		}
+
+		/**
+		 * Loads the specified page data.
+		 * 
+		 * @param pageMapName
+		 * @param id
+		 * @param versionNumber
+		 * @param ajaxVersionNumber
+		 * @return page data or null if the page is no longer in pagemap file
+		 */
+		public synchronized byte[] loadPage(String pageMapName, int id, int versionNumber,
+				int ajaxVersionNumber)
+		{
+			byte[] result = null;
+			PageMapEntry entry = getPageMapEntry(pageMapName, false);
+			if (entry != null)
+			{
+				PageWindow window = entry.manager.getPageWindow(id, versionNumber,
+						ajaxVersionNumber);
+				if (window != null)
+				{
+					result = loadPage(window, entry.fileName);
+				}
+			}
+			return result;
+		}
+
+		/**
+		 * Deletes all files for this session.
+		 */
+		public synchronized void unbind()
+		{
+			while (pageMapEntryList.size() > 0)
+			{
+				removePageMapEntry((PageMapEntry)pageMapEntryList.get(pageMapEntryList.size() - 1));
+			}
+			File sessionFolder = getSessionFolder(sessionId, false);
+			if (sessionFolder.exists())
+			{
+				sessionFolder.delete();
+			}
+		}
+
+		/**
+		 * Returns true if the given page exists for specified pageMap
+		 * 
+		 * @param pageMapName
+		 * @param pageId
+		 * @param versionNumber
+		 * @return
+		 */
+		public synchronized boolean exists(String pageMapName, int pageId, int versionNumber)
+		{
+			PageMapEntry entry = getPageMapEntry(pageMapName, false);
+			return entry.getManager().getPageWindow(pageId, versionNumber, -1) != null;
+		}
+	}
+
+	/**
+	 * Returns the folder for the specified sessions. If the folder doesn't
+	 * exist and the create flag is set, the folder will be created.
+	 * 
+	 * @param sessionId
+	 * @param create
+	 * @return
+	 */
+	private File getSessionFolder(String sessionId, boolean create)
+	{
+		File storeFolder = new File(fileStoreFolder, appName + "-filestore");
+		File sessionFolder = new File(storeFolder, sessionId);
+		if (create && sessionFolder.exists() == false)
+		{
+			mkdirs(sessionFolder);
+		}
+		return sessionFolder;
+	}
+
+
+	/**
+	 * Utility method for creating a directory
+	 * 
+	 * @param file
+	 */
+	private void mkdirs(File file)
+	{
+		// for some reason, simple file.mkdirs sometimes fails under heavy load
+		for (int j = 0; j < 5; ++j)
+		{
+			for (int i = 0; i < 10; ++i)
+			{
+				if (file.mkdirs())
+				{
+					return;
+				}
+			}
+			try
+			{
+				Thread.sleep(100);
+			}
+			catch (InterruptedException ignore)
+			{
+			}
+		}
+		log.error("Failed to make directory " + file);
+	}
+
+	/**
+	 * Returns the file name for specified pagemap. If the session folder
+	 * (folder that contains the file) does not exist and createSessionFolder is
+	 * true, the folder will becreated.
+	 * 
+	 * @param sessionId
+	 * @param pageMapName
+	 * @param createSessionFolder
+	 * @return file name for pagemap
+	 */
+	private String getPageMapFileName(String sessionId, String pageMapName,
+			boolean createSessionFolder)
+	{
+		File sessionFolder = getSessionFolder(sessionId, createSessionFolder);
+		return new File(sessionFolder, "pm-" + pageMapName).getAbsolutePath();
+	}
+
+	private final int maxSizePerPageMap;
+
+	/**
+	 * Return maximum pagemap file size (in bytes).
+	 * 
+	 * @return
+	 */
+	protected int getMaxSizePerPageMap()
+	{
+		return maxSizePerPageMap;
+	}
+
+	private final int maxSizePerSession;
+
+	/**
+	 * Returns maximum size per session (in bytes). After the session exceeds
+	 * this size, appropriate number of last recently used pagemap files will be
+	 * removed.
+	 * 
+	 * @return
+	 */
+	protected int getMaxSizePerSession()
+	{
+		return maxSizePerSession;
+	}
+
+	private final FileChannelPool fileChannelPool;
+
+	private final File fileStoreFolder;
+
+	/**
+	 * Returns the "root" file store folder.
+	 * 
+	 * @return
+	 */
+	protected File getFileStoreFolder()
+	{
+		return fileStoreFolder;
+	}
+
+	private final String appName;
+
+	/**
+	 * Creates a new {@link DiskPageStore} instance.
+	 * 
+	 * @param fileStoreFolder
+	 *            folder in which the session folders containing pagemap files
+	 *            will be stored
+	 * @param maxSizePerPagemap
+	 *            the maximum size of pagemap file (in bytes)
+	 * @param maxSizePerSession
+	 *            the maximum size of session (in bytes)
+	 * @param fileChannelPoolCapacity
+	 *            the maximum number of concurrently opened files (higher number
+	 *            improves performance under heavy load).
+	 */
+	public DiskPageStore(File fileStoreFolder, int maxSizePerPagemap, int maxSizePerSession,
+			int fileChannelPoolCapacity)
+	{
+		maxSizePerPageMap = maxSizePerPagemap;
+		this.maxSizePerSession = maxSizePerSession;
+		fileChannelPool = new FileChannelPool(fileChannelPoolCapacity);
+		this.fileStoreFolder = fileStoreFolder;
+
+		if (maxSizePerSession < maxSizePerPageMap)
+		{
+			throw new IllegalArgumentException(
+					"Provided maximum session size must be bigger than maximum pagemap size");
+		}
+
+		this.fileStoreFolder.mkdirs();
+		appName = Application.get().getApplicationKey();
+
+		initPageSavingThread();
+	}
+
+	private static File getDefaultFileStoreFolder()
+	{
+		return (File)((WebApplication)Application.get()).getServletContext().getAttribute(
+				"javax.servlet.context.tempdir");
+	}
+
+	/**
+	 * Creates a new {@link DiskPageStore} instance.
+	 * 
+	 * @param maxSizePerPagemap
+	 *            the maximum size of pagemap file (in bytes)
+	 * @param maxSizePerSession
+	 *            the maximum size of session (in bytes)
+	 * @param fileChannelPoolCapacity
+	 *            the maximum number of concurrently opened files (higher number
+	 *            improves performance under heavy load).
+	 * 
+	 */
+	public DiskPageStore(int maxSizePerPagemap, int maxSizePerSession, int fileChannelPoolCapacity)
+	{
+		this(getDefaultFileStoreFolder(), maxSizePerPagemap, maxSizePerSession,
+				fileChannelPoolCapacity);
+	}
+
+	/**
+	 * Creates a new {@link DiskPageStore} instance.
+	 */
+	public DiskPageStore()
+	{
+		this((int)Bytes.megabytes(10).bytes(), (int)Bytes.megabytes(100).bytes(), 25);
+	}
+
+	/**
+	 * @see org.apache.wicket.protocol.http.SecondLevelCacheSessionStore.IPageStore#destroy()
+	 */
+	public void destroy()
+	{
+		fileChannelPool.destroy();
+		if (pageSavingThread != null)
+		{
+			pageSavingThread.stop();
+		}
+	}
+
+	private final Map /* <String, SessionEntry> */sessionIdToEntryMap = new ConcurrentHashMap();
+
+	/**
+	 * Returns the SessionEntry for session with given id. If the entry does not
+	 * yet exist and the createIfDoesNotExist attribute is set, new SessionEntry
+	 * will be created.
+	 * 
+	 * @param sessionId
+	 * @param createIfDoesNotExist
+	 * @return
+	 */
+	protected SessionEntry getSessionEntry(String sessionId, boolean createIfDoesNotExist)
+	{
+		SessionEntry entry = (SessionEntry)sessionIdToEntryMap.get(sessionId);
+		if (entry == null && createIfDoesNotExist)
+		{
+			synchronized (sessionIdToEntryMap)
+			{
+				entry = (SessionEntry)sessionIdToEntryMap.get(sessionId);
+				if (entry == null)
+				{
+					entry = new SessionEntry();
+					entry.sessionId = sessionId;
+					sessionIdToEntryMap.put(sessionId, entry);
+				}
+			}
+		}
+		return entry;
+	}
+
+	/**
+	 * @see org.apache.wicket.protocol.http.SecondLevelCacheSessionStore.IPageStore#getPage(java.lang.String,
+	 *      java.lang.String, int, int, int)
+	 */
+	public Page getPage(String sessionId, String pagemap, int id, int versionNumber,
+			int ajaxVersionNumber)
+	{
+		SessionEntry entry = getSessionEntry(sessionId, false);
+		if (entry != null)
+		{
+			byte[] data;
+
+			if (isSynchronous())
+			{
+				data = entry.loadPage(pagemap, id, versionNumber, ajaxVersionNumber);
+			}
+			else
+			{
+				// we need to make sure that the there are no pending pages to
+				// be saved before loading a page
+				List pages = getPagesToSaveList(sessionId);
+				synchronized (pages)
+				{
+					flushPagesToSaveList(sessionId, pages);
+					data = entry.loadPage(pagemap, id, versionNumber, ajaxVersionNumber);
+				}
+			}
+
+			if (data != null)
+			{
+				return deserializePage(data, versionNumber);
+			}
+		}
+
+		return null;
+	}
+
+	/**
+	 * @see org.apache.wicket.protocol.http.SecondLevelCacheSessionStore.IPageStore#pageAccessed(java.lang.String,
+	 *      org.apache.wicket.Page)
+	 */
+	public void pageAccessed(String sessionId, Page page)
+	{
+	}
+
+	/**
+	 * Removes the page (or entire pagemap) from specified session.
+	 * 
+	 * @param entry
+	 * @param pageMap
+	 * @param id
+	 *            page id to remove or -1 if the whole pagemap should be removed
+	 */
+	private void removePage(SessionEntry entry, String pageMap, int id)
+	{
+		if (id != -1)
+		{
+			entry.removePage(pageMap, id);
+		}
+		else
+		{
+			entry.removePageMap(pageMap);
+		}
+	}
+
+	/**
+	 * @see org.apache.wicket.protocol.http.SecondLevelCacheSessionStore.IPageStore#removePage(java.lang.String,
+	 *      java.lang.String, int)
+	 */
+	public void removePage(String sessionId, String pageMap, int id)
+	{
+		SessionEntry entry = getSessionEntry(sessionId, false);
+		if (entry != null)
+		{
+			if (isSynchronous())
+			{
+				removePage(entry, pageMap, id);
+			}
+			else
+			{
+				// we need to make sure that the there are no pending pages to
+				// be saved before removing the page (or pagemap)
+				List pages = getPagesToSaveList(sessionId);
+				synchronized (pages)
+				{
+					flushPagesToSaveList(sessionId, pages);
+					removePage(entry, pageMap, id);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Stores the serialized pages. The storing is done either immediately (in
+	 * synchronous mode) or it's scheduled to be stored by the worker thread.
+	 * 
+	 * @param sessionId
+	 * @param pages
+	 */
+	protected void storeSerializedPages(String sessionId, List /* <SerializedPage> */pages)
+	{
+		SessionEntry entry = getSessionEntry(sessionId, true);
+
+		if (isSynchronous())
+		{
+			for (Iterator i = pages.iterator(); i.hasNext();)
+			{
+				SerializedPage serializedPage = (SerializedPage)i.next();
+				entry.savePage(serializedPage);
+			}
+		}
+		else
+		{
+			schedulePagesSave(sessionId, pages);
+		}
+	}
+
+	/**
+	 * Hook for processing serialized pages (e.g. sending those across cluster)
+	 * 
+	 * @param sessionId
+	 * @param pages
+	 */
+	protected void onPagesSerialized(String sessionId, List /* <SerializedPage */pages)
+	{
+
+	}
+
+	/**
+	 * @see org.apache.wicket.protocol.http.SecondLevelCacheSessionStore.IPageStore#storePage(java.lang.String,
+	 *      org.apache.wicket.Page)
+	 */
+	public void storePage(String sessionId, Page page)
+	{
+		List pages = serializePage(page);
+
+		cacheSerializedPage(sessionId, page, pages);
+
+		onPagesSerialized(sessionId, pages);
+
+		storeSerializedPages(sessionId, pages);
+	}
+
+	/**
+	 * @see org.apache.wicket.protocol.http.SecondLevelCacheSessionStore.IPageStore#unbind(java.lang.String)
+	 */
+	public void unbind(String sessionId)
+	{
+		SessionEntry entry = (SessionEntry)sessionIdToEntryMap.get(sessionId);
+		if (entry != null)
+		{
+			if (isSynchronous())
+			{
+				entry.unbind();
+			}
+			else
+			{
+				List pages = getPagesToSaveList(sessionId);
+				synchronized (pages)
+				{
+					flushPagesToSaveList(sessionId, pages);
+					entry.unbind();
+				}
+				pagesToSaveAll.remove(sessionId);
+			}
+		}
+	}
+
+	// map from session id to serializedpage list
+	// this contains lists for all active sessions
+	private final Map /* <String, List<SerializedPage>> */pagesToSaveAll = new ConcurrentHashMap();
+
+	// contains list of serialized pages to be saved - only non empty lists
+	private final Map /* <String, List<SerializedPage>> */pagesToSaveActive = new ConcurrentHashMap();
+
+	/**
+	 * Returns the list of pages to be saved for the specified session id. If
+	 * the list is not found, new list is created.
+	 * 
+	 * @param sessionId
+	 * @return
+	 */
+	protected List getPagesToSaveList(String sessionId)
+	{
+		List list = (List)pagesToSaveAll.get(sessionId);
+		if (list == null)
+		{
+			synchronized (pagesToSaveAll)
+			{
+				list = (List)pagesToSaveAll.get(sessionId);
+				if (list == null)
+				{
+					list = new ArrayList();
+					pagesToSaveAll.put(sessionId, list);
+				}
+			}
+		}
+		return list;
+	}
+
+	/**
+	 * Saves all entries from the specified list.
+	 * 
+	 * @param sessionId
+	 * @param list
+	 */
+	protected void flushPagesToSaveList(String sessionId, List /* <SerializedPage> */list)
+	{
+		if (list != null)
+		{
+			for (Iterator i = list.iterator(); i.hasNext();)
+			{
+				SerializedPage page = (SerializedPage)i.next();
+				getSessionEntry(sessionId, true).savePage(page);
+			}
+			list.clear();
+		}
+	}
+
+	/**
+	 * Schedules the pages to be saved by the worker thread.
+	 * 
+	 * @param sessionId
+	 * @param pages
+	 */
+	private void schedulePagesSave(String sessionId, List/* <SerializedPage> */pages)
+	{
+		List list = getPagesToSaveList(sessionId);
+		synchronized (list)
+		{
+			list.addAll(pages);
+
+			if (list.size() > 0 && pagesToSaveActive.containsKey(sessionId) == false)
+			{
+				pagesToSaveActive.put(sessionId, list);
+			}
+		}
+	}
+
+	/**
+	 * Worker thread that saves the serialized pages. Saving pages in the
+	 * separate thread results in smoother performance under load.
+	 * 
+	 * @author Matej Knopp
+	 */
+	private class PageSavingThread implements Runnable
+	{
+		private volatile boolean stop = false;
+
+		public void run()
+		{
+			while (stop == false)
+			{
+				// wait until we have something to save
+				while (pagesToSaveActive.isEmpty())
+				{
+					try
+					{
+						Thread.sleep(getSavingThreadSleepTime());
+					}
+					catch (InterruptedException ignore)
+					{
+					}
+				}
+
+				// iterate through lists of pages to be saved
+				for (Iterator i = pagesToSaveActive.entrySet().iterator(); i.hasNext();)
+				{
+					Map.Entry entry = (Map.Entry)i.next();
+					String sessionId = (String)entry.getKey();
+					List pages = (List)entry.getValue();
+
+					synchronized (pages)
+					{
+						try
+						{
+							flushPagesToSaveList(sessionId, pages);
+						}
+						catch (Exception e)
+						{
+							log
+									.error(
+											"Error flushing serialized pages from worker thread for session " +
+													sessionId, e);
+						}
+						i.remove();
+					}
+				}
+			}
+		}
+
+		/**
+		 * Stops the worker thread.
+		 */
+		public void stop()
+		{
+			stop = true;
+		}
+	};
+
+	/**
+	 * Initializes the worker thread.
+	 */
+	private void initPageSavingThread()
+	{
+		if (isSynchronous() == false)
+		{
+			pageSavingThread = new PageSavingThread();
+			Thread t = new Thread(pageSavingThread, "PageSavingThread-" + appName);
+			t.setDaemon(true);
+			t.setPriority(Thread.MAX_PRIORITY);
+			t.start();
+		}
+	}
+
+	private PageSavingThread pageSavingThread = null;
+
+	/**
+	 * Returns the amount time in milliseconds for the saving thread to sleep
+	 * between checking whether there are pending serialized pages to be saved.
+	 * 
+	 * @return
+	 */
+	protected int getSavingThreadSleepTime()
+	{
+		return 100;
+	}
+
+	/**
+	 * Returns whether the {@link DiskPageStore} should work in synchronous or
+	 * asynchronous mode. Asynchronous mode uses a worker thread to save pages,
+	 * which results in smoother performance.
+	 * 
+	 * @return
+	 */
+	protected boolean isSynchronous()
+	{
+		return false;
+	}
+
+	private int lastRecentlySerializedPagesCacheSize = 50;
+
+	/**
+	 * Sets the number of last recently serialized pages kept in cache. The
+	 * cache is used to aid performance on session replication.
+	 * 
+	 * @param lastRecentlySerializedPagesCacheSize
+	 */
+	public void setLastRecentlySerializedPagesCacheSize(int lastRecentlySerializedPagesCacheSize)
+	{
+		this.lastRecentlySerializedPagesCacheSize = lastRecentlySerializedPagesCacheSize;
+	}
+
+	/**
+	 * @return
+	 */
+	public int getLastRecentlySerializedPagesCacheSize()
+	{
+		return lastRecentlySerializedPagesCacheSize;
+	}
+
+	private final List /* SerializedPageWithSession */lastRecentlySerializedPagesCache = new ArrayList(
+			lastRecentlySerializedPagesCacheSize);
+
+	private SerializedPageWithSession removePageFromLastRecentlySerializedPagesCache(Page page)
+	{
+		for (Iterator i = lastRecentlySerializedPagesCache.iterator(); i.hasNext();)
+		{
+			SerializedPageWithSession entry = (SerializedPageWithSession)i.next();
+			if (entry != null && entry.page.get() == page)
+			{
+				i.remove();
+				return entry;
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Store the serialized page in the request metadata,.
+	 * 
+	 * @param sessionId
+	 * @param page
+	 * @param pagesList
+	 */
+	private void cacheSerializedPage(String sessionId, Page page,
+			List /* <SerializedPage> */pagesList)
+	{
+		if (getLastRecentlySerializedPagesCacheSize() > 0)
+		{
+			SerializedPageWithSession entry = new SerializedPageWithSession(sessionId, page,
+					pagesList);
+
+			synchronized (lastRecentlySerializedPagesCache)
+			{
+				removePageFromLastRecentlySerializedPagesCache(page);
+				lastRecentlySerializedPagesCache.add(entry);
+				if (lastRecentlySerializedPagesCache.size() > getLastRecentlySerializedPagesCacheSize())
+				{
+					lastRecentlySerializedPagesCache.remove(0);
+				}
+			}
+		}
+	}
+
+	/**
+	 * 
+	 * @author Matej Knopp
+	 */
+	private static class SerializedPageWithSession implements Serializable
+	{
+		private static final long serialVersionUID = 1L;
+
+		// this is used for lookup on pagemap serialization. We don't have the
+		// session id at that point, because it can happen outside the request
+		// thread. We only have the page instance and we need to use it as a key
+		private final transient WeakReference /* <Page> */page;
+
+		// list of serialized pages
+		private final List pages;
+
+		private final String sessionId;
+
+		// after deserialization, we need to be able to know which page to load
+		private final int pageId;
+		private final String pageMapName;
+		private final int versionNumber;
+		private final int ajaxVersionNumber;
+
+		private SerializedPageWithSession(String sessionId, Page page,
+				List /* <SerializablePage> */pages)
+		{
+			this.sessionId = sessionId;
+			pageId = page.getNumericId();
+			pageMapName = page.getPageMapName();
+			versionNumber = page.getCurrentVersionNumber();
+			ajaxVersionNumber = page.getAjaxVersionNumber();
+			this.pages = pages;
+			this.page = new WeakReference(page);
+		}
+	};
+
+	/**
+	 * @see org.apache.wicket.protocol.http.SecondLevelCacheSessionStore.ISerializationAwarePageStore#prepareForSerialization(org.apache.wicket.Page)
+	 */
+	public Serializable prepareForSerialization(Page page)
+	{
+		Serializable result = page;
+
+		if (getLastRecentlySerializedPagesCacheSize() > 0)
+		{
+			SerializedPageWithSession entry;
+			synchronized (lastRecentlySerializedPagesCache)
+			{
+				entry = removePageFromLastRecentlySerializedPagesCache(page);
+			}
+
+			if (entry != null)
+			{
+				result = entry;
+			}
+		}
+
+		return result;
+	}
+
+	/**
+	 * @see org.apache.wicket.protocol.http.SecondLevelCacheSessionStore.ISerializationAwarePageStore#restoreAfterSerialization(java.io.Serializable)
+	 */
+	public Page restoreAfterSerialization(Serializable serializable)
+	{
+		if (serializable instanceof Page)
+		{
+			return (Page)serializable;
+		}
+		else if (serializable instanceof SerializedPageWithSession)
+		{
+			SerializedPageWithSession page = (SerializedPageWithSession)serializable;
+			storeSerializedPages(page.sessionId, page.pages);
+			return getPage(page.sessionId, page.pageMapName, page.pageId, page.versionNumber,
+					page.ajaxVersionNumber);
+		}
+		else
+		{
+			throw new IllegalArgumentException("Unknown object type");
+		}
+	}
+
+	/**
+	 * @see org.apache.wicket.protocol.http.SecondLevelCacheSessionStore.IPageStore#containsPage(java.lang.String,
+	 *      java.lang.String, int, int)
+	 */
+	public boolean containsPage(String sessionId, String pageMapName, int pageId, int pageVersion)
+	{
+		SessionEntry entry = getSessionEntry(sessionId, false);
+		if (entry != null)
+		{
+			byte[] data;
+
+			if (isSynchronous())
+			{
+				return entry.exists(pageMapName, pageId, pageVersion);
+			}
+			else
+			{
+				// we need to make sure that the there are no pending pages to
+				// be saved before loading a page
+				List pages = getPagesToSaveList(sessionId);
+				synchronized (pages)
+				{
+					flushPagesToSaveList(sessionId, pages);
+					return entry.exists(pageMapName, pageId, pageVersion);
+				}
+			}
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+
+	private static final Logger log = LoggerFactory.getLogger(DiskPageStore.class);
+
+}