View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.shared.context.flash;
20  
21  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
22  import org.apache.myfaces.shared.util.ExternalContextUtils;
23  
24  import javax.faces.application.FacesMessage;
25  import javax.faces.context.ExternalContext;
26  import javax.faces.context.FacesContext;
27  import javax.faces.context.Flash;
28  import javax.faces.event.PhaseId;
29  import javax.servlet.http.Cookie;
30  import javax.servlet.http.HttpServletResponse;
31  import java.io.Serializable;
32  import java.math.BigInteger;
33  import java.security.NoSuchAlgorithmException;
34  import java.security.SecureRandom;
35  import java.util.ArrayList;
36  import java.util.Collection;
37  import java.util.Iterator;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.Set;
41  import java.util.concurrent.atomic.AtomicLong;
42  import java.util.logging.Logger;
43  
44  /**
45   * Implementation of Flash object
46   * 
47   * @author Leonardo Uribe (latest modification by $Author: lu4242 $)
48   * @author Jakob Korherr
49   * @version $Revision: 1533536 $ $Date: 2013-10-18 11:20:12 -0500 (Fri, 18 Oct 2013) $
50   */
51  public class FlashImpl extends Flash
52  {
53      
54      // ~ static fields --------------------------------------------------------
55      
56      private static final Logger log = Logger.getLogger(FlashImpl.class.getName());
57      
58      /**
59       * Defines whether flash scope is disabled, preventing add the Flash cookie to the response. 
60       * 
61       * <p>This is useful for applications that does not require to use flash scope, and instead uses other scopes.</p>
62       */
63      @JSFWebConfigParam(defaultValue="false",since="2.0.5")
64      private static final String FLASH_SCOPE_DISABLED_PARAM = "org.apache.myfaces.FLASH_SCOPE_DISABLED";
65  
66      /**
67       * Use this prefix instead of the whole class name, because
68       * this makes the Cookies and the SubKeyMap operations (actually
69       * every String based operation where this is used as a key) faster.
70       */
71      private static final String FLASH_PREFIX = "oam.Flash";
72      
73      /**
74       * Key on application map to keep current instance
75       */
76      static final String FLASH_INSTANCE = FLASH_PREFIX + ".INSTANCE";
77  
78      /**
79       * Key to store if this setRedirect(true) was called on this request,
80       * and to store the redirect Cookie.
81       */
82      static final String FLASH_REDIRECT = FLASH_PREFIX + ".REDIRECT";
83      
84      /**
85       * Key to store the value of the redirect cookie
86       */
87      static final String FLASH_PREVIOUS_REQUEST_REDIRECT 
88              = FLASH_PREFIX + ".REDIRECT.PREVIOUSREQUEST";
89      
90      /**
91       * Key used to check if this request should keep messages
92       */
93      static final String FLASH_KEEP_MESSAGES = FLASH_PREFIX + ".KEEP_MESSAGES";
94  
95      /**
96       * Key used to store the messages list in the render FlashMap.
97       */
98      static final String FLASH_KEEP_MESSAGES_LIST = "KEEPMESSAGESLIST";
99  
100     /**
101      * Session map prefix to flash maps
102      */
103     static final String FLASH_SESSION_MAP_SUBKEY_PREFIX = FLASH_PREFIX + ".SCOPE";
104     
105     /**
106      * Key for the cached render FlashMap instance on the request map.
107      */
108     static final String FLASH_RENDER_MAP = FLASH_PREFIX + ".RENDERMAP";
109     
110     /**
111      * Key for the current render FlashMap token.
112      */
113     static final String FLASH_RENDER_MAP_TOKEN = FLASH_PREFIX + ".RENDERMAP.TOKEN";
114     
115     /**
116      * Key for the cached execute FlashMap instance on the request map.
117      */
118     static final String FLASH_EXECUTE_MAP = FLASH_PREFIX + ".EXECUTEMAP";
119 
120     /**
121      * Key for the current execute FlashMap token.
122      */
123     static final String FLASH_EXECUTE_MAP_TOKEN = FLASH_PREFIX + ".EXECUTEMAP.TOKEN";
124     
125     /**
126      * Token separator.
127      */
128     static final char SEPARATOR_CHAR = '.';
129     
130      // ~ static methods  -----------------------------------------------------
131     
132     /**
133      * Return a Flash instance from the application map
134      * 
135      * @param context
136      * @return
137      */
138     public static Flash getCurrentInstance(ExternalContext context)
139     {
140         Map<String, Object> applicationMap = context.getApplicationMap();
141         
142         Flash flash = (Flash) applicationMap.get(FLASH_INSTANCE);
143         if (flash == null)
144         {
145             // synchronize the ApplicationMap to ensure that only
146             // once instance of FlashImpl is created and stored in it.
147             synchronized (applicationMap)
148             {
149                 // check again, because first try was un-synchronized
150                 flash = (Flash) applicationMap.get(FLASH_INSTANCE);
151                 if (flash == null)
152                 {
153                     flash = new FlashImpl(context);
154                     applicationMap.put(FLASH_INSTANCE, flash);
155                 }
156             }
157         }
158 
159         return flash;
160     }
161 
162     /**
163      * Returns a cryptographically secure random number to use as the _count seed
164      */
165     private static long _getSeed()
166     {
167         SecureRandom rng;
168         try
169         {
170             // try SHA1 first
171             rng = SecureRandom.getInstance("SHA1PRNG");
172         }
173         catch (NoSuchAlgorithmException e)
174         {
175             // SHA1 not present, so try the default (which could potentially not be
176             // cryptographically secure)
177             rng = new SecureRandom();
178         }
179 
180         // use 48 bits for strength and fill them in
181         byte[] randomBytes = new byte[6];
182         rng.nextBytes(randomBytes);
183 
184         // convert to a long
185         return new BigInteger(randomBytes).longValue();
186     }
187     
188     // ~ private fields and constructor ---------------------------------------
189     
190     // the current token value
191     private final AtomicLong _count;
192     private boolean _flashScopeDisabled;
193     
194     public FlashImpl(ExternalContext externalContext)
195     {
196         _count = new AtomicLong(_getSeed());
197 
198         // Read whether flash scope is disabled.
199         _flashScopeDisabled = "true".equalsIgnoreCase(externalContext.getInitParameter(FLASH_SCOPE_DISABLED_PARAM));
200     }
201     
202     // ~ methods from javax.faces.context.Flash -------------------------------
203 
204     /**
205      * Used to restore the redirect value and the FacesMessages of the previous 
206      * request and to manage the flashMap tokens for this request before phase
207      * restore view starts.
208      */
209     @Override
210     public void doPrePhaseActions(FacesContext facesContext)
211     {
212         if (!_flashScopeDisabled)
213         {
214             final PhaseId currentPhaseId = facesContext.getCurrentPhaseId();
215         
216             if (PhaseId.RESTORE_VIEW.equals(currentPhaseId))
217             {
218                 // restore the redirect value
219                 // note that the result of this method is used in many places, 
220                 // thus it has to be the first thing to do
221                 _restoreRedirectValue(facesContext);
222             
223                 // restore the FlashMap token from the previous request
224                 // and create a new token for this request
225                 _manageFlashMapTokens(facesContext);
226             
227                 // try to restore any saved messages
228                 _restoreMessages(facesContext);
229             }
230         }
231     }
232     
233     /**
234      * Used to destroy the executeMap and to save all FacesMessages for the
235      * next request, but only if this is the last invocation of this method
236      * in the current lifecycle (if redirect phase 5, otherwise phase 6).
237      */
238     @Override
239     public void doPostPhaseActions(FacesContext facesContext)
240     {
241         if (!_flashScopeDisabled)
242         {
243             // do the actions only if this is the last time
244             // doPostPhaseActions() is called on this request
245             if (_isLastPhaseInRequest(facesContext))
246             {
247                 if (_isRedirectTrueOnThisRequest(facesContext))
248                 {
249                     // copy entries from executeMap to renderMap, if they do not exist
250                     Map<String, Object> renderMap = _getRenderFlashMap(facesContext);
251 
252                     for (Map.Entry<String, Object> entry 
253                         : _getExecuteFlashMap(facesContext).entrySet())
254                     {
255                         if (!renderMap.containsKey(entry.getKey()))
256                         {
257                             renderMap.put(entry.getKey(), entry.getValue());
258                         }
259                     }
260                 }
261             
262                 // remove execute Map entries from session (--> "destroy" executeMap)
263                 _clearExecuteFlashMap(facesContext);
264             
265                 // save the current FacesMessages in the renderMap, if wanted
266                 // Note that this also works on a redirect even though the redirect
267                 // was already performed and the response has already been committed,
268                 // because the renderMap is stored in the session.
269                 _saveMessages(facesContext);
270             }
271         }
272     }
273     
274     /**
275      * Return the value of this property for the flash for this session.
276      * 
277      * This must be false unless:
278      *   - setRedirect(boolean) was called for the current lifecycle traversal
279      *     with true as the argument.
280      *   - The current lifecycle traversal for this session is in the "execute"
281      *     phase and the previous traversal had setRedirect(boolean) called with
282      *     true as the argument.
283      */
284     @Override
285     public boolean isRedirect()
286     {
287         FacesContext facesContext = FacesContext.getCurrentInstance();
288         boolean thisRedirect = _isRedirectTrueOnThisRequest(facesContext);
289         boolean prevRedirect = _isRedirectTrueOnPreviousRequest(facesContext);
290         boolean executePhase = !PhaseId.RENDER_RESPONSE.equals(facesContext.getCurrentPhaseId());
291         
292         return thisRedirect || (executePhase && prevRedirect);
293     }
294     
295     @Override
296     public void setRedirect(boolean redirect)
297     {
298         // FIXME this method has a design flaw, because the only valid value
299         // is true and it should only be called by the NavigationHandler
300         // in a redirect case RIGHT BEFORE ExternalContext.redirect().
301         // Maybe a PreRedirectEvent issued by the ExternalContext would be a good
302         // choice for JSF 2.1.
303         
304         FacesContext facesContext = FacesContext.getCurrentInstance();
305         ExternalContext externalContext = facesContext.getExternalContext();
306         Map<String, Object> requestMap = externalContext.getRequestMap();
307         
308         // save the value on the requestMap for this request
309         Boolean alreadySet = (Boolean) requestMap.get(FLASH_REDIRECT);
310         alreadySet = (alreadySet == null ? Boolean.FALSE : Boolean.TRUE);
311         
312         // if true and not already set, store it for the following request
313         if (!alreadySet && redirect)
314         {
315             requestMap.put(FLASH_REDIRECT, Boolean.TRUE);
316             
317             // save redirect=true for the next request
318             _saveRedirectValue(facesContext);
319         }
320         else
321         {
322             if (alreadySet)
323             {
324                 log.warning("Multiple call to setRedirect() ignored.");
325             }
326             else // redirect = false
327             {
328                 log.warning("Ignored call to setRedirect(false), because this "
329                         + "should only be set to true by the NavigationHandler. "
330                         + "No one else should change it.");
331             }
332         }
333     }
334     
335     /**
336      * Take a value from the requestMap, or if it does not exist from the
337      * execute FlashMap, and put it on the render FlashMap, so it is visible on
338      * the next request.
339      */
340     @Override
341     public void keep(String key)
342     {
343         _checkFlashScopeDisabled();
344         FacesContext facesContext = FacesContext.getCurrentInstance();
345         Map<String, Object> requestMap = facesContext.getExternalContext().getRequestMap();
346         Object value = requestMap.get(key);
347         
348         // if the key does not exist in the requestMap,
349         // try to get it from the execute FlashMap
350         if (value == null)
351         {
352             Map<String, Object> executeMap = _getExecuteFlashMap(facesContext);
353             // Null-check, because in the GET request of a POST-REDIRECT-GET 
354             // pattern there is no execute map
355             if (executeMap != null)
356             {
357                 value = executeMap.get(key);
358                 // Store it on request map so we can get it later. For example, 
359                 // this is used by org.apache.myfaces.el.FlashELResolver to return
360                 // the value that has been promoted.
361                 requestMap.put(key, value);
362             }
363         }
364         
365         // put it in the render FlashMap
366         _getRenderFlashMap(facesContext).put(key, value);
367     }
368 
369     /**
370      * This is just an alias for the request scope map.
371      */
372     @Override
373     public void putNow(String key, Object value)
374     {
375         _checkFlashScopeDisabled();
376         FacesContext.getCurrentInstance().getExternalContext()
377                 .getRequestMap().put(key, value);
378     }
379 
380     /**
381      * Returns the value of a previous call to setKeepMessages() from this
382      * request. If there was no call yet, false is returned.
383      */
384     @Override
385     public boolean isKeepMessages()
386     {
387         FacesContext facesContext = FacesContext.getCurrentInstance();
388         ExternalContext externalContext = facesContext.getExternalContext();
389         Map<String, Object> requestMap = externalContext.getRequestMap();
390         Boolean keepMessages = (Boolean) requestMap.get(FLASH_KEEP_MESSAGES);
391         
392         return (keepMessages == null ? Boolean.FALSE : keepMessages);
393     }
394     
395     /**
396      * If this property is true, the messages should be kept for the next 
397      * request, no matter if it is a normal postback case or a POST-
398      * REDIRECT-GET case.
399      * 
400      * Note that we don't have to store this value for the next request
401      * (like setRedirect()), because we will know if it was true on the 
402      * next request, if we can find any stored messages in the FlashMap.
403      * (also see _saveMessages() and _restoreMessages()).
404      */
405     @Override
406     public void setKeepMessages(boolean keepMessages)
407     {
408         FacesContext facesContext = FacesContext.getCurrentInstance();
409         ExternalContext externalContext = facesContext.getExternalContext();
410         Map<String, Object> requestMap = externalContext.getRequestMap();
411         requestMap.put(FLASH_KEEP_MESSAGES, keepMessages);
412     }
413     
414     // ~ Methods from Map interface -------------------------------------------
415     
416     // NOTE that all these methods do not necessarily delegate to the same Map,
417     // because we differentiate between reading and writing operations.
418     
419     public void clear()
420     {
421         _checkFlashScopeDisabled();
422         _getFlashMapForWriting().clear();
423     }
424 
425     public boolean containsKey(Object key)
426     {
427         _checkFlashScopeDisabled();
428         return _getFlashMapForReading().containsKey(key);
429     }
430 
431     public boolean containsValue(Object value)
432     {
433         _checkFlashScopeDisabled();
434         return _getFlashMapForReading().containsValue(value);
435     }
436 
437     public Set<java.util.Map.Entry<String, Object>> entrySet()
438     {
439         _checkFlashScopeDisabled();
440         return _getFlashMapForReading().entrySet();
441     }
442 
443     public Object get(Object key)
444     {
445         _checkFlashScopeDisabled();
446         if (key == null)
447         {
448             return null;
449         }
450         
451         if ("keepMessages".equals(key))
452         {
453             return isKeepMessages();
454         }
455         else if ("redirect".equals(key))
456         {
457             return isRedirect();
458         }
459         
460         return _getFlashMapForReading().get(key);
461     }
462     
463     public boolean isEmpty()
464     {
465         _checkFlashScopeDisabled();
466         return _getFlashMapForReading().isEmpty();
467     }
468 
469     public Set<String> keySet()
470     {
471         _checkFlashScopeDisabled();
472         return _getFlashMapForReading().keySet();
473     }
474 
475     public Object put(String key, Object value)
476     {
477         _checkFlashScopeDisabled();
478         if (key == null)
479         {
480             return null;
481         }
482         
483         if ("keepMessages".equals(key))
484         {
485             Boolean booleanValue = _convertToBoolean(value);
486             this.setKeepMessages(booleanValue);
487             return booleanValue;
488         }
489         else if ("redirect".equals(key))
490         {
491             Boolean booleanValue = _convertToBoolean(value);
492             this.setRedirect(booleanValue);
493             return booleanValue;
494         }
495         else
496         {
497             return _getFlashMapForWriting().put(key, value); 
498         }
499     }
500 
501     public void putAll(Map<? extends String, ? extends Object> m)
502     {
503         _checkFlashScopeDisabled();
504         _getFlashMapForWriting().putAll(m);
505     }
506 
507     public Object remove(Object key)
508     {
509         _checkFlashScopeDisabled();
510         return _getFlashMapForWriting().remove(key);
511     }
512 
513     public int size()
514     {
515         _checkFlashScopeDisabled();
516         return _getFlashMapForReading().size();
517     }
518 
519     public Collection<Object> values()
520     {
521         _checkFlashScopeDisabled();
522         return _getFlashMapForReading().values();
523     }
524     
525     // ~ Implementation methods ----------------------------------------------- 
526     
527     /**
528      * Returns true if the current phase is the last phase in the request
529      * and thus if doPostPhaseActions() is called for the last time.
530      * 
531      * This will be true if either we are in phase 6 (render response)
532      * or if setRedirect(true) was called on this request and we are
533      * in phase 5 (invoke application).
534      */
535     private boolean _isLastPhaseInRequest(FacesContext facesContext)
536     {
537         final PhaseId currentPhaseId = facesContext.getCurrentPhaseId();
538         
539         boolean lastPhaseNormalRequest = PhaseId.RENDER_RESPONSE.equals(currentPhaseId);
540         // According to the spec, if there is a redirect, responseComplete() 
541         // has been called, and Flash.setRedirect() has been called too,
542         // so we just need to check both are present.
543         boolean lastPhaseIfRedirect = facesContext.getResponseComplete()
544                 && _isRedirectTrueOnThisRequest(facesContext);
545         
546         return lastPhaseNormalRequest || lastPhaseIfRedirect;
547     }
548     
549     /**
550      * Return true if setRedirect(true) was called on this request.
551      * @param facesContext
552      * @return
553      */
554     private boolean _isRedirectTrueOnThisRequest(FacesContext facesContext)
555     {
556         ExternalContext externalContext = facesContext.getExternalContext();
557         Map<String, Object> requestMap = externalContext.getRequestMap();
558         Boolean redirect = (Boolean) requestMap.get(FLASH_REDIRECT);
559         
560         return Boolean.TRUE.equals(redirect);
561     }
562     
563     /**
564      * Return true if setRedirect(true) was called on the previous request.
565      * Precondition: doPrePhaseActions() must have been called on restore view phase.
566      * @param facesContext
567      * @return
568      */
569     private boolean _isRedirectTrueOnPreviousRequest(FacesContext facesContext)
570     {
571         ExternalContext externalContext = facesContext.getExternalContext();
572         Map<String, Object> requestMap = externalContext.getRequestMap();
573         Boolean redirect = (Boolean) requestMap.get(FLASH_PREVIOUS_REQUEST_REDIRECT);
574         
575         return Boolean.TRUE.equals(redirect);
576     }
577     
578     /**
579      * Saves the value of setRedirect() for the next request, if it was true
580      */
581     private void _saveRedirectValue(FacesContext facesContext)
582     {
583         ExternalContext externalContext = facesContext.getExternalContext();
584         
585         // This request contains a redirect. This condition is in general 
586         // triggered by a NavigationHandler. After a redirect all request scope
587         // values get lost, so in order to preserve this value we need to
588         // pass it between request. One strategy is use a cookie that is never sent
589         // to the client. Other alternative is use the session map.
590         // See _restoreRedirectValue() for restoring this value.
591         HttpServletResponse httpResponse = ExternalContextUtils
592                 .getHttpServletResponse(externalContext);
593         if (httpResponse != null)
594         {
595             Cookie cookie = _createFlashCookie(FLASH_REDIRECT, "true", externalContext);
596             httpResponse.addCookie(cookie);
597         }
598         else
599         {
600             externalContext.getSessionMap().put(FLASH_REDIRECT, true);
601         }
602     }
603 
604     /**
605      * Restores the redirect value of the previous request and saves
606      * it in the RequestMap under the key FLASH_PREVIOUS_REQUEST_REDIRECT.
607      * Must not be called more than once per request.
608      * After this method was invoked, the requestMap will contain Boolean.TRUE
609      * if setRedirect(true) was called on the previous request or Boolean.FALSE
610      * or null otherwise.
611      */
612     private void _restoreRedirectValue(FacesContext facesContext)
613     {
614         ExternalContext externalContext = facesContext.getExternalContext();
615         HttpServletResponse httpResponse = ExternalContextUtils
616                 .getHttpServletResponse(externalContext);
617         if (httpResponse != null)
618         {
619             // Request values are lost after a redirect. We can create a 
620             // temporal cookie to pass the params between redirect calls.
621             // It is better than use HttpSession object, because this cookie
622             // is never sent by the server.
623             Cookie cookie = (Cookie) externalContext
624                     .getRequestCookieMap().get(FLASH_REDIRECT);
625             if (cookie != null)
626             {
627                 // the cookie exists means there was a redirect, regardless of the value
628                 externalContext.getRequestMap().put(
629                         FLASH_PREVIOUS_REQUEST_REDIRECT, Boolean.TRUE);
630                 
631                 // A redirect happened, so it is safe to remove the cookie, setting
632                 // the maxAge to 0 seconds. The effect is we passed FLASH_REDIRECT param 
633                 // to this request object
634                 cookie.setMaxAge(0);
635                 cookie.setPath(_getCookiePath(externalContext));
636                 //MYFACES-3354 jetty 6.1.5 does not allow this,
637                 //call setMaxAge(0) is enough
638                 //cookie.setValue(null);
639                 httpResponse.addCookie(cookie);
640             }
641         }
642         else
643         {
644             // Note that on portlet world we can't create cookies,
645             // so we are forced to use the session map. Anyway, 
646             // according to the Bridge implementation(for example see 
647             // org.apache.myfaces.portlet.faces.bridge.BridgeImpl)
648             // session object is created at start faces request
649             Map<String, Object> sessionMap = externalContext.getSessionMap();
650             
651             // remove the value from the sessionMap
652             Boolean redirect = (Boolean) sessionMap.remove(FLASH_REDIRECT);
653             
654             // put the value into the requestMap
655             externalContext.getRequestMap().put(
656                     FLASH_PREVIOUS_REQUEST_REDIRECT, redirect);
657         }
658     }
659     
660     /**
661      * Saves the current FacesMessages as a List on the render FlashMap for the
662      * next request if isKeepMessages() is true. Otherwise it removes any
663      * existing FacesMessages-List from the renderFlashMap. 
664      * @param facesContext
665      */
666     private void _saveMessages(FacesContext facesContext)
667     {
668         if (isKeepMessages()) 
669         {
670             // get all messages from the FacesContext and store
671             // them on the renderMap
672             List<MessageEntry> messageList = null;
673                 
674             Iterator<String> iterClientIds = facesContext.getClientIdsWithMessages();
675             while (iterClientIds.hasNext())
676             {
677                 String clientId = (String) iterClientIds.next();
678                 Iterator<FacesMessage> iterMessages = facesContext.getMessages(clientId);
679                 
680                 while (iterMessages.hasNext())
681                 {
682                     FacesMessage message = iterMessages.next();
683     
684                     if (messageList == null)
685                     {
686                         messageList = new ArrayList<MessageEntry>();
687                     }
688                     messageList.add(new MessageEntry(clientId, message));
689                 }
690             }
691     
692             _getRenderFlashMap(facesContext).put(FLASH_KEEP_MESSAGES_LIST, messageList);
693         }
694         else
695         {
696             // do not keep messages --> remove messagesList from renderMap
697             _getRenderFlashMap(facesContext).remove(FLASH_KEEP_MESSAGES_LIST);
698         }
699     }
700 
701     /**
702      * Restore any saved FacesMessages from the previous request.
703      * Note that we don't need to save the keepMessages value for this request,
704      * because we just have to check if the value for FLASH_KEEP_MESSAGES_LIST exists.
705      * @param facesContext
706      */
707     @SuppressWarnings("unchecked")
708     private void _restoreMessages(FacesContext facesContext)
709     {
710         List<MessageEntry> messageList = (List<MessageEntry>) 
711                 _getExecuteFlashMap(facesContext).get(FLASH_KEEP_MESSAGES_LIST);
712 
713         if (messageList != null)
714         {
715             Iterator<MessageEntry> iterMessages = messageList.iterator();
716 
717             while (iterMessages.hasNext())
718             {
719                 MessageEntry entry = iterMessages.next();
720                 facesContext.addMessage(entry.clientId, entry.message);
721             }
722 
723             // we can now remove the messagesList from the flashMap
724             _getExecuteFlashMap(facesContext).remove(FLASH_KEEP_MESSAGES_LIST);
725         }
726     }
727     
728     /**
729      * Take the render map key and store it as a key for the next request.
730      * 
731      * On the next request we can get it with _getRenderFlashMapTokenFromPreviousRequest().
732      * @param externalContext
733      */
734     private void _saveRenderFlashMapTokenForNextRequest(ExternalContext externalContext)
735     {
736         String tokenValue = (String) externalContext.getRequestMap().get(FLASH_RENDER_MAP_TOKEN);
737         
738         HttpServletResponse httpResponse = ExternalContextUtils.getHttpServletResponse(externalContext);
739         if (httpResponse != null)
740         {
741             Cookie cookie = _createFlashCookie(FLASH_RENDER_MAP_TOKEN, tokenValue, externalContext);
742             httpResponse.addCookie(cookie);
743         }
744         else
745         {
746             //Use HttpSession or PortletSession object
747             Map<String, Object> sessionMap = externalContext.getSessionMap();
748             sessionMap.put(FLASH_RENDER_MAP_TOKEN, tokenValue);
749         }
750     }
751     
752     /**
753      * Retrieve the map token of the render map from the previous request.
754      * 
755      * Returns the value of _saveRenderFlashMapTokenForNextRequest() from
756      * the previous request.
757      * @param externalContext
758      * @return
759      */
760     private String _getRenderFlashMapTokenFromPreviousRequest(ExternalContext externalContext)
761     {
762         String tokenValue = null;
763         HttpServletResponse httpResponse = ExternalContextUtils.getHttpServletResponse(externalContext);
764         if (httpResponse != null)
765         {
766             //Use a cookie
767             Cookie cookie = (Cookie) externalContext.getRequestCookieMap().get(FLASH_RENDER_MAP_TOKEN);
768             if (cookie != null)
769             {
770                 tokenValue = cookie.getValue();
771             }
772         }
773         else
774         {
775             //Use HttpSession or PortletSession object
776             Map<String, Object> sessionMap = externalContext.getSessionMap();
777             tokenValue = (String) sessionMap.get(FLASH_RENDER_MAP_TOKEN);
778         }
779         return tokenValue;
780     }
781     
782     /**
783      * Restores the render FlashMap token from the previous request.
784      * This is the token of the executeMap for this request.
785      * Furthermore it also creates a new token for this request's renderMap
786      * (and thus implicitly a new renderMap).
787      * @param facesContext
788      */
789     private void _manageFlashMapTokens(FacesContext facesContext)
790     {
791         ExternalContext externalContext = facesContext.getExternalContext();
792         Map<String, Object> requestMap = externalContext.getRequestMap();
793 
794         final String previousRenderToken 
795                 = _getRenderFlashMapTokenFromPreviousRequest(externalContext);
796         if (previousRenderToken != null)
797         {
798             // "restore" the renderMap from the previous request
799             // and put it as the executeMap for this request
800             requestMap.put(FLASH_EXECUTE_MAP_TOKEN, previousRenderToken);
801         }
802         else
803         {
804             if (facesContext.isPostback())
805             {
806                 // on a postback, we should always have a previousToken
807                 log.warning("Identifier for execute FlashMap was lost on " +
808                         "the postback, thus FlashScope information is gone.");
809             }
810             
811             // create a new token (and thus a new Map) for this request's 
812             // executeMap so that we have an executeMap in any possible case.
813             final String newExecuteToken = _getNextToken();
814             requestMap.put(FLASH_EXECUTE_MAP_TOKEN, newExecuteToken);
815         }
816         
817         // create a new token (and thus a new Map) for this request's renderMap
818         final String newRenderToken = _getNextToken();
819         requestMap.put(FLASH_RENDER_MAP_TOKEN, newRenderToken);
820         
821         // we now have the final render token for this request, thus we can
822         // already save it for the next request, because it won't change
823         _saveRenderFlashMapTokenForNextRequest(externalContext);
824     }
825     
826     /**
827      * Get the next token to be assigned to this request
828      * 
829      * @return
830      */
831     private String _getNextToken()
832     {
833         // atomically increment the value
834         long nextToken = _count.incrementAndGet();
835 
836         // convert using base 36 because it is a fast efficient subset of base-64
837         return Long.toString(nextToken, 36);
838     }
839 
840     /**
841      * Create a new subkey-wrapper of the session map with the given prefix.
842      * This wrapper is used to implement the maps for the flash scope.
843      * For more information see the SubKeyMap doc.
844      */
845     private Map<String, Object> _createSubKeyMap(FacesContext context, String prefix)
846     {
847         ExternalContext external = context.getExternalContext();
848         Map<String, Object> sessionMap = external.getSessionMap();
849 
850         return new SubKeyMap<Object>(sessionMap, prefix);
851     }
852 
853     /**
854      * Return the flash map created on this traversal.
855      * 
856      * This FlashMap will be the execute FlashMap of the next traversal.
857      * 
858      * Note that it is supposed that FLASH_RENDER_MAP_TOKEN is initialized
859      * before restore view phase (see doPrePhaseActions() for details).
860      * 
861      * @param context
862      * @return
863      */
864     @SuppressWarnings("unchecked")
865     private Map<String, Object> _getRenderFlashMap(FacesContext context)
866     {
867         // Note that we don't have to synchronize here, because it is no problem
868         // if we create more SubKeyMaps with the same subkey, because they are
869         // totally equal and point to the same entries in the SessionMap.
870         
871         Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
872         Map<String, Object> map = (Map<String, Object>) requestMap.get(FLASH_RENDER_MAP);
873         if (map == null)
874         {
875             String token = (String) requestMap.get(FLASH_RENDER_MAP_TOKEN);
876             String fullToken = FLASH_SESSION_MAP_SUBKEY_PREFIX + SEPARATOR_CHAR + token;
877             map =  _createSubKeyMap(context, fullToken);
878             requestMap.put(FLASH_RENDER_MAP, map);
879         }
880         return map;
881     }
882     
883     /**
884      * Return the execute Flash Map.
885      * 
886      * This FlashMap was the render FlashMap of the previous traversal. 
887      * 
888      * @param context
889      * @return
890      */
891     @SuppressWarnings("unchecked")
892     private Map<String, Object> _getExecuteFlashMap(FacesContext context)
893     {
894         // Note that we don't have to synchronize here, because it is no problem
895         // if we create more SubKeyMaps with the same subkey, because they are
896         // totally equal and point to the same entries in the SessionMap.
897         
898         Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
899         Map<String, Object> map = (Map<String, Object>) requestMap.get(FLASH_EXECUTE_MAP);
900         if (map == null)
901         {
902             String token = (String) requestMap.get(FLASH_EXECUTE_MAP_TOKEN);
903             String fullToken = FLASH_SESSION_MAP_SUBKEY_PREFIX + SEPARATOR_CHAR + token;
904             map = _createSubKeyMap(context, fullToken);
905             requestMap.put(FLASH_EXECUTE_MAP, map);
906         }
907         return map;
908     }
909     
910     /**
911      * Get the proper map according to the current phase:
912      * 
913      * Normal case:
914      * 
915      * - First request, restore view phase (create a new one): render map n
916      * - First request, execute phase: Skipped
917      * - First request, render  phase: render map n
918      * 
919      *   Render map n saved and put as execute map n
920      * 
921      * - Second request, execute phase: execute map n
922      * - Second request, render  phase: render map n+1
923      * 
924      * Post Redirect Get case: Redirect is triggered by a call to setRedirect(true) from NavigationHandler
925      * or earlier using c:set tag.
926      * 
927      * - First request, restore view phase (create a new one): render map n
928      * - First request, execute phase: Skipped
929      * - First request, render  phase: render map n
930      * 
931      *   Render map n saved and put as execute map n
932      * 
933      * POST
934      * 
935      * - Second request, execute phase: execute map n
936      *   Note that render map n+1 is also created here to perform keep().
937      * 
938      * REDIRECT
939      * 
940      * - NavigationHandler do the redirect, requestMap data lost, called Flash.setRedirect(true)
941      * 
942      *   Render map n+1 saved and put as render map n+1 on GET request.
943      * 
944      * GET
945      * 
946      * - Third  request, restore view phase (create a new one): render map n+1 (restorred)
947      *   (isRedirect() should return true as javadoc says)
948      * - Third  request, execute phase: skipped
949      * - Third  request, render  phase: render map n+1
950      * 
951      * In this way proper behavior is preserved even in the case of redirect, since the GET part is handled as
952      * the "render" part of the current traversal, keeping the semantic of flash object.
953      * 
954      * @return
955      */
956     private Map<String, Object> _getActiveFlashMap()
957     {
958         FacesContext facesContext = FacesContext.getCurrentInstance();
959         if (PhaseId.RENDER_RESPONSE.equals(facesContext.getCurrentPhaseId()) 
960                 || !facesContext.isPostback())
961         {
962             return _getRenderFlashMap(facesContext);
963         }
964         else
965         {
966             return _getExecuteFlashMap(facesContext);
967         }
968     }
969     
970     /**
971      * Returns the FlashMap used in the reading methods of java.util.Map
972      * like e.g. get() or values().
973      * @return
974      */
975     private Map<String, Object> _getFlashMapForReading()
976     {
977         return _getExecuteFlashMap(FacesContext.getCurrentInstance());
978     }
979     
980     /**
981      * Returns the FlashMap used in the writing methods of java.util.Map
982      * like e.g. put() or clear().
983      * @return
984      */
985     private Map<String, Object> _getFlashMapForWriting()
986     {
987         return _getActiveFlashMap();
988     }
989 
990     /**
991      * Destroy the execute FlashMap, because it is not needed anymore.
992      * @param facesContext
993      */
994     private void _clearExecuteFlashMap(FacesContext facesContext)
995     {
996         Map<String, Object> map = _getExecuteFlashMap(facesContext);
997 
998         // Clear everything - note that because of naming conventions,
999         // this will in fact automatically recurse through all children
1000         // grandchildren etc. - which is kind of a design flaw of SubKeyMap,
1001         // but one we're relying on
1002         
1003         // NOTE that we do not need a null check here, because there will
1004         // always be an execute Map, however sometimes an empty one!
1005         map.clear();
1006     }
1007 
1008     /**
1009      * Creates a Cookie with the given name and value.
1010      * In addition, it will be configured with maxAge=-1, the current request path and secure value.
1011      *
1012      * @param name
1013      * @param value
1014      * @param externalContext
1015      * @return
1016      */
1017     private Cookie _createFlashCookie(String name, String value, ExternalContext externalContext)
1018     {
1019         Cookie cookie = new Cookie(name, value);
1020 
1021         cookie.setMaxAge(-1);
1022         cookie.setPath(_getCookiePath(externalContext));
1023         cookie.setSecure(externalContext.isSecure());
1024 
1025         return cookie;
1026     }
1027 
1028     /**
1029      * Returns the path for the Flash-Cookies.
1030      * @param externalContext
1031      * @return
1032      */
1033     private String _getCookiePath(ExternalContext externalContext)
1034     {
1035         String contextPath = externalContext.getRequestContextPath();
1036 
1037         if (contextPath == null || "".equals(contextPath))
1038         {
1039             contextPath = "/";
1040         }
1041 
1042         return contextPath;
1043     }
1044     
1045     /**
1046      * Convert the Object to a Boolean.
1047      * @param value
1048      * @return
1049      */
1050     private Boolean _convertToBoolean(Object value)
1051     {
1052         Boolean booleanValue;
1053         if (value instanceof Boolean)
1054         {
1055             booleanValue = (Boolean) value;
1056         }
1057         else
1058         {
1059             booleanValue = Boolean.parseBoolean(value.toString());
1060         }
1061         return booleanValue;
1062     }
1063     
1064     /**
1065      * Checks whether flash scope is disabled.
1066      * @throws FlashScopeDisabledException if flash scope is disabled
1067      */
1068     private void _checkFlashScopeDisabled()
1069     {
1070         if (_flashScopeDisabled)
1071         {
1072             throw new FlashScopeDisabledException("Flash scope was disabled by context param " 
1073                 + FLASH_SCOPE_DISABLED_PARAM + " but erroneously accessed");
1074         }
1075     }
1076     
1077     // ~ Inner classes --------------------------------------------------------
1078     
1079     /**
1080      * Class used to store a FacesMessage with its clientId.
1081      */
1082     private static class MessageEntry implements Serializable
1083     {
1084         private static final long serialVersionUID = -690264660230199234L;
1085         private final String clientId;
1086         private final FacesMessage message;
1087 
1088         public MessageEntry(String clientId, FacesMessage message)
1089         {
1090             this.clientId = clientId;
1091             this.message = message;
1092         }
1093     }
1094     
1095 }