You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by eh...@apache.org on 2007/04/04 08:19:22 UTC

svn commit: r525432 - in /incubator/wicket/branches/wicket-1.x/jdk-1.4/wicket/src/main/java/wicket: PageMap.java RequestCycle.java Session.java

Author: ehillenius
Date: Tue Apr  3 23:19:21 2007
New Revision: 525432

URL: http://svn.apache.org/viewvc?view=rev&rev=525432
Log:
fixes for stateless session support

Modified:
    incubator/wicket/branches/wicket-1.x/jdk-1.4/wicket/src/main/java/wicket/PageMap.java
    incubator/wicket/branches/wicket-1.x/jdk-1.4/wicket/src/main/java/wicket/RequestCycle.java
    incubator/wicket/branches/wicket-1.x/jdk-1.4/wicket/src/main/java/wicket/Session.java

Modified: incubator/wicket/branches/wicket-1.x/jdk-1.4/wicket/src/main/java/wicket/PageMap.java
URL: http://svn.apache.org/viewvc/incubator/wicket/branches/wicket-1.x/jdk-1.4/wicket/src/main/java/wicket/PageMap.java?view=diff&rev=525432&r1=525431&r2=525432
==============================================================================
--- incubator/wicket/branches/wicket-1.x/jdk-1.4/wicket/src/main/java/wicket/PageMap.java (original)
+++ incubator/wicket/branches/wicket-1.x/jdk-1.4/wicket/src/main/java/wicket/PageMap.java Tue Apr  3 23:19:21 2007
@@ -29,23 +29,25 @@
  */
 public abstract class PageMap implements IClusterable, IPageMap
 {
-	private static final long serialVersionUID = 1L;
+	/**
+	 * Visitor interface for visiting entries in this map
+	 * 
+	 * @author Jonathan Locke
+	 */
+	static interface IVisitor
+	{
+		/**
+		 * @param entry
+		 *            The page map entry
+		 */
+		public void entry(final IPageMapEntry entry);
+	}
 
 
 	/** Name of default pagemap */
 	public static final String DEFAULT_NAME = null;
 
-	/** URL to continue to after a given page. */
-	private String interceptContinuationURL;
-
-	/** Name of this page map */
-	private final String name;
-
-	/** Next available page identifier in this page map. */
-	private int pageId = 0;
-
-	/** The session where this PageMap resides */
-	private transient Session session;
+	private static final long serialVersionUID = 1L;
 
 	/**
 	 * Gets a page map for a page map name, automatically creating the page map
@@ -62,19 +64,17 @@
 		return (session != null) ? session.pageMapForName(pageMapName, true) : null;
 	}
 
-	/**
-	 * Visitor interface for visiting entries in this map
-	 * 
-	 * @author Jonathan Locke
-	 */
-	static interface IVisitor
-	{
-		/**
-		 * @param entry
-		 *            The page map entry
-		 */
-		public void entry(final IPageMapEntry entry);
-	}
+	/** URL to continue to after a given page. */
+	private String interceptContinuationURL;
+
+	/** Name of this page map */
+	private final String name;
+
+	/** Next available page identifier in this page map. */
+	private int pageId = 0;
+
+	/** The session where this PageMap resides */
+	private transient Session session;
 
 
 	/**
@@ -97,6 +97,61 @@
 
 
 	/**
+	 * @see wicket.IPageMap#attributeForId(int)
+	 */
+	public final String attributeForId(final int id)
+	{
+		return attributePrefix() + id;
+	}
+
+	/**
+	 * @see wicket.IPageMap#clear()
+	 */
+	public void clear()
+	{
+		// Remove all entries
+		visitEntries(new IVisitor()
+		{
+			public void entry(IPageMapEntry entry)
+			{
+				removeEntry(entry);
+			}
+		});
+	}
+
+	/**
+	 * Redirects to any intercept page previously specified by a call to
+	 * redirectToInterceptPage.
+	 * 
+	 * @return True if an original destination was redirected to
+	 * @see PageMap#redirectToInterceptPage(Page)
+	 */
+	public final boolean continueToOriginalDestination()
+	{
+		// Get request cycle
+		final RequestCycle cycle = RequestCycle.get();
+
+		// If there's a place to go to
+		if (interceptContinuationURL != null)
+		{
+			cycle.setRequestTarget(new RedirectRequestTarget(interceptContinuationURL));
+
+			// Reset interception URL
+			interceptContinuationURL = null;
+
+			// Force session to replicate page maps
+			dirty();
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * @see wicket.IPageMap#get(int, int)
+	 */
+	public abstract Page get(final int id, int versionNumber);
+
+	/**
 	 * @see wicket.IPageMap#getEntry(int)
 	 */
 	public final IPageMapEntry getEntry(final int id)
@@ -121,6 +176,28 @@
 	}
 
 	/**
+	 * @see wicket.IPageMap#getSizeInBytes()
+	 */
+	public final long getSizeInBytes()
+	{
+		long size = Objects.sizeof(this);
+		Iterator it = getEntries().iterator();
+		while (it.hasNext())
+		{
+			IPageMapEntry entry = (IPageMapEntry)it.next();
+			if (entry instanceof Page)
+			{
+				size += ((Page)entry).getSizeInBytes();
+			}
+			else
+			{
+				size += Objects.sizeof(entry);
+			}
+		}
+		return size;
+	}
+
+	/**
 	 * @see wicket.IPageMap#isDefault()
 	 */
 	public final boolean isDefault()
@@ -137,53 +214,10 @@
 		return this.pageId++;
 	}
 
-	protected final void dirty()
-	{
-		session.dirtyPageMap(this);
-	}
-
 	/**
-	 * @see wicket.IPageMap#attributeForId(int)
-	 */
-	public final String attributeForId(final int id)
-	{
-		return attributePrefix() + id;
-	}
-
-	/**
-	 * @return The attribute prefix for this page map
-	 */
-	final String attributePrefix()
-	{
-		return Session.pageMapEntryAttributePrefix + name + ":";
-	}
-
-	/**
-	 * Redirects to any intercept page previously specified by a call to
-	 * redirectToInterceptPage.
-	 * 
-	 * @return True if an original destination was redirected to
-	 * @see PageMap#redirectToInterceptPage(Page)
+	 * @see wicket.IPageMap#put(wicket.Page)
 	 */
-	public final boolean continueToOriginalDestination()
-	{
-		// Get request cycle
-		final RequestCycle cycle = RequestCycle.get();
-
-		// If there's a place to go to
-		if (interceptContinuationURL != null)
-		{
-			cycle.setRequestTarget(new RedirectRequestTarget(interceptContinuationURL));
-
-			// Reset interception URL
-			interceptContinuationURL = null;
-
-			// Force session to replicate page maps
-			dirty();
-			return true;
-		}
-		return false;
-	}
+	public abstract void put(final Page page);
 
 	/**
 	 * Redirects browser to an intermediate page such as a sign-in page. The
@@ -192,12 +226,16 @@
 	 * continue to the current URL at some later time; otherwise just use
 	 * setResponsePage or, when you are in a constructor, redirectTo.
 	 * 
-	 * @param page
-	 *            The page to temporarily redirect to
+	 * @param pageClazz
+	 *            The page clazz to temporarily redirect to
 	 */
-	public final void redirectToInterceptPage(final Page page)
+	public final void redirectToInterceptPage(final Class pageClazz)
 	{
-		Session.get().bind();
+		Session session = Session.get();
+		if (session.isTemporary())
+		{
+			session.bind();
+		}
 		// Get the request cycle
 		final RequestCycle cycle = RequestCycle.get();
 
@@ -206,26 +244,30 @@
 		interceptContinuationURL = cycle.getRequest().getURL();
 
 		// Page map is dirty
-		dirty();
+		session.dirtyPageMap(this);
 
 		// Redirect to the page
 		cycle.setRedirect(true);
-		cycle.setResponsePage(page);
+		cycle.setResponsePage(pageClazz);
 	}
-	
-    /**
+
+	/**
 	 * 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
 	 * continue to the current URL at some later time; otherwise just use
 	 * setResponsePage or, when you are in a constructor, redirectTo.
 	 * 
-	 * @param pageClazz
-	 *            The page clazz to temporarily redirect to
+	 * @param page
+	 *            The page to temporarily redirect to
 	 */
-	public final void redirectToInterceptPage(final Class pageClazz)
+	public final void redirectToInterceptPage(final Page page)
 	{
-		Session.get().bind();
+		Session session = Session.get();
+		if (session.isTemporary())
+		{
+			session.bind();
+		}
 		// Get the request cycle
 		final RequestCycle cycle = RequestCycle.get();
 
@@ -234,51 +276,11 @@
 		interceptContinuationURL = cycle.getRequest().getURL();
 
 		// Page map is dirty
-		session.dirtyPageMap(this);
+		dirty();
 
 		// Redirect to the page
 		cycle.setRedirect(true);
-		cycle.setResponsePage(pageClazz);
-	}	
-
-	/**
-	 * @see wicket.IPageMap#clear()
-	 */
-	public void clear()
-	{
-		// Remove all entries
-		visitEntries(new IVisitor()
-		{
-			public void entry(IPageMapEntry entry)
-			{
-				removeEntry(entry);
-			}
-		});
-	}
-
-	/**
-	 * @see wicket.IPageMap#setSession(wicket.Session)
-	 */
-	public final void setSession(final Session session)
-	{
-		this.session = session;
-	}
-
-	/**
-	 * @param visitor
-	 *            The visitor to call at each Page in this PageMap.
-	 */
-	protected final void visitEntries(final IVisitor visitor)
-	{
-		final List attributes = session.getAttributeNames();
-		for (final Iterator iterator = attributes.iterator(); iterator.hasNext();)
-		{
-			final String attribute = (String)iterator.next();
-			if (attribute.startsWith(attributePrefix()))
-			{
-				visitor.entry((IPageMapEntry)session.getAttribute(attribute));
-			}
-		}
+		cycle.setResponsePage(page);
 	}
 
 	/**
@@ -308,62 +310,68 @@
 	public abstract void removeEntry(final IPageMapEntry entry);
 
 	/**
-	 * @see wicket.IPageMap#put(wicket.Page)
+	 * @see wicket.IPageMap#setSession(wicket.Session)
 	 */
-	public abstract void put(final Page page);
-
+	public final void setSession(final Session session)
+	{
+		this.session = session;
+	}
 
 	/**
-	 * @see wicket.IPageMap#get(int, int)
+	 * @see java.lang.Object#toString()
 	 */
-	public abstract Page get(final int id, int versionNumber);
+	public String toString()
+	{
+		return "[PageMap name=" + name + "]";
+	}
+
 
 	/**
-	 * @see wicket.IPageMap#getSizeInBytes()
+	 * @return List of entries in this page map
 	 */
-	public final long getSizeInBytes()
+	private final List getEntries()
 	{
-		long size = Objects.sizeof(this);
-		Iterator it = getEntries().iterator();
-		while(it.hasNext())
+		final List attributes = session.getAttributeNames();
+		final List list = new ArrayList();
+		for (final Iterator iterator = attributes.iterator(); iterator.hasNext();)
 		{
-			IPageMapEntry entry = (IPageMapEntry)it.next();
-			if (entry instanceof Page)
-			{
-				size += ((Page)entry).getSizeInBytes();
-			}
-			else
+			final String attribute = (String)iterator.next();
+			if (attribute.startsWith(attributePrefix()))
 			{
-				size += Objects.sizeof(entry);
+				list.add(session.getAttribute(attribute));
 			}
 		}
-		return size;
+		return list;
+	}
+
+	protected final void dirty()
+	{
+		session.dirtyPageMap(this);
 	}
 
 	/**
-	 * @return List of entries in this page map
+	 * @param visitor
+	 *            The visitor to call at each Page in this PageMap.
 	 */
-	private final List getEntries()
+	protected final void visitEntries(final IVisitor visitor)
 	{
 		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));
