/*
 * 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_tomahawk.context.flash;

import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
import org.apache.myfaces.shared_tomahawk.util.ExternalContextUtils;

import javax.faces.application.FacesMessage;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.Flash;
import javax.faces.event.PhaseId;
import javax.servlet.ServletRequest;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Logger;

/**
 * Implementation of Flash object
 * 
 * @author Leonardo Uribe (latest modification by $Author: lu4242 $)
 * @author Jakob Korherr
 * @version $Revision: 1202947 $ $Date: 2011-11-16 19:16:48 -0500 (Wed, 16 Nov 2011) $
 */
public class FlashImpl extends Flash
{
    
    // ~ static fields --------------------------------------------------------
    
    private static final Logger logger = Logger.getLogger(FlashImpl.class.getName());
    
    /**
     * Defines whether flash scope is disabled, preventing add the Flash cookie to the response. 
     * 
     * <p>This is useful for applications that does not require to use flash scope, and instead uses other scopes.</p>
     */
    @JSFWebConfigParam(defaultValue="false",since="2.0.5")
    private static final String FLASH_SCOPE_DISABLED_PARAM = "org.apache.myfaces.FLASH_SCOPE_DISABLED";

    /**
     * Use this prefix instead of the whole class name, because
     * this makes the Cookies and the SubKeyMap operations (actually
     * every String based operation where this is used as a key) faster.
     */
    private static final String FLASH_PREFIX = "oam.Flash";
    
    /**
     * Key on application map to keep current instance
     */
    static final String FLASH_INSTANCE = FLASH_PREFIX + ".INSTANCE";

    /**
     * Key to store if this setRedirect(true) was called on this request,
     * and to store the redirect Cookie.
     */
    static final String FLASH_REDIRECT = FLASH_PREFIX + ".REDIRECT";
    
    /**
     * Key to store the value of the redirect cookie
     */
    static final String FLASH_PREVIOUS_REQUEST_REDIRECT 
            = FLASH_PREFIX + ".REDIRECT.PREVIOUSREQUEST";
    
    /**
     * Key used to check if this request should keep messages
     */
    static final String FLASH_KEEP_MESSAGES = FLASH_PREFIX + ".KEEP_MESSAGES";

    /**
     * Key used to store the messages list in the render FlashMap.
     */
    static final String FLASH_KEEP_MESSAGES_LIST = "KEEPMESSAGESLIST";

    /**
     * Session map prefix to flash maps
     */
    static final String FLASH_SESSION_MAP_SUBKEY_PREFIX = FLASH_PREFIX + ".SCOPE";
    
    /**
     * Key for the cached render FlashMap instance on the request map.
     */
    static final String FLASH_RENDER_MAP = FLASH_PREFIX + ".RENDERMAP";
    
    /**
     * Key for the current render FlashMap token.
     */
    static final String FLASH_RENDER_MAP_TOKEN = FLASH_PREFIX + ".RENDERMAP.TOKEN";
    
    /**
     * Key for the cached execute FlashMap instance on the request map.
     */
    static final String FLASH_EXECUTE_MAP = FLASH_PREFIX + ".EXECUTEMAP";

    /**
     * Key for the current execute FlashMap token.
     */
    static final String FLASH_EXECUTE_MAP_TOKEN = FLASH_PREFIX + ".EXECUTEMAP.TOKEN";
    
    /**
     * Token separator.
     */
    static final char SEPARATOR_CHAR = '.';
    
     // ~ static methods  -----------------------------------------------------
    
    /**
     * Return a Flash instance from the application map
     * 
     * @param context
     * @return
     */
    public static Flash getCurrentInstance(ExternalContext context)
    {
        Map<String, Object> applicationMap = context.getApplicationMap();
        
        Flash flash = (Flash) applicationMap.get(FLASH_INSTANCE);
        if (flash == null)
        {
            // synchronize the ApplicationMap to ensure that only
            // once instance of FlashImpl is created and stored in it.
            synchronized (applicationMap)
            {
                // check again, because first try was un-synchronized
                flash = (Flash) applicationMap.get(FLASH_INSTANCE);
                if (flash == null)
                {
                    flash = new FlashImpl(context);
                    applicationMap.put(FLASH_INSTANCE, flash);
                }
            }
        }

        return flash;
    }

