You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by lu...@apache.org on 2014/11/01 00:56:10 UTC

svn commit: r1635886 - in /myfaces/core/trunk: impl/src/main/java/org/apache/myfaces/application/viewstate/ shared/ shared/src/main/java/org/apache/myfaces/shared/config/ shared/src/main/java/org/apache/myfaces/shared/context/flash/ shared/src/test/jav...

Author: lu4242
Date: Fri Oct 31 23:56:09 2014
New Revision: 1635886

URL: http://svn.apache.org/r1635886
Log:
MYFACES-3936 Flash object requires cleanup strategy when client window feature is used

Added:
    myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/context/flash/ClientWindowFlashTokenLRUMap.java
    myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/context/flash/FlashClientWindowTokenCollection.java
    myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/context/flash/ReleasableFlash.java
Modified:
    myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/application/viewstate/SerializedViewCollection.java
    myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/application/viewstate/ServerSideStateCacheImpl.java
    myfaces/core/trunk/shared/pom.xml
    myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/config/MyfacesConfig.java
    myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/context/flash/FlashImpl.java
    myfaces/core/trunk/shared/src/test/java/org/apache/myfaces/shared/context/flash/FlashImplTest.java

Modified: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/application/viewstate/SerializedViewCollection.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/application/viewstate/SerializedViewCollection.java?rev=1635886&r1=1635885&r2=1635886&view=diff
==============================================================================
--- myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/application/viewstate/SerializedViewCollection.java (original)
+++ myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/application/viewstate/SerializedViewCollection.java Fri Oct 31 23:56:09 2014
@@ -23,11 +23,10 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.logging.Level;
 import java.util.logging.Logger;
 import javax.faces.context.FacesContext;
 import org.apache.commons.collections.map.LRUMap;
-import org.apache.myfaces.shared.util.WebConfigParamUtils;
+import org.apache.myfaces.shared.config.MyfacesConfig;
 import org.apache.myfaces.spi.ViewScopeProvider;
 
 /**
@@ -42,7 +41,7 @@ class SerializedViewCollection implement
     private static final long serialVersionUID = -3734849062185115847L;
     private final List<SerializedViewKey> _keys = 
         new ArrayList<SerializedViewKey>(
-            ServerSideStateCacheImpl.DEFAULT_NUMBER_OF_VIEWS_IN_SESSION);
+            MyfacesConfig.INIT_PARAM_NUMBER_OF_VIEWS_IN_SESSION_DEFAULT);
     private final Map<SerializedViewKey, Object> _serializedViews = 
         new HashMap<SerializedViewKey, Object>();
     /**
@@ -233,8 +232,7 @@ class SerializedViewCollection implement
 
     protected Integer getNumberOfSequentialViewsInSession(FacesContext context)
     {
-        return WebConfigParamUtils.getIntegerInitParameter( context.getExternalContext(),
-                ServerSideStateCacheImpl.NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION_PARAM);
+        return MyfacesConfig.getCurrentInstance(context.getExternalContext()).getNumberOfSequentialViewsInSession();
     }
 
     /**
@@ -245,31 +243,7 @@ class SerializedViewCollection implement
      */
     protected int getNumberOfViewsInSession(FacesContext context)
     {
-        String value = context.getExternalContext().getInitParameter(
-                ServerSideStateCacheImpl.NUMBER_OF_VIEWS_IN_SESSION_PARAM);
-        int views = ServerSideStateCacheImpl.DEFAULT_NUMBER_OF_VIEWS_IN_SESSION;
-        if (value != null)
-        {
-            try
-            {
-                views = Integer.parseInt(value);
-                if (views <= 0)
-                {
-                    log.severe("Configured value for " + ServerSideStateCacheImpl.NUMBER_OF_VIEWS_IN_SESSION_PARAM
-                              + " is not valid, must be an value > 0, using default value ("
-                              + ServerSideStateCacheImpl.DEFAULT_NUMBER_OF_VIEWS_IN_SESSION);
-                    views = ServerSideStateCacheImpl.DEFAULT_NUMBER_OF_VIEWS_IN_SESSION;
-                }
-            }
-            catch (Throwable e)
-            {
-                log.log( Level.SEVERE, "Error determining the value for "
-                       + ServerSideStateCacheImpl.NUMBER_OF_VIEWS_IN_SESSION_PARAM
-                       + ", expected an integer value > 0, using default value ("
-                       + ServerSideStateCacheImpl.DEFAULT_NUMBER_OF_VIEWS_IN_SESSION + "): " + e.getMessage(), e);
-            }
-        }
-        return views;
+        return MyfacesConfig.getCurrentInstance(context.getExternalContext()).getNumberOfViewsInSession();
     }
 
     public synchronized void putLastWindowKey(FacesContext context, String id, SerializedViewKey key)