+				visitor.entry((IPageMapEntry)session.getAttribute(attribute));
 			}
 		}
-		return list;
 	}
 
 
 	/**
-	 * @see java.lang.Object#toString()
+	 * @return The attribute prefix for this page map
 	 */
-	public String toString()
+	final String attributePrefix()
 	{
-		return "[PageMap name=" + name + "]";
+		return Session.pageMapEntryAttributePrefix + name + ":";
 	}
 }

Modified: incubator/wicket/branches/wicket-1.x/jdk-1.4/wicket/src/main/java/wicket/RequestCycle.java
URL: http://svn.apache.org/viewvc/incubator/wicket/branches/wicket-1.x/jdk-1.4/wicket/src/main/java/wicket/RequestCycle.java?view=diff&rev=525432&r1=525431&r2=525432
==============================================================================
--- incubator/wicket/branches/wicket-1.x/jdk-1.4/wicket/src/main/java/wicket/RequestCycle.java (original)
+++ incubator/wicket/branches/wicket-1.x/jdk-1.4/wicket/src/main/java/wicket/RequestCycle.java Tue Apr  3 23:19:21 2007
@@ -716,13 +716,14 @@
 		}
 		else
 		{
-			if (listener == IRedirectListener.INTERFACE)
+
+			page.setPageStateless(Boolean.FALSE);
+			// make session non-volatile if not already so
+			Session session = Session.get();
+			if (session.isTemporary())
 			{
-				page.setPageStateless(Boolean.FALSE);
+				session.bind();
 			}
-
-			// 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);

Modified: incubator/wicket/branches/wicket-1.x/jdk-1.4/wicket/src/main/java/wicket/Session.java
URL: http://svn.apache.org/viewvc/incubator/wicket/branches/wicket-1.x/jdk-1.4/wicket/src/main/java/wicket/Session.java?view=diff&rev=525432&r1=525431&r2=525432
==============================================================================
--- incubator/wicket/branches/wicket-1.x/jdk-1.4/wicket/src/main/java/wicket/Session.java (original)
+++ incubator/wicket/branches/wicket-1.x/jdk-1.4/wicket/src/main/java/wicket/Session.java Tue Apr  3 23:19:21 2007
@@ -18,6 +18,7 @@
 
 import java.io.Serializable;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -122,14 +123,19 @@
 public abstract class Session implements IClusterable, IConverterLocator
 {
 
-	private static final long serialVersionUID = 1L;
-
-	/** meta data key for missing body tags logging. */
-	public static final MetaDataKey PAGEMAP_ACCESS_MDK = new MetaDataKey(
-			PageMapAccessMetaData.class)
+	/**
+	 * Visitor interface for visiting page maps
+	 * 
+	 * @author Jonathan Locke
+	 */
+	public static interface IPageMapVisitor
 	{
-		private static final long serialVersionUID = 1L;
-	};
+		/**
+		 * @param pageMap
+		 *            The page map
+		 */
+		public void pageMap(final IPageMap pageMap);
+	}
 
 	/**
 	 * meta data for recording map map access.
@@ -152,11 +158,17 @@
 		}
 	}
 
+	/** meta data key for missing body tags logging. */
+	public static final MetaDataKey PAGEMAP_ACCESS_MDK = new MetaDataKey(
+			PageMapAccessMetaData.class)
+	{
+		private static final long serialVersionUID = 1L;
+	};
+
 	/** Name of session attribute under which this session is stored */
 	public static final String SESSION_ATTRIBUTE_NAME = "session";
 
-	/** Prefix for attributes holding page map entries */
-	static final String pageMapEntryAttributePrefix = "p:";
+	private static final long serialVersionUID = 1L;
 
 	/** Thread-local current session. */
 	private static final ThreadLocal current = new ThreadLocal();
@@ -173,59 +185,18 @@
 	/** Attribute prefix for page maps stored in the session */
 	private static final String pageMapAttributePrefix = "m:";
 
-	/**
-	 * Cached instance of agent info which is typically designated by calling
-	 * {@link RequestCycle#newClientInfo()}.
-	 */
-	private ClientInfo clientInfo;
-
-	/** The converter instance. */
-	private transient IConverterLocator converterSupplier;
-
-	/** True if session state has been changed */
-	private transient boolean dirty = false;
-
-	/** The locale to use when loading resources for this session. */
-	private Locale locale;
-
-	/** A number to generate names for auto create pagemaps */
-	private int autoCreatePageMapCounter = 0;
-
-	/** A linked list for last used pagemap queue */
-	private LinkedList/* <IPageMap> */usedPageMaps = new LinkedList();
-
-	/** Any special "skin" style to use when loading resources. */
-	private String style;
-
-	/** feedback messages */
-	private FeedbackMessages feedbackMessages = new FeedbackMessages();
-
-	private transient Map pageMapsUsedInRequest;
-
-	/** cached id because you can't access the id after session unbound */
-	private String id = null;
-
-	/**
-	 * Temporary instance of the session store. Should be set on each request as
-	 * it is not supposed to go in the session.
-	 */
-	private transient ISessionStore sessionStore;
-
-	/** Application level meta data. */
-	private MetaDataEntry[] metaData;
+	/** Prefix for attributes holding page map entries */
+	static final String pageMapEntryAttributePrefix = "p:";
 
 	/**
-	 * Visitor interface for visiting page maps
+	 * Checks if the <code>Session</code> threadlocal is set in this thread
 	 * 
-	 * @author Jonathan Locke
+	 * @return true if {@link Session#get()} can return the instance of session,
+	 *         false otherwise
 	 */
-	public static interface IPageMapVisitor
+	public static boolean exists()
 	{
-		/**
-		 * @param pageMap
-		 *            The page map
-		 */
-		public void pageMap(final IPageMap pageMap);
+		return current.get() != null;
 	}
 
 	/**
@@ -245,17 +216,6 @@
 	}
 
 	/**
-	 * Checks if the <code>Session</code> threadlocal is set in this thread
-	 * 
-	 * @return true if {@link Session#get()} can return the instance of session,
-	 *         false otherwise
-	 */
-	public static boolean exists()
-	{
-		return current.get() != null;
-	}
-
-	/**
 	 * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL IT.
 	 * <p>
 	 * Sets session for calling thread.
@@ -284,6 +244,53 @@
 	}
 
 	/**
+	 * Holds attributes for sessions that are still temporary/ not bound to a
+	 * session store. Only used when {@link #isTemporary()} is true.
+	 */
+	private transient Map temporarySessionAttributes;
+
+	/**
+	 * Cached instance of agent info which is typically designated by calling
+	 * {@link RequestCycle#newClientInfo()}.
+	 */
+	private ClientInfo clientInfo;
+
+	/** The converter instance. */
+	private transient IConverterLocator converterSupplier;
+
+	/** True if session state has been changed */
+	private transient boolean dirty = false;
+
+	/** The locale to use when loading resources for this session. */
+	private Locale locale;
+
+	/** A number to generate names for auto create pagemaps */
+	private int autoCreatePageMapCounter = 0;
+
+	/** A linked list for last used pagemap queue */
+	private LinkedList/* <IPageMap> */usedPageMaps = new LinkedList();
+
+	/** Any special "skin" style to use when loading resources. */
+	private String style;
+
+	/** feedback messages */
+	private FeedbackMessages feedbackMessages = new FeedbackMessages();
+
+	private transient Map pageMapsUsedInRequest;
+
+	/** cached id because you can't access the id after session unbound */
+	private String id = null;
+
+	/**
+	 * Temporary instance of the session store. Should be set on each request as
+	 * it is not supposed to go in the session.
+	 */
+	private transient ISessionStore sessionStore;
+
+	/** Application level meta data. */
+	private MetaDataEntry[] metaData;
+
+	/**
 	 * Constructor.
 	 * 
 	 * @param application
@@ -303,43 +310,75 @@
 
 	/**
 	 * 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.
+	 * {@link ISessionStore session store} if not already done so.
+	 * <p>
+	 * 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>
 	 * <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>
+	 * <p>
+	 * This method should not typically be called by clients
+	 * </p>
 	 */
 	public final void bind()
 	{
 		ISessionStore store = getSessionStore();
 		Request request = RequestCycle.get().getRequest();
-		if (store.getSessionId(request, false) == null)
+		if (store.lookup(request) == null)
+		{
+			synchronized (this)
+			{
+				// explicitly create a session
+				this.id = store.getSessionId(request, true);
+				// bind it
+				store.bind(request, this);
+
+				if (temporarySessionAttributes != null)
+				{
+					for (Iterator i = temporarySessionAttributes.entrySet().iterator(); i.hasNext();)
+					{
+						Entry entry = (Entry)i.next();
+						store.setAttribute(request, String.valueOf(entry.getKey()), entry
+								.getValue());
+					}
+					temporarySessionAttributes = null;
+				}
+
+				RequestCycle.get().setUpdateSession(true);
+			}
+		}
+		else
 		{
-			// explicitly create a session
-			this.id = store.getSessionId(request, true);
-			// bind it
-			store.bind(request, this);
+			log.warn("trying to bind an already bound and non-temporary session");
 		}
-		RequestCycle.get().setUpdateSession(true);
 	}
 
 	/**
-	 * 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.
+	 * Cleans up any unrendered, dangling feedback messages there may be. This
+	 * implementation calls {@link FeedbackMessages#clearComponentSpecific()} to
+	 * aggresively ensure there won't be memory leaks. Clients can override this
+	 * method to e.g. call {@link FeedbackMessages#clearPageSpecific(Page)}.
+	 * <p>
+	 * This method should be called from by the framework right before a even
+	 * handler is called. There is no need for clients to call this method
+	 * directly
+	 * </p>
 	 * 
-	 * @return Whether this session is temporary (which is the same as it's id
-	 *         being null)
+	 * @param page
+	 *            any current page (the page on which the event handler is that
+	 *            is about to be processed)
 	 */
-	public final boolean isTemporary()
+	public void cleanupFeedbackMessages(Page page)
 	{
-		return getId() == null;
+		feedbackMessages.clearComponentSpecific();
 	}
 
 	/**
@@ -358,6 +397,49 @@
 	}
 
 	/**
+	 * Automatically creates a page map, giving it a session unique name.
+	 * 
+	 * @return Created PageMap
+	 */
+	public synchronized final IPageMap createAutoPageMap()
+	{
+		return newPageMap(createAutoPageMapName());
+	}
+
+	/**
+	 * With this call you can create a pagemap name but not create the pagemap
+	 * itself already. It will give the first pagemap name where it couldn't
+	 * find a current pagemap for.
+	 * 
+	 * It will return the same name if you call it 2 times in a row.
+	 * 
+	 * @return The created pagemap name
+	 */
+	public synchronized final String createAutoPageMapName()
+	{
+		String name = "wicket-" + autoCreatePageMapCounter;
+		IPageMap pm = pageMapForName(name, false);
+		while (pm != null)
+		{
+			autoCreatePageMapCounter++;
+			name = "wicket-" + autoCreatePageMapCounter;
+			pm = pageMapForName(name, false);
+		}
+		return name;
+	}
+
+	/**
+	 * Registers an error feedback message for this session
+	 * 
+	 * @param message
+	 *            The feedback message
+	 */
+	public final void error(final String message)
+	{
+		addFeedbackMessage(message, FeedbackMessage.ERROR);
+	}
+
+	/**
 	 * Get the application that is currently working with this session.
 	 * 
 	 * @return Returns the application.
@@ -404,6 +486,28 @@
 	}
 
 	/**
+	 * Gets the converter instance. This method returns the cached converter for
+	 * the current locale. Whenever the locale is changed, the cached value is
+	 * cleared and the converter will be recreated for the new locale on a next
+	 * request.
+	 * 
+	 * @param type
+	 *            TODO
+	 * 
+	 * @return the converter
+	 */
+	public final IConverter getConverter(Class/* <?> */type)
+	{
+		if (converterSupplier == null)
+		{
+			// Let the factory create a new converter
+			converterSupplier = getApplication().getApplicationSettings()
+					.getConverterLocatorFactory().newConverterLocator();
+		}
+		return converterSupplier.getConverter(type);
+	}
+
+	/**
 	 * @return The default page map
 	 */
 	public final IPageMap getDefaultPageMap()
@@ -412,6 +516,16 @@
 	}
 
 	/**
+	 * Gets feedback messages stored in session
+	 * 
+	 * @return unmodifiable list of feedback messages
+	 */
+	public final FeedbackMessages getFeedbackMessages()
+	{
+		return feedbackMessages;
+	}
+
+	/**
 	 * Gets the unique id for this session from the underlying SessionStore. May
 	 * be null if a concrete session is not yet created.
 	 * 
@@ -535,80 +649,28 @@
 		}
 		return null;
 	}
-
-	/**
-	 * @return The page factory for this session
-	 */
-	public final IPageFactory getPageFactory()
-	{
-		return getApplication().getSessionSettings().getPageFactory();
-	}
-
-	/**
-	 * @param page
-	 *            The page, or null if no page context is available
-	 * @return The page factory for the page, or the default page factory if
-	 *         page was null
-	 */
-	public final IPageFactory getPageFactory(final Page page)
-	{
-		if (page != null)
-		{
-			return page.getPageFactory();
-		}
-		return getPageFactory();
-	}
-
-	/**
-	 * Gets a page map for the given name, automatically creating it if need be.
-	 * 
-	 * @param pageMapName
-	 *            Name of page map, or null for default page map
-	 * @param autoCreate
-	 *            True if the page map should be automatically created if it
-	 *            does not exist
-	 * @return PageMap for name
-	 */
-	public final IPageMap pageMapForName(String pageMapName, final boolean autoCreate)
-	{
-		IPageMap pageMap = (IPageMap)getAttribute(attributeForPageMapName(pageMapName));
-		if (pageMap == null && autoCreate)
-		{
-			pageMap = newPageMap(pageMapName);
-		}
-		return pageMap;
-	}
-
-	/**
-	 * Automatically creates a page map, giving it a session unique name.
-	 * 
-	 * @return Created PageMap
+
+	/**
+	 * @return The page factory for this session
 	 */