    /**
     * Returns a cryptographically secure random number to use as the _count seed
     */
    private static long _getSeed()
    {
        SecureRandom rng;
        try
        {
            // try SHA1 first
            rng = SecureRandom.getInstance("SHA1PRNG");
        }
        catch (NoSuchAlgorithmException e)
        {
            // SHA1 not present, so try the default (which could potentially not be
            // cryptographically secure)
            rng = new SecureRandom();
        }

        // use 48 bits for strength and fill them in
        byte[] randomBytes = new byte[6];
        rng.nextBytes(randomBytes);

        // convert to a long
        return new BigInteger(randomBytes).longValue();
    }
    
    // ~ private fields and constructor ---------------------------------------
    
    // the current token value
    private final AtomicLong _count;
    private boolean _flashScopeDisabled;
    
    public FlashImpl(ExternalContext externalContext)
    {
        _count = new AtomicLong(_getSeed());

        // Read whether flash scope is disabled.
        _flashScopeDisabled = "true".equalsIgnoreCase(externalContext.getInitParameter(FLASH_SCOPE_DISABLED_PARAM));
    }
    
    // ~ methods from javax.faces.context.Flash -------------------------------

    /**
     * Used to restore the redirect value and the FacesMessages of the previous 
     * request and to manage the flashMap tokens for this request before phase
     * restore view starts.
     */
    @Override
    public void doPrePhaseActions(FacesContext facesContext)
    {
        if (!_flashScopeDisabled)
        {
            final PhaseId currentPhaseId = facesContext.getCurrentPhaseId();
        
            if (PhaseId.RESTORE_VIEW.equals(currentPhaseId))
            {
                // restore the redirect value
                // note that the result of this method is used in many places, 
                // thus it has to be the first thing to do
                _restoreRedirectValue(facesContext);
            
                // restore the FlashMap token from the previous request
                // and create a new token for this request
                _manageFlashMapTokens(facesContext);
            
                // try to restore any saved messages
                _restoreMessages(facesContext);
            }
        }
    }
    
    /**
     * Used to destroy the executeMap and to save all FacesMessages for the
     * next request, but only if this is the last invocation of this method
     * in the current lifecycle (if redirect phase 5, otherwise phase 6).
     */
    @Override
    public void doPostPhaseActions(FacesContext facesContext)
    {
        if (!_flashScopeDisabled)
        {
            // do the actions only if this is the last time
            // doPostPhaseActions() is called on this request
            if (_isLastPhaseInRequest(facesContext))
            {
                if (_isRedirectTrueOnThisRequest(facesContext))
                {
                    // copy entries from executeMap to renderMap, if they do not exist
                    Map<String, Object> renderMap = _getRenderFlashMap(facesContext);

                    for (Map.Entry<String, Object> entry 
                        : _getExecuteFlashMap(facesContext).entrySet())
                    {
                        if (!renderMap.containsKey(entry.getKey()))
                        {
                            renderMap.put(entry.getKey(), entry.getValue());
                        }
                    }
                }
            
                // remove execute Map entries from session (--> "destroy" executeMap)
                _clearExecuteFlashMap(facesContext);
            
                // save the current FacesMessages in the renderMap, if wanted
                // Note that this also works on a redirect even though the redirect
                // was already performed and the response has already been committed,
                // because the renderMap is stored in the session.
                _saveMessages(facesContext);
            }
        }
    }
    
    /**
     * Return the value of this property for the flash for this session.
     * 
     * This must be false unless:
     *   - setRedirect(boolean) was called for the current lifecycle traversal
     *     with true as the argument.
     *   - The current lifecycle traversal for this session is in the "execute"
     *     phase and the previous traversal had setRedirect(boolean) called with
     *     true as the argument.
     */
    @Override
    public boolean isRedirect()
    {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        boolean thisRedirect = _isRedirectTrueOnThisRequest(facesContext);
        boolean prevRedirect = _isRedirectTrueOnPreviousRequest(facesContext);
        boolean executePhase = !PhaseId.RENDER_RESPONSE.equals(facesContext.getCurrentPhaseId());
        
        return thisRedirect || (executePhase && prevRedirect);
    }
    
