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);
+
+}