You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by jc...@apache.org on 2006/11/10 22:46:19 UTC

svn commit: r473514 - in /incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket: AccessStackPageMap.java Component.java ISessionFactory.java Page.java PageMap.java RequestCycle.java Session.java

Author: jcompagner
Date: Fri Nov 10 13:46:18 2006
New Revision: 473514

URL: http://svn.apache.org/viewvc?view=rev&rev=473514
Log:
stateless support

Added:
    incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/AccessStackPageMap.java
Modified:
    incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/Component.java
    incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/ISessionFactory.java
    incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/Page.java
    incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/PageMap.java
    incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/RequestCycle.java
    incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/Session.java

Added: incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/AccessStackPageMap.java
URL: http://svn.apache.org/viewvc/incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/AccessStackPageMap.java?view=auto&rev=473514
==============================================================================
--- incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/AccessStackPageMap.java (added)
+++ incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/AccessStackPageMap.java Fri Nov 10 13:46:18 2006
@@ -0,0 +1,447 @@
+/*
+ * $Id: AccessStackPageMap.java 5791 2006-05-20 00:32:57 +0000 (Sat, 20 May
+ * 2006) joco01 $ $Revision: 461715 $ $Date: 2006-05-20 00:32:57 +0000 (Sat, 20
+ * May 2006) $
+ * 
+ * ==============================================================================
+ * Licensed 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 wicket;
+
+import java.io.Serializable;
+import java.util.Iterator;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import wicket.session.pagemap.IPageMapEntry;
+import wicket.util.collections.ArrayListStack;
+
+/**
+ * A container for pages held in the session. PageMap is a parameter to several
+ * methods in the Wicket API. You can get a PageMap by name from a Session with
+ * Session.getPageMap(String pageMapName) or more conveniently with
+ * PageMap.forName(String pageMapName). But you should not hold onto a reference
+ * to the pagemap (just as you should not hold onto a reference to your Session
+ * but should get it each time you need it instead). Instead, create a strongly
+ * typed accessor method like this:
+ * 
+ * <pre>
+ * public PageMap getMyPageMap()
+ * {
+ * 	return PageMap.forName(&quot;myPageMapName&quot;);
+ * }
+ * </pre>
+ * 
+ * If the page map with the given name is not found, one will be automatically
+ * created.
+ * 
+ * @author Jonathan Locke
+ */
+public final class AccessStackPageMap extends PageMap implements Serializable
+{
+	private static final long serialVersionUID = 1L;
+
+	/** Log. */
+	private static final Log log = LogFactory.getLog(AccessStackPageMap.class);
+
+
+	/** Stack of entry accesses by id */
+	private final ArrayListStack accessStack = new ArrayListStack(8);
+
+
+	/**
+	 * Holds information about a pagemap access
+	 * 
+	 * @author Jonathan
+	 */
+	public static class Access implements Serializable
+	{
+		private static final long serialVersionUID = 1L;
+
+		int id;
+		int version;
+
+		/**
+		 * @see java.lang.Object#equals(java.lang.Object)
+		 */
+		public boolean equals(Object obj)
+		{
+			if (obj instanceof Access)
+			{
+				Access tmp = (Access)obj;
+				return tmp.id == id && tmp.version == version;
+			}
+			return false;
+		}
+
+		/**
+		 * Gets id.
+		 * 
+		 * @return id
+		 */
+		public final int getId()
+		{
+			return id;
+		}
+
+		/**
+		 * Gets version.
+		 * 
+		 * @return version
+		 */
+		public final int getVersion()
+		{
+			return version;
+		}
+
+		/**
+		 * @see java.lang.Object#hashCode()
+		 */
+		public int hashCode()
+		{
+			return id + (version << 16);
+		}
+
+		/**
+		 * @see java.lang.Object#toString()
+		 */
+		public String toString()
+		{
+			return "[Access id=" + id + ", version=" + version + "]";
+		}
+	}
+
+	/**
+	 * Constructor
+	 * 
+	 * @param name
+	 *            The name of this page map
+	 * @param session
+	 *            The session holding this page map
+	 */
+	public AccessStackPageMap(final String name, final Session session)
+	{
+		super(name, session);
+	}
+
+	/**
+	 * Removes all pages from this map
+	 */
+	public final void clear()
+	{
+		super.clear();
+		// Clear access stack
+		accessStack.clear();
+		dirty();
+	}
+
+	// TODO Post 1.2: We should encode the page id of the current page into the
+	// URL for truly stateless pages so we can adjust the stack correctly
+
+	/**
+	 * Returns a stack of PageMap.Access entries pushed in the order that the
+	 * pages and versions were accessed.
+	 * 
+	 * @return Stack containing ids of entries in access order.
+	 */
+	public final ArrayListStack getAccessStack()
+	{
+		return accessStack;
+	}
+
+	/**
+	 * @return Number of page versions stored in this page map
+	 */
+	public final int getVersions()
+	{
+		return accessStack.size();
+	}
+
+	/**
+	 * Gets the most recently accessed page map entry off the top of the entry
+	 * access stack. This is guaranteed to be the most recently accessed entry
+	 * IF AND ONLY IF the user just came from a stateful page. If the user could
+	 * get to the current page from a stateless page, this method may not work
+	 * if the user uses the back button. For a detailed explanation of this
+	 * issue, see getAccessStack().
+	 * 
+	 * @see PageMap#getAccessStack()
+	 * 
+	 * @return Previous pagemap entry in terms of access
+	 */
+	public final IPageMapEntry lastAccessedEntry()
+	{
+		return getEntry(peekAccess().getId());
+	}
+
+
+	/**
+	 * @param entry
+	 *            The entry to remove
+	 */
+	public final void removeEntry(final IPageMapEntry entry)
+	{
+		if(entry == null)
+		{
+			// 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;
+		}
+		// Remove entry from session
+		Session session = getSession();
+		synchronized (session)
+		{
+			session.removeAttribute(attributeForId(entry.getNumericId()));
+
+			// Remove page from acccess stack
+			final Iterator stack = accessStack.iterator();
+			while (stack.hasNext())
+			{
+				final Access access = (Access)stack.next();
+				if (access.id == entry.getNumericId())
+				{
+					stack.remove();
+				}
+			}
+
+			// Let the session know we changed the pagemap
+			dirty();
+		}
+	}
+
+	/**
+	 * Retrieves page with given id.
+	 * 
+	 * @param id
+	 *            The page identifier
+	 * @param versionNumber
+	 *            The version to get
+	 * @return Any page having the given id
+	 */
+	protected final Page get(final int id, int versionNumber)
+	{
+		final IPageMapEntry entry = (IPageMapEntry)getSession().getAttribute(attributeForId(id));
+		if (entry != null)
+		{
+			// Get page as dirty
+			Page page = entry.getPage();
+
+			// TODO Performance: Is this really the case is a page always dirty
+			// even if we just render it again? POSSIBLE ANSWER: The page could
+			// mark itself as clean to prevent replication, but the reverse is
+			// probably not desirable (pages marking themselves dirty manually)
+			// We ought to think about this a bit and consider whether this
+			// could be tied in with version management. It's only when a page's
+			// version changes that it should be considered dirty, because then
+			// some kind of state changed. Right? - Jonathan
+			page.dirty();
+
+			// Get the version of the page requested from the page
+			final Page version = page.getVersion(versionNumber);
+
+			// Entry has been accessed
+			// pushAccess(entry);
+			// Entry has been accessed
+			access(entry, versionOf(entry));
+
+
+			// Is the requested version available?
+			if (version != null)
+			{
+				// Need to update session with new page?
+				if (version != page)
+				{
+					// This is our new page
+					page = version;
+
+					// Replaces old page entry
+					page.getPageMap().put(page);
+				}
+			}
+			else
+			{
+				if (log.isInfoEnabled())
+				{
+					log.info("Unable to get version " + versionNumber + " of page " + page);
+				}
+				return null;
+			}
+			return page;
+		}
+		return null;
+	}
+
+	/**
+	 * @param page
+	 *            The page to put into this map
+	 */
+	protected final void put(final Page page)
+	{
+		// Page only goes into session if it is stateless
+		if (!page.isPageStateless())
+		{
+			Session session = getSession();
+			// Get page map entry from page
+			final IPageMapEntry entry = page.getPageMapEntry();
+
+			// Entry has been accessed
+			pushAccess(entry);
+
+			// Store entry in session
+			final String attribute = attributeForId(entry.getNumericId());
+
+			if (session.getAttribute(attribute) == null)
+			{
+				// Set attribute if it is a new page, so that it will exists
+				// already for other threads that can come on the same time.
+				session.setAttribute(attribute, entry);
+			}
+			else
+			{
+				// Else don't set it directly but add to the dirty map
+				session.dirtyPage(page);
+			}
+
+			// Evict any page(s) as need be
+			session.getApplication().getSessionSettings().getPageMapEvictionStrategy().evict(this);
+		}
+	}
+
+	/**
+	 * @param entry
+	 *            Add entry to access list
+	 * @param version
+	 *            Version number being accessed
+	 */
+	private final void access(final IPageMapEntry entry, final int version)
+	{
+		// See if the version being accessed is already in the stack
+		boolean add = true;
+		int id = entry.getNumericId();
+		for (int i = accessStack.size() - 1; i >= 0; i--)
+		{
+			final Access access = (Access)accessStack.get(i);
+
+			// If we found id and version in access stack
+			if (access.id == id && access.version == version)
+			{
+				// No need to add since id and version are already in stack
+				add = false;
+
+				// Pop entries to reveal that version at top of stack
+				// because the user used the back button
+				while (i < accessStack.size() - 1)
+				{
+					// Pop unreachable access off top of stack
+					final Access topAccess = popAccess();
+
+					// Get entry for access
+					final IPageMapEntry top = getEntry(topAccess.getId());
+
+					// If it's a page we can remove version info
+					if (top instanceof Page)
+					{
+						// If there's more than one version
+						Page topPage = (Page)top;
+						if (topPage.getVersions() > 1)
+						{
+							// Remove version the top access version (-1)
+							topPage.getVersion(topAccess.getVersion() - 1);
+						}
+						else
+						{
+							// Remove whole page
+							remove(topPage);
+						}
+					}
+					else if(top != null)
+					{
+						// Remove entry
+						removeEntry(top);
+					}
+				}
+				break;
+			}
+		}
+
+		// If the user did not use the back button
+		if (add)
+		{
+			pushAccess(entry);
+		}
+	}
+
+	/**
+	 * @return Access entry on top of the access stack
+	 */
+	private final Access peekAccess()
+	{
+		return (Access)accessStack.peek();
+	}
+
+	/**
+	 * Removes access entry on top of stack
+	 * 
+	 * @return Access entry on top of the access stack
+	 */
+	private final Access popAccess()
+	{
+		dirty();
+		return (Access)accessStack.pop();
+	}
+
+	/**
+	 * @param entry
+	 *            Entry that was accessed
+	 */
+	private final void pushAccess(IPageMapEntry entry)
+	{
+		// Create new access entry
+		final Access access = new Access();
+		access.id = entry.getNumericId();
+		access.version = versionOf(entry);
+		if (accessStack.size() > 0)
+		{
+			if (peekAccess().equals(access))
+			{
+				return;
+			}
+			int index = accessStack.indexOf(access);
+			if (index >= 0)
+			{
+				accessStack.remove(index);
+			}
+		}
+		accessStack.push(access);
+		dirty();
+	}
+
+	/**
+	 * @param entry
+	 *            Page map entry
+	 * @return Version of entry
+	 */
+	private final int versionOf(final IPageMapEntry entry)
+	{
+		if (entry instanceof Page)
+		{
+			return ((Page)entry).getCurrentVersionNumber();
+		}
+
+		// If entry is not a page, it cannot have versions because the Page
+		// is constructed on the fly.
+		return 0;
+	}
+}

