Author: dan.j.allen
Date: 2009-04-08 18:58:39 -0400 (Wed, 08 Apr 2009)
New Revision: 10362
Added:
trunk/ui/src/main/java/org/jboss/seam/ui/RenderStampStore.java
Modified:
trunk/doc/Seam_Reference_Guide/en-US/Components.xml
trunk/ui/src/main/java/org/jboss/seam/ui/renderkit/TokenRendererBase.java
Log:
JBSEAM-4076
Modified: trunk/doc/Seam_Reference_Guide/en-US/Components.xml
===================================================================
--- trunk/doc/Seam_Reference_Guide/en-US/Components.xml 2009-04-08 22:51:53 UTC (rev
10361)
+++ trunk/doc/Seam_Reference_Guide/en-US/Components.xml 2009-04-08 22:58:39 UTC (rev
10362)
@@ -192,6 +192,28 @@
</para>
</listitem>
</varlistentry>
+ <varlistentry>
+
<term><literal>org.jboss.seam.ui.renderStampStore</literal></term>
+ <listitem>
+ <para>
+ A component (session-scoped by default) responsible for
maintaining
+ a collection of render stamps. A render stamp is an indicator as
+ to whether a form which was rendered has been submitted. This
store
+ is useful when the client-side state saving method of JSF is
being
+ used because it puts the determination of whether a form has
been
+ posted in the control of the server rather than in the component
tree
+ which is maintained on the client.
+ </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>maxSize</literal> — The
maximum number of stamps
+ to be kept in the store. Default: 100
+ </para>
+ </listitem>
+ </itemizedlist>
+ </listitem>
+ </varlistentry>
</variablelist>
<para>
Added: trunk/ui/src/main/java/org/jboss/seam/ui/RenderStampStore.java
===================================================================
--- trunk/ui/src/main/java/org/jboss/seam/ui/RenderStampStore.java
(rev 0)
+++ trunk/ui/src/main/java/org/jboss/seam/ui/RenderStampStore.java 2009-04-08 22:58:39 UTC
(rev 10362)
@@ -0,0 +1,96 @@
+package org.jboss.seam.ui;
+
+import static org.jboss.seam.ScopeType.SESSION;
+import static org.jboss.seam.annotations.Install.BUILT_IN;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.jboss.seam.Component;
+import org.jboss.seam.annotations.AutoCreate;
+import org.jboss.seam.annotations.Install;
+import org.jboss.seam.annotations.Name;
+import org.jboss.seam.annotations.Scope;
+import org.jboss.seam.annotations.intercept.BypassInterceptors;
+
+/**
+ * A class that stores render stamps for use with <s:token> when client
side
+ * state saving is in use. By default the render stamp store will never remove a
+ * render stamp unless instructed to by a UIToken. If the maxSize property is
+ * larger than zero then it will control the maximum number of tokens stored,
+ * with the oldest token being removed when a token is inserted that will take
+ * the store over the maxSize limit. The default maxSize is 100.
+ *
+ * @author Stuart Douglas
+ */
+(a)Name("org.jboss.seam.ui.renderStampStore")
+@Scope(SESSION)
+@Install(precedence = BUILT_IN, value = false)
+@AutoCreate
+@BypassInterceptors
+public class RenderStampStore implements Serializable {
+
+ class RenderStamp {
+ String stamp;
+ Date timeStamp;
+ }
+
+ int maxSize = 100;
+
+ Map<String, RenderStamp> store = new ConcurrentHashMap<String,
RenderStamp>();
+
+ /**
+ * Stores a stamp in the store, and returns the key it is stored under.
+ */
+ public String storeStamp(String stamp) {
+ if (maxSize > 0) {
+ if (store.size() == maxSize) {
+ Date oldest = null;
+ String oldestSigniture = null;
+ for (String sig : store.keySet()) {
+ RenderStamp s = store.get(sig);
+ if (oldest == null || s.timeStamp.before(oldest)) {
+ oldestSigniture = sig;
+ }
+ }
+ store.remove(oldestSigniture);
+ }
+ }
+ RenderStamp s = new RenderStamp();
+ s.stamp = stamp;
+ s.timeStamp = new Date();
+ String key;
+ do {
+ key = UUID.randomUUID().toString();
+ } while (!store.containsKey(key));
+ store.put(key, s);
+ return key;
+ }
+
+ public void removeStamp(String viewSigniture) {
+ store.remove(viewSigniture);
+ }
+
+ public String getStamp(String viewSigniture) {
+ RenderStamp s = store.get(viewSigniture);
+ if (s != null) {
+ return store.get(viewSigniture).stamp;
+ }
+ return null;
+ }
+
+ public static RenderStampStore instance() {
+ return (RenderStampStore) Component.getInstance(RenderStampStore.class);
+ }
+
+ public int getMaxSize() {
+ return maxSize;
+ }
+
+ public void setMaxSize(int maxSize) {
+ this.maxSize = maxSize;
+ }
+}
Modified: trunk/ui/src/main/java/org/jboss/seam/ui/renderkit/TokenRendererBase.java
===================================================================
--- trunk/ui/src/main/java/org/jboss/seam/ui/renderkit/TokenRendererBase.java 2009-04-08
22:51:53 UTC (rev 10361)
+++ trunk/ui/src/main/java/org/jboss/seam/ui/renderkit/TokenRendererBase.java 2009-04-08
22:58:39 UTC (rev 10362)
@@ -10,6 +10,7 @@
import javax.faces.context.ResponseWriter;
import javax.servlet.http.HttpSession;
+import org.jboss.seam.ui.RenderStampStore;
import org.jboss.seam.ui.UnauthorizedCommandException;
import org.jboss.seam.ui.component.UIToken;
import org.jboss.seam.ui.util.HTML;
@@ -20,7 +21,10 @@
/**
* <p>
* The <strong>TokenRendererBase</strong> renders the form's signature as
a
- * hidden form field for the UIToken component.
+ * hidden form field for the UIToken component. If the renderStampStore
+ * component is enabled, the actually signature will be stored in the session
+ * and the key to this token store in the hidden form field, providing the same
+ * guarantee for client-side state saving as with server-side state saving.
* </p>
*
* <p>
@@ -41,18 +45,30 @@
* sha1(signature = contextPath + viewId + "," + formClientId +
"," + random alphanum + sessionId, salt = clientUid)
* </pre>
*
- * <p>The decode method performs the following steps:</p>
+ * <p>
+ * The decode method performs the following steps:
+ * </p>
* <ol>
- * <li>check if this is a postback, otherwise skip the check</li>
- * <li>check that this form was the one that was submitted, otherwise skip the
check</li>
- * <li>get the unique client identifier (from cookie), otherwise throw an exception
that the browser must have unique identifier</li>
- * <li>get the javax.faces.FormSignature request parameter, otherwise throw an
exception that the form signature is missing</li>
- * <li>generate the hash as before and verify that it equals the value of the
javax.faces.FormSignature request parameter, otherwise throw an exception</li>
+ * <li>Check if this is a postback, otherwise skip the check</li>
+ * <li>Check that this form was the one that was submitted, otherwise skip the
+ * check</li>
+ * <li>Get the unique client identifier (from cookie), otherwise throw an
+ * exception that the browser must have unique identifier</li>
+ * <li>Get the javax.faces.FormSignature request parameter, otherwise throw an
+ * exception that the form signature is missing</li>
+ * <li>If the renderStampStore component is enabled, retrieve the render stamp
+ * from the store using the key stored in the render stamp attribute of the
form.</li>
+ * <li>Generate the hash as before and verify that it equals the value of the
+ * javax.faces.FormSignature request parameter, otherwise throw an exception</li>
* </ol>
*
- * <p>If all of that passes, we are okay to process the form (advance to validate
phase as decode() is called in apply request values).</p>
+ * <p>
+ * If all of that passes, we are okay to process the form (advance to validate
+ * phase as decode() is called in apply request values).
+ * </p>
*
* @author Dan Allen
+ * @author Stuart Douglas
* @see UnauthorizedCommandException
*/
public class TokenRendererBase extends RendererBase
@@ -60,7 +76,7 @@
public static final String FORM_SIGNATURE_PARAM =
"javax.faces.FormSignature";
public static final String RENDER_STAMP_ATTR = "javax.faces.RenderStamp";
-
+
private static final String COOKIE_CHECK_SCRIPT_KEY =
"org.jboss.seam.ui.COOKIE_CHECK_SCRIPT";
@Override
@@ -93,7 +109,12 @@
{
throw new UnauthorizedCommandException(viewId, "Form signature
invalid");
}
-
+ RenderStampStore store = RenderStampStore.instance();
+ if (store != null)
+ {
+ // remove the key from the store if we are using it
+
store.removeStamp(String.valueOf(form.getAttributes().get(RENDER_STAMP_ATTR)));
+ }
form.getAttributes().remove(RENDER_STAMP_ATTR);
}
}
@@ -107,11 +128,23 @@
{
throw new IllegalStateException("UIToken must be inside a UIForm.");
}
-
+
+ String renderStamp = RandomStringUtils.randomAlphanumeric(50);
+ RenderStampStore store = RenderStampStore.instance();
+ if (store != null)
+ {
+ // if the store is not null we store the key
+ // instead of the actual stamp; this puts the
+ // server in control of this value rather than
+ // the component tree, which is owned by the client
+ // when using client-side state saving
+ renderStamp = store.storeStamp(renderStamp);
+ }
+
writeCookieCheckScript(context, writer, token);
token.getClientUidSelector().seed();
- form.getAttributes().put(RENDER_STAMP_ATTR,
RandomStringUtils.randomAlphanumeric(50));
+ form.getAttributes().put(RENDER_STAMP_ATTR, renderStamp);
writer.startElement(HTML.INPUT_ELEM, component);
writer.writeAttribute(HTML.TYPE_ATTR, HTML.INPUT_TYPE_HIDDEN, HTML.TYPE_ATTR);
writer.writeAttribute(HTML.NAME_ATTR, FORM_SIGNATURE_PARAM, HTML.NAME_ATTR);
@@ -141,7 +174,16 @@
String rawViewSignature = context.getExternalContext().getRequestContextPath() +
"," + context.getViewRoot().getViewId() + "," +
form.getClientId(context);
if (useRenderStamp)
{
- rawViewSignature += "," +
form.getAttributes().get(RENDER_STAMP_ATTR);
+ String renderStamp = form.getAttributes().get(RENDER_STAMP_ATTR).toString();
+ RenderStampStore store = RenderStampStore.instance();
+ if (store != null)
+ {
+ // if we are using the RenderStampStore the key to access the render
+ // stamp
+ // is stored in the view root instead of the actual render stamp
+ renderStamp = store.getStamp(renderStamp);
+ }
+ rawViewSignature += "," + renderStamp;
}
if (useSessionId)
{
@@ -164,5 +206,5 @@
return null;
}
}
-
+
}