    @Override
    public void setRedirect(boolean redirect)
    {
        // FIXME this method has a design flaw, because the only valid value
        // is true and it should only be called by the NavigationHandler
        // in a redirect case RIGHT BEFORE ExternalContext.redirect().
        // Maybe a PreRedirectEvent issued by the ExternalContext would be a good
        // choice for JSF 2.1.
        
        FacesContext facesContext = FacesContext.getCurrentInstance();
        ExternalContext externalContext = facesContext.getExternalContext();
        Map<String, Object> requestMap = externalContext.getRequestMap();
        
        // save the value on the requestMap for this request
        Boolean alreadySet = (Boolean) requestMap.get(FLASH_REDIRECT);
        alreadySet = (alreadySet == null ? Boolean.FALSE : Boolean.TRUE);
        
        // if true and not already set, store it for the following request
        if (!alreadySet && redirect)
        {
            requestMap.put(FLASH_REDIRECT, Boolean.TRUE);
            
            // save redirect=true for the next request
            _saveRedirectValue(facesContext);
        }
        else
        {
            if (alreadySet)
            {
                logger.warning("Multiple call to setRedirect() ignored.");
            }
            else // redirect = false
            {
                logger.warning("Ignored call to setRedirect(false), because this "
                        + "should only be set to true by the NavigationHandler. "
                        + "No one else should change it.");
            }
        }
    }
    
    /**
     * Take a value from the requestMap, or if it does not exist from the
     * execute FlashMap, and put it on the render FlashMap, so it is visible on
     * the next request.
     */
    @Override
    public void keep(String key)
    {
        _checkFlashScopeDisabled();
        FacesContext facesContext = FacesContext.getCurrentInstance();
        Map<String, Object> requestMap = facesContext.getExternalContext().getRequestMap();
        Object value = requestMap.get(key);
        
        // if the key does not exist in the requestMap,
        // try to get it from the execute FlashMap
        if (value == null)
        {
            Map<String, Object> executeMap = _getExecuteFlashMap(facesContext);
            // Null-check, because in the GET request of a POST-REDIRECT-GET 
            // pattern there is no execute map
            if (executeMap != null)
            {
                value = executeMap.get(key);
                // Store it on request map so we can get it later. For example, 
                // this is used by org.apache.myfaces.el.FlashELResolver to return
                // the value that has been promoted.
                requestMap.put(key, value);
            }
        }
        
        // put it in the render FlashMap
        _getRenderFlashMap(facesContext).put(key, value);
    }

    /**
     * This is just an alias for the request scope map.
     */
    @Override
    public void putNow(String key, Object value)
    {
        _checkFlashScopeDisabled();
        FacesContext.getCurrentInstance().getExternalContext()
                .getRequestMap().put(key, value);
    }

    /**
     * Returns the value of a previous call to setKeepMessages() from this
     * request. If there was no call yet, false is returned.
     */
    @Override
    public boolean isKeepMessages()
    {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        ExternalContext externalContext = facesContext.getExternalContext();
        Map<String, Object> requestMap = externalContext.getRequestMap();
        Boolean keepMessages = (Boolean) requestMap.get(FLASH_KEEP_MESSAGES);
        
        return (keepMessages == null ? Boolean.FALSE : keepMessages);
    }
    
    /**
     * If this property is true, the messages should be kept for the next 
     * request, no matter if it is a normal postback case or a POST-
     * REDIRECT-GET case.
     * 
     * Note that we don't have to store this value for the next request
     * (like setRedirect()), because we will know if it was true on the 
     * next request, if we can find any stored messages in the FlashMap.
     * (also see _saveMessages() and _restoreMessages()).
     */
    @Override
    public void setKeepMessages(boolean keepMessages)
    {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        ExternalContext externalContext = facesContext.getExternalContext();
        Map<String, Object> requestMap = externalContext.getRequestMap();
        requestMap.put(FLASH_KEEP_MESSAGES, keepMessages);
    }
    
    // ~ Methods from Map interface -------------------------------------------
    
    // NOTE that all these methods do not necessarily delegate to the same Map,
    // because we differentiate between reading and writing operations.
    
    public void clear()
    {
        _checkFlashScopeDisabled();
        _getFlashMapForWriting().clear();
    }

    public boolean containsKey(Object key)
    {
        _checkFlashScopeDisabled();
        return _getFlashMapForReading().containsKey(key);
    }

    public boolean containsValue(Object value)
    {
        _checkFlashScopeDisabled();
        return _getFlashMapForReading().containsValue(value);
    }