-	public synchronized final IPageMap createAutoPageMap()
+	public final IPageFactory getPageFactory()
 	{
-		return newPageMap(createAutoPageMapName());
+		return getApplication().getSessionSettings().getPageFactory();
 	}
 
 	/**
-	 * With this call you can create a pagemap name but not create the pagemap
-	 * itself already. It will give the first pagemap name where it couldn't
-	 * find a current pagemap for.
-	 * 
-	 * It will return the same name if you call it 2 times in a row.
-	 * 
-	 * @return The created pagemap name
+	 * @param page
+	 *            The page, or null if no page context is available
+	 * @return The page factory for the page, or the default page factory if
+	 *         page was null
 	 */
-	public synchronized final String createAutoPageMapName()
+	public final IPageFactory getPageFactory(final Page page)
 	{
-		String name = "wicket-" + autoCreatePageMapCounter;
-		IPageMap pm = pageMapForName(name, false);
-		while (pm != null)
+		if (page != null)
 		{
-			autoCreatePageMapCounter++;
-			name = "wicket-" + autoCreatePageMapCounter;
-			pm = pageMapForName(name, false);
+			return page.getPageFactory();
 		}
-		return name;
+		return getPageFactory();
 	}
 
 	/**
@@ -653,6 +715,17 @@
 	}
 
 	/**
+	 * Registers an informational feedback message for this session
+	 * 
+	 * @param message
+	 *            The feedback message
+	 */
+	public final void info(final String message)
+	{
+		addFeedbackMessage(message, FeedbackMessage.INFO);
+	}
+
+	/**
 	 * Set the session for each PageMap
 	 */
 	public final void init()