Modified: incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/Component.java
URL: http://svn.apache.org/viewvc/incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/Component.java?view=diff&rev=473514&r1=473513&r2=473514
==============================================================================
--- incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/Component.java (original)
+++ incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/Component.java Fri Nov 10 13:46:18 2006
@@ -2649,6 +2649,45 @@
 	{
 		return this.getFlag(FLAG_IGNORE_ATTRIBUTE_MODIFIER);
 	}
+	
+	/**
+	 * Returns whether the component can be stateless. Being able to be
+	 * stateless doesn't necessary mean, that the component should be stateless.
+	 * Whether the component should be stateless depends on
+	 * 
+	 * @return whether the component can be stateless
+	 */
+	protected boolean getStatelessHint()
+	{
+		return true;
+	}
+
+	/**
+	 * Returns if the component is stateless or not. It checks the stateless
+	 * hint if that is false it returns directly false. If that is still true it
+	 * checks all its behaviours if they can be stateless.
+	 * 
+	 * @return whether the component is stateless.
+	 */
+	public final boolean isStateless()
+	{
+		if (!getStatelessHint())
+		{
+			return false;
+		}
+
+		final Iterator behaviors = getBehaviors().iterator();
+
+		while (behaviors.hasNext())
+		{
+			IBehavior behavior = (IBehavior)behaviors.next();
+			if (!behavior.getStatelessHint())
+			{
+				return false;
+			}
+		}
+		return true;
+	}
 
 	/**
 	 * Called just after a component is rendered.

Modified: incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/ISessionFactory.java
URL: http://svn.apache.org/viewvc/incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/ISessionFactory.java?view=diff&rev=473514&r1=473513&r2=473514
==============================================================================
--- incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/ISessionFactory.java (original)
+++ incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/ISessionFactory.java Fri Nov 10 13:46:18 2006
@@ -25,10 +25,16 @@
  */
 public interface ISessionFactory
 {
+
 	/**
 	 * Creates a new session
 	 * 
+	 * @param request
+	 *            The request that will create this session.
+	 * 
 	 * @return The session
+	 * 
+	 * @since 1.3
 	 */
-	Session newSession();
+	Session newSession(Request request);
 }