    public Set<java.util.Map.Entry<String, Object>> entrySet()
    {
        _checkFlashScopeDisabled();
        return _getFlashMapForReading().entrySet();
    }

    public Object get(Object key)
    {
        _checkFlashScopeDisabled();
        if (key == null)
        {
            return null;
        }
        
        if ("keepMessages".equals(key))
        {
            return isKeepMessages();
        }
        else if ("redirect".equals(key))
        {
            return isRedirect();
        }
        
        return _getFlashMapForReading().get(key);
    }
    
    public boolean isEmpty()
    {
        _checkFlashScopeDisabled();
        return _getFlashMapForReading().isEmpty();
    }

    public Set<String> keySet()
    {
        _checkFlashScopeDisabled();
        return _getFlashMapForReading().keySet();
    }

    public Object put(String key, Object value)
    {
        _checkFlashScopeDisabled();
        if (key == null)
        {
            return null;
        }
        
        if ("keepMessages".equals(key))
        {
            Boolean booleanValue = _convertToBoolean(value);
            this.setKeepMessages(booleanValue);
            return booleanValue;
        }
        else if ("redirect".equals(key))
        {
            Boolean booleanValue = _convertToBoolean(value);
            this.setRedirect(booleanValue);
            return booleanValue;
        }
        else
        {
            return _getFlashMapForWriting().put(key, value); 
        }
    }

    public void putAll(Map<? extends String, ? extends Object> m)
    {
        _checkFlashScopeDisabled();
        _getFlashMapForWriting().putAll(m);
    }

    public Object remove(Object key)
    {
        _checkFlashScopeDisabled();
        return _getFlashMapForWriting().remove(key);
    }

    public int size()
    {
        _checkFlashScopeDisabled();
        return _getFlashMapForReading().size();
    }

    public Collection<Object> values()
    {
        _checkFlashScopeDisabled();
        return _getFlashMapForReading().values();
    }
    
    // ~ Implementation methods ----------------------------------------------- 
    
    /**
     * Returns true if the current phase is the last phase in the request
     * and thus if doPostPhaseActions() is called for the last time.
     * 
     * This will be true if either we are in phase 6 (render response)
     * or if setRedirect(true) was called on this request and we are
     * in phase 5 (invoke application).
     */
    private boolean _isLastPhaseInRequest(FacesContext facesContext)
    {
        final PhaseId currentPhaseId = facesContext.getCurrentPhaseId();
        
        boolean lastPhaseNormalRequest = PhaseId.RENDER_RESPONSE.equals(currentPhaseId);
        boolean lastPhaseIfRedirect = PhaseId.INVOKE_APPLICATION.equals(currentPhaseId) 
                && _isRedirectTrueOnThisRequest(facesContext);
        
        return lastPhaseNormalRequest || lastPhaseIfRedirect;
    }
    
    /**
     * Return true if setRedirect(true) was called on this request.
     * @param facesContext
     * @return
     */
    private boolean _isRedirectTrueOnThisRequest(FacesContext facesContext)
    {
        ExternalContext externalContext = facesContext.getExternalContext();
        Map<String, Object> requestMap = externalContext.getRequestMap();
        Boolean redirect = (Boolean) requestMap.get(FLASH_REDIRECT);
        
        return Boolean.TRUE.equals(redirect);
    }
    
    /**
     * Return true if setRedirect(true) was called on the previous request.
     * Precondition: doPrePhaseActions() must have been called on restore view phase.
     * @param facesContext
     * @return
     */
    private boolean _isRedirectTrueOnPreviousRequest(FacesContext facesContext)
    {
        ExternalContext externalContext = facesContext.getExternalContext();
        Map<String, Object> requestMap = externalContext.getRequestMap();
        Boolean redirect = (Boolean) requestMap.get(FLASH_PREVIOUS_REQUEST_REDIRECT);
        
        return Boolean.TRUE.equals(redirect);
    }
    
