You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by sv...@apache.org on 2018/07/02 21:25:23 UTC
[4/5] wicket git commit: WICKET-6563 new IPageStore implementation
http://git-wip-us.apache.org/repos/asf/wicket/blob/bcf76f51/wicket-core/src/main/java/org/apache/wicket/pageStore/AsynchronousPageStore.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/AsynchronousPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/AsynchronousPageStore.java
index 43993ba..3428d02 100644
--- a/wicket-core/src/main/java/org/apache/wicket/pageStore/AsynchronousPageStore.java
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/AsynchronousPageStore.java
@@ -17,41 +17,43 @@
package org.apache.wicket.pageStore;
import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
+import org.apache.wicket.MetaDataKey;
+import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.page.IManageablePage;
import org.apache.wicket.util.lang.Args;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
- * Facade for {@link IPageStore} that does the actual saving in worker thread.
+ * Facade for {@link IPageStore} moving {@link #addPage(IPageContext, IManageablePage)} to a worker thread.
* <p>
- * Creates an {@link Entry} for each double (sessionId, page) and puts it in {@link #entries} queue
- * if there is room. Acts as producer.<br/>
- * Later {@link PageSavingRunnable} reads in blocking manner from {@link #entries} and saves each
- * entry. Acts as consumer.
- * </p>
- * It starts only one instance of {@link PageSavingRunnable} because all we need is to make the page
+ * Creates an {@link PendingAdd} for {@link #addPage(IPageContext, IManageablePage)} and puts ito a {@link #queue}.
+ * Later {@link PageAddingRunnable} reads in blocking manner from {@link #queue} and performs the add.
+ * <p>
+ * It starts only one instance of {@link PageAddingRunnable} because all we need is to make the page
* storing asynchronous. We don't want to write concurrently in the wrapped {@link IPageStore},
* though it may happen in the extreme case when the queue is full. These cases should be avoided.
*
- * Based on AsynchronousDataStore (@author Matej Knopp).
- *
+ * @author Matej Knopp
* @author manuelbarzi
*/
-public class AsynchronousPageStore implements IPageStore
+public class AsynchronousPageStore extends DelegatingPageStore
{
/** Log for reporting. */
private static final Logger log = LoggerFactory.getLogger(AsynchronousPageStore.class);
/**
- * The time to wait when adding an {@link Entry} into the entries. In millis.
+ * The time to wait when adding an {@link PendingAdd} into the entries. In millis.
*/
private static final long OFFER_WAIT = 30L;
@@ -66,20 +68,15 @@ public class AsynchronousPageStore implements IPageStore
private final Thread pageSavingThread;
/**
- * The wrapped {@link IPageStore} that actually stores that pages
- */
- private final IPageStore delegate;
-
- /**
* The queue where the entries which have to be saved are temporary stored
*/
- private final BlockingQueue<Entry> entries;
+ private final BlockingQueue<PendingAdd> queue;
/**
- * A map 'sessionId:::pageId' -> {@link Entry}. Used for fast retrieval of {@link Entry}s which
+ * A map 'sessionId:::pageId' -> {@link PendingAdd}. Used for fast retrieval of {@link PendingAdd}s which
* are not yet stored by the wrapped {@link IPageStore}
*/
- private final ConcurrentMap<String, Entry> entryMap;
+ private final ConcurrentMap<String, PendingAdd> queueMap;
/**
* Construct.
@@ -91,29 +88,18 @@ public class AsynchronousPageStore implements IPageStore
*/
public AsynchronousPageStore(final IPageStore delegate, final int capacity)
{
- this.delegate = Args.notNull(delegate, "delegate");
- entries = new LinkedBlockingQueue<>(capacity);
- entryMap = new ConcurrentHashMap<>();
+ super(delegate);
+
+ queue = new LinkedBlockingQueue<>(capacity);
+ queueMap = new ConcurrentHashMap<>();
- PageSavingRunnable savingRunnable = new PageSavingRunnable(delegate, entries, entryMap);
+ PageAddingRunnable savingRunnable = new PageAddingRunnable(delegate, queue, queueMap);
pageSavingThread = new Thread(savingRunnable, "Wicket-AsyncPageStore-PageSavingThread");
pageSavingThread.setDaemon(true);
pageSavingThread.start();
}
/**
- * Little helper
- *
- * @param sessionId
- * @param pageId
- * @return Entry
- */
- private Entry getEntry(final String sessionId, final int pageId)
- {
- return entryMap.get(getKey(sessionId, pageId));
- }
-
- /**
*
* @param pageId
* @param sessionId
@@ -125,83 +111,198 @@ public class AsynchronousPageStore implements IPageStore
}
/**
- *
- * @param entry
- * @return generated key
+ * A pending asynchronous add in the queue.
+ * <p>
+ * Used as {@link IPageContext} for the call to the delegate {@link IPageStore#addPage(IPageContext, IManageablePage)}.
*/
- private static String getKey(final Entry entry)
- {
- return getKey(entry.sessionId, entry.page.getPageId());
- }
-
- /**
- * The structure used for an entry in the queue
- */
- private static class Entry
+ private static class PendingAdd implements IPageContext
{
+ private final IPageContext context;
+
private final String sessionId;
+
private final IManageablePage page;
- public Entry(final String sessionId, final IManageablePage page)
+ /**
+ * Is this context passed to an asynchronously called {@link IPageStore#addPage(IPageContext, IManageablePage)}.
+ */
+ private boolean asynchronous = false;
+
+ /**
+ * Cache of session attributes which may filled in {@link IPageStore#canBeAsynchronous(IPageContext)},
+ * so these are available asynchronously later on.
+ */
+ private Map<String, Serializable> attributeCache = new HashMap<>();
+
+ public PendingAdd(final IPageContext context, final IManageablePage page)
{
- this.sessionId = Args.notNull(sessionId, "sessionId");
+ this.context = Args.notNull(context, "context");
this.page = Args.notNull(page, "page");
+
+ context.bind();
+ this.sessionId = context.getSessionId();
+ }
+
+ /**
+ *
+ * @param entry
+ * @return generated key
+ */
+ private String getKey()
+ {
+ return AsynchronousPageStore.getKey(sessionId, page.getPageId());
}
@Override
- public int hashCode()
+ public String toString()
{
- final int prime = 31;
- int result = 1;
- result = prime * result + page.getPageId();
- result = prime * result + sessionId.hashCode();
- return result;
+ return "Entry [sessionId=" + sessionId + ", pageId=" + page.getPageId() + "]";
}
+ /**
+ * Prevents access to request when called asynchronously.
+ */
@Override
- public boolean equals(Object obj)
+ public <T> void setRequestData(MetaDataKey<T> key, T value)
{
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
- return false;
- Entry other = (Entry)obj;
- if (page.getPageId() != other.page.getPageId())
- return false;
- if (!sessionId.equals(other.sessionId))
- return false;
- return true;
+ if (asynchronous) {
+ throw new WicketRuntimeException("no request available asynchronuously");
+ }
+
+ context.setRequestData(key, value);
}
+ /**
+ * Prevents access to request when called asynchronously.
+ */
@Override
- public String toString()
+ public <T> T getRequestData(MetaDataKey<T> key)
{
- return "Entry [sessionId=" + sessionId + ", pageId=" + page.getPageId() + "]";
+ if (asynchronous) {
+ throw new WicketRuntimeException("no request available asynchronuously");
+ }
+
+ return context.getRequestData(key);
}
+ /**
+ * Prevents access to request when called asynchronously.
+ * <p>
+ * All values set from {@link IPageStore#canBeAsynchronous(IPageContext)} are kept
+ * for later retrieval.
+ */
+ @Override
+ public <T extends Serializable> void setSessionAttribute(String key, T value)
+ {
+ if (asynchronous) {
+ throw new WicketRuntimeException("no session available asynchronuously");
+ }
+
+ if (value != null) {
+ attributeCache.put(key, value);
+ }
+
+ context.setSessionAttribute(key, value);
+ }
+
+ /**
+ * Prevents access to the session when called asynchronously.
+ * <p>
+ * All values set from {@link IPageStore#canBeAsynchronous(IPageContext)} are still
+ * available.
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T extends Serializable> T getSessionAttribute(String key)
+ {
+ if (asynchronous) {
+ T value = (T)attributeCache.get(key);
+ if (value != null) {
+ return value;
+ }
+
+ throw new WicketRuntimeException("no session available asynchronuously");
+ }
+
+ T value = context.getSessionAttribute(key);
+ if (value != null) {
+ attributeCache.put(key, value);
+ }
+
+ return value;
+ }
+
+ /**
+ * Prevents access to the session when called asynchronously.
+ */
+ @Override
+ public <T extends Serializable> void setSessionData(MetaDataKey<T> key, T value)
+ {
+ if (asynchronous) {
+ throw new WicketRuntimeException("no session available asynchronuously");
+ }
+
+ context.setSessionData(key, value);
+ }
+
+ /**
+ * Gets data from the session.
+ */
+ @Override
+ public <T extends Serializable> T getSessionData(MetaDataKey<T> key)
+ {
+ return context.getSessionData(key);
+ }
+
+ /**
+ * Prevents access to the session when called asynchronously.
+ * <p>
+ * Has no effect when session is already bound.
+ */
+ @Override
+ public void bind()
+ {
+ if (sessionId != null) {
+ // already bound
+ return;
+ }
+
+ if (asynchronous) {
+ throw new WicketRuntimeException("no session available asynchronuously");
+ }
+
+ context.bind();
+ }
+
+ /**
+ * Get the identifier of the session, maybe <code>null</code> if not bound.
+ */
+ @Override
+ public String getSessionId()
+ {
+ return sessionId;
+ }
}
/**
- * The thread that acts as consumer of {@link Entry}ies
+ * The consumer of {@link PendingAdd}s.
*/
- private static class PageSavingRunnable implements Runnable
+ private static class PageAddingRunnable implements Runnable
{
- private static final Logger log = LoggerFactory.getLogger(PageSavingRunnable.class);
+ private static final Logger log = LoggerFactory.getLogger(PageAddingRunnable.class);
- private final BlockingQueue<Entry> entries;
+ private final BlockingQueue<PendingAdd> entries;
- private final ConcurrentMap<String, Entry> entryMap;
+ private final ConcurrentMap<String, PendingAdd> addQueue;
private final IPageStore delegate;
- private PageSavingRunnable(IPageStore delegate, BlockingQueue<Entry> entries,
- ConcurrentMap<String, Entry> entryMap)
+ private PageAddingRunnable(IPageStore delegate, BlockingQueue<PendingAdd> entries,
+ ConcurrentMap<String, PendingAdd> entryMap)
{
this.delegate = delegate;
this.entries = entries;
- this.entryMap = entryMap;
+ this.addQueue = entryMap;
}
@Override
@@ -209,21 +310,21 @@ public class AsynchronousPageStore implements IPageStore
{
while (!Thread.interrupted())
{
- Entry entry = null;
+ PendingAdd add = null;
try
{
- entry = entries.poll(POLL_WAIT, TimeUnit.MILLISECONDS);
+ add = entries.poll(POLL_WAIT, TimeUnit.MILLISECONDS);
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
}
- if (entry != null)
+ if (add != null)
{
- log.debug("Saving asynchronously: {}...", entry);
- delegate.storePage(entry.sessionId, entry.page);
- entryMap.remove(getKey(entry));
+ log.debug("Saving asynchronously: {}...", add);
+ delegate.addPage(add, add.page);
+ addQueue.remove(add.getKey());
}
}
}
@@ -245,102 +346,85 @@ public class AsynchronousPageStore implements IPageStore
}
}
- delegate.destroy();
+ super.destroy();
}
@Override
- public IManageablePage getPage(String sessionId, int pageId)
+ public IManageablePage getPage(IPageContext context, int pageId)
{
- Entry entry = getEntry(sessionId, pageId);
+ PendingAdd entry = queueMap.get(getKey(context.getSessionId(), pageId));
if (entry != null)
{
- log.debug(
- "Returning the page of a non-stored entry with session id '{}' and page id '{}'",
- sessionId, pageId);
+ log.debug("Returning the page of a non-stored entry with page id '{}'", pageId);
return entry.page;
}
- IManageablePage page = delegate.getPage(sessionId, pageId);
+ IManageablePage page = super.getPage(context, pageId);
- log.debug("Returning the page of a stored entry with session id '{}' and page id '{}'",
- sessionId, pageId);
+ log.debug("Returning the page of a stored entry with page id '{}'", pageId);
return page;
}
@Override
- public void removePage(String sessionId, int pageId)
+ public void removePage(IPageContext context, IManageablePage page)
{
- String key = getKey(sessionId, pageId);
+ String key = getKey(context.getSessionId(), page.getPageId());
if (key != null)
{
- Entry entry = entryMap.remove(key);
+ PendingAdd entry = queueMap.remove(key);
if (entry != null)
{
- entries.remove(entry);
+ queue.remove(entry);
}
}
- delegate.removePage(sessionId, pageId);
+ super.removePage(context, page);
}
@Override
- public void storePage(String sessionId, IManageablePage page)
+ public void addPage(IPageContext context, IManageablePage page)
{
- Entry entry = new Entry(sessionId, page);
- String key = getKey(entry);
- entryMap.put(key, entry);
-
- try
- {
- if (entries.offer(entry, OFFER_WAIT, TimeUnit.MILLISECONDS))
+ PendingAdd add = new PendingAdd(context, page);
+ if (getDelegate().canBeAsynchronous(add)) {
+ add.asynchronous = true;
+
+ String key = add.getKey();
+ queueMap.put(key, add);
+ try
{
- log.debug("Offered for storing asynchronously page with id '{}' in session '{}'",
- page.getPageId(), sessionId);
+ if (queue.offer(add, OFFER_WAIT, TimeUnit.MILLISECONDS))
+ {
+ log.debug("Offered for storing asynchronously page with id '{}'", page.getPageId());
+ return;
+ }
+ else
+ {
+ log.debug("Storing synchronously page with id '{}'", page.getPageId());
+ queueMap.remove(key);
+ }
}
- else
+ catch (InterruptedException e)
{
- log.debug("Storing synchronously page with id '{}' in session '{}'",
- page.getPageId(), sessionId);
- entryMap.remove(key);
- delegate.storePage(sessionId, page);
+ log.error(e.getMessage(), e);
+ queueMap.remove(key);
}
}
- catch (InterruptedException e)
- {
- log.error(e.getMessage(), e);
- entryMap.remove(key);
- delegate.storePage(sessionId, page);
- }
- }
-
- @Override
- public void unbind(String sessionId)
- {
- delegate.unbind(sessionId);
- }
-
- @Override
- public Serializable prepareForSerialization(String sessionId, Serializable page)
- {
- return delegate.prepareForSerialization(sessionId, page);
- }
-
- @Override
- public Object restoreAfterSerialization(Serializable serializable)
- {
- return delegate.restoreAfterSerialization(serializable);
+
+ super.addPage(context, page);
}
@Override
- public IManageablePage convertToPage(Object page)
+ public void removeAllPages(IPageContext context)
{
- return delegate.convertToPage(page);
- }
-
- @Override
- public boolean canBeAsynchronous()
- {
- // should not wrap in another AsynchronousPageStore
- return false;
+ Iterator<PendingAdd> iterator = queue.iterator();
+ while (iterator.hasNext()) {
+ PendingAdd add = iterator.next();
+
+ if (add.sessionId.equals(context.getSessionId())) {
+ iterator.remove();
+ }
+ }
+
+ super.removeAllPages(context);
}
-}
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/wicket/blob/bcf76f51/wicket-core/src/main/java/org/apache/wicket/pageStore/DefaultPageContext.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/DefaultPageContext.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/DefaultPageContext.java
new file mode 100644
index 0000000..257f593
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/DefaultPageContext.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.pageStore;
+
+import java.io.Serializable;
+
+import org.apache.wicket.MetaDataKey;
+import org.apache.wicket.Session;
+import org.apache.wicket.request.cycle.RequestCycle;
+
+/**
+ * Default page context using a {@link Session}.
+ *
+ * @author Juergen Donnerstag
+ * @author svenmeier
+ */
+public class DefaultPageContext implements IPageContext
+{
+ private Session session;
+
+ public DefaultPageContext(Session session) {
+ this.session = Session.get();
+ }
+
+ /**
+ * @see org.apache.wicket.pageStore.IPageContext#bind()
+ */
+ @Override
+ public void bind()
+ {
+ session.bind();
+ }
+
+ /**
+ * @see org.apache.wicket.pageStore.IPageContext#getSessionId()
+ */
+ @Override
+ public String getSessionId()
+ {
+ return session.getId();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T extends Serializable> T getSessionAttribute(String key)
+ {
+ return (T)session.getAttribute(key);
+ }
+
+ @Override
+ public <T extends Serializable> void setSessionAttribute(String key, T value)
+ {
+ session.setAttribute(key, value);
+ }
+
+ @Override
+ public <T extends Serializable> T getSessionData(MetaDataKey<T> key)
+ {
+ return session.getMetaData(key);
+ }
+
+ @Override
+ public <T extends Serializable> void setSessionData(MetaDataKey<T> key, T value)
+ {
+ session.setMetaData(key, value);
+ }
+
+ @Override
+ public <T> T getRequestData(MetaDataKey<T> key)
+ {
+ RequestCycle requestCycle = RequestCycle.get();
+ if (requestCycle == null)
+ {
+ throw new IllegalStateException("Not a request thread.");
+ }
+ return requestCycle.getMetaData(key);
+ }
+
+ @Override
+ public <T> void setRequestData(MetaDataKey<T> key, T value)
+ {
+ RequestCycle requestCycle = RequestCycle.get();
+ if (requestCycle == null)
+ {
+ throw new IllegalStateException("Not a request thread.");
+ }
+ requestCycle.setMetaData(key, value);
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/bcf76f51/wicket-core/src/main/java/org/apache/wicket/pageStore/DefaultPageStore.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/DefaultPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/DefaultPageStore.java
deleted file mode 100644
index f324b85..0000000
--- a/wicket-core/src/main/java/org/apache/wicket/pageStore/DefaultPageStore.java
+++ /dev/null
@@ -1,469 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.wicket.pageStore;
-
-import java.io.Serializable;
-import java.lang.ref.SoftReference;
-import java.util.Iterator;
-import java.util.concurrent.ConcurrentLinkedDeque;
-
-import org.apache.wicket.page.IManageablePage;
-import org.apache.wicket.serialize.ISerializer;
-import org.apache.wicket.util.lang.Args;
-import org.apache.wicket.util.lang.Objects;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link IPageStore} that converts {@link IManageablePage} instances to {@link SerializedPage}s
- * before passing them to the {@link IDataStore} to store them and the same in the opposite
- * direction when loading {@link SerializedPage} from the data store.
- *
- */
-public class DefaultPageStore extends AbstractCachingPageStore<DefaultPageStore.SerializedPage>
-{
- private static final Logger LOG = LoggerFactory.getLogger(DefaultPageStore.class);
-
- /**
- * Construct.
- *
- * @param pageSerializer
- * the {@link ISerializer} that will be used to convert pages from/to byte arrays
- * @param dataStore
- * the {@link IDataStore} that actually stores the pages
- * @param cacheSize
- * the number of pages to cache in memory before passing them to
- * {@link IDataStore#storeData(String, int, byte[])}
- */
- public DefaultPageStore(final ISerializer pageSerializer, final IDataStore dataStore,
- final int cacheSize)
- {
- super(pageSerializer, dataStore, new SerializedPagesCache(cacheSize));
- }
-
- @Override
- public void storePage(final String sessionId, final IManageablePage page)
- {
- SerializedPage serialized = createSerializedPage(sessionId, page);
- if (serialized != null)
- {
- int pageId = page.getPageId();
- pagesCache.storePage(sessionId, pageId, serialized);
- storePageData(sessionId, pageId, serialized.getData());
- }
- }
-
- @Override
- public IManageablePage convertToPage(final Object object)
- {
- if (object == null)
- {
- return null;
- }
- else if (object instanceof IManageablePage)
- {
- return (IManageablePage)object;
- }
- else if (object instanceof SerializedPage)
- {
- SerializedPage page = (SerializedPage)object;
- byte data[] = page.getData();
- if (data == null)
- {
- data = getPageData(page.getSessionId(), page.getPageId());
- }
- if (data != null)
- {
- return deserializePage(data);
- }
- return null;
- }
-
- String type = object.getClass().getName();
- throw new IllegalArgumentException("Unknown object type " + type);
- }
-
- /**
- * Reloads the {@link SerializedPage} from the backing {@link IDataStore} if the
- * {@link SerializedPage#data} is stripped earlier
- *
- * @param serializedPage
- * the {@link SerializedPage} with empty {@link SerializedPage#data} slot
- * @return the fully functional {@link SerializedPage}
- */
- private SerializedPage restoreStrippedSerializedPage(final SerializedPage serializedPage)
- {
- SerializedPage result = pagesCache.getPage(serializedPage.getSessionId(),
- serializedPage.getPageId());
- if (result != null)
- {
- return result;
- }
-
- byte data[] = getPageData(serializedPage.getSessionId(), serializedPage.getPageId());
- return new SerializedPage(serializedPage.getSessionId(), serializedPage.getPageId(), data);
- }
-
- @Override
- public Serializable prepareForSerialization(final String sessionId, final Serializable page)
- {
- if (dataStore.isReplicated())
- {
- return null;
- }
-
- SerializedPage result = null;
-
- if (page instanceof IManageablePage)
- {
- IManageablePage _page = (IManageablePage)page;
- result = pagesCache.getPage(sessionId, _page.getPageId());
- if (result == null)
- {
- result = createSerializedPage(sessionId, _page);
- if (result != null)
- {
- pagesCache.storePage(sessionId, _page.getPageId(), result);
- }
- }
- }
- else if (page instanceof SerializedPage)
- {
- SerializedPage _page = (SerializedPage)page;
- if (_page.getData() == null)
- {
- result = restoreStrippedSerializedPage(_page);
- }
- else
- {
- result = _page;
- }
- }
-
- if (result != null)
- {
- return result;
- }
- return page;
- }
-
- /**
- *
- * @return Always true for this implementation
- */
- protected boolean storeAfterSessionReplication()
- {
- return true;
- }
-
- @Override
- public Object restoreAfterSerialization(final Serializable serializable)
- {
- if (serializable == null)
- {
- return null;
- }
- else if (!storeAfterSessionReplication() || serializable instanceof IManageablePage)
- {
- return serializable;
- }
- else if (serializable instanceof SerializedPage)
- {
- SerializedPage page = (SerializedPage)serializable;
- if (page.getData() != null)
- {
- storePageData(page.getSessionId(), page.getPageId(), page.getData());
- return new SerializedPage(page.getSessionId(), page.getPageId(), null);
- }
- return page;
- }
-
- String type = serializable.getClass().getName();
- throw new IllegalArgumentException("Unknown object type " + type);
- }
-
- /**
- * A representation of {@link IManageablePage} that knows additionally the id of the http
- * session in which this {@link IManageablePage} instance is used. The {@link #sessionId} and
- * {@link #pageId} are used for better clustering in the {@link IDataStore} structures.
- */
- protected static class SerializedPage implements Serializable
- {
- private static final long serialVersionUID = 1L;
-
- /**
- * The id of the serialized {@link IManageablePage}
- */
- private final int pageId;
-
- /**
- * The id of the http session in which the serialized {@link IManageablePage} is used.
- */
- private final String sessionId;
-
- /**
- * The serialized {@link IManageablePage}
- */
- private final byte[] data;
-
- public SerializedPage(String sessionId, int pageId, byte[] data)
- {
- this.pageId = pageId;
- this.sessionId = sessionId;
- this.data = data;
- }
-
- public byte[] getData()
- {
- return data;
- }
-
- public int getPageId()
- {
- return pageId;
- }
-
- public String getSessionId()
- {
- return sessionId;
- }
-
- @Override
- public boolean equals(Object obj)
- {
- if (this == obj)
- {
- return true;
- }
- if ((obj instanceof SerializedPage) == false)
- {
- return false;
- }
- SerializedPage rhs = (SerializedPage)obj;
- return Objects.equal(getPageId(), rhs.getPageId()) &&
- Objects.equal(getSessionId(), rhs.getSessionId());
- }
-
- @Override
- public int hashCode()
- {
- return Objects.hashCode(getPageId(), getSessionId());
- }
- }
-
- /**
- *
- * @param sessionId
- * @param page
- * @return the serialized page information
- */
- protected SerializedPage createSerializedPage(final String sessionId, final IManageablePage page)
- {
- Args.notNull(sessionId, "sessionId");
- Args.notNull(page, "page");
-
- SerializedPage serializedPage = null;
-
- byte[] data = serializePage(page);
-
- if (data != null)
- {
- serializedPage = new SerializedPage(sessionId, page.getPageId(), data);
- }
- else if (LOG.isWarnEnabled())
- {
- LOG.warn("Page {} cannot be serialized. See previous logs for possible reasons.", page);
- }
- return serializedPage;
- }
-
- /**
- * Cache that stores serialized pages. This is important to make sure that a single page is not
- * serialized twice or more when not necessary.
- * <p>
- * For example a page is serialized during request, but it might be also later serialized on
- * session replication. The purpose of this cache is to make sure that the data obtained from
- * first serialization is reused on second serialization.
- *
- * @author Matej Knopp
- */
- static class SerializedPagesCache implements SecondLevelPageCache<String, Integer, SerializedPage>
- {
- private final int maxSize;
-
- private final ConcurrentLinkedDeque<SoftReference<SerializedPage>> cache;
-
- /**
- * Constructor.
- *
- * @param maxSize
- * The maximum number of entries to cache
- */
- public SerializedPagesCache(final int maxSize)
- {
- this.maxSize = maxSize;
- cache = new ConcurrentLinkedDeque<>();
- }
-
- /**
- *
- * @param sessionId
- * @param pageId
- * @return the removed {@link SerializedPage} or <code>null</code> - otherwise
- */
- @Override
- public SerializedPage removePage(final String sessionId, final Integer pageId)
- {
- if (maxSize > 0)
- {
- Args.notNull(sessionId, "sessionId");
- Args.notNull(pageId, "pageId");
-
- SerializedPage sample = new SerializedPage(sessionId, pageId, null);
-
- for (Iterator<SoftReference<SerializedPage>> i = cache.iterator(); i.hasNext();)
- {
- SoftReference<SerializedPage> ref = i.next();
- SerializedPage entry = ref.get();
- if (sample.equals(entry))
- {
- i.remove();
- return entry;
- }
- }
- }
- return null;
- }
-
- /**
- * Removes all {@link SerializedPage}s for the session with <code>sessionId</code> from the
- * cache.
- *
- * @param sessionId
- */
- @Override
- public void removePages(String sessionId)
- {
- if (maxSize > 0)
- {
- Args.notNull(sessionId, "sessionId");
-
- for (Iterator<SoftReference<SerializedPage>> i = cache.iterator(); i.hasNext();)
- {
- SoftReference<SerializedPage> ref = i.next();
- SerializedPage entry = ref.get();
- if (entry != null && entry.getSessionId().equals(sessionId))
- {
- i.remove();
- }
- }
- }
- }
-
- /**
- * Returns a {@link SerializedPage} by looking it up by <code>sessionId</code> and
- * <code>pageId</code>. If there is a match then it is <i>touched</i>, i.e. it is moved at
- * the top of the cache.
- *
- * @param sessionId
- * @param pageId
- * @return the found serialized page or <code>null</code> when not found
- */
- @Override
- public SerializedPage getPage(String sessionId, Integer pageId)
- {
- SerializedPage result = null;
- if (maxSize > 0)
- {
- Args.notNull(sessionId, "sessionId");
- Args.notNull(pageId, "pageId");
-
- SerializedPage sample = new SerializedPage(sessionId, pageId, null);
-
- for (Iterator<SoftReference<SerializedPage>> i = cache.iterator(); i.hasNext();)
- {
- SoftReference<SerializedPage> ref = i.next();
- SerializedPage entry = ref.get();
- if (sample.equals(entry))
- {
- i.remove();
- result = entry;
- break;
- }
- }
-
- if (result != null)
- {
- // move to top
- internalStore(result);
- }
- }
- return result;
- }
-
- /**
- * Store the serialized page in cache
- *
- * @param page
- * the data to serialize (page id, session id, bytes)
- */
- @Override
- public void storePage(String sessionId, Integer pageId, SerializedPage page)
- {
- if (maxSize > 0)
- {
- Args.notNull(sessionId, "sessionId");
- Args.notNull(pageId, "pageId");
- Args.notNull(page, "page");
-
- for (Iterator<SoftReference<SerializedPage>> i = cache.iterator(); i.hasNext();)
- {
- SoftReference<SerializedPage> r = i.next();
- SerializedPage entry = r.get();
- if (entry != null && entry.equals(page))
- {
- i.remove();
- break;
- }
- }
-
- internalStore(page);
- }
- }
-
- private void internalStore(SerializedPage page)
- {
- cache.push(new SoftReference<>(page));
- while (cache.size() > maxSize)
- {
- cache.pollLast();
- }
- }
-
- @Override
- public void destroy()
- {
- cache.clear();
- }
- }
-
- @Override
- public boolean canBeAsynchronous()
- {
- return true;
- }
-}
http://git-wip-us.apache.org/repos/asf/wicket/blob/bcf76f51/wicket-core/src/main/java/org/apache/wicket/pageStore/DelegatingPageStore.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/DelegatingPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/DelegatingPageStore.java
new file mode 100644
index 0000000..304988a
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/DelegatingPageStore.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.pageStore;
+
+import org.apache.wicket.page.IManageablePage;
+import org.apache.wicket.util.lang.Args;
+
+/**
+ * An {@link IPageStore} that delegates to another storage.
+ */
+public abstract class DelegatingPageStore implements IPageStore
+{
+ private IPageStore delegate;
+
+ protected DelegatingPageStore(IPageStore delegate) {
+ this.delegate = Args.notNull(delegate, "delegate");
+ }
+
+ public IPageStore getDelegate()
+ {
+ return delegate;
+ }
+
+ @Override
+ public void addPage(IPageContext context, IManageablePage page) {
+ delegate.addPage(context, page);
+ }
+
+ @Override
+ public void removePage(IPageContext context, IManageablePage page) {
+ delegate.removePage(context, page);
+ }
+
+ @Override
+ public void removeAllPages(IPageContext context) {
+ delegate.removeAllPages(context);
+ }
+
+ @Override
+ public IManageablePage getPage(IPageContext context, int id) {
+ return delegate.getPage(context, id);
+ }
+
+ @Override
+ public void detach(IPageContext context) {
+ delegate.detach(context);
+ }
+
+ @Override
+ public void destroy() {
+ delegate.destroy();
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/bcf76f51/wicket-core/src/main/java/org/apache/wicket/pageStore/DiskDataStore.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/DiskDataStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/DiskDataStore.java
deleted file mode 100644
index 2dd95e4..0000000
--- a/wicket-core/src/main/java/org/apache/wicket/pageStore/DiskDataStore.java
+++ /dev/null
@@ -1,589 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.wicket.pageStore;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.OutputStream;
-import java.io.RandomAccessFile;
-import java.io.Serializable;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-import org.apache.wicket.WicketRuntimeException;
-import org.apache.wicket.pageStore.PageWindowManager.PageWindow;
-import org.apache.wicket.util.file.Files;
-import org.apache.wicket.util.io.IOUtils;
-import org.apache.wicket.util.lang.Args;
-import org.apache.wicket.util.lang.Bytes;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * A data store implementation which stores the data on disk (in a file system)
- */
-public class DiskDataStore implements IDataStore
-{
- private static final Logger log = LoggerFactory.getLogger(DiskDataStore.class);
-
- private static final String INDEX_FILE_NAME = "DiskDataStoreIndex";
-
- private final String applicationName;
-
- private final Bytes maxSizePerPageSession;
-
- private final File fileStoreFolder;
-
- private final ConcurrentMap<String, SessionEntry> sessionEntryMap;
-
- /**
- * Construct.
- *
- * @param applicationName
- * @param fileStoreFolder
- * @param maxSizePerSession
- */
- public DiskDataStore(final String applicationName, final File fileStoreFolder,
- final Bytes maxSizePerSession)
- {
- this.applicationName = applicationName;
- this.fileStoreFolder = fileStoreFolder;
- maxSizePerPageSession = Args.notNull(maxSizePerSession, "maxSizePerSession");
- sessionEntryMap = new ConcurrentHashMap<>();
-
- try
- {
- if (this.fileStoreFolder.exists() || this.fileStoreFolder.mkdirs())
- {
- loadIndex();
- }
- else
- {
- log.warn("Cannot create file store folder for some reason.");
- }
- }
- catch (SecurityException e)
- {
- throw new WicketRuntimeException(
- "SecurityException occurred while creating DiskDataStore. Consider using a non-disk based IDataStore implementation. "
- + "See org.apache.wicket.Application.setPageManagerProvider(IPageManagerProvider)",
- e);
- }
- }
-
- /**
- * @see org.apache.wicket.pageStore.IDataStore#destroy()
- */
- @Override
- public void destroy()
- {
- log.debug("Destroying...");
- saveIndex();
- log.debug("Destroyed.");
- }
-
- /**
- * @see org.apache.wicket.pageStore.IDataStore#getData(java.lang.String, int)
- */
- @Override
- public byte[] getData(final String sessionId, final int id)
- {
- byte[] pageData = null;
- SessionEntry sessionEntry = getSessionEntry(sessionId, false);
- if (sessionEntry != null)
- {
- pageData = sessionEntry.loadPage(id);
- }
-
- if (log.isDebugEnabled())
- {
- log.debug("Returning data{} for page with id '{}' in session with id '{}'",
- pageData != null ? "" : "(null)", id, sessionId);
- }
- return pageData;
- }
-
- /**
- * @see org.apache.wicket.pageStore.IDataStore#isReplicated()
- */
- @Override
- public boolean isReplicated()
- {
- return false;
- }
-
- /**
- * @see org.apache.wicket.pageStore.IDataStore#removeData(java.lang.String, int)
- */
- @Override
- public void removeData(final String sessionId, final int id)
- {
- SessionEntry sessionEntry = getSessionEntry(sessionId, false);
- if (sessionEntry != null)
- {
- if (log.isDebugEnabled())
- {
- log.debug("Removing data for page with id '{}' in session with id '{}'", id, sessionId);
- }
- sessionEntry.removePage(id);
- }
- }
-
- /**
- * @see org.apache.wicket.pageStore.IDataStore#removeData(java.lang.String)
- */
- @Override
- public void removeData(final String sessionId)
- {
- SessionEntry sessionEntry = getSessionEntry(sessionId, false);
- if (sessionEntry != null)
- {
- log.debug("Removing data for pages in session with id '{}'", sessionId);
- synchronized (sessionEntry)
- {
- sessionEntryMap.remove(sessionEntry.sessionId);
- sessionEntry.unbind();
- }
- }
- }
-
- /**
- * @see org.apache.wicket.pageStore.IDataStore#storeData(java.lang.String, int, byte[])
- */
- @Override
- public void storeData(final String sessionId, final int id, final byte[] data)
- {
- SessionEntry sessionEntry = getSessionEntry(sessionId, true);
- if (sessionEntry != null)
- {
- log.debug("Storing data for page with id '{}' in session with id '{}'", id, sessionId);
- sessionEntry.savePage(id, data);
- }
- }
-
- /**
- *
- * @param sessionId
- * @param create
- * @return the session entry
- */
- protected SessionEntry getSessionEntry(final String sessionId, final boolean create)
- {
- if (!create)
- {
- return sessionEntryMap.get(sessionId);
- }
-
- SessionEntry entry = new SessionEntry(this, sessionId);
- SessionEntry existing = sessionEntryMap.putIfAbsent(sessionId, entry);
- return existing != null ? existing : entry;
- }
-
- /**
- * Load the index
- */
- @SuppressWarnings("unchecked")
- private void loadIndex()
- {
- File storeFolder = getStoreFolder();
- File index = new File(storeFolder, INDEX_FILE_NAME);
- if (index.exists() && index.length() > 0)
- {
- try
- {
- InputStream stream = new FileInputStream(index);
- ObjectInputStream ois = new ObjectInputStream(stream);
- try
- {
- Map<String, SessionEntry> map = (Map<String, SessionEntry>)ois.readObject();
- sessionEntryMap.clear();
- sessionEntryMap.putAll(map);
-
- for (Entry<String, SessionEntry> entry : sessionEntryMap.entrySet())
- {
- // initialize the diskPageStore reference
- SessionEntry sessionEntry = entry.getValue();
- sessionEntry.diskDataStore = this;
- }
- } finally {
- stream.close();
- ois.close();
- }
- }
- catch (Exception e)
- {
- log.error("Couldn't load DiskDataStore index from file " + index + ".", e);
- }
- }
- Files.remove(index);
- }
-
- /**
- *
- */
- private void saveIndex()
- {
- File storeFolder = getStoreFolder();
- if (storeFolder.exists())
- {
- File index = new File(storeFolder, INDEX_FILE_NAME);
- Files.remove(index);
- try
- {
- OutputStream stream = new FileOutputStream(index);
- ObjectOutputStream oos = new ObjectOutputStream(stream);
- try
- {
- Map<String, SessionEntry> map = new HashMap<>(sessionEntryMap.size());
- for (Entry<String, SessionEntry> e : sessionEntryMap.entrySet())
- {
- if (e.getValue().unbound == false)
- {
- map.put(e.getKey(), e.getValue());
- }
- }
- oos.writeObject(map);
- } finally {
- stream.close();
- oos.close();
- }
- }
- catch (Exception e)
- {
- log.error("Couldn't write DiskDataStore index to file " + index + ".", e);
- }
- }
- }
-
- /**
- *
- */
- protected static class SessionEntry implements Serializable
- {
- private static final long serialVersionUID = 1L;
-
- private final String sessionId;
- private transient DiskDataStore diskDataStore;
- private String fileName;
- private PageWindowManager manager;
- private boolean unbound = false;
-
- protected SessionEntry(DiskDataStore diskDataStore, String sessionId)
- {
- this.diskDataStore = diskDataStore;
- this.sessionId = sessionId;
- }
-
- public PageWindowManager getManager()
- {
- if (manager == null)
- {
- manager = new PageWindowManager(diskDataStore.maxSizePerPageSession.bytes());
- }
- return manager;
- }
-
- private String getFileName()
- {
- if (fileName == null)
- {
- fileName = diskDataStore.getSessionFileName(sessionId, true);
- }
- return fileName;
- }
-
- /**
- * @return session id
- */
- public String getSessionId()
- {
- return sessionId;
- }
-
- /**
- * Saves the serialized page to appropriate file.
- *
- * @param pageId
- * @param data
- */
- public synchronized void savePage(int pageId, byte data[])
- {
- if (unbound)
- {
- return;
- }
- // only save page that has some data
- if (data != null)
- {
- // allocate window for page
- PageWindow window = getManager().createPageWindow(pageId, data.length);
-
- FileChannel channel = getFileChannel(true);
- if (channel != null)
- {
- try
- {
- // write the content
- channel.write(ByteBuffer.wrap(data), window.getFilePartOffset());
- }
- catch (IOException e)
- {
- log.error("Error writing to a channel " + channel, e);
- }
- finally
- {
- IOUtils.closeQuietly(channel);
- }
- }
- else
- {
- log.warn(
- "Cannot save page with id '{}' because the data file cannot be opened.",
- pageId);
- }
- }
- }
-
- /**
- * Removes the page from pagemap file.
- *
- * @param pageId
- */
- public synchronized void removePage(int pageId)
- {
- if (unbound)
- {
- return;
- }
- getManager().removePage(pageId);
- }
-
- /**
- * Loads the part of pagemap file specified by the given PageWindow.
- *
- * @param window
- * @return serialized page data
- */
- public byte[] loadPage(PageWindow window)
- {
- byte[] result = null;
- FileChannel channel = getFileChannel(false);
- if (channel != null)
- {
- ByteBuffer buffer = ByteBuffer.allocate(window.getFilePartSize());
- try
- {
- channel.read(buffer, window.getFilePartOffset());
- if (buffer.hasArray())
- {
- result = buffer.array();
- }
- }
- catch (IOException e)
- {
- log.error("Error reading from file channel " + channel, e);
- }
- finally
- {
- IOUtils.closeQuietly(channel);
- }
- }
- return result;
- }
-
- private FileChannel getFileChannel(boolean create)
- {
- FileChannel channel = null;
- File file = new File(getFileName());
- if (create || file.exists())
- {
- String mode = create ? "rw" : "r";
- try
- {
- RandomAccessFile randomAccessFile = new RandomAccessFile(file, mode);
- channel = randomAccessFile.getChannel();
- }
- catch (FileNotFoundException fnfx)
- {
- // can happen if the file is locked. WICKET-4176
- log.error(fnfx.getMessage(), fnfx);
- }
- }
- return channel;
- }
-
- /**
- * Loads the specified page data.
- *
- * @param id
- * @return page data or null if the page is no longer in pagemap file
- */
- public synchronized byte[] loadPage(int id)
- {
- if (unbound)
- {
- return null;
- }
- byte[] result = null;
- PageWindow window = getManager().getPageWindow(id);
- if (window != null)
- {
- result = loadPage(window);
- }
- return result;
- }
-
- /**
- * Deletes all files for this session.
- */
- public synchronized void unbind()
- {
- File sessionFolder = diskDataStore.getSessionFolder(sessionId, false);
- if (sessionFolder.exists())
- {
- Files.removeFolder(sessionFolder);
- cleanup(sessionFolder);
- }
- unbound = true;
- }
-
- /**
- * deletes the sessionFolder's parent and grandparent, if (and only if) they are empty.
- *
- * @see #createPathFrom(String sessionId)
- * @param sessionFolder
- * must not be null
- */
- private void cleanup(final File sessionFolder)
- {
- File high = sessionFolder.getParentFile();
- if (high != null && high.list().length == 0)
- {
- if (Files.removeFolder(high))
- {
- File low = high.getParentFile();
- if (low != null && low.list().length == 0)
- {
- Files.removeFolder(low);
- }
- }
- }
- }
- }
-
- /**
- * Returns the file name for specified session. If the session folder (folder that contains the
- * file) does not exist and createSessionFolder is true, the folder will be created.
- *
- * @param sessionId
- * @param createSessionFolder
- * @return file name for pagemap
- */
- private String getSessionFileName(String sessionId, boolean createSessionFolder)
- {
- File sessionFolder = getSessionFolder(sessionId, createSessionFolder);
- return new File(sessionFolder, "data").getAbsolutePath();
- }
-
- /**
- * This folder contains sub-folders named as the session id for which they hold the data.
- *
- * @return the folder where the pages are stored
- */
- protected File getStoreFolder()
- {
- return new File(fileStoreFolder, applicationName + "-filestore");
- }
-
- /**
- * Returns the folder for the specified sessions. If the folder doesn't exist and the create
- * flag is set, the folder will be created.
- *
- * @param sessionId
- * @param create
- * @return folder used to store session data
- */
- protected File getSessionFolder(String sessionId, final boolean create)
- {
- File storeFolder = getStoreFolder();
-
- sessionId = sessionId.replace('*', '_');
- sessionId = sessionId.replace('/', '_');
- sessionId = sessionId.replace(':', '_');
-
- sessionId = createPathFrom(sessionId);
-
- File sessionFolder = new File(storeFolder, sessionId);
- if (create && sessionFolder.exists() == false)
- {
- Files.mkdirs(sessionFolder);
- }
- return sessionFolder;
- }
-
- /**
- * creates a three-level path from the sessionId in the format 0000/0000/<sessionId>. The two
- * prefixing directories are created from the sessionId's hashcode and thus, should be well
- * distributed.
- *
- * This is used to avoid problems with Filesystems allowing no more than 32k entries in a
- * directory.
- *
- * Note that the prefix paths are created from Integers and not guaranteed to be four chars
- * long.
- *
- * @param sessionId
- * must not be null
- * @return path in the form 0000/0000/sessionId
- */
- private String createPathFrom(final String sessionId)
- {
- int sessionIdHashCode = sessionId.hashCode();
- if (sessionIdHashCode == Integer.MIN_VALUE) {
- // Math.abs(MIN_VALUE) == MIN_VALUE, so avoid it
- sessionIdHashCode += 1;
- }
- int hash = Math.abs(sessionIdHashCode);
- String low = String.valueOf(hash % 9973);
- String high = String.valueOf((hash / 9973) % 9973);
- StringBuilder bs = new StringBuilder(sessionId.length() + 10);
- bs.append(low);
- bs.append(File.separator);
- bs.append(high);
- bs.append(File.separator);
- bs.append(sessionId);
-
- return bs.toString();
- }
-
- @Override
- public boolean canBeAsynchronous()
- {
- return true;
- }
-}
http://git-wip-us.apache.org/repos/asf/wicket/blob/bcf76f51/wicket-core/src/main/java/org/apache/wicket/pageStore/DiskPageStore.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/DiskPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/DiskPageStore.java
new file mode 100644
index 0000000..b810218
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/DiskPageStore.java
@@ -0,0 +1,719 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.pageStore;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.io.Serializable;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+
+import org.apache.wicket.Session;
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.page.IManageablePage;
+import org.apache.wicket.pageStore.disk.PageWindowManager;
+import org.apache.wicket.pageStore.disk.PageWindowManager.FileWindow;
+import org.apache.wicket.serialize.ISerializer;
+import org.apache.wicket.util.file.Files;
+import org.apache.wicket.util.io.IOUtils;
+import org.apache.wicket.util.lang.Args;
+import org.apache.wicket.util.lang.Bytes;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A storage of pages on disk.
+ */
+public class DiskPageStore implements IPersistentPageStore
+{
+ private static final Logger log = LoggerFactory.getLogger(DiskPageStore.class);
+
+ private static final String KEY = "wicket:DiskPageStore";
+
+ private static final String INDEX_FILE_NAME = "DiskDataStoreIndex";
+
+ /**
+ * A cache that holds all page stores.
+ */
+ private static final ConcurrentMap<String, DiskPageStore> DISK_STORES = new ConcurrentHashMap<>();
+
+ private final String applicationName;
+
+ private final ISerializer serializer;
+
+ private final Bytes maxSizePerSession;
+
+ private final File fileStoreFolder;
+
+ private final ConcurrentMap<String, DiskData> diskDatas;
+
+ public DiskPageStore(String applicationName, ISerializer serializer, File fileStoreFolder, Bytes maxSizePerSession)
+ {
+ this.applicationName = Args.notNull(applicationName, "applicationName");
+ this.serializer = Args.notNull(serializer, "serializer");
+ this.maxSizePerSession = Args.notNull(maxSizePerSession, "maxSizePerSession");
+ this.fileStoreFolder = Args.notNull(fileStoreFolder, "fileStoreFolder");
+
+ this.diskDatas = new ConcurrentHashMap<>();
+
+ try
+ {
+ if (this.fileStoreFolder.exists() || this.fileStoreFolder.mkdirs())
+ {
+ loadIndex();
+ }
+ else
+ {
+ log.warn("Cannot create file store folder for some reason.");
+ }
+ }
+ catch (SecurityException e)
+ {
+ throw new WicketRuntimeException(
+ "SecurityException occurred while creating DiskDataStore. Consider using a non-disk based IDataStore implementation. "
+ + "See org.apache.wicket.Application.setPageManagerProvider(IPageManagerProvider)",
+ e);
+ }
+
+ if (DISK_STORES.containsKey(applicationName))
+ {
+ throw new IllegalStateException("Store for application with key '" + applicationName + "' already exists.");
+ }
+ DISK_STORES.put(applicationName, this);
+ }
+
+ @Override
+ public void destroy()
+ {
+ log.debug("Destroying...");
+ saveIndex();
+
+ DISK_STORES.remove(applicationName);
+
+ log.debug("Destroyed.");
+ }
+
+ @Override
+ public boolean canBeAsynchronous(IPageContext context)
+ {
+ // session attribute must be added here *before* any asynchronous calls
+ // when session is no longer available
+ getSessionAttribute(context, true);
+
+ return true;
+ }
+
+ @Override
+ public IManageablePage getPage(IPageContext context, int id)
+ {
+ IManageablePage page = null;
+
+ DiskData diskData = getDiskData(context, false);
+ if (diskData != null)
+ {
+ byte[] data = diskData.loadPage(id);
+ if (data != null) {
+ page = (IManageablePage)serializer.deserialize(data);
+ }
+ }
+
+ if (log.isDebugEnabled())
+ {
+ log.debug("Returning page with id '{}' in session with id '{}'", page != null ? "" : "(null)", id, context.getSessionId());
+ }
+
+ return page;
+ }
+
+ @Override
+ public void removePage(IPageContext context, IManageablePage page)
+ {
+ DiskData diskData = getDiskData(context, false);
+ if (diskData != null)
+ {
+ if (log.isDebugEnabled())
+ {
+ log.debug("Removing page with id '{}' in session with id '{}'", page.getPageId(), context.getSessionId());
+ }
+ diskData.removeData(page.getPageId());
+ }
+ }
+
+ @Override
+ public void removeAllPages(IPageContext context)
+ {
+ DiskData diskData = getDiskData(context, false);
+ if (diskData != null)
+ {
+ removeDiskData(diskData);
+ }
+ }
+
+ protected void removeDiskData(DiskData diskData) {
+ synchronized (diskDatas)
+ {
+ diskDatas.remove(diskData.sessionIdentifier);
+ diskData.unbind();
+ }
+ }
+
+ @Override
+ public void addPage(IPageContext context, IManageablePage page)
+ {
+ DiskData diskData = getDiskData(context, true);
+ if (diskData != null)
+ {
+ log.debug("Storing data for page with id '{}' in session with id '{}'", page.getPageId(), context.getSessionId());
+
+ byte[] data = serializer.serialize(page);
+
+ diskData.savePage(page.getPageId(), page.getClass(), data);
+ }
+ }
+
+ /**
+ *
+ * @param context
+ * @param create
+ * @return the session entry
+ */
+ protected DiskData getDiskData(final IPageContext context, final boolean create)
+ {
+ SessionAttribute attribute = getSessionAttribute(context, create);
+
+ if (!create && attribute == null) {
+ return null;
+ }
+
+ return getDiskData(attribute.identifier, create);
+ }
+
+ protected DiskData getDiskData(String sessionIdentifier, boolean create) {
+ if (!create)
+ {
+ return diskDatas.get(sessionIdentifier);
+ }
+
+ DiskData data = new DiskData(this, sessionIdentifier);
+ DiskData existing = diskDatas.putIfAbsent(sessionIdentifier, data);
+ return existing != null ? existing : data;
+ }
+
+ protected SessionAttribute getSessionAttribute(IPageContext context, boolean create)
+ {
+ context.bind();
+
+ SessionAttribute attribute = context.getSessionAttribute(KEY);
+ if (attribute == null && create) {
+ attribute = new SessionAttribute(applicationName, context.getSessionId());
+ context.setSessionAttribute(KEY, attribute);
+ }
+ return attribute;
+ }
+
+ /**
+ * Load the index
+ */
+ @SuppressWarnings("unchecked")
+ private void loadIndex()
+ {
+ File storeFolder = getStoreFolder();
+
+ File index = new File(storeFolder, INDEX_FILE_NAME);
+ if (index.exists() && index.length() > 0)
+ {
+ try
+ {
+ InputStream stream = new FileInputStream(index);
+ ObjectInputStream ois = new ObjectInputStream(stream);
+ try
+ {
+ diskDatas.clear();
+
+ for (DiskData diskData : (List<DiskData>)ois.readObject()) {
+ diskData.pageStore = this;
+ diskDatas.put(diskData.sessionIdentifier, diskData);
+ }
+ } finally {
+ stream.close();
+ ois.close();
+ }
+ }
+ catch (Exception e)
+ {
+ log.error("Couldn't load DiskDataStore index from file " + index + ".", e);
+ }
+ }
+ Files.remove(index);
+ }
+
+ /**
+ *
+ */
+ private void saveIndex()
+ {
+ File storeFolder = getStoreFolder();
+ if (storeFolder.exists())
+ {
+ File index = new File(storeFolder, INDEX_FILE_NAME);
+ Files.remove(index);
+ try
+ {
+ OutputStream stream = new FileOutputStream(index);
+ ObjectOutputStream oos = new ObjectOutputStream(stream);
+ try
+ {
+ List<DiskData> list = new ArrayList<>(diskDatas.size());
+ for (DiskData diskData : diskDatas.values())
+ {
+ if (diskData.sessionIdentifier != null)
+ {
+ list.add(diskData);
+ }
+ }
+ oos.writeObject(list);
+ } finally {
+ stream.close();
+ oos.close();
+ }
+ }
+ catch (Exception e)
+ {
+ log.error("Couldn't write DiskDataStore index to file " + index + ".", e);
+ }
+ }
+ }
+
+ @Override
+ public Set<String> getSessionIdentifiers()
+ {
+ return Collections.unmodifiableSet(diskDatas.keySet());
+ }
+
+ /**
+ *
+ * @param session key
+ * @return a list of the last N page windows
+ */
+ @Override
+ public List<IPersistedPage> getPersistentPages(String sessionIdentifier)
+ {
+ List<IPersistedPage> pages = new ArrayList<>();
+
+ DiskData diskData = getDiskData(sessionIdentifier, false);
+ if (diskData != null)
+ {
+ PageWindowManager windowManager = diskData.getManager();
+
+ pages.addAll(windowManager.getFileWindows());
+ }
+ return pages;
+ }
+
+ @Override
+ public String getSessionIdentifier(IPageContext context)
+ {
+ SessionAttribute sessionAttribute = getSessionAttribute(context, true);
+
+ return sessionAttribute.identifier;
+ }
+
+ @Override
+ public Bytes getTotalSize()
+ {
+ long size = 0;
+
+ synchronized (diskDatas)
+ {
+ for (DiskData diskData : diskDatas.values()) {
+ size = size + diskData.size();
+ }
+ }
+
+ return Bytes.bytes(size);
+ }
+
+ /**
+ * Data held on disk.
+ */
+ protected static class DiskData implements Serializable
+ {
+ private static final long serialVersionUID = 1L;
+
+ private transient DiskPageStore pageStore;
+
+ private transient String fileName;
+
+ private String sessionIdentifier;
+
+ private PageWindowManager manager;
+
+ protected DiskData(DiskPageStore pageStore, String sessionIdentifier)
+ {
+ this.pageStore = pageStore;
+
+ this.sessionIdentifier = sessionIdentifier;
+ }
+
+ public long size()
+ {
+ return manager.getTotalSize();
+ }
+
+ public PageWindowManager getManager()
+ {
+ if (manager == null)
+ {
+ manager = new PageWindowManager(pageStore.maxSizePerSession.bytes());
+ }
+ return manager;
+ }
+
+ private String getFileName()
+ {
+ if (fileName == null)
+ {
+ fileName = pageStore.getSessionFileName(sessionIdentifier, true);
+ }
+ return fileName;
+ }
+
+ /**
+ * @return session id
+ */
+ public String getKey()
+ {
+ return sessionIdentifier;
+ }
+
+ /**
+ * Saves the serialized page to appropriate file.
+ *
+ * @param pageId
+ * @param clazz
+ * @param data
+ */
+ public synchronized void savePage(int pageId, Class<? extends IManageablePage> clazz, byte data[])
+ {
+ if (sessionIdentifier == null)
+ {
+ return;
+ }
+
+ // only save page that has some data
+ if (data != null)
+ {
+ // allocate window for page
+ FileWindow window = getManager().createPageWindow(pageId, clazz, data.length);
+
+ FileChannel channel = getFileChannel(true);
+ if (channel != null)
+ {
+ try
+ {
+ // write the content
+ channel.write(ByteBuffer.wrap(data), window.getFilePartOffset());
+ }
+ catch (IOException e)
+ {
+ log.error("Error writing to a channel " + channel, e);
+ }
+ finally
+ {
+ IOUtils.closeQuietly(channel);
+ }
+ }
+ else
+ {
+ log.warn(
+ "Cannot save page with id '{}' because the data file cannot be opened.",
+ pageId);
+ }
+ }
+ }
+
+ /**
+ * Removes the page from pagemap file.
+ *
+ * @param pageId
+ */
+ public synchronized void removeData(int pageId)
+ {
+ if (sessionIdentifier == null)
+ {
+ return;
+ }
+
+ getManager().removePage(pageId);
+ }
+
+ /**
+ * Loads the part of pagemap file specified by the given PageWindow.
+ *
+ * @param window
+ * @return serialized page data
+ */
+ public byte[] loadData(FileWindow window)
+ {
+ byte[] result = null;
+ FileChannel channel = getFileChannel(false);
+ if (channel != null)
+ {
+ ByteBuffer buffer = ByteBuffer.allocate(window.getFilePartSize());
+ try
+ {
+ channel.read(buffer, window.getFilePartOffset());
+ if (buffer.hasArray())
+ {
+ result = buffer.array();
+ }
+ }
+ catch (IOException e)
+ {
+ log.error("Error reading from file channel " + channel, e);
+ }
+ finally
+ {
+ IOUtils.closeQuietly(channel);
+ }
+ }
+ return result;
+ }
+
+ private FileChannel getFileChannel(boolean create)
+ {
+ FileChannel channel = null;
+ File file = new File(getFileName());
+ if (create || file.exists())
+ {
+ String mode = create ? "rw" : "r";
+ try
+ {
+ RandomAccessFile randomAccessFile = new RandomAccessFile(file, mode);
+ channel = randomAccessFile.getChannel();
+ }
+ catch (FileNotFoundException fnfx)
+ {
+ // can happen if the file is locked. WICKET-4176
+ log.error(fnfx.getMessage(), fnfx);
+ }
+ }
+ return channel;
+ }
+
+ /**
+ * Loads the specified page data.
+ *
+ * @param id
+ * @return page data or null if the page is no longer in pagemap file
+ */
+ public synchronized byte[] loadPage(int id)
+ {
+ if (sessionIdentifier == null)
+ {
+ return null;
+ }
+
+ FileWindow window = getManager().getPageWindow(id);
+ if (window == null)
+ {
+ return null;
+ }
+
+ return loadData(window);
+ }
+
+ /**
+ * Deletes all files for this session.
+ */
+ public synchronized void unbind()
+ {
+ File sessionFolder = pageStore.getSessionFolder(sessionIdentifier, false);
+ if (sessionFolder.exists())
+ {
+ Files.removeFolder(sessionFolder);
+ cleanup(sessionFolder);
+ }
+
+ sessionIdentifier = null;
+ }
+
+ /**
+ * deletes the sessionFolder's parent and grandparent, if (and only if) they are empty.
+ *
+ * @see #createPathFrom(String sessionId)
+ * @param sessionFolder
+ * must not be null
+ */
+ private void cleanup(final File sessionFolder)
+ {
+ File high = sessionFolder.getParentFile();
+ if (high != null && high.list().length == 0)
+ {
+ if (Files.removeFolder(high))
+ {
+ File low = high.getParentFile();
+ if (low != null && low.list().length == 0)
+ {
+ Files.removeFolder(low);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the file name for specified session. If the session folder (folder that contains the
+ * file) does not exist and createSessionFolder is true, the folder will be created.
+ *
+ * @param sessionIdentifier
+ * @param createSessionFolder
+ * @return file name for pagemap
+ */
+ private String getSessionFileName(String sessionIdentifier, boolean createSessionFolder)
+ {
+ File sessionFolder = getSessionFolder(sessionIdentifier, createSessionFolder);
+ return new File(sessionFolder, "data").getAbsolutePath();
+ }
+
+ /**
+ * This folder contains sub-folders named as the session id for which they hold the data.
+ *
+ * @return the folder where the pages are stored
+ */
+ protected File getStoreFolder()
+ {
+ return new File(fileStoreFolder, applicationName + "-filestore");
+ }
+
+ /**
+ * Returns the folder for the specified sessions. If the folder doesn't exist and the create
+ * flag is set, the folder will be created.
+ *
+ * @param sessionIdentifier
+ * @param create
+ * @return folder used to store session data
+ */
+ protected File getSessionFolder(String sessionIdentifier, final boolean create)
+ {
+ File storeFolder = getStoreFolder();
+
+ sessionIdentifier = sessionIdentifier.replace('*', '_');
+ sessionIdentifier = sessionIdentifier.replace('/', '_');
+ sessionIdentifier = sessionIdentifier.replace(':', '_');
+
+ sessionIdentifier = createPathFrom(sessionIdentifier);
+
+ File sessionFolder = new File(storeFolder, sessionIdentifier);
+ if (create && sessionFolder.exists() == false)
+ {
+ Files.mkdirs(sessionFolder);
+ }
+ return sessionFolder;
+ }
+
+ /**
+ * creates a three-level path from the sessionId in the format 0000/0000/<sessionId>. The two
+ * prefixing directories are created from the sessionId's hashcode and thus, should be well
+ * distributed.
+ *
+ * This is used to avoid problems with Filesystems allowing no more than 32k entries in a
+ * directory.
+ *
+ * Note that the prefix paths are created from Integers and not guaranteed to be four chars
+ * long.
+ *
+ * @param sessionId
+ * must not be null
+ * @return path in the form 0000/0000/sessionId
+ */
+ private String createPathFrom(final String sessionId)
+ {
+ int sessionIdHashCode = sessionId.hashCode();
+ if (sessionIdHashCode == Integer.MIN_VALUE) {
+ // Math.abs(MIN_VALUE) == MIN_VALUE, so avoid it
+ sessionIdHashCode += 1;
+ }
+ int hash = Math.abs(sessionIdHashCode);
+ String low = String.valueOf(hash % 9973);
+ String high = String.valueOf((hash / 9973) % 9973);
+ StringBuilder bs = new StringBuilder(sessionId.length() + 10);
+ bs.append(low);
+ bs.append(File.separator);
+ bs.append(high);
+ bs.append(File.separator);
+ bs.append(sessionId);
+
+ return bs.toString();
+ }
+
+ /**
+ * Attribute held in session.
+ */
+ static class SessionAttribute implements Serializable, HttpSessionBindingListener {
+
+ private final String applicationName;
+
+ /**
+ * The identifier of the session, must not be equal to {@link Session#getId()}, e.g. when
+ * the container changes the id after authorization.
+ */
+ public final String identifier;
+
+ public SessionAttribute(String applicationName, String sessionIdentifier) {
+ this.applicationName = Args.notNull(applicationName, "applicationName");
+ this.identifier = Args.notNull(sessionIdentifier, "sessionIdentifier");
+ }
+
+
+ @Override
+ public void valueBound(HttpSessionBindingEvent event)
+ {
+ }
+
+ @Override
+ public void valueUnbound(HttpSessionBindingEvent event)
+ {
+ DiskPageStore store = DISK_STORES.get(applicationName);
+ if (store == null) {
+ log.warn("Cannot remove data '{}' because disk store for application '{}' is no longer present.", identifier, applicationName);
+ } else {
+ DiskData diskData = store.getDiskData(identifier, false);
+ if (diskData != null) {
+ store.removeDiskData(diskData);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/wicket/blob/bcf76f51/wicket-core/src/main/java/org/apache/wicket/pageStore/GroupingPageStore.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/GroupingPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/GroupingPageStore.java
new file mode 100644
index 0000000..1771763
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/GroupingPageStore.java
@@ -0,0 +1,289 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.pageStore;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.wicket.MetaDataEntry;
+import org.apache.wicket.MetaDataKey;
+import org.apache.wicket.Session;
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.page.IManageablePage;
+import org.apache.wicket.util.string.Strings;
+
+/**
+ * An {@link IPageStore} that groups pages.
+ *
+ * @see #getGroup(IManageablePage)
+ */
+public abstract class GroupingPageStore extends DelegatingPageStore
+{
+
+ private static final String DEFAULT_GROUP = "default";
+
+ private static final MetaDataKey<SessionData> KEY = new MetaDataKey<SessionData>()
+ {
+ private static final long serialVersionUID = 1L;
+ };
+
+ private int maxGroups;
+
+ /**
+ * Is a group of a page stable.
+ */
+ private boolean stableGroups = false;
+
+ /**
+ * @param delegate
+ * store to delegate to
+ * @param maxGroups
+ * maximum groups to keep
+ */
+ public GroupingPageStore(IPageStore delegate, int maxGroups)
+ {
+ super(delegate);
+
+ this.maxGroups = maxGroups;
+ }
+
+ /**
+ * Indicate that groups are stable, i.e. the group of a page never changes.
+ */
+ public GroupingPageStore withStableGroups()
+ {
+ stableGroups = true;
+
+ return this;
+ }
+
+ /**
+ * Get the group of a page, default is <code>"default"</code>
+ *
+ * @return group of page, must not be empty
+ */
+ protected String getGroup(IManageablePage page)
+ {
+ return DEFAULT_GROUP;
+ }
+
+ private String getGroupInternal(IManageablePage page)
+ {
+ String group = getGroup(page);
+
+ if (Strings.isEmpty(group))
+ {
+ throw new WicketRuntimeException("group must not be empy");
+ }
+
+ return group;
+ }
+
+ @Override
+ public void addPage(IPageContext context, IManageablePage page)
+ {
+ SessionData sessionData = getSessionData(context);
+
+ sessionData.addPage(context, page, getGroupInternal(page), maxGroups, stableGroups, getDelegate());
+ }
+
+ @Override
+ public void removePage(IPageContext context, IManageablePage page)
+ {
+ SessionData sessionData = getSessionData(context);
+
+ sessionData.removePage(context, page, getDelegate());
+ }
+
+ @Override
+ public void removeAllPages(IPageContext context)
+ {
+ SessionData sessionData = getSessionData(context);
+
+ sessionData.removeAllPages(context, getDelegate());
+ }
+
+ @Override
+ public IManageablePage getPage(IPageContext context, int id)
+ {
+ SessionData sessionData = getSessionData(context);
+
+ return sessionData.getPage(context, id, getDelegate());
+ }
+
+ private SessionData getSessionData(IPageContext context)
+ {
+ SessionData data = context.getSessionData(KEY);
+ if (data == null)
+ {
+ context.bind();
+ data = new SessionData();
+
+ context.setSessionData(KEY, data);
+ }
+
+ return data;
+ }
+
+ /**
+ * Data kept in the {@link Session}.
+ */
+ static class SessionData implements Serializable
+ {
+ Set<String> groups = new LinkedHashSet<>();
+
+ private Map<String, MetaDataEntry<?>[]> metaData = new HashMap<>();
+
+ public synchronized <T> void setMetaData(String group, MetaDataKey<T> key, T value)
+ {
+ metaData.put(group, key.set(metaData.get(group), value));
+ }
+
+ public synchronized <T> T getMetaData(String group, MetaDataKey<T> key)
+ {
+ return key.get(metaData.get(group));
+ }
+
+ public synchronized void addPage(IPageContext context, IManageablePage page, String group, int maxGroups, boolean stableGroups, IPageStore delegate)
+ {
+ if (stableGroups == false)
+ {
+ // group might have changed, so remove page first from all groups
+ for (String other : groups)
+ {
+ delegate.removePage(new GroupContext(context, this, other), page);
+ }
+ }
+
+ groups.add(group);
+ delegate.addPage(new GroupContext(context, this, group), page);
+
+ Iterator<String> iterator = groups.iterator();
+ int size = groups.size();
+ while (size > maxGroups)
+ {
+ String other = iterator.next();
+ iterator.remove();
+ size--;
+
+ delegate.removeAllPages(new GroupContext(context, this, other));
+ }
+ }
+
+ public IManageablePage getPage(IPageContext context, int id, IPageStore delegate)
+ {
+ for (String group : groups)
+ {
+ IManageablePage page = delegate.getPage(new GroupContext(context, this, group), id);
+ if (page != null)
+ {
+ return page;
+ }
+ }
+ return null;
+ }
+
+ public synchronized void removePage(IPageContext context, IManageablePage page, IPageStore delegate)
+ {
+ for (String group : groups)
+ {
+ delegate.removePage(new GroupContext(context, this, group), page);
+ }
+ }
+
+ public synchronized void removeAllPages(IPageContext context, IPageStore delegate)
+ {
+ for (String group : groups)
+ {
+ delegate.removeAllPages(new GroupContext(context, this, group));
+ }
+ }
+ }
+
+ /**
+ * Context passed to the delegate store to group data and attributes.
+ */
+ static class GroupContext implements IPageContext
+ {
+
+ private final IPageContext context;
+
+ private final SessionData sessionData;
+
+ private final String group;
+
+ public GroupContext(IPageContext context, SessionData sessionData, String group)
+ {
+ this.context = context;
+ this.sessionData = sessionData;
+ this.group = group;
+ }
+
+ @Override
+ public String getSessionId()
+ {
+ return context.getSessionId() + "_" + group;
+ }
+
+ @Override
+ public <T extends Serializable> void setSessionData(MetaDataKey<T> key, T value)
+ {
+ sessionData.setMetaData(group, key, value);
+ }
+
+ @Override
+ public <T extends Serializable> T getSessionData(MetaDataKey<T> key)
+ {
+ return sessionData.getMetaData(group, key);
+ }
+
+ @Override
+ public <T extends Serializable> void setSessionAttribute(String key, T value)
+ {
+ context.setSessionAttribute(key + "_" + group, value);
+ }
+
+ @Override
+ public <T extends Serializable> T getSessionAttribute(String key)
+ {
+ return context.getSessionAttribute(key + "_" + group);
+ }
+
+ @Override
+ public <T> void setRequestData(MetaDataKey<T> key, T data)
+ {
+ throw new WicketRuntimeException("no request available for group");
+ }
+
+ @Override
+ public <T> T getRequestData(MetaDataKey<T> key)
+ {
+ throw new WicketRuntimeException("no request available for group");
+ }
+
+
+ @Override
+ public void bind()
+ {
+ context.bind();
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/wicket/blob/bcf76f51/wicket-core/src/main/java/org/apache/wicket/pageStore/IDataStore.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/IDataStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/IDataStore.java
deleted file mode 100644
index b59176a..0000000
--- a/wicket-core/src/main/java/org/apache/wicket/pageStore/IDataStore.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.wicket.pageStore;
-
-/**
- * Data stores are used to persist (read & write) Wicket page data to a persistent store like e.g.
- * files or databases.
- *
- * @see IPageStore
- */
-public interface IDataStore
-{
- /**
- * Get data associated with the session id and page id.
- *
- * @param sessionId
- * Session ID
- * @param id
- * Page ID
- * @return All the page data persisted
- */
- byte[] getData(String sessionId, int id);
-
- /**
- * Remove all persisted data related to the session id and page id
- *
- * @param sessionId
- * Session ID
- * @param id
- * Page ID
- */
- void removeData(String sessionId, int id);
-
- /**
- * Remove all page data for the session id
- *
- * @param sessionId
- * Session ID
- */
- void removeData(String sessionId);
-
- /**
- * Store the page data
- *
- * @param sessionId
- * Session ID
- * @param id
- * Page ID
- * @param data
- * Page data
- */
- void storeData(String sessionId, int id, byte[] data);
-
- /**
- * Properly close the data store and possibly open resource handles
- */
- void destroy();
-
- /**
- *
- * @return whether the data store is replicated
- */
- boolean isReplicated();
-
- /**
- * @return whether the implementation can be wrapped in {@link AsynchronousDataStore}
- */
- boolean canBeAsynchronous();
-}
http://git-wip-us.apache.org/repos/asf/wicket/blob/bcf76f51/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageContext.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageContext.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageContext.java
new file mode 100644
index 0000000..5b9fcfb
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageContext.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.pageStore;
+
+import java.io.Serializable;
+
+import org.apache.wicket.MetaDataKey;
+import org.apache.wicket.page.IManageablePage;
+
+/**
+ * Context of a {@link IManageablePage} when it is store in a {@link IPageStore}, decoupling it from
+ * request cycle and session.
+ *
+ * @author Matej Knopp
+ * @author svenmeier
+ */
+public interface IPageContext
+{
+ /**
+ * Set data into the current request.
+ *
+ * @param key
+ * key
+ * @param value
+ * value
+ */
+ <T> void setRequestData(MetaDataKey<T> key, T value);
+
+ /**
+ * Get data from the current request.
+ *
+ * @param key
+ * key
+ * @return value
+ */
+ <T> T getRequestData(MetaDataKey<T> key);
+
+ /**
+ * Set an attribute in the session.
+ *
+ * @param key
+ * key
+ * @param value
+ * value
+ */
+ <T extends Serializable> void setSessionAttribute(String key, T value);
+
+ /**
+ * Get an attribute from the session.
+ *
+ * @param key
+ * key
+ * @param value
+ * value
+ */
+ <T extends Serializable> T getSessionAttribute(String key);
+
+ /**
+ * Set data into the session.
+ *
+ * @param key
+ * key
+ * @param value
+ * value
+ */
+ <T extends Serializable> void setSessionData(MetaDataKey<T> key, T value);
+
+ /**
+ * Get data from the session.
+ *
+ * @param key
+ * key
+ * @return value
+ */
+ <T extends Serializable> T getSessionData(MetaDataKey<T> key);
+
+ /**
+ * Bind the current session. This make a temporary session become persistent across requests.
+ */
+ void bind();
+
+ /**
+ * Get the identifier of the session.
+ *
+ * @return session id
+ */
+ String getSessionId();
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/wicket/blob/bcf76f51/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageStore.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageStore.java
index ed1b9d3..8860b33 100644
--- a/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageStore.java
+++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/IPageStore.java
@@ -16,96 +16,79 @@
*/
package org.apache.wicket.pageStore;
-import java.io.Serializable;
-
import org.apache.wicket.page.IManageablePage;
/**
- * {@link IPageStore} role is to mediate the storing and loading of pages done by {@link IDataStore}
- * s. {@link IPageStore} may pre-process the pages before passing them to
- * {@link IDataStore#storeData(String, int, byte[])} and to post-process them after
- * {@link IDataStore#getData(String, int)}.
- *
- * @see IDataStore
+ * A store of pages
*/
public interface IPageStore
{
/**
- * Destroy the store.
- */
- void destroy();
-
- /**
- * Restores a page from the persistent layer.
+ * This method must be called before any attempt to call
+ * {@link #addPage(IPageContext, IManageablePage)} asynchronously, as done by
+ * {@link AsynchronousPageStore}.
*
- * @param sessionId
- * The session of the page that must be removed
- * @param pageId
- * The id of the page.
- * @return The page
+ * @return whether {@link #addPage(IPageContext, IManageablePage)} may be called asynchronously,
+ * default is <code>false</code>
*/
- IManageablePage getPage(String sessionId, int pageId);
+ default boolean canBeAsynchronous(IPageContext context)
+ {
+ return false;
+ }
/**
- * Removes a page from the persistent layer.
+ * Stores the page-
*
- * @param sessionId
- * The session of the page that must be removed
- * @param pageId
- * The id of the page.
+ * @param context
+ * the context of the page
+ * @param id
+ * the id of the page.
*/
- void removePage(String sessionId, int pageId);
+ void addPage(IPageContext context, IManageablePage page);
/**
- * Stores the page to a persistent layer. The page should be stored under the id and the version
- * number.
+ * Removes a page from storage.
*
- * @param sessionId
- * The session of the page that must be removed
- * @param page
- * The page to store
+ * @param context
+ * the context of the page
+ * @param id
+ * the id of the page.
*/
- void storePage(String sessionId, IManageablePage page);
+ void removePage(IPageContext context, IManageablePage page);
/**
- * The page store should cleanup all the pages for that sessionid.
+ * All pages should be removed from storage for the given context.
*
- * @param sessionId
- * The session of the page that must be removed
+ * @param context
+ * the context of the pages
*/
- void unbind(String sessionId);
+ void removeAllPages(IPageContext context);
/**
- * Process the page before the it gets serialized. The page can be either real page instance or
- * object returned by {@link #restoreAfterSerialization(Serializable)}.
+ * Restores a page from storage.
*
- * @param sessionId
- * The session of the page that must be removed
- * @param page
- * @return The Page itself or a SerializedContainer for that page
+ * @param context
+ * the context of the page
+ * @param id
+ * the id of the page.
+ * @return the page
*/
- Serializable prepareForSerialization(String sessionId, Serializable page);
+ IManageablePage getPage(IPageContext context, int id);
/**
- * This method should restore the serialized page to intermediate object that can be converted
- * to real page instance using {@link #convertToPage(Object)}.
+ * Detach from the current context.
*
- * @param serializable
- * @return Page
+ * @param context
+ * the context of the pages
*/
- Object restoreAfterSerialization(Serializable serializable);
+ default void detach(IPageContext context)
+ {
+ }
/**
- * Converts a page representation to an instance of {@link IManageablePage}
- *
- * @param page
- * some kind of page representation
- * @return page
- */
- IManageablePage convertToPage(Object page);
-
- /**
- * @return whether the implementation can be wrapped in {@link AsynchronousPageStore}
+ * Destroy the store.
*/
- boolean canBeAsynchronous();
+ default void destroy()
+ {
+ }
}