@@ -680,6 +753,19 @@
 	}
 
 	/**
+	 * 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;
+	}
+
+	/**
 	 * Creates a new page map with a given name
 	 * 
 	 * @param name
@@ -721,6 +807,26 @@
 	}
 
 	/**
+	 * Gets a page map for the given name, automatically creating it if need be.
+	 * 
+	 * @param pageMapName
+	 *            Name of page map, or null for default page map
+	 * @param autoCreate
+	 *            True if the page map should be automatically created if it
+	 *            does not exist
+	 * @return PageMap for name
+	 */
+	public final IPageMap pageMapForName(String pageMapName, final boolean autoCreate)
+	{
+		IPageMap pageMap = (IPageMap)getAttribute(attributeForPageMapName(pageMapName));
+		if (pageMap == null && autoCreate)
+		{
+			pageMap = newPageMap(pageMapName);
+		}
+		return pageMap;
+	}
+
+	/**
 	 * @param pageMap
 	 *            Page map to remove
 	 */
@@ -761,6 +867,7 @@
 		dirty();
 	}
 
+
 	/**
 	 * Set the locale for this session.
 	 * 
@@ -854,18 +961,6 @@
 		}
 	}
 
-
-	/**
-	 * Registers an informational feedback message for this session
-	 * 
-	 * @param message
-	 *            The feedback message
-	 */
-	public final void info(final String message)
-	{
-		addFeedbackMessage(message, FeedbackMessage.INFO);
-	}
-
 	/**
 	 * Registers a warning feedback message for this session
 	 * 
@@ -878,49 +973,6 @@
 	}
 
 	/**
-	 * Registers an error feedback message for this session
-	 * 
-	 * @param message
-	 *            The feedback message
-	 */
-	public final void error(final String message)
-	{
-		addFeedbackMessage(message, FeedbackMessage.ERROR);
-	}
-
-	/**
-	 * Gets feedback messages stored in session
-	 * 
-	 * @return unmodifiable list of feedback messages
-	 */
-	public final FeedbackMessages getFeedbackMessages()
-	{
-		return feedbackMessages;
-	}
-
-	/**
-	 * Gets the converter instance. This method returns the cached converter for
-	 * the current locale. Whenever the locale is changed, the cached value is
-	 * cleared and the converter will be recreated for the new locale on a next
-	 * request.
-	 * 
-	 * @param type
-	 *            TODO
-	 * 
-	 * @return the converter
-	 */
-	public final IConverter getConverter(Class/* <?> */type)
-	{
-		if (converterSupplier == null)
-		{
-			// Let the factory create a new converter
-			converterSupplier = getApplication().getApplicationSettings()
-					.getConverterLocatorFactory().newConverterLocator();
-		}
-		return converterSupplier.getConverter(type);
-	}
-
-	/**
 	 * Adds a feedback message to the list of messages
 	 * 
 	 * @param message
@@ -934,6 +986,16 @@
 	}
 
 	/**
+	 * @param pageMapName
+	 *            Name of page map
+	 * @return Session attribute holding page map
+	 */
+	private final String attributeForPageMapName(final String pageMapName)
+	{
+		return pageMapAttributePrefix + pageMapName;
+	}
+
+	/**
 	 * Any detach logic for session subclasses. This is called on the end of
 	 * handling a request, when the RequestCycle is about to be detached from
 	 * the current thread.
@@ -959,10 +1021,20 @@
 	 */
 	protected final Object getAttribute(final String name)
 	{
-		RequestCycle cycle = RequestCycle.get();
-		if (cycle != null)
+		if (!isTemporary())
+		{
+			RequestCycle cycle = RequestCycle.get();
+			if (cycle != null)
+			{
+				return getSessionStore().getAttribute(cycle.getRequest(), name);
+			}
+		}
+		else
 		{
-			return getSessionStore().getAttribute(cycle.getRequest(), name);
+			if (temporarySessionAttributes != null)
+			{
+				return temporarySessionAttributes.get(name);
+			}
 		}
 		return null;
 	}