    /**
     * Saves the value of setRedirect() for the next request, if it was true
     */
    private void _saveRedirectValue(FacesContext facesContext)
    {
        ExternalContext externalContext = facesContext.getExternalContext();
        
        // This request contains a redirect. This condition is in general 
        // triggered by a NavigationHandler. After a redirect all request scope
        // values get lost, so in order to preserve this value we need to
        // pass it between request. One strategy is use a cookie that is never sent
        // to the client. Other alternative is use the session map.
        // See _restoreRedirectValue() for restoring this value.
        HttpServletResponse httpResponse = ExternalContextUtils
                .getHttpServletResponse(externalContext);
        if (httpResponse != null)
        {
            Cookie cookie = _createFlashCookie(FLASH_REDIRECT, "true", externalContext);
            httpResponse.addCookie(cookie);
        }
        else
        {
            externalContext.getSessionMap().put(FLASH_REDIRECT, true);
        }
    }

    /**
     * Restores the redirect value of the previous request and saves
     * it in the RequestMap under the key FLASH_PREVIOUS_REQUEST_REDIRECT.
     * Must not be called more than once per request.
     * After this method was invoked, the requestMap will contain Boolean.TRUE
     * if setRedirect(true) was called on the previous request or Boolean.FALSE
     * or null otherwise.
     */
    private void _restoreRedirectValue(FacesContext facesContext)
    {
        ExternalContext externalContext = facesContext.getExternalContext();
        HttpServletResponse httpResponse = ExternalContextUtils
                .getHttpServletResponse(externalContext);
        if (httpResponse != null)
        {
            // Request values are lost after a redirect. We can create a 
            // temporal cookie to pass the params between redirect calls.
            // It is better than use HttpSession object, because this cookie
            // is never sent by the server.
            Cookie cookie = (Cookie) externalContext
                    .getRequestCookieMap().get(FLASH_REDIRECT);
            if (cookie != null)
            {
                // the cookie exists means there was a redirect, regardless of the value
                externalContext.getRequestMap().put(
                        FLASH_PREVIOUS_REQUEST_REDIRECT, Boolean.TRUE);
                
                // A redirect happened, so it is safe to remove the cookie, setting
                // the maxAge to 0 seconds. The effect is we passed FLASH_REDIRECT param 
                // to this request object
                cookie.setMaxAge(0);
                cookie.setPath(_getCookiePath(externalContext));
                //MYFACES-3354 jetty 6.1.5 does not allow this,
                //call setMaxAge(0) is enough
                //cookie.setValue(null);
                httpResponse.addCookie(cookie);
            }
        }
        else
        {
            // Note that on portlet world we can't create cookies,
            // so we are forced to use the session map. Anyway, 
            // according to the Bridge implementation(for example see 
            // org.apache.myfaces.portlet.faces.bridge.BridgeImpl)
            // session object is created at start faces request
            Map<String, Object> sessionMap = externalContext.getSessionMap();
            
            // remove the value from the sessionMap
            Boolean redirect = (Boolean) sessionMap.remove(FLASH_REDIRECT);
            
            // put the value into the requestMap
            externalContext.getRequestMap().put(
                    FLASH_PREVIOUS_REQUEST_REDIRECT, redirect);
        }
    }
    
    /**
     * Saves the current FacesMessages as a List on the render FlashMap for the
     * next request if isKeepMessages() is true. Otherwise it removes any
     * existing FacesMessages-List from the renderFlashMap. 
     * @param facesContext
     */
    private void _saveMessages(FacesContext facesContext)
    {
        if (isKeepMessages()) 
        {
            // get all messages from the FacesContext and store
            // them on the renderMap
            List<MessageEntry> messageList = null;
                
            Iterator<String> iterClientIds = facesContext.getClientIdsWithMessages();
            while (iterClientIds.hasNext())
            {
                String clientId = (String) iterClientIds.next();
                Iterator<FacesMessage> iterMessages = facesContext.getMessages(clientId);
                
                while (iterMessages.hasNext())
                {
                    FacesMessage message = iterMessages.next();
    
                    if (messageList == null)
                    {
                        messageList = new ArrayList<MessageEntry>();
                    }
                    messageList.add(new MessageEntry(clientId, message));
                }
            }
    
            _getRenderFlashMap(facesContext).put(FLASH_KEEP_MESSAGES_LIST, messageList);
        }
        else
        {
            // do not keep messages --> remove messagesList from renderMap
            _getRenderFlashMap(facesContext).remove(FLASH_KEEP_MESSAGES_LIST);
        }
    }

