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