@@ -972,12 +1044,22 @@
 	 */
 	protected final List getAttributeNames()
 	{
-		RequestCycle cycle = RequestCycle.get();
-		if (cycle != null)
+		if (!isTemporary())
 		{
-			return getSessionStore().getAttributeNames(cycle.getRequest());
+			RequestCycle cycle = RequestCycle.get();
+			if (cycle != null)
+			{
+				return getSessionStore().getAttributeNames(cycle.getRequest());
+			}
 		}
-		return null;
+		else
+		{
+			if (temporarySessionAttributes != null)
+			{
+				return new ArrayList(temporarySessionAttributes.keySet());
+			}
+		}
+		return Collections.EMPTY_LIST;
 	}
 
 	/**
@@ -1007,10 +1089,20 @@
 	 */
 	protected final void removeAttribute(String name)
 	{
-		RequestCycle cycle = RequestCycle.get();
-		if (cycle != null)
+		if (!isTemporary())
+		{
+			RequestCycle cycle = RequestCycle.get();
+			if (cycle != null)
+			{
+				getSessionStore().removeAttribute(cycle.getRequest(), name);
+			}
+		}
+		else
 		{
-			getSessionStore().removeAttribute(cycle.getRequest(), name);
+			if (temporarySessionAttributes != null)
+			{
+				temporarySessionAttributes.remove(name);
+			}
 		}
 	}
 