Modified: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/application/viewstate/ServerSideStateCacheImpl.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/application/viewstate/ServerSideStateCacheImpl.java?rev=1635886&r1=1635885&r2=1635886&view=diff
==============================================================================
--- myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/application/viewstate/ServerSideStateCacheImpl.java (original)
+++ myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/application/viewstate/ServerSideStateCacheImpl.java Fri Oct 31 23:56:09 2014
@@ -42,6 +42,7 @@ import javax.faces.lifecycle.ClientWindo
 
 import org.apache.myfaces.application.StateCache;
 import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
+import org.apache.myfaces.shared.config.MyfacesConfig;
 import org.apache.myfaces.shared.renderkit.RendererUtils;
 import org.apache.myfaces.shared.util.MyFacesObjectInputStream;
 import org.apache.myfaces.shared.util.WebConfigParamUtils;
@@ -67,37 +68,13 @@ class ServerSideStateCacheImpl extends S
     public static final String RESTORED_VIEW_KEY_REQUEST_ATTR = 
         ServerSideStateCacheImpl.class.getName() + ".RESTORED_VIEW_KEY";
     
-    /**
-     * Defines the amount (default = 20) of the latest views are stored in session.
-     * 
-     * <p>Only applicable if state saving method is "server" (= default).
-     * </p>
-     * 
-     */
-    @JSFWebConfigParam(defaultValue="20",since="1.1", classType="java.lang.Integer", group="state", tags="performance")
-    public static final String NUMBER_OF_VIEWS_IN_SESSION_PARAM = "org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION";
+    public static final String NUMBER_OF_VIEWS_IN_SESSION_PARAM = MyfacesConfig.INIT_PARAM_NUMBER_OF_VIEWS_IN_SESSION;
 
-    /**
-     * Indicates the amount of views (default is not active) that should be stored in session between sequential
-     * POST or POST-REDIRECT-GET if org.apache.myfaces.USE_FLASH_SCOPE_PURGE_VIEWS_IN_SESSION is true.
-     * 
-     * <p>Only applicable if state saving method is "server" (= default). For example, if this param has value = 2 and 
-     * in your custom webapp there is a form that is clicked 3 times, only 2 views
-     * will be stored and the third one (the one stored the first time) will be
-     * removed from session, even if the view can
-     * store more sessions org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION.
-     * This feature becomes useful for multi-window applications.
-     * where without this feature a window can swallow all view slots so
-     * the other ones will throw ViewExpiredException.</p>
-     */
-    @JSFWebConfigParam(since="2.0.6", classType="java.lang.Integer", group="state", tags="performance")
     public static final String NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION_PARAM
-            = "org.apache.myfaces.NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION";
+            = MyfacesConfig.INIT_PARAM_NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION;
     