Modified: incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/Page.java
URL: http://svn.apache.org/viewvc/incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/Page.java?view=diff&rev=473514&r1=473513&r2=473514
==============================================================================
--- incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/Page.java (original)
+++ incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/Page.java Fri Nov 10 13:46:18 2006
@@ -153,6 +153,9 @@
 	/** True if component changes are being tracked. */
 	private static final short FLAG_TRACK_CHANGES = FLAG_RESERVED4;
 
+	/** True if the page should try to be stateless */
+	private static final int FLAG_STATELESS_HINT = FLAG_RESERVED5;
+
 	/** Log. */
 	private static final Log log = LogFactory.getLog(Page.class);
 
@@ -178,7 +181,7 @@
 	 * Boolean if the page is stateless, so it doesn't have to be in the page
 	 * map, will be set in urlFor
 	 */
-	private transient boolean stateless = true;
+	private transient Boolean stateless = null;
 
 	/** Version manager for this page */
 	private IPageVersionManager versionManager;
@@ -357,7 +360,7 @@
 		renderedComponents = null;
 
 		// Reset it to stateless so that it can be tested again
-		this.stateless = true;
+		this.stateless = null;
 
 		// Set form component values from cookies
 		setFormComponentValuesFromCookies();
@@ -415,8 +418,13 @@
 		// Check rendering if it happened fully
 		checkRendering(this);
 