@@ -1024,33 +1116,47 @@
 	 */
 	protected final void setAttribute(String name, Object value)
 	{
-		RequestCycle cycle = RequestCycle.get();
-		if (cycle == null)
+		if (!isTemporary())
 		{
-			throw new WicketRuntimeException("Can not set the attribute. No RequestCycle available");
-		}
+			RequestCycle cycle = RequestCycle.get();
+			if (cycle == null)
+			{
+				throw new IllegalStateException(
+						"Cannot set the attribute: no RequestCycle available");
+			}
 
-		ISessionStore store = getSessionStore();
-		Request request = cycle.getRequest();
+			ISessionStore store = getSessionStore();
+			Request request = cycle.getRequest();
 
-		// extra check on session binding event
-		if (value == this)
-		{
-			Object current = store.getAttribute(request, name);
-			if (current == null)
+			// extra check on session binding event
+			if (value == this)
 			{
-				String id = store.getSessionId(request, false);
-				if (id != null)
+				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);
+					}
 				}
 			}
-		}
 
-		// Set the actual attribute
-		store.setAttribute(request, name, value);
+			// Set the actual attribute
+			store.setAttribute(request, name, value);
+		}
+		else
+		{
+			// we don't have to synchronize, as it is impossible a temporary
+			// session instance gets shared across threads
+			if (temporarySessionAttributes == null)
+			{
+				temporarySessionAttributes = new HashMap(3);
+			}
+			temporarySessionAttributes.put(name, value);
+		}
 	}
 
 	/**
@@ -1145,26 +1251,6 @@
 	}
 
 	/**
-	 * Cleans up any unrendered, dangling feedback messages there may be. This
-	 * implementation calls {@link FeedbackMessages#clearComponentSpecific()} to
-	 * aggresively ensure there won't be memory leaks. Clients can override this
-	 * method to e.g. call {@link FeedbackMessages#clearPageSpecific(Page)}.
-	 * <p>
-	 * This method should be called from by the framework right before a even
-	 * handler is called. There is no need for clients to call this method
-	 * directly
-	 * </p>
-	 * 
-	 * @param page
-	 *            any current page (the page on which the event handler is that
-	 *            is about to be processed)
-	 */
-	public void cleanupFeedbackMessages(Page page)
-	{
-		feedbackMessages.clearComponentSpecific();
-	}
-
-	/**
 	 * @param page
 	 *            The page to add to dirty objects list
 	 */