-    /**
-     * Default value for <code>org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION</code> context parameter.
-     */
-    public static final int DEFAULT_NUMBER_OF_VIEWS_IN_SESSION = 20;
+    public static final int DEFAULT_NUMBER_OF_VIEWS_IN_SESSION = 
+            MyfacesConfig.INIT_PARAM_NUMBER_OF_VIEWS_IN_SESSION_DEFAULT;
 
     /**
      * Indicate if the state should be serialized before save it on the session. 
@@ -714,9 +691,8 @@ class ServerSideStateCacheImpl extends S
     {
         if (!_numberOfSequentialViewsInSessionSet)
         {
-            _numberOfSequentialViewsInSession = WebConfigParamUtils.getIntegerInitParameter(
-                    externalContext, 
-                    NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION_PARAM);
+            _numberOfSequentialViewsInSession = MyfacesConfig.getCurrentInstance(externalContext)
+                    .getNumberOfSequentialViewsInSession();
             _numberOfSequentialViewsInSessionSet = true;
         }
         return _numberOfSequentialViewsInSession;

Modified: myfaces/core/trunk/shared/pom.xml
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/shared/pom.xml?rev=1635886&r1=1635885&r2=1635886&view=diff
==============================================================================
--- myfaces/core/trunk/shared/pom.xml (original)
+++ myfaces/core/trunk/shared/pom.xml Fri Oct 31 23:56:09 2014
@@ -77,6 +77,11 @@
         </dependency>
         
         <dependency>
+            <groupId>commons-collections</groupId>
+            <artifactId>commons-collections</artifactId>
+        </dependency>
+        
+        <dependency>
             <groupId>commons-codec</groupId>
             <artifactId>commons-codec</artifactId>
         </dependency>

Modified: myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/config/MyfacesConfig.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/config/MyfacesConfig.java?rev=1635886&r1=1635885&r2=1635886&view=diff
==============================================================================
--- myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/config/MyfacesConfig.java (original)
+++ myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/config/MyfacesConfig.java Fri Oct 31 23:56:09 2014
@@ -19,6 +19,8 @@
 package org.apache.myfaces.shared.config;
 
 
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import javax.faces.context.ExternalContext;
 import javax.servlet.ServletContext;
 
@@ -475,7 +477,52 @@ public class MyfacesConfig
     @JSFWebConfigParam(defaultValue="false",since="2.0.5")
     public static final String INIT_PARAM_FLASH_SCOPE_DISABLED = "org.apache.myfaces.FLASH_SCOPE_DISABLED";
     public static final boolean INIT_PARAM_FLASH_SCOPE_DISABLED_DEFAULT = false;
+    
+    /**
+     * Defines the amount (default = 20) of the latest views are stored in session.
+     * 
+     * <p>Only applicable if state saving method is "server" (= default).
+     * </p>
+     * 
+     */
+    @JSFWebConfigParam(defaultValue="20",since="1.1", classType="java.lang.Integer", group="state", tags="performance")
+    public static final String INIT_PARAM_NUMBER_OF_VIEWS_IN_SESSION = "org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION";
+
+    /**
+     * Default value for <code>org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION</code> context parameter.
+     */
+    public static final int INIT_PARAM_NUMBER_OF_VIEWS_IN_SESSION_DEFAULT = 20;    
 
+    /**
+     * Indicates the amount of views (default is not active) that should be stored in session between sequential
+     * POST or POST-REDIRECT-GET if org.apache.myfaces.USE_FLASH_SCOPE_PURGE_VIEWS_IN_SESSION is true.
+     * 
+     * <p>Only applicable if state saving method is "server" (= default). For example, if this param has value = 2 and 
+     * in your custom webapp there is a form that is clicked 3 times, only 2 views
+     * will be stored and the third one (the one stored the first time) will be
+     * removed from session, even if the view can
+     * store more sessions org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION.
+     * This feature becomes useful for multi-window applications.
+     * where without this feature a window can swallow all view slots so
+     * the other ones will throw ViewExpiredException.</p>
+     */
+    @JSFWebConfigParam(since="2.0.6", classType="java.lang.Integer", group="state", tags="performance", 
+            defaultValue = "4")
+    public static final String INIT_PARAM_NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION
+            = "org.apache.myfaces.NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION";
+    public static final Integer INIT_PARAM_NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION_DEFAULT = 4;
+    
+    /**
+     * Indicate the max number of flash tokens stored into session. It is only active when 
+     * javax.faces.CLIENT_WINDOW_MODE is enabled. Each flash token is associated to one client window id at
+     * the same time, so this param is related to the limit of active client windows per session. 
+     * By default is the same number as in 
+     * (org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION / 
+     * org.apache.myfaces.NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION) + 1 = 6.
+     */
+    @JSFWebConfigParam(since="2.2.6", group="state", tags="performance")
+    static final String NUMBER_OF_FLASH_TOKENS_IN_SESSION = "org.apache.myfaces.NUMBER_OF_FLASH_TOKENS_IN_SESSION";
+    
     private boolean _prettyHtml;
     private boolean _detectJavascript;
     private boolean _allowJavascript;