-		// Add/touch the response page in the session (its pagemap).
-		getSession().touch(this);
+		if (!isPageStateless())
+		{
+			// trigger creation of the actual session in case it was deferred
+			Session.get().getSessionStore().getSessionId(RequestCycle.get().getRequest(), true);
+			// Add/touch the response page in the session (its pagemap).
+			getSession().touch(this);
+		}
 	}
 
 	/**
@@ -545,6 +553,18 @@
 	}
 
 	/**
+	 * Returns whether the page should try to be stateless. To be stateless,
+	 * getStatelessHint() of every component on page (and it's behavior) must
+	 * return true and the page must be bookmarkable.
+	 * 
+	 * @see wicket.Component#getStatelessHint()
+	 */
+	public final boolean getStatelessHint()
+	{
+		return getFlag(FLAG_STATELESS_HINT);
+	}
+	
+	/**
 	 * Override this method to implement a custom way of producing a version of
 	 * a Page when it cannot be found in the Session.
 	 * 
@@ -641,6 +661,37 @@
 		});
 		return buffer.toString();
 	}
+	
+	/**
+	 * Bookmarkable page can be instantiated using a bookmarkable URL.
+	 * 
+	 * @return Returns true if the page is bookmarkable.
+	 */
+	public boolean isBookmarkable()
+	{
+		try
+		{
+			if (getClass().getConstructor(new Class[] { PageParameters.class }) != null)
+			{
+				return true;
+			}
+
+		}
+		catch (Exception ignore)
+		{
+			try
+			{
+				if (getClass().getConstructor(new Class[] {}) != null)
+				{
+					return true;
+				}
+			}
+			catch (Exception ignore2)
+			{
+			}
+		}
+		return false;
+	}
 
 	/**
 	 * Override this method and return true if your page is used to display
@@ -656,6 +707,71 @@
 	}
 
 	/**
+	 * Set page stateless
+	 * 
+	 * @param stateless
+	 */
+	void setPageStateless(Boolean stateless)
+	{
+		this.stateless = stateless;
+	}
+
+	/**
+	 * Gets whether the page is stateless. Components on stateless page must not
+	 * render any statefull urls, and components on statefull page must not
+	 * render any stateless urls. Statefull urls are urls, which refer to a
+	 * certain (current) page instance.
+	 * 
+	 * @return Whether to page is stateless
+	 */
+	public final boolean isPageStateless()
+	{
+		if (isBookmarkable() == false)
+		{
+			stateless = Boolean.FALSE;
+			if (getStatelessHint())
+			{
+				log.warn("Page '" + this + "' is not stateless because it is not bookmarkable, "
+						+ "but the stateless hint is set to true!");
+			}
+		}
+
+		if (stateless == null)
+		{
+			final Object[] returnArray = new Object[1];
+			Object returnValue = visitChildren(Component.class, new IVisitor()
+			{
+				public Object component(Component component)
+				{
+					if (!component.isStateless())
+					{
+						returnArray[0] = component;
+						return Boolean.FALSE;
+					}
+
+					return CONTINUE_TRAVERSAL;
+				}
+			});
+			if (returnValue == null)
+			{
+				stateless = Boolean.TRUE;
+			}
+			else if (returnValue instanceof Boolean)
+			{
+				stateless = (Boolean)returnValue;
+			}
+
+			if (!stateless.booleanValue() && getStatelessHint())
+			{
+				log.warn("Page '" + this + "' is not stateless because of '" + returnArray[0]
+						+ "' but the stateless hint is set to true!");
+			}
+		}
+
+		return stateless.booleanValue();
+	}
+	
+	/**
 	 * Redirect to this page.
 	 * 
 	 * @see wicket.IRedirectListener#onRedirect()
@@ -961,20 +1077,6 @@
 	}
 
 	/**
-	 * @return Return true from this method if you want to keep a page out of
-	 *         the session.
-	 */
-	final boolean isStateless()
-	{
-		return stateless;
-	}
-
-	final void setStateless(boolean stateless)
-	{
-		this.stateless = stateless;
-	}
-
-	/**
 	 * Sets values for form components based on cookie values in the request.
 	 * 
 	 */
@@ -1006,6 +1108,24 @@
 		this.pageMapName = pageMap.getName();
 	}
 	
+	/**
+	 * Sets whether the page should try to be stateless. To be stateless,
+	 * getStatelessHint() of every component on page (and it's behavior) must
+	 * return true and the page must be bookmarkable.
+	 * 
+	 * @param value
+	 *            whether the page should try to be stateless
+	 */
+	public final void setStatelessHint(boolean value)
+	{
+		if (value && !isBookmarkable())
+		{
+			throw new WicketRuntimeException(
+					"Can't set stateless hint to true on a page when the page is not bookmarkable, page: "
+							+ this);
+		}
+		setFlag(FLAG_STATELESS_HINT, value);
+	}	
 	
 	/**
 	 * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL OR

Modified: incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/PageMap.java
URL: http://svn.apache.org/viewvc/incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/PageMap.java?view=diff&rev=473514&r1=473513&r2=473514
==============================================================================
--- incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/PageMap.java (original)
+++ incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/PageMap.java Fri Nov 10 13:46:18 2006
@@ -1,6 +1,6 @@
 /*
- * $Id$ $Revision:
- * 1.67 $ $Date$
+ * $Id:PageMap.java 5583 2006-04-30 22:23:23 +0000 (zo, 30 apr 2006) joco01 $
+ * $Revision:5583 $ $Date:2006-04-30 22:23:23 +0000 (zo, 30 apr 2006) $
  * 
  * ==============================================================================
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
@@ -22,46 +22,19 @@
 import java.util.Iterator;
 import java.util.List;
 
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
 import wicket.session.pagemap.IPageMapEntry;
-import wicket.util.collections.ArrayListStack;
 import wicket.util.lang.Objects;
 
 /**
- * A container for pages held in the session. PageMap is a parameter to several
- * methods in the Wicket API. You can get a PageMap by name from a Session with
- * Session.getPageMap(String pageMapName) or more conveniently with
- * PageMap.forName(String pageMapName). But you should not hold onto a reference
- * to the pagemap (just as you should not hold onto a reference to your Session
- * but should get it each time you need it instead). Instead, create a strongly
- * typed accessor method like this:
- * 
- * <pre>
- * public PageMap getMyPageMap()
- * {
- * 	return PageMap.forName(&quot;myPageMapName&quot;);
- * }
- * </pre>
- * 
- * If the page map with the given name is not found, one will be automatically
- * created.
- * 
- * @author Jonathan Locke
+ * @author jcompagner
  */