@@ -1177,31 +1263,6 @@
 		}
 	}
 
-	/**
-	 * INTERNAL API. The request cycle when detached will call this.
-	 * 
-	 */
-	final void requestDetached()
-	{
-		if (pageMapsUsedInRequest != null)
-		{
-			synchronized (pageMapsUsedInRequest)
-			{
-				Thread t = Thread.currentThread();
-				Iterator it = pageMapsUsedInRequest.entrySet().iterator();
-				while (it.hasNext())
-				{
-					Entry entry = (Entry)it.next();
-					if (entry.getValue() == t)
-					{
-						it.remove();
-					}
-				}
-				pageMapsUsedInRequest.notifyAll();
-			}
-		}
-	}
-
 
 	/**
 	 * @param map
@@ -1222,16 +1283,6 @@
 	}
 
 	/**
-	 * @param pageMapName
-	 *            Name of page map
-	 * @return Session attribute holding page map
-	 */
-	private final String attributeForPageMapName(final String pageMapName)
-	{
-		return pageMapAttributePrefix + pageMapName;
-	}
-
-	/**
 	 * @return The current thread dirty objects list
 	 */
 	List getDirtyObjectsList()
@@ -1243,5 +1294,30 @@
 			dirtyObjects.set(list);
 		}
 		return list;
+	}
+
+	/**
+	 * INTERNAL API. The request cycle when detached will call this.
+	 * 
+	 */
+	final void requestDetached()
+	{
+		if (pageMapsUsedInRequest != null)
+		{
+			synchronized (pageMapsUsedInRequest)
+			{
+				Thread t = Thread.currentThread();
+				Iterator it = pageMapsUsedInRequest.entrySet().iterator();
+				while (it.hasNext())
+				{
+					Entry entry = (Entry)it.next();
+					if (entry.getValue() == t)
+					{
+						it.remove();
+					}
+				}
+				pageMapsUsedInRequest.notifyAll();
+			}
+		}
 	}
 }