@@ -514,6 +561,9 @@ public class MyfacesConfig
     private boolean _strictJsf2FaceletsCompatibility;
     private boolean _renderFormViewStateAtBegin;
     private boolean _flashScopeDisabled;
+    private Integer _numberOfViewsInSession;
+    private Integer _numberOfSequentialViewsInSession;
+    private Integer _numberOfFlashTokensInSession;
 
     private static final boolean TOMAHAWK_AVAILABLE;
     private static final boolean MYFACES_IMPL_AVAILABLE;
@@ -621,6 +671,11 @@ public class MyfacesConfig
         setStrictJsf2FaceletsCompatibility(INIT_PARAM_STRICT_JSF_2_FACELETS_COMPATIBILITY_DEFAULT);
         setRenderFormViewStateAtBegin(INIT_PARAM_RENDER_FORM_VIEW_STATE_AT_BEGIN_DEFAULT);
         setFlashScopeDisabled(INIT_PARAM_FLASH_SCOPE_DISABLED_DEFAULT);
+        setNumberOfViewsInSession(INIT_PARAM_NUMBER_OF_VIEWS_IN_SESSION_DEFAULT);
+        setNumberOfSequentialViewsInSession(INIT_PARAM_NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION_DEFAULT);
+        setNumberOfFlashTokensInSession(
+                (INIT_PARAM_NUMBER_OF_VIEWS_IN_SESSION_DEFAULT / 
+                        INIT_PARAM_NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION_DEFAULT)+1);
     }
 
     private static MyfacesConfig createAndInitializeMyFacesConfig(ExternalContext extCtx)
@@ -751,6 +806,68 @@ public class MyfacesConfig
                 INIT_PARAM_FLASH_SCOPE_DISABLED,
                 INIT_PARAM_FLASH_SCOPE_DISABLED_DEFAULT));
         