-public final class PageMap implements Serializable
+public abstract class PageMap implements Serializable
 {
-	/** Name of default pagemap */
-	public static final String DEFAULT_NAME = null;
-
-	/** Log. */
-	private static final Log log = LogFactory.getLog(PageMap.class);
-
 	private static final long serialVersionUID = 1L;
 
-	/** Stack of entry accesses by id */
-	private final ArrayListStack/* <Access> */accessStack = new ArrayListStack(8);
+
+	/** Name of default pagemap */
+	public static final String DEFAULT_NAME = null;
 
 	/** URL to continue to after a given page. */
 	private String interceptContinuationURL;
@@ -76,65 +49,18 @@
 	private transient Session session;
 
 	/**
-	 * Holds information about a pagemap access
+	 * Gets a page map for a page map name, automatically creating the page map
+	 * if it does not exist. If you do not want the pagemap to be automatically
+	 * created, you can call Session.pageMapForName(pageMapName, false).
 	 * 
-	 * @author Jonathan
+	 * @param pageMapName
+	 *            The name of the page map to get
+	 * @return The PageMap with the given name from the current session
 	 */
-	public static class Access implements Serializable
+	public static PageMap forName(final String pageMapName)
 	{
-		private static final long serialVersionUID = 1L;
-
-		int id;
-		int version;
-
-		/**
-		 * @see java.lang.Object#equals(java.lang.Object)
-		 */
-		public boolean equals(Object obj)
-		{
-			if (obj instanceof Access)
-			{
-				Access tmp = (Access)obj;
-				return tmp.id == id && tmp.version == version;
-			}
-			return false;
-		}
-
-		/**
-		 * Gets id.
-		 * 
-		 * @return id
-		 */
-		public final int getId()
-		{
-			return id;
-		}
-
-		/**
-		 * Gets version.
-		 * 
-		 * @return version
-		 */
-		public final int getVersion()
-		{
-			return version;
-		}
-
-		/**
-		 * @see java.lang.Object#hashCode()
-		 */
-		public int hashCode()
-		{
-			return id + (version << 16);
-		}
-
-		/**
-		 * @see java.lang.Object#toString()
-		 */
-		public String toString()
-		{
-			return "[Access id=" + id + ", version=" + version + "]";
-		}
+		Session session = Session.get();
+		return (session != null) ? session.pageMapForName(pageMapName, true) : null;
 	}
 
 	/**
@@ -151,20 +77,6 @@
 		public void entry(final IPageMapEntry entry);
 	}
 
-	/**
-	 * Gets a page map for a page map name, automatically creating the page map
-	 * if it does not exist. If you do not want the pagemap to be automatically
-	 * created, you can call Session.pageMapForName(pageMapName, false).
-	 * 
-	 * @param pageMapName
-	 *            The name of the page map to get
-	 * @return The PageMap with the given name from the current session
-	 */
-	public static PageMap forName(final String pageMapName)
-	{
-		Session session = Session.get();
-		return (session != null) ? session.pageMapForName(pageMapName, true) : null;
-	}
 
 	/**
 	 * Constructor
@@ -174,7 +86,7 @@
 	 * @param session
 	 *            The session holding this page map
 	 */
-	PageMap(final String name, final Session session)
+	public PageMap(String name, Session session)
 	{
 		this.name = name;
 		if (session == null)
@@ -184,37 +96,6 @@
 		this.session = session;
 	}
 
-	/**
-	 * Removes all pages from this map
-	 */
-	public final void clear()
-	{
-		// Remove all entries
-		visitEntries(new IVisitor()
-		{
-			public void entry(IPageMapEntry entry)
-			{
-				removeEntry(entry);
-			}
-		});
-
-		// Clear access stack
-		accessStack.clear();
-	}
-
-	// TODO Post 1.2: We should encode the page id of the current page into the
-	// URL for truly stateless pages so we can adjust the stack correctly
-
-	/**
-	 * Returns a stack of PageMap.Access entries pushed in the order that the
-	 * pages and versions were accessed.
-	 * 
-	 * @return Stack containing ids of entries in access order.
-	 */
-	public final ArrayListStack getAccessStack()
-	{
-		return accessStack;
-	}
 
 	/**
 	 * Retrieves entry with given id.
@@ -225,7 +106,7 @@
 	 */
 	public final IPageMapEntry getEntry(final int id)
 	{
-		return(IPageMapEntry)session.getAttribute(attributeForId(id));
+		return (IPageMapEntry)session.getAttribute(attributeForId(id));
 	}
 
 	/**
@@ -245,36 +126,6 @@
 	}
 
 	/**
-	 * @return Size of this page map in bytes, including a sum of the sizes of
-	 *         all the pages it contains.
-	 */
-	public final long getSizeInBytes()
-	{
-		long size = Objects.sizeof(this);
-		for (Iterator iterator = getEntries().iterator(); iterator.hasNext();)
-		{
-			IPageMapEntry entry = (IPageMapEntry)iterator.next();
-			if (entry instanceof Page)
-			{
-				size += ((Page)entry).getSizeInBytes();
-			}
-			else
-			{
-				size += Objects.sizeof(entry);
-			}
-		}
-		return size;
-	}
-
-	/**
-	 * @return Number of page versions stored in this page map
-	 */
-	public final int getVersions()
-	{
-		return accessStack.size();
-	}
-
-	/**
 	 * @return True if this is the default page map
 	 */
 	public final boolean isDefault()
@@ -283,86 +134,17 @@
 	}
 
 	/**
-	 * Gets the most recently accessed page map entry off the top of the entry
-	 * access stack. This is guaranteed to be the most recently accessed entry
-	 * IF AND ONLY IF the user just came from a stateful page. If the user could
-	 * get to the current page from a stateless page, this method may not work
-	 * if the user uses the back button. For a detailed explanation of this
-	 * issue, see getAccessStack().
-	 * 
-	 * @see PageMap#getAccessStack()
-	 * 
-	 * @return Previous pagemap entry in terms of access
-	 */
-	public final IPageMapEntry lastAccessedEntry()
-	{
-		return getEntry(peekAccess().getId());
-	}
-
-	/**
-	 * Removes this PageMap from the Session.
-	 */
-	public final void remove()
-	{
-		// First clear all pages from the session for this pagemap
-		clear();
-
-		// Then remove the pagemap itself
-		session.removePageMap(this);
-	}
-
-	/**
-	 * Removes the page from the pagemap
-	 * 
-	 * @param page
-	 *            page to be removed from the pagemap
-	 */
-	public final void remove(final Page page)
-	{
-		// Remove the pagemap entry from session
-		removeEntry(page.getPageMapEntry());
-	}
-
-	/**
-	 * @param entry
-	 *            The entry to remove
+	 * @return The next id for this pagemap
 	 */