    /**
     * Restore any saved FacesMessages from the previous request.
     * Note that we don't need to save the keepMessages value for this request,
     * because we just have to check if the value for FLASH_KEEP_MESSAGES_LIST exists.
     * @param facesContext
     */
    @SuppressWarnings("unchecked")
    private void _restoreMessages(FacesContext facesContext)
    {
        List<MessageEntry> messageList = (List<MessageEntry>) 
                _getExecuteFlashMap(facesContext).get(FLASH_KEEP_MESSAGES_LIST);

        if (messageList != null)
        {
            Iterator<MessageEntry> iterMessages = messageList.iterator();

            while (iterMessages.hasNext())
            {
                MessageEntry entry = iterMessages.next();
                facesContext.addMessage(entry.clientId, entry.message);
            }

            // we can now remove the messagesList from the flashMap
            _getExecuteFlashMap(facesContext).remove(FLASH_KEEP_MESSAGES_LIST);
        }
    }
    
    /**
     * Take the render map key and store it as a key for the next request.
     * 
     * On the next request we can get it with _getRenderFlashMapTokenFromPreviousRequest().
     * @param externalContext
     */
    private void _saveRenderFlashMapTokenForNextRequest(ExternalContext externalContext)
    {
        String tokenValue = (String) externalContext.getRequestMap().get(FLASH_RENDER_MAP_TOKEN);
        
        HttpServletResponse httpResponse = ExternalContextUtils.getHttpServletResponse(externalContext);
        if (httpResponse != null)
        {
            Cookie cookie = _createFlashCookie(FLASH_RENDER_MAP_TOKEN, tokenValue, externalContext);
            httpResponse.addCookie(cookie);
        }
        else
        {
            //Use HttpSession or PortletSession object
            Map<String, Object> sessionMap = externalContext.getSessionMap();
            sessionMap.put(FLASH_RENDER_MAP_TOKEN, tokenValue);
        }
    }
    
    /**
     * Retrieve the map token of the render map from the previous request.
     * 
     * Returns the value of _saveRenderFlashMapTokenForNextRequest() from
     * the previous request.
     * @param externalContext
     * @return
     */
    private String _getRenderFlashMapTokenFromPreviousRequest(ExternalContext externalContext)
    {
        String tokenValue = null;
        HttpServletResponse httpResponse = ExternalContextUtils.getHttpServletResponse(externalContext);
        if (httpResponse != null)
        {
            //Use a cookie
            Cookie cookie = (Cookie) externalContext.getRequestCookieMap().get(FLASH_RENDER_MAP_TOKEN);
            if (cookie != null)
            {
                tokenValue = cookie.getValue();
            }
        }
        else
        {
            //Use HttpSession or PortletSession object
            Map<String, Object> sessionMap = externalContext.getSessionMap();
            tokenValue = (String) sessionMap.get(FLASH_RENDER_MAP_TOKEN);
        }
        return tokenValue;
    }
    
    /**
     * Restores the render FlashMap token from the previous request.
     * This is the token of the executeMap for this request.
     * Furthermore it also creates a new token for this request's renderMap
     * (and thus implicitly a new renderMap).
     * @param facesContext
     */
    private void _manageFlashMapTokens(FacesContext facesContext)
    {
        ExternalContext externalContext = facesContext.getExternalContext();
        Map<String, Object> requestMap = externalContext.getRequestMap();

        final String previousRenderToken 
                = _getRenderFlashMapTokenFromPreviousRequest(externalContext);
        if (previousRenderToken != null)
        {
            // "restore" the renderMap from the previous request
            // and put it as the executeMap for this request
            requestMap.put(FLASH_EXECUTE_MAP_TOKEN, previousRenderToken);
        }
        else
        {
            if (facesContext.isPostback())
            {
                // on a postback, we should always have a previousToken
                logger.warning("Identifier for execute FlashMap was lost on " +
                        "the postback, thus FlashScope information is gone.");
            }
            
            // create a new token (and thus a new Map) for this request's 
            // executeMap so that we have an executeMap in any possible case.
            final String newExecuteToken = _getNextToken();
            requestMap.put(FLASH_EXECUTE_MAP_TOKEN, newExecuteToken);
        }
        
        // create a new token (and thus a new Map) for this request's renderMap
        final String newRenderToken = _getNextToken();
        requestMap.put(FLASH_RENDER_MAP_TOKEN, newRenderToken);
        
        // we now have the final render token for this request, thus we can
        // already save it for the next request, because it won't change
        _saveRenderFlashMapTokenForNextRequest(externalContext);
    }
    