+        try
+        {
+            myfacesConfig.setNumberOfSequentialViewsInSession(WebConfigParamUtils.getIntegerInitParameter(
+                    extCtx, 
+                    INIT_PARAM_NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION,
+                    INIT_PARAM_NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION_DEFAULT));
+            Integer views = myfacesConfig.getNumberOfSequentialViewsInSession();
+            if (views == null || views < 0)
+            {
+                Logger.getLogger(MyfacesConfig.class.getName()).severe(
+                        "Configured value for " + INIT_PARAM_NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION
+                          + " is not valid, must be an value >= 0, using default value ("
+                          + INIT_PARAM_NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION_DEFAULT);
+                views = INIT_PARAM_NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION_DEFAULT;
+            }
+        }
+        catch (Throwable e)
+        {
+            Logger.getLogger(MyfacesConfig.class.getName()).log(Level.SEVERE, "Error determining the value for "
+                   + INIT_PARAM_NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION
+                   + ", expected an integer value > 0, using default value ("
+                   + INIT_PARAM_NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION_DEFAULT + "): " + e.getMessage(), e);
+        }        
+        try
+        {
+            myfacesConfig.setNumberOfViewsInSession(WebConfigParamUtils.getIntegerInitParameter(
+                        extCtx, 
+                        INIT_PARAM_NUMBER_OF_VIEWS_IN_SESSION,
+                        INIT_PARAM_NUMBER_OF_VIEWS_IN_SESSION_DEFAULT));
+            Integer views = myfacesConfig.getNumberOfViewsInSession();
+            if (views == null || views <= 0)
+            {
+                Logger.getLogger(MyfacesConfig.class.getName()).severe(
+                        "Configured value for " + INIT_PARAM_NUMBER_OF_VIEWS_IN_SESSION
+                          + " is not valid, must be an value > 0, using default value ("
+                          + INIT_PARAM_NUMBER_OF_VIEWS_IN_SESSION_DEFAULT);
+                views = INIT_PARAM_NUMBER_OF_VIEWS_IN_SESSION_DEFAULT;
+            }
+        }
+        catch (Throwable e)
+        {
+            Logger.getLogger(MyfacesConfig.class.getName()).log(Level.SEVERE, "Error determining the value for "
+                   + INIT_PARAM_NUMBER_OF_VIEWS_IN_SESSION
+                   + ", expected an integer value > 0, using default value ("
+                   + INIT_PARAM_NUMBER_OF_VIEWS_IN_SESSION_DEFAULT + "): " + e.getMessage(), e);
+        }
+
+        Integer numberOfFlashTokensInSessionDefault;
+        Integer i = myfacesConfig.getNumberOfSequentialViewsInSession();
+        int j = myfacesConfig.getNumberOfViewsInSession();
+        if (i != null && i.intValue() > 0)
+        {
+            numberOfFlashTokensInSessionDefault = (j / i.intValue()) + 1;
+        }
+        else
+        {
+            numberOfFlashTokensInSessionDefault = j + 1;
+        }
+        myfacesConfig.setNumberOfFlashTokensInSession(WebConfigParamUtils.getIntegerInitParameter(
+                        extCtx, 
+                        INIT_PARAM_NUMBER_OF_VIEWS_IN_SESSION, numberOfFlashTokensInSessionDefault));
+        
         if (TOMAHAWK_AVAILABLE)
         {
             myfacesConfig.setDetectJavascript(getBooleanInitParameter(extCtx, INIT_PARAM_DETECT_JAVASCRIPT,
@@ -1338,4 +1455,52 @@ public class MyfacesConfig
     {
         this._flashScopeDisabled = flashScopeDisabled;
     }
+
+    /**
+     * @return the _numberOfViewsInSession
+     */
+    public Integer getNumberOfViewsInSession()
+    {
+        return _numberOfViewsInSession;
+    }
+
+    /**
+     * @param _numberOfViewsInSession the _numberOfViewsInSession to set
+     */
+    public void setNumberOfViewsInSession(Integer numberOfViewsInSession)
+    {
+        this._numberOfViewsInSession = numberOfViewsInSession;
+    }
+
+    /**
+     * @return the _numberOfSequentialViewsInSession
+     */
+    public Integer getNumberOfSequentialViewsInSession()
+    {
+        return _numberOfSequentialViewsInSession;
+    }
+
+    /**
+     * @param _numberOfSequentialViewsInSession the _numberOfSequentialViewsInSession to set
+     */
+    public void setNumberOfSequentialViewsInSession(Integer numberOfSequentialViewsInSession)
+    {
+        this._numberOfSequentialViewsInSession = numberOfSequentialViewsInSession;
+    }
+
+    /**
+     * @return the _numberOfFlashTokensInSession
+     */
+    public Integer getNumberOfFlashTokensInSession()
+    {
+        return _numberOfFlashTokensInSession;
+    }
+
+    /**
+     * @param _numberOfFlashTokensInSession the _numberOfFlashTokensInSession to set
+     */
+    public void setNumberOfFlashTokensInSession(Integer numberOfFlashTokensInSession)
+    {
+        this._numberOfFlashTokensInSession = numberOfFlashTokensInSession;
+    }
 }

Added: myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/context/flash/ClientWindowFlashTokenLRUMap.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/context/flash/ClientWindowFlashTokenLRUMap.java?rev=1635886&view=auto
==============================================================================
--- myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/context/flash/ClientWindowFlashTokenLRUMap.java (added)
+++ myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/context/flash/ClientWindowFlashTokenLRUMap.java Fri Oct 31 23:56:09 2014
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.shared.context.flash;
+
+import java.io.Serializable;
+import java.util.Map;
+import javax.faces.FacesWrapper;
+import javax.faces.context.FacesContext;
+import javax.faces.context.Flash;
+import org.apache.commons.collections.map.LRUMap;
+
+/**
+ *
+ */
+class ClientWindowFlashTokenLRUMap extends LRUMap implements Serializable
+{
+
+    public ClientWindowFlashTokenLRUMap()
+    {
+    }
+
+    public ClientWindowFlashTokenLRUMap(int maxSize)
+    {
+        super(maxSize);
+    }
+
+    public ClientWindowFlashTokenLRUMap(int maxSize, boolean scanUntilRemovable)
+    {
+        super(maxSize, scanUntilRemovable);
+    }
+
+    public ClientWindowFlashTokenLRUMap(int maxSize, float loadFactor)
+    {
+        super(maxSize, loadFactor);
+    }
+
+    public ClientWindowFlashTokenLRUMap(int maxSize, float loadFactor, boolean scanUntilRemovable)
+    {
+        super(maxSize, loadFactor, scanUntilRemovable);
+    }
+
+    public ClientWindowFlashTokenLRUMap(Map map)
+    {
+        super(map);
+    }
+
+    public ClientWindowFlashTokenLRUMap(Map map, boolean scanUntilRemovable)
+    {
+        super(map, scanUntilRemovable);
+    }
+    
+    public synchronized String getValue(String key)
+    {
+        return (String) get(key);
+    }
+
+    @Override
+    protected boolean removeLRU(LinkEntry entry)
+    {
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+        Flash flash = facesContext.getExternalContext().getFlash();
+        if (flash != null)
+        {
+            ReleasableFlash rf = null;
+            while (flash != null)
+            {
+                if (flash instanceof ReleasableFlash)
+                {
+                    rf = (ReleasableFlash) flash;
+                    break;
+                }
+                if (flash instanceof FacesWrapper)
+                {
+                    flash = ((FacesWrapper<? extends Flash>) flash).getWrapped();
+                }
+                else
+                {
+                    flash = null;
+                }
+            }
+            if (rf != null)
+            {
+                rf.clearFlashMap(facesContext, (String) entry.getKey(), (String) entry.getValue());
+            }
+        }
+        return super.removeLRU(entry);
+    }
+}

Added: myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/context/flash/FlashClientWindowTokenCollection.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/context/flash/FlashClientWindowTokenCollection.java?rev=1635886&view=auto
==============================================================================
--- myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/context/flash/FlashClientWindowTokenCollection.java (added)
+++ myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/context/flash/FlashClientWindowTokenCollection.java Fri Oct 31 23:56:09 2014
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.shared.context.flash;
+
+import java.io.Serializable;
+
+/**
+ * This class is a wrapper used to deal with concurrency issues when accessing the inner LRUMap.
+ */
+class FlashClientWindowTokenCollection implements Serializable
+{
+    private ClientWindowFlashTokenLRUMap lruMap;
+
+    public FlashClientWindowTokenCollection(ClientWindowFlashTokenLRUMap lruMap)
+    {
+        this.lruMap = lruMap;
+    }
+
+    public FlashClientWindowTokenCollection()
+    {
+    }
+    
+    public synchronized void put(String key, String value)
+    {
+        lruMap.put(key, value);
+    }
+    
+    public synchronized String get(String key)
+    {
+        return (String) lruMap.get(key);
+    }
+    
+    public synchronized void remove(String key)
+    {
+        lruMap.remove(key);
+    }
+    
+    public synchronized boolean isEmpty()
+    {
+        return lruMap.isEmpty();
+    }
+}

Modified: myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/context/flash/FlashImpl.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/context/flash/FlashImpl.java?rev=1635886&r1=1635885&r2=1635886&view=diff
==============================================================================
--- myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/context/flash/FlashImpl.java (original)
+++ myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/context/flash/FlashImpl.java Fri Oct 31 23:56:09 2014
@@ -51,7 +51,7 @@ import org.apache.myfaces.shared.util.Se
 /**
  * Implementation of Flash object
  */
-public class FlashImpl extends Flash
+public class FlashImpl extends Flash implements ReleasableFlash
 {
     
     // ~ static fields --------------------------------------------------------
@@ -117,6 +117,8 @@ public class FlashImpl extends Flash
      */
     static final String FLASH_EXECUTE_MAP_TOKEN = FLASH_PREFIX + ".EXECUTEMAP.TOKEN";
     
+    static final String FLASH_CW_LRU_MAP = FLASH_PREFIX + ".CW.LRUMAP";
+    
     /**
      * Token separator.
      */
@@ -267,6 +269,8 @@ public class FlashImpl extends Flash
                 // was already performed and the response has already been committed,
                 // because the renderMap is stored in the session.
                 _saveMessages(facesContext);
+                
+                _clearRenderFlashTokenIfMapEmpty(facesContext);
             }
         }
     }