-	public final void removeEntry(final IPageMapEntry entry)
+	final int nextId()
 	{
-		if(entry == null)
-		{
-			// 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;
-		}
-		// Remove entry from session
-		synchronized (session)
-		{
-			session.removeAttribute(attributeForId(entry.getNumericId()));
-	
-			// Remove page from acccess stack
-			final Iterator stack = accessStack.iterator();
-			while (stack.hasNext())
-			{
-				final Access access = (Access)stack.next();
-				if (access.id == entry.getNumericId())
-				{
-					stack.remove();
-				}
-			}
-	
-			// Let the session know we changed the pagemap
-			session.dirtyPageMap(this);
-		}
+		dirty();
+		return this.pageId++;
 	}
 
-	/**
-	 * @see java.lang.Object#toString()
-	 */
-	public String toString()
+	protected final void dirty()
 	{
-		return "[PageMap name=" + name + ", access=" + accessStack + "]";
+		session.dirtyPageMap(this);
 	}
 
 	/**
@@ -371,7 +153,7 @@
 	 * @return The session attribute for the given page (for replication of
 	 *         state)
 	 */
-	final String attributeForId(final int id)
+	public final String attributeForId(final int id)
 	{
 		return attributePrefix() + id;
 	}
@@ -424,119 +206,13 @@
 			interceptContinuationURL = null;
 
 			// Force session to replicate page maps
-			session.dirtyPageMap(this);
+			dirty();
 			return true;
 		}
 		return false;
 	}
 
 	/**
-	 * Retrieves page with given id.
-	 * 
-	 * @param id
-	 *            The page identifier
-	 * @param versionNumber
-	 *            The version to get
-	 * @return Any page having the given id
-	 */
-	final Page get(final int id, int versionNumber)
-	{
-		final IPageMapEntry entry = (IPageMapEntry)session.getAttribute(attributeForId(id));
-		if (entry != null)
-		{
-			// Get page as dirty
-			Page page = entry.getPage();
-
-			// TODO Performance: Is this really the case is a page always dirty
-			// even if we just render it again? POSSIBLE ANSWER: The page could
-			// mark itself as clean to prevent replication, but the reverse is
-			// probably not desirable (pages marking themselves dirty manually)
-			// We ought to think about this a bit and consider whether this
-			// could be tied in with version management. It's only when a page's
-			// version changes that it should be considered dirty, because then
-			// some kind of state changed. Right? - Jonathan
-			page.dirty();
-
-			// Get the version of the page requested from the page
-			final Page version = page.getVersion(versionNumber);
-
-			// Entry has been accessed
-			//pushAccess(entry);
-			// Entry has been accessed
-			access(entry, versionOf(entry));
-			
-
-			// Is the requested version available?
-			if (version != null)
-			{
-				// Need to update session with new page?
-				if (version != page)
-				{
-					// This is our new page
-					page = version;
-
-					// Replaces old page entry
-					page.getPageMap().put(page);
-				}
-			}
-			else
-			{
-				if (log.isInfoEnabled())
-				{
-					log.info("Unable to get version " + versionNumber + " of page " + page);
-				}
-				return null;
-			}
-			return page;
-		}
-		return null;
-	}
-
-	/**
-	 * @return The next id for this pagemap
-	 */
-	final int nextId()
-	{
-		session.dirtyPageMap(this);
-		return this.pageId++;
-	}
-
-	/**
-	 * @param page
-	 *            The page to put into this map
-	 */
-	final void put(final Page page)
-	{
-		// Page only goes into session if it is stateless
-		if (!page.isStateless())
-		{
-			// Get page map entry from page
-			final IPageMapEntry entry = page.getPageMapEntry();
-
-			// Entry has been accessed
-			pushAccess(entry);
-
-			// Store entry in session
-			final String attribute = attributeForId(entry.getNumericId());
-			
-			if(session.getAttribute(attribute) == null)
-			{
-				// Set attribute if it is a new page, so that it will exists
-				// already for other threads that can come on the same time.
-				session.setAttribute(attribute, entry);
-			}
-			else
-			{
-				// Else don't set it directly but add to the dirty map
-				session.dirtyPage(page);
-			}
-
-			// Evict any page(s) as need be
-			session.getApplication().getSessionSettings().getPageMapEvictionStrategy().evict(this);
-		}
-	}
-
-	/**
 	 * Redirects browser to an intermediate page such as a sign-in page. The
 	 * current request's URL is saved exactly as it was requested for future use
 	 * by continueToOriginalDestination(); Only use this method when you plan to
@@ -548,6 +224,7 @@
 	 */
 	final void redirectToInterceptPage(final Page page)
 	{
+		Session.get().bind();
 		// Get the request cycle
 		final RequestCycle cycle = RequestCycle.get();
 
@@ -556,14 +233,14 @@
 		interceptContinuationURL = cycle.getRequest().getURL();
 
 		// Page map is dirty
-		session.dirtyPageMap(this);
+		dirty();
 
 		// Redirect to the page
 		cycle.setRedirect(true);
 		cycle.setResponsePage(page);
 	}
-
-	/**
+	
+    /**
 	 * Redirects browser to an intermediate page such as a sign-in page. The
 	 * current request's URL is saved exactly as it was requested for future use
 	 * by continueToOriginalDestination(); Only use this method when you plan to
@@ -575,6 +252,7 @@
 	 */
 	final void redirectToInterceptPage(final Class pageClazz)
 	{
+		Session.get().bind();
 		// Get the request cycle
 		final RequestCycle cycle = RequestCycle.get();
 
@@ -588,8 +266,23 @@
 		// Redirect to the page
 		cycle.setRedirect(true);
 		cycle.setResponsePage(pageClazz);
+	}	
+
+	/**
+	 * Removes all pages from this map
+	 */
+	public void clear()
+	{
+		// Remove all entries
+		visitEntries(new IVisitor()
+		{
+			public void entry(IPageMapEntry entry)
+			{
+				removeEntry(entry);
+			}
+		});
 	}
