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