@@ -751,9 +755,8 @@ public class FlashImpl extends Flash
         ClientWindow clientWindow = externalContext.getClientWindow();
         if (clientWindow != null)
         {
-            //Use HttpSession or PortletSession object
-            Map<String, Object> sessionMap = externalContext.getSessionMap();
-            sessionMap.put(FLASH_RENDER_MAP_TOKEN+SEPARATOR_CHAR+clientWindow.getId(), tokenValue);
+            FlashClientWindowTokenCollection lruMap = getFlashClientWindowTokenCollection(externalContext, true);
+            lruMap.put(clientWindow.getId(), tokenValue);
         }
         else
         {
@@ -786,9 +789,11 @@ public class FlashImpl extends Flash
         ClientWindow clientWindow = externalContext.getClientWindow();
         if (clientWindow != null)
         {
-            Map<String, Object> sessionMap = externalContext.getSessionMap();
-            tokenValue = (String) sessionMap.get(FLASH_RENDER_MAP_TOKEN+
-                    SEPARATOR_CHAR+clientWindow.getId());
+            FlashClientWindowTokenCollection lruMap = getFlashClientWindowTokenCollection(externalContext, false);
+            if (lruMap != null)
+            {
+                tokenValue = (String) lruMap.get(clientWindow.getId());
+            }
         }
         else
         {
@@ -836,9 +841,19 @@ public class FlashImpl extends Flash
         {
             if (facesContext.isPostback())
             {
-                // on a postback, we should always have a previousToken
-                log.warning("Identifier for execute FlashMap was lost on " +
-                        "the postback, thus FlashScope information is gone.");
+                if (facesContext.getExternalContext().getClientWindow() == null)
+                {
+                    // on a postback, we should always have a previousToken
+                    log.warning("Identifier for execute FlashMap was lost on " +
+                            "the postback, thus FlashScope information is gone.");
+                }
+                else
+                {
+                    // Next token was not preserved in session, which means flash map
+                    // is empty. Create a new token and store it as execute map, which
+                    // will be empty.
+                    requestMap.put(FLASH_EXECUTE_MAP_TOKEN, _getNextToken());
+                }
             }
             
             // create a new token (and thus a new Map) for this request's 
@@ -906,7 +921,7 @@ public class FlashImpl extends Flash
         if (map == null)
         {
             String token = (String) requestMap.get(FLASH_RENDER_MAP_TOKEN);
-            String fullToken = FLASH_SESSION_MAP_SUBKEY_PREFIX + SEPARATOR_CHAR + token;
+            String fullToken = FLASH_SESSION_MAP_SUBKEY_PREFIX + SEPARATOR_CHAR + token + SEPARATOR_CHAR;
             map =  _createSubKeyMap(context, fullToken);
             requestMap.put(FLASH_RENDER_MAP, map);
         }
@@ -933,7 +948,7 @@ public class FlashImpl extends Flash
         if (map == null)
         {
             String token = (String) requestMap.get(FLASH_EXECUTE_MAP_TOKEN);
-            String fullToken = FLASH_SESSION_MAP_SUBKEY_PREFIX + SEPARATOR_CHAR + token;
+            String fullToken = FLASH_SESSION_MAP_SUBKEY_PREFIX + SEPARATOR_CHAR + token + SEPARATOR_CHAR;
             map = _createSubKeyMap(context, fullToken);
             requestMap.put(FLASH_EXECUTE_MAP, map);
         }
@@ -1041,6 +1056,82 @@ public class FlashImpl extends Flash
         // always be an execute Map, however sometimes an empty one!
         map.clear();
     }
+    
+    private void _clearRenderFlashTokenIfMapEmpty(FacesContext facesContext)
+    {
+        // Keep in mind that we cannot remove a cookie once the response has been sent,
+        // but we can remove an attribute from session anytime, so the idea is check
+        // if the map is empty or not and if that so, do not save the token. The effect
+        // is we can reduce the session size a bit.
+        ExternalContext externalContext = facesContext.getExternalContext();
+        Object session = facesContext.getExternalContext().getSession(false);
+        ClientWindow clientWindow = externalContext.getClientWindow();
+        if (session != null && clientWindow != null)
+        {
+            Map<String, Object> map = _getRenderFlashMap(facesContext);
+            if (map.isEmpty())
+            {
+                // Remove token, because it is not necessary
+                FlashClientWindowTokenCollection lruMap = getFlashClientWindowTokenCollection(externalContext, false);
+                if (lruMap != null)
+                {
+                    lruMap.remove(clientWindow.getId());
+                    Map<String, Object> sessionMap = externalContext.getSessionMap();
+                    if (lruMap.isEmpty())
+                    {
+                        sessionMap.remove(FLASH_CW_LRU_MAP);
+                    }
+                    else
+                    {
+                        //refresh remove
+                        sessionMap.put(FLASH_CW_LRU_MAP, lruMap);
+                    }
+                }
+            }
+        }
+    }
+    
+    protected FlashClientWindowTokenCollection getFlashClientWindowTokenCollection(
+            ExternalContext externalContext, boolean create)
+    {
+        Object session = externalContext.getSession(false);
+        if (session == null && !create)
+        {
+            return null;
+        }
+        Map<String, Object> sessionMap = externalContext.getSessionMap();
+        FlashClientWindowTokenCollection lruMap = (FlashClientWindowTokenCollection)
+                sessionMap.get(FLASH_CW_LRU_MAP);
+        if (lruMap == null)
+        {
+            Integer ft = MyfacesConfig.getCurrentInstance(externalContext).getNumberOfFlashTokensInSession();
+            lruMap = new FlashClientWindowTokenCollection(new ClientWindowFlashTokenLRUMap(ft));
+        }    
+        
+        if (create)
+        {
+            sessionMap.put(FLASH_CW_LRU_MAP, lruMap);
+        }
+        return lruMap;
+    }
+
+    public void clearFlashMap(FacesContext facesContext, String clientWindowId, String token)
+    {
+        if (!_flashScopeDisabled)
+        {
+            ExternalContext externalContext = facesContext.getExternalContext();
+            ClientWindow clientWindow = externalContext.getClientWindow();
+            if (clientWindow != null)
+            {
+                if (token != null)
+                {
+                    String fullToken = FLASH_SESSION_MAP_SUBKEY_PREFIX + SEPARATOR_CHAR + token + SEPARATOR_CHAR;
+                    Map<String, Object> map =  _createSubKeyMap(facesContext, fullToken);
+                    map.clear();
+                }
+            }
+        }
+    }
 
     /**
      * Creates a Cookie with the given name and value.

Added: myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/context/flash/ReleasableFlash.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/context/flash/ReleasableFlash.java?rev=1635886&view=auto
==============================================================================
--- myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/context/flash/ReleasableFlash.java (added)
+++ myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/context/flash/ReleasableFlash.java Fri Oct 31 23:56:09 2014
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.shared.context.flash;
+
+import javax.faces.context.FacesContext;
+
+/**
+ * Implement this interface, so ClientWindowFlashTokenLRUMap can remove the resources
+ * associated with the client window / flash scope token, when an entry is discarded by
+ * the LRU algorithm.
+ */
+public interface ReleasableFlash
+{
+    public void clearFlashMap(FacesContext facesContext, String clientWindowId, String token);
+    
+}

Modified: myfaces/core/trunk/shared/src/test/java/org/apache/myfaces/shared/context/flash/FlashImplTest.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/shared/src/test/java/org/apache/myfaces/shared/context/flash/FlashImplTest.java?rev=1635886&r1=1635885&r2=1635886&view=diff
==============================================================================
--- myfaces/core/trunk/shared/src/test/java/org/apache/myfaces/shared/context/flash/FlashImplTest.java (original)
+++ myfaces/core/trunk/shared/src/test/java/org/apache/myfaces/shared/context/flash/FlashImplTest.java Fri Oct 31 23:56:09 2014
@@ -100,7 +100,7 @@ public class FlashImplTest extends Abstr
         final String renderToken = (String) externalContext
                 .getRequestMap().get(FlashImpl.FLASH_RENDER_MAP_TOKEN);
         final String sessionMapKey = FlashImpl.FLASH_SESSION_MAP_SUBKEY_PREFIX + 
-                FlashImpl.SEPARATOR_CHAR + renderToken + "testkey1";
+                FlashImpl.SEPARATOR_CHAR + renderToken + FlashImpl.SEPARATOR_CHAR + "testkey1";
         
         // Assertion
         Assert.assertEquals("The render FlashMap must use the session Map to store the values.",