-	
+
 	/**
 	 * @param session
 	 *            Session to set
@@ -617,146 +310,100 @@
 	}
 
 	/**
-	 * @param entry
-	 *            Add entry to access list
-	 * @param version
-	 *            Version number being accessed
-	 */
-	private final void access(final IPageMapEntry entry, final int version)
-	{
-		// See if the version being accessed is already in the stack
-		boolean add = true;
-		int id = entry.getNumericId();
-		for (int i = accessStack.size()-1; i >=0 ; i--)
-		{
-			final Access access = (Access)accessStack.get(i);
-
-			// If we found id and version in access stack
-			if (access.id == id && access.version == version)
-			{
-				// No need to add since id and version are already in stack
-				add = false;
-
-				// Pop entries to reveal that version at top of stack
-				// because the user used the back button
-				while (i < accessStack.size() - 1)
-				{
-					// Pop unreachable access off top of stack
-					final Access topAccess = popAccess();
-
-					// Get entry for access
-					final IPageMapEntry top = getEntry(topAccess.getId());
-
-					// If it's a page we can remove version info
-					if (top instanceof Page)
-					{
-						// If there's more than one version
-						Page topPage = (Page)top;
-						if (topPage.getVersions() > 1)
-						{
-							// Remove version the top access version (-1)
-							topPage.getVersion(topAccess.getVersion()-1);
-						}
-						else
-						{
-							// Remove whole page
-							remove(topPage);
-						}
-					}
-					else
-					{
-						// Remove entry
-						removeEntry(top);
-					}
-				}
-				break;
-			}
-		}
+	 * Removes this PageMap from the Session.
+	 */
+	public final void remove()
+	{
+		// First clear all pages from the session for this pagemap
+		clear();
 
-		// If the user did not use the back button
-		if (add)
-		{
-			pushAccess(entry);
-		}
+		// Then remove the pagemap itself
+		session.removePageMap(this);
 	}
 
 	/**
-	 * @return List of entries in this page map
+	 * Removes the page from the pagemap
+	 * 
+	 * @param page
+	 *            page to be removed from the pagemap
 	 */
-	private final List getEntries()
+	public final void remove(final Page page)
 	{
-		final List attributes = session.getAttributeNames();
-		final List list = new ArrayList();
-		for (final Iterator iterator = attributes.iterator(); iterator.hasNext();)
-		{
-			final String attribute = (String)iterator.next();
-			if (attribute.startsWith(attributePrefix()))
-			{
-				list.add(session.getAttribute(attribute));
-			}
-		}
-		return list;
+		// Remove the pagemap entry from session
+		removeEntry(page.getPageMapEntry());
 	}
 
 	/**
-	 * @return Access entry on top of the access stack
+	 * @param entry
+	 *            The entry to remove
 	 */
-	private final Access peekAccess()
-	{
-		return (Access)accessStack.peek();
-	}
+	protected abstract void removeEntry(final IPageMapEntry entry);
+
+	/**
+	 * @param page
+	 *            The page to put into this map
+	 */
+	protected abstract void put(final Page page);
+
 
 	/**
-	 * Removes access entry on top of stack
+	 * Retrieves page with given id.
 	 * 
-	 * @return Access entry on top of the access stack
+	 * @param id
+	 *            The page identifier
+	 * @param versionNumber
+	 *            The version to get
+	 * @return Any page having the given id
 	 */
-	private final Access popAccess()
-	{
-		session.dirtyPageMap(this);
-		return (Access)accessStack.pop();
-	}
+	protected abstract Page get(final int id, int versionNumber);
 
 	/**
-	 * @param entry
-	 *            Entry that was accessed
+	 * @return Size of this page map in bytes, including a sum of the sizes of
+	 *         all the pages it contains.
 	 */
-	private final void pushAccess(IPageMapEntry entry)
+	public final long getSizeInBytes()
 	{
-		// Create new access entry
-		final Access access = new Access();
-		access.id = entry.getNumericId();
-		access.version = versionOf(entry);
-		if(accessStack.size() > 0)
+		long size = Objects.sizeof(this);
+		Iterator it = getEntries().iterator();
+		while(it.hasNext())
 		{
-			if(peekAccess().equals(access))
+			IPageMapEntry entry = (IPageMapEntry)it.next();
+			if (entry instanceof Page)
 			{
-				return;
+				size += ((Page)entry).getSizeInBytes();
 			}
-			int index = accessStack.indexOf(access);
-			if (index >= 0)
+			else
 			{
-				accessStack.remove(index);
+				size += Objects.sizeof(entry);
 			}
 		}
-		accessStack.push(access);
-		session.dirtyPageMap(this);
+		return size;
 	}
 
 	/**
-	 * @param entry
-	 *            Page map entry
-	 * @return Version of entry
+	 * @return List of entries in this page map
 	 */
-	private final int versionOf(final IPageMapEntry entry)
+	private final List getEntries()
 	{
-		if (entry instanceof Page)
+		final List attributes = session.getAttributeNames();
+		final List list = new ArrayList();
+		for (final Iterator iterator = attributes.iterator(); iterator.hasNext();)
 		{
-			return ((Page)entry).getCurrentVersionNumber();
+			final String attribute = (String)iterator.next();
+			if (attribute.startsWith(attributePrefix()))
+			{
+				list.add(session.getAttribute(attribute));
+			}
 		}
+		return list;
+	}
+
 
-		// If entry is not a page, it cannot have versions because the Page
-		// is constructed on the fly.
-		return 0;
+	/**
+	 * @see java.lang.Object#toString()
+	 */
+	public String toString()
+	{
+		return "[PageMap name=" + name + "]";
 	}
 }

Modified: incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/RequestCycle.java
URL: http://svn.apache.org/viewvc/incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/RequestCycle.java?view=diff&rev=473514&r1=473513&r2=473514
==============================================================================
--- incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/RequestCycle.java (original)
+++ incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/RequestCycle.java Fri Nov 10 13:46:18 2006
@@ -24,10 +24,12 @@
 import org.apache.commons.logging.LogFactory;
 
 import wicket.protocol.http.BufferedWebResponse;