    /**
     * Get the next token to be assigned to this request
     * 
     * @return
     */
    private String _getNextToken()
    {
        // atomically increment the value
        long nextToken = _count.incrementAndGet();

        // convert using base 36 because it is a fast efficient subset of base-64
        return Long.toString(nextToken, 36);
    }

    /**
     * Create a new subkey-wrapper of the session map with the given prefix.
     * This wrapper is used to implement the maps for the flash scope.
     * For more information see the SubKeyMap doc.
     */
    private Map<String, Object> _createSubKeyMap(FacesContext context, String prefix)
    {
        ExternalContext external = context.getExternalContext();
        Map<String, Object> sessionMap = external.getSessionMap();

        return new SubKeyMap<Object>(sessionMap, prefix);
    }

    /**
     * Return the flash map created on this traversal.
     * 
     * This FlashMap will be the execute FlashMap of the next traversal.
     * 
     * Note that it is supposed that FLASH_RENDER_MAP_TOKEN is initialized
     * before restore view phase (see doPrePhaseActions() for details).
     * 
     * @param context
     * @return
     */
    @SuppressWarnings("unchecked")
    private Map<String, Object> _getRenderFlashMap(FacesContext context)
    {
        // Note that we don't have to synchronize here, because it is no problem
        // if we create more SubKeyMaps with the same subkey, because they are
        // totally equal and point to the same entries in the SessionMap.
        
        Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
        Map<String, Object> map = (Map<String, Object>) requestMap.get(FLASH_RENDER_MAP);
        if (map == null)
        {
            String token = (String) requestMap.get(FLASH_RENDER_MAP_TOKEN);
            String fullToken = FLASH_SESSION_MAP_SUBKEY_PREFIX + SEPARATOR_CHAR + token;
            map =  _createSubKeyMap(context, fullToken);
            requestMap.put(FLASH_RENDER_MAP, map);
        }
        return map;
    }
    
    /**
     * Return the execute Flash Map.
     * 
     * This FlashMap was the render FlashMap of the previous traversal. 
     * 
     * @param context
     * @return
     */
    @SuppressWarnings("unchecked")
    private Map<String, Object> _getExecuteFlashMap(FacesContext context)
    {
        // Note that we don't have to synchronize here, because it is no problem
        // if we create more SubKeyMaps with the same subkey, because they are
        // totally equal and point to the same entries in the SessionMap.
        
        Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
        Map<String, Object> map = (Map<String, Object>) requestMap.get(FLASH_EXECUTE_MAP);
        if (map == null)
        {
            String token = (String) requestMap.get(FLASH_EXECUTE_MAP_TOKEN);
            String fullToken = FLASH_SESSION_MAP_SUBKEY_PREFIX + SEPARATOR_CHAR + token;
            map = _createSubKeyMap(context, fullToken);
            requestMap.put(FLASH_EXECUTE_MAP, map);
        }
        return map;
    }
    
    /**
     * Get the proper map according to the current phase:
     * 
     * Normal case:
     * 
     * - First request, restore view phase (create a new one): render map n
     * - First request, execute phase: Skipped
     * - First request, render  phase: render map n
     * 
     *   Render map n saved and put as execute map n
     * 
     * - Second request, execute phase: execute map n
     * - Second request, render  phase: render map n+1
     * 
     * Post Redirect Get case: Redirect is triggered by a call to setRedirect(true) from NavigationHandler
     * or earlier using c:set tag.
     * 
     * - First request, restore view phase (create a new one): render map n
     * - First request, execute phase: Skipped
     * - First request, render  phase: render map n
     * 
     *   Render map n saved and put as execute map n
     * 
     * POST
     * 
     * - Second request, execute phase: execute map n
     *   Note that render map n+1 is also created here to perform keep().
     * 
     * REDIRECT
     * 
     * - NavigationHandler do the redirect, requestMap data lost, called Flash.setRedirect(true)
     * 
     *   Render map n+1 saved and put as render map n+1 on GET request.
     * 
     * GET
     * 
     * - Third  request, restore view phase (create a new one): render map n+1 (restorred)
     *   (isRedirect() should return true as javadoc says)
     * - Third  request, execute phase: skipped
     * - Third  request, render  phase: render map n+1
     * 
     * In this way proper behavior is preserved even in the case of redirect, since the GET part is handled as
     * the "render" part of the current traversal, keeping the semantic of flash object.
     * 
     * @return
     */
    private Map<String, Object> _getActiveFlashMap()
    {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        if (PhaseId.RENDER_RESPONSE.equals(facesContext.getCurrentPhaseId()) 
                || !facesContext.isPostback())
        {
            return _getRenderFlashMap(facesContext);
        }
        else
        {
            return _getExecuteFlashMap(facesContext);
        }
    }
    
