View Javadoc

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