+import wicket.protocol.http.IRequestLogger;
 import wicket.request.ClientInfo;
 import wicket.request.IRequestCodingStrategy;
 import wicket.request.IRequestCycleProcessor;
 import wicket.request.RequestParameters;
+import wicket.request.target.component.BookmarkableListenerInterfaceRequestTarget;
 import wicket.request.target.component.BookmarkablePageRequestTarget;
 import wicket.request.target.component.ComponentRequestTarget;
 import wicket.request.target.component.IBookmarkablePageRequestTarget;
@@ -653,10 +655,26 @@
 	{
 		// Get Page holding component and mark it as stateful.
 		final Page page = component.getPage();
-		page.setStateless(false);
+		final IRequestTarget target;
+		if (listener != IRedirectListener.INTERFACE && component.isStateless()
+				&& page.isBookmarkable())
+		{
+			target = new BookmarkableListenerInterfaceRequestTarget(page.getPageMap().getName(),
+					page.getClass(), new PageParameters(), component, listener);
+		}
+		else
+		{
+			if (listener == IRedirectListener.INTERFACE)
+			{
+				page.setPageStateless(Boolean.FALSE);
+			}
 
-		// Get the listener interface name
-		final IRequestTarget target = new ListenerInterfaceRequestTarget(page, component, listener);
+			// trigger creation of the actual session in case it was deferred
+			session.getSessionStore().getSessionId(request, true);
+
+			// Get the listener interface name
+			target = new ListenerInterfaceRequestTarget(page, component, listener);
+		}
 		final IRequestCodingStrategy requestCodingStrategy = getProcessor()
 				.getRequestCodingStrategy();
 		return requestCodingStrategy.encode(this, target);
@@ -860,11 +878,11 @@
 			}
 		}
 
-//		IRequestLogger requestLogger = getApplication().getRequestLogger();
-//		if (requestLogger != null)
-//		{
-//			requestLogger.requestTime((System.currentTimeMillis() - startTime));
-//		}
+		IRequestLogger requestLogger = getApplication().getRequestLogger();
+		if (requestLogger != null)
+		{
+			requestLogger.requestTime((System.currentTimeMillis() - startTime));
+		}
 		
 		try
 		{

Modified: incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/Session.java
URL: http://svn.apache.org/viewvc/incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/Session.java?view=diff&rev=473514&r1=473513&r2=473514
==============================================================================
--- incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/Session.java (original)
+++ incubator/wicket/branches/wicket-1.x/wicket/src/main/java/wicket/Session.java Fri Nov 10 13:46:18 2006
@@ -265,6 +265,46 @@
 	}
 
 	/**
+	 * Force binding this session to the application's
+	 * {@link ISessionStore session store}. A Wicket application can operate in
+	 * a session-less mode as long as stateless pages are used. Session objects
+	 * will be then created for each request, but they will only live for that
+	 * request. You can recognize temporary sessions by calling
+	 * {@link #isTemporary()} which basically checks whether the session's id is
+	 * null. Hence, temporary sessions have no session id.
+	 * <p>
+	 * By calling this method, the session will be bound (made not-temporary) if
+	 * it was not bound yet. It is useful for cases where you want to be
+	 * absolutely sure this session object will be available in next requests.
+	 * </p>
+	 */
+	public final void bind()
+	{
+		ISessionStore store = getSessionStore();
+		Request request = RequestCycle.get().getRequest();
+		if (store.getSessionId(request, false) == null)
+		{
+			// explicitly create a session
+			this.id = store.getSessionId(request, true);
+			// bind it
+			store.bind(request, this);
+		}
+	}
+
+	/**
+	 * Whether this session is temporary. A Wicket application can operate in a
+	 * session-less mode as long as stateless pages are used. If this session
+	 * object is temporary, it will not be available on a next request.
+	 * 
+	 * @return Whether this session is temporary (which is the same as it's id
+	 *         being null)
+	 */
+	public final boolean isTemporary()
+	{
+		return getId() == null;
+	}
+	
+	/**
 	 * Removes all pages from the session. Although this method should rarely be
 	 * needed, it is available (possibly for security reasons).
 	 */
@@ -334,15 +374,17 @@
 	}
 
 	/**
-	 * Gets the unique id for this session from the underlying SessionStore
+	 * Gets the unique id for this session from the underlying SessionStore. May
+	 * be null if a concrete session is not yet created.
 	 * 
-	 * @return The unique id for this session
+	 * @return The unique id for this session or null if it is a temporary
+	 *         session
 	 */
 	public final String getId()
 	{
 		if (id == null)
 		{
-			id = getSessionStore().getSessionId(RequestCycle.get().getRequest());
+			id = getSessionStore().getSessionId(RequestCycle.get().getRequest(), false);
 
 			// we have one?
 			if (id != null)
@@ -617,7 +659,7 @@
 		}
 
 		// Create new page map
-		final PageMap pageMap = new PageMap(name, this);
+		final PageMap pageMap = getSessionStore().createPageMap(name, this);
 		setAttribute(attributeForPageMapName(name), pageMap);
 		dirty();
 		return pageMap;
@@ -914,6 +956,8 @@
 		if (cycle != null)
 		{
 			getSessionStore().removeAttribute(cycle.getRequest(), name);
+			System.err.println("removing " + name +  ", the current list " + getSessionStore().getAttributeNames(cycle.getRequest()));
+			
 		}
 	}
 
@@ -942,9 +986,13 @@
 			Object current = store.getAttribute(request, name);
 			if (current == null)
 			{
-				// this is a new instance. wherever it came from, bind the
-				// session now
-				store.bind(request, (Session)value);
+				String id = store.getSessionId(request, false);
+				if (id != null)
+				{
+					// this is a new instance. wherever it came from, bind the
+					// session now
+					store.bind(request, (Session)value);
+				}
 			}
 		}