    /**
     * Returns the FlashMap used in the reading methods of java.util.Map
     * like e.g. get() or values().
     * @return
     */
    private Map<String, Object> _getFlashMapForReading()
    {
        return _getExecuteFlashMap(FacesContext.getCurrentInstance());
    }
    
    /**
     * Returns the FlashMap used in the writing methods of java.util.Map
     * like e.g. put() or clear().
     * @return
     */
    private Map<String, Object> _getFlashMapForWriting()
    {
        return _getActiveFlashMap();
    }

    /**
     * Destroy the execute FlashMap, because it is not needed anymore.
     * @param facesContext
     */
    private void _clearExecuteFlashMap(FacesContext facesContext)
    {
        Map<String, Object> map = _getExecuteFlashMap(facesContext);

        // Clear everything - note that because of naming conventions,
        // this will in fact automatically recurse through all children
        // grandchildren etc. - which is kind of a design flaw of SubKeyMap,
        // but one we're relying on
        
        // NOTE that we do not need a null check here, because there will
        // always be an execute Map, however sometimes an empty one!
        map.clear();
    }

    /**
     * Creates a Cookie with the given name and value.
     * In addition, it will be configured with maxAge=-1, the current request path and secure value.
     *
     * @param name
     * @param value
     * @param externalContext
     * @return
     */
    private Cookie _createFlashCookie(String name, String value, ExternalContext externalContext)
    {
        Cookie cookie = new Cookie(name, value);

        cookie.setMaxAge(-1);
        cookie.setPath(_getCookiePath(externalContext));
        cookie.setSecure(_isCurrentRequestSecure(externalContext));

        return cookie;
    }

    /**
     * Returns the path for the Flash-Cookies.
     * @param externalContext
     * @return
     */
    private String _getCookiePath(ExternalContext externalContext)
    {
        String contextPath = externalContext.getRequestContextPath();

        if (contextPath == null || "".equals(contextPath))
        {
            contextPath = "/";
        }

        return contextPath;
    }

    /**
     * Return true if the current request is secure (--> HTTPS).
     *
     * @param externalContext
     * @return
     */
    private boolean _isCurrentRequestSecure(ExternalContext externalContext)
    {
        boolean secure = false;

        // NOTE that ExternalContext.isSecure() is only available
        // since JSF 2.1, thus we have to check ServletRequest directly here
        Object requestObject = externalContext.getRequest();
        if (requestObject instanceof ServletRequest)
        {
            secure = ((ServletRequest) requestObject).isSecure();
        }

        return secure;
    }
    
    /**
     * Convert the Object to a Boolean.
     * @param value
     * @return
     */
    private Boolean _convertToBoolean(Object value)
    {
        Boolean booleanValue;
        if (value instanceof Boolean)
        {
            booleanValue = (Boolean) value;
        }
        else
        {
            booleanValue = Boolean.parseBoolean(value.toString());
        }
        return booleanValue;
    }
    
    /**
     * Checks whether flash scope is disabled.
     * @throws FlashScopeDisabledException if flash scope is disabled
     */
    private void _checkFlashScopeDisabled()
    {
        if (_flashScopeDisabled) {
            throw new FlashScopeDisabledException("Flash scope was disabled by context param " 
                + FLASH_SCOPE_DISABLED_PARAM + " but erroneously accessed");
        }
    }
    
    // ~ Inner classes --------------------------------------------------------
    
    /**
     * Class used to store a FacesMessage with its clientId.
     */
    private static class MessageEntry implements Serializable
    {
        private static final long serialVersionUID = -690264660230199234L;
        private final String clientId;
        private final FacesMessage message;

        public MessageEntry(String clientId, FacesMessage message)
        {
            this.clientId = clientId;
            this.message = message;
        }
    }
    
}
