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.renderkit;
20  
21  import java.io.ByteArrayInputStream;
22  import java.io.ByteArrayOutputStream;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.ObjectInputStream;
26  import java.io.ObjectOutputStream;
27  import java.io.OutputStream;
28  import java.io.Serializable;
29  import java.security.AccessController;
30  import java.security.PrivilegedActionException;
31  import java.security.PrivilegedExceptionAction;
32  import java.util.ArrayList;
33  import java.util.HashMap;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.logging.Level;
37  import java.util.logging.Logger;
38  import java.util.zip.GZIPInputStream;
39  import java.util.zip.GZIPOutputStream;
40  
41  import javax.faces.context.ExternalContext;
42  import javax.faces.context.FacesContext;
43  
44  import org.apache.commons.collections.map.AbstractReferenceMap;
45  import org.apache.commons.collections.map.ReferenceMap;
46  import org.apache.myfaces.application.StateCache;
47  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
48  import org.apache.myfaces.shared.renderkit.RendererUtils;
49  import org.apache.myfaces.shared.util.MyFacesObjectInputStream;
50  import org.apache.myfaces.shared.util.WebConfigParamUtils;
51  
52  class ServerSideStateCacheImpl extends StateCache<Object, Object>
53  {
54      private static final Logger log = Logger.getLogger(ServerSideStateCacheImpl.class.getName());
55      
56      private static final String SERIALIZED_VIEW_SESSION_ATTR= 
57          ServerSideStateCacheImpl.class.getName() + ".SERIALIZED_VIEW";
58      
59      private static final String RESTORED_SERIALIZED_VIEW_REQUEST_ATTR = 
60          ServerSideStateCacheImpl.class.getName() + ".RESTORED_SERIALIZED_VIEW";
61  
62      private static final String RESTORED_VIEW_KEY_REQUEST_ATTR = 
63          ServerSideStateCacheImpl.class.getName() + ".RESTORED_VIEW_KEY";
64      
65      /**
66       * Defines the amount (default = 20) of the latest views are stored in session.
67       * 
68       * <p>Only applicable if state saving method is "server" (= default).
69       * </p>
70       * 
71       */
72      @JSFWebConfigParam(defaultValue="20",since="1.1", classType="java.lang.Integer", group="state", tags="performance")
73      private static final String NUMBER_OF_VIEWS_IN_SESSION_PARAM = "org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION";
74  
75      /**
76       * Indicates the amount of views (default is not active) that should be stored in session between sequential
77       * POST or POST-REDIRECT-GET if org.apache.myfaces.USE_FLASH_SCOPE_PURGE_VIEWS_IN_SESSION is true.
78       * 
79       * <p>Only applicable if state saving method is "server" (= default). For example, if this param has value = 2 and 
80       * in your custom webapp there is a form that is clicked 3 times, only 2 views
81       * will be stored and the third one (the one stored the first time) will be
82       * removed from session, even if the view can
83       * store more sessions org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION.
84       * This feature becomes useful for multi-window applications.
85       * where without this feature a window can swallow all view slots so
86       * the other ones will throw ViewExpiredException.</p>
87       */
88      @JSFWebConfigParam(since="2.0.6", classType="java.lang.Integer", group="state", tags="performance")
89      private static final String NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION_PARAM
90              = "org.apache.myfaces.NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION";
91      
92      /**
93       * Default value for <code>org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION</code> context parameter.
94       */
95      private static final int DEFAULT_NUMBER_OF_VIEWS_IN_SESSION = 20;
96  
97      /**
98       * Indicate if the state should be serialized before save it on the session. 
99       * <p>
100      * Only applicable if state saving method is "server" (= default).
101      * If <code>true</code> (default) the state will be serialized to a byte stream before it is written to the session.
102      * If <code>false</code> the state will not be serialized to a byte stream.
103      * </p>
104      */
105     @JSFWebConfigParam(defaultValue="true",since="1.1", expectedValues="true,false", group="state", tags="performance")
106     private static final String SERIALIZE_STATE_IN_SESSION_PARAM = "org.apache.myfaces.SERIALIZE_STATE_IN_SESSION";
107 
108     /**
109      * Indicates that the serialized state will be compressed before it is written to the session. By default true.
110      * 
111      * Only applicable if state saving method is "server" (= default) and if
112      * <code>org.apache.myfaces.SERIALIZE_STATE_IN_SESSION</code> is <code>true</code> (= default).
113      * If <code>true</code> (default) the serialized state will be compressed before it is written to the session.
114      * If <code>false</code> the state will not be compressed.
115      */
116     @JSFWebConfigParam(defaultValue="true",since="1.1", expectedValues="true,false", group="state", tags="performance")
117     private static final String COMPRESS_SERVER_STATE_PARAM = "org.apache.myfaces.COMPRESS_STATE_IN_SESSION";
118 
119     /**
120      * Default value for <code>org.apache.myfaces.COMPRESS_STATE_IN_SESSION</code> context parameter.
121      */
122     private static final boolean DEFAULT_COMPRESS_SERVER_STATE_PARAM = true;
123 
124     /**
125      * Default value for <code>org.apache.myfaces.SERIALIZE_STATE_IN_SESSION</code> context parameter.
126      */
127     private static final boolean DEFAULT_SERIALIZE_STATE_IN_SESSION = true;
128 
129     /**
130      * Define the way of handle old view references(views removed from session), making possible to
131      * store it in a cache, so the state manager first try to get the view from the session. If is it
132      * not found and soft or weak ReferenceMap is used, it try to get from it.
133      * <p>
134      * Only applicable if state saving method is "server" (= default).
135      * </p>
136      * <p>
137      * The gc is responsible for remove the views, according to the rules used for soft, weak or phantom
138      * references. If a key in soft and weak mode is garbage collected, its values are purged.
139      * </p>
140      * <p>
141      * By default no cache is used, so views removed from session became phantom references.
142      * </p>
143      * <ul> 
144      * <li> off, no: default, no cache is used</li> 
145      * <li> hard-soft: use an ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.SOFT)</li>
146      * <li> soft: use an ReferenceMap(AbstractReferenceMap.SOFT, AbstractReferenceMap.SOFT, true) </li>
147      * <li> soft-weak: use an ReferenceMap(AbstractReferenceMap.SOFT, AbstractReferenceMap.WEAK, true) </li>
148      * <li> weak: use an ReferenceMap(AbstractReferenceMap.WEAK, AbstractReferenceMap.WEAK, true) </li>
149      * </ul>
150      * 
151      */
152     @JSFWebConfigParam(defaultValue="off", expectedValues="off, no, hard-soft, soft, soft-weak, weak",
153                        since="1.2.5", group="state", tags="performance")
154     private static final String CACHE_OLD_VIEWS_IN_SESSION_MODE = "org.apache.myfaces.CACHE_OLD_VIEWS_IN_SESSION_MODE";
155     
156     /**
157      * This option uses an hard-soft ReferenceMap, but it could cause a 
158      * memory leak, because the keys are not removed by any method
159      * (MYFACES-1660). So use with caution.
160      */
161     private static final String CACHE_OLD_VIEWS_IN_SESSION_MODE_HARD_SOFT = "hard-soft";
162     
163     private static final String CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT = "soft";
164     
165     private static final String CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT_WEAK = "soft-weak";
166     
167     private static final String CACHE_OLD_VIEWS_IN_SESSION_MODE_WEAK = "weak";
168     
169     private static final String CACHE_OLD_VIEWS_IN_SESSION_MODE_OFF = "off";
170 
171     /**
172      * Allow use flash scope to keep track of the views used in session and the previous ones,
173      * so server side state saving can delete old views even if POST-REDIRECT-GET pattern is used.
174      * 
175      * <p>
176      * Only applicable if state saving method is "server" (= default).
177      * The default value is false.</p>
178      */
179     @JSFWebConfigParam(since="2.0.6", defaultValue="false", expectedValues="true, false", group="state")
180     private static final String USE_FLASH_SCOPE_PURGE_VIEWS_IN_SESSION
181             = "org.apache.myfaces.USE_FLASH_SCOPE_PURGE_VIEWS_IN_SESSION";
182 
183     private static final int UNCOMPRESSED_FLAG = 0;
184     private static final int COMPRESSED_FLAG = 1;
185 
186     private static final int JSF_SEQUENCE_INDEX = 0;
187     
188     private Boolean _useFlashScopePurgeViewsInSession = null;
189     
190     private Integer _numberOfSequentialViewsInSession = null;
191     private boolean _numberOfSequentialViewsInSessionSet = false;
192 
193     //------------------------------------- METHODS COPIED FROM JspStateManagerImpl--------------------------------
194 
195     protected Integer getServerStateId(Object[] state)
196     {
197       if (state != null)
198       {
199           Object serverStateId = state[JSF_SEQUENCE_INDEX];
200           if (serverStateId != null)
201           {
202               return Integer.valueOf((String) serverStateId, Character.MAX_RADIX);
203           }
204       }
205       return null;
206     }
207 
208     protected void saveSerializedViewInServletSession(FacesContext context,
209                                                       Object serializedView)
210     {
211         Map<String, Object> sessionMap = context.getExternalContext().getSessionMap();
212         SerializedViewCollection viewCollection = (SerializedViewCollection) sessionMap
213                 .get(SERIALIZED_VIEW_SESSION_ATTR);
214         if (viewCollection == null)
215         {
216             viewCollection = new SerializedViewCollection();
217             sessionMap.put(SERIALIZED_VIEW_SESSION_ATTR, viewCollection);
218         }
219 
220         Map<Object,Object> attributeMap = context.getAttributes();
221         
222         SerializedViewKey key = null;
223         if (getNumberOfSequentialViewsInSession(context.getExternalContext()) != null &&
224             getNumberOfSequentialViewsInSession(context.getExternalContext()) > 0)
225         {
226             key = (SerializedViewKey) attributeMap.get(RESTORED_VIEW_KEY_REQUEST_ATTR);
227             
228             if (key == null )
229             {
230                 if (isUseFlashScopePurgeViewsInSession(context.getExternalContext()) && 
231                     Boolean.TRUE.equals(context.getExternalContext().getRequestMap()
232                             .get("oam.Flash.REDIRECT.PREVIOUSREQUEST")))
233                 {
234                     key = (SerializedViewKey)
235                             context.getExternalContext().getFlash().get(RESTORED_VIEW_KEY_REQUEST_ATTR);
236                 }
237             }
238         }
239         
240         viewCollection.add(context, serializeView(context, serializedView), getNextViewSequence(context), key);
241 
242         /*
243         if (isUseFlashScopePurgeViewsInSession(context.getExternalContext()) && 
244             context.getExternalContext().getFlash().isRedirect())
245         {
246             context.getExternalContext().getFlash().put(RESTORED_VIEW_KEY_REQUEST_ATTR, new SerializedViewKey(context));
247             context.getExternalContext().getFlash().keep(RESTORED_VIEW_KEY_REQUEST_ATTR);
248         }*/
249         
250         // replace the value to notify the container about the change
251         sessionMap.put(SERIALIZED_VIEW_SESSION_ATTR, viewCollection);
252     }
253 
254     protected Object getSerializedViewFromServletSession(FacesContext context, String viewId, Integer sequence)
255     {
256         ExternalContext externalContext = context.getExternalContext();
257         Map<Object, Object> attributeMap = context.getAttributes();
258         Object serializedView = null;
259         if (attributeMap.containsKey(RESTORED_SERIALIZED_VIEW_REQUEST_ATTR))
260         {
261             serializedView = attributeMap.get(RESTORED_SERIALIZED_VIEW_REQUEST_ATTR);
262         }
263         else
264         {
265             SerializedViewCollection viewCollection = (SerializedViewCollection) externalContext
266                     .getSessionMap().get(SERIALIZED_VIEW_SESSION_ATTR);
267             if (viewCollection != null)
268             {
269                 /*
270                 String sequenceStr = externalContext.getRequestParameterMap().get(
271                        RendererUtils.SEQUENCE_PARAM);
272                 Integer sequence = null;
273                 if (sequenceStr == null)
274                 {
275                     // use latest sequence
276                     Map map = externalContext.getSessionMap();
277                     sequence = (Integer) map.get(RendererUtils.SEQUENCE_PARAM);
278                 }
279                 else
280                 {
281                     sequence = new Integer(sequenceStr);
282                 }
283                 */
284                 if (sequence != null)
285                 {
286                     Object state = viewCollection.get(sequence, viewId);
287                     if (state != null)
288                     {
289                         serializedView = deserializeView(state);
290                     }
291                 }
292             }
293             attributeMap.put(RESTORED_SERIALIZED_VIEW_REQUEST_ATTR, serializedView);
294             
295             if (getNumberOfSequentialViewsInSession(externalContext) != null &&
296                 getNumberOfSequentialViewsInSession(externalContext) > 0)
297             {
298                 SerializedViewKey key = new SerializedViewKey(viewId, sequence);
299                 attributeMap.put(RESTORED_VIEW_KEY_REQUEST_ATTR, key);
300                 
301                 if (isUseFlashScopePurgeViewsInSession(externalContext))
302                 {
303                     externalContext.getFlash().put(RESTORED_VIEW_KEY_REQUEST_ATTR, key);
304                     externalContext.getFlash().keep(RESTORED_VIEW_KEY_REQUEST_ATTR);
305                 }
306             }
307 
308             nextViewSequence(context);
309         }
310         return serializedView;
311     }
312 
313     public int getNextViewSequence(FacesContext context)
314     {
315         if (!context.getAttributes().containsKey(RendererUtils.SEQUENCE_PARAM))
316         {
317             nextViewSequence(context);
318         }
319 
320         Integer sequence = (Integer) context.getAttributes().get(RendererUtils.SEQUENCE_PARAM);
321         return sequence.intValue();
322     }
323 
324     public void nextViewSequence(FacesContext facescontext)
325     {
326         ExternalContext externalContext = facescontext.getExternalContext();
327         Object sessionObj = externalContext.getSession(true);
328         Integer sequence = null;
329         synchronized(sessionObj) // synchronized to increase sequence if multiple requests
330                                  // are handled at the same time for the session
331         {
332             Map<String, Object> map = externalContext.getSessionMap();
333             sequence = (Integer) map.get(RendererUtils.SEQUENCE_PARAM);
334             if(sequence == null || sequence.intValue() == Integer.MAX_VALUE)
335             {
336                 sequence = Integer.valueOf(1);
337             }
338             else
339             {
340                 sequence = Integer.valueOf(sequence.intValue() + 1);
341             }
342             map.put(RendererUtils.SEQUENCE_PARAM, sequence);
343         }
344         facescontext.getAttributes().put(RendererUtils.SEQUENCE_PARAM, sequence);
345     }
346 
347     protected Object serializeView(FacesContext context, Object serializedView)
348     {
349         if (log.isLoggable(Level.FINEST))
350         {
351             log.finest("Entering serializeView");
352         }
353 
354         if(isSerializeStateInSession(context))
355         {
356             if (log.isLoggable(Level.FINEST))
357             {
358                 log.finest("Processing serializeView - serialize state in session");
359             }
360 
361             ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
362             try
363             {
364                 OutputStream os = baos;
365                 if(isCompressStateInSession(context))
366                 {
367                     if (log.isLoggable(Level.FINEST))
368                     {
369                         log.finest("Processing serializeView - serialize compressed");
370                     }
371 
372                     os.write(COMPRESSED_FLAG);
373                     os = new GZIPOutputStream(os, 1024);
374                 }
375                 else
376                 {
377                     if (log.isLoggable(Level.FINEST))
378                     {
379                         log.finest("Processing serializeView - serialize uncompressed");
380                     }
381 
382                     os.write(UNCOMPRESSED_FLAG);
383                 }
384 
385                 //Object[] stateArray = (Object[]) serializedView;
386 
387                 ObjectOutputStream out = new ObjectOutputStream(os);
388                 
389                 out.writeObject(serializedView);
390                 //out.writeObject(stateArray[0]);
391                 //out.writeObject(stateArray[1]);
392                 out.close();
393                 baos.close();
394 
395                 if (log.isLoggable(Level.FINEST))
396                 {
397                     log.finest("Exiting serializeView - serialized. Bytes : " + baos.size());
398                 }
399                 return baos.toByteArray();
400             }
401             catch (IOException e)
402             {
403                 log.log(Level.SEVERE, "Exiting serializeView - Could not serialize state: " + e.getMessage(), e);
404                 return null;
405             }
406         }
407 
408 
409         if (log.isLoggable(Level.FINEST))
410         {
411             log.finest("Exiting serializeView - do not serialize state in session.");
412         }
413 
414         return serializedView;
415 
416     }
417 
418     /**
419      * Reads the value of the <code>org.apache.myfaces.SERIALIZE_STATE_IN_SESSION</code> context parameter.
420      * @see #SERIALIZE_STATE_IN_SESSION_PARAM
421      * @param context <code>FacesContext</code> for the request we are processing.
422      * @return boolean true, if the server state should be serialized in the session
423      */
424     protected boolean isSerializeStateInSession(FacesContext context)
425     {
426         String value = context.getExternalContext().getInitParameter(
427                 SERIALIZE_STATE_IN_SESSION_PARAM);
428         boolean serialize = DEFAULT_SERIALIZE_STATE_IN_SESSION;
429         if (value != null)
430         {
431            serialize = Boolean.valueOf(value);
432         }
433         return serialize;
434     }
435 
436     /**
437      * Reads the value of the <code>org.apache.myfaces.COMPRESS_STATE_IN_SESSION</code> context parameter.
438      * @see #COMPRESS_SERVER_STATE_PARAM
439      * @param context <code>FacesContext</code> for the request we are processing.
440      * @return boolean true, if the server state steam should be compressed
441      */
442     protected boolean isCompressStateInSession(FacesContext context)
443     {
444         String value = context.getExternalContext().getInitParameter(
445                 COMPRESS_SERVER_STATE_PARAM);
446         boolean compress = DEFAULT_COMPRESS_SERVER_STATE_PARAM;
447         if (value != null)
448         {
449            compress = Boolean.valueOf(value);
450         }
451         return compress;
452     }
453 
454     protected Object deserializeView(Object state)
455     {
456         if (log.isLoggable(Level.FINEST))
457         {
458             log.finest("Entering deserializeView");
459         }
460 
461         if(state instanceof byte[])
462         {
463             if (log.isLoggable(Level.FINEST))
464             {
465                 log.finest("Processing deserializeView - deserializing serialized state. Bytes : "
466                            + ((byte[]) state).length);
467             }
468 
469             try
470             {
471                 ByteArrayInputStream bais = new ByteArrayInputStream((byte[]) state);
472                 InputStream is = bais;
473                 if(is.read() == COMPRESSED_FLAG)
474                 {
475                     is = new GZIPInputStream(is);
476                 }
477                 ObjectInputStream ois = null;
478                 try
479                 {
480                     final ObjectInputStream in = new MyFacesObjectInputStream(is);
481                     ois = in;
482                     Object object = null;
483                     if (System.getSecurityManager() != null) 
484                     {
485                         object = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() 
486                         {
487                             public Object run() throws PrivilegedActionException, IOException, ClassNotFoundException
488                             {
489                                 //return new Object[] {in.readObject(), in.readObject()};
490                                 return in.readObject();
491                             }
492                         });
493                     }
494                     else
495                     {
496                         //object = new Object[] {in.readObject(), in.readObject()};
497                         object = in.readObject();
498                     }
499                     return object;
500                 }
501                 finally
502                 {
503                     if (ois != null)
504                     {
505                         ois.close();
506                         ois = null;
507                     }
508                 }
509             }
510             catch (PrivilegedActionException e) 
511             {
512                 log.log(Level.SEVERE, "Exiting deserializeView - Could not deserialize state: " + e.getMessage(), e);
513                 return null;
514             }
515             catch (IOException e)
516             {
517                 log.log(Level.SEVERE, "Exiting deserializeView - Could not deserialize state: " + e.getMessage(), e);
518                 return null;
519             }
520             catch (ClassNotFoundException e)
521             {
522                 log.log(Level.SEVERE, "Exiting deserializeView - Could not deserialize state: " + e.getMessage(), e);
523                 return null;
524             }
525         }
526         else if (state instanceof Object[])
527         {
528             if (log.isLoggable(Level.FINEST))
529             {
530                 log.finest("Exiting deserializeView - state not serialized.");
531             }
532 
533             return state;
534         }
535         else if(state == null)
536         {
537             log.severe("Exiting deserializeView - this method should not be called with a null-state.");
538             return null;
539         }
540         else
541         {
542             log.severe("Exiting deserializeView - this method should not be called with a state of type : "
543                        + state.getClass());
544             return null;
545         }
546     }
547     
548     /*
549     public static Integer getViewSequence(FacesContext facescontext)
550     {
551         Map map = facescontext.getExternalContext().getRequestMap();
552         Integer sequence = (Integer) map.get(RendererUtils.SEQUENCE_PARAM);
553         if (sequence == null)
554         {
555             sequence = new Integer(1);
556             map.put(RendererUtils.SEQUENCE_PARAM, sequence);
557 
558             synchronized (facescontext.getExternalContext().getSession(true))
559             {
560                 facescontext.getExternalContext().getSessionMap().put(RendererUtils.SEQUENCE_PARAM, sequence);
561             }
562         }
563         return sequence;
564     }*/    
565 
566     protected static class SerializedViewCollection implements Serializable
567     {
568         private static final long serialVersionUID = -3734849062185115847L;
569 
570         private final List<SerializedViewKey> _keys
571                 = new ArrayList<SerializedViewKey>(DEFAULT_NUMBER_OF_VIEWS_IN_SESSION);
572         private final Map<SerializedViewKey, Object> _serializedViews = new HashMap<SerializedViewKey, Object>();
573         
574         private final Map<SerializedViewKey, SerializedViewKey> _precedence = 
575             new HashMap<SerializedViewKey, SerializedViewKey>();
576 
577         // old views will be hold as soft references which will be removed by
578         // the garbage collector if free memory is low
579         private transient Map<Object, Object> _oldSerializedViews = null;
580 
581         public synchronized void add(FacesContext context, Object state, Integer nextSequence,
582                                      SerializedViewKey previousRestoredKey)
583         {
584             SerializedViewKey key = new SerializedViewKey(context.getViewRoot().getViewId(), nextSequence);
585             _serializedViews.put(key, state);
586 
587             Integer maxCount = getNumberOfSequentialViewsInSession(context);
588             if (maxCount != null)
589             {
590                 if (previousRestoredKey != null)
591                 {
592                     _precedence.put((SerializedViewKey) key, previousRestoredKey);
593                 }
594             }
595 
596             while (_keys.remove(key))
597             {
598                 // do nothing
599             }
600             _keys.add(key);
601 
602             if (previousRestoredKey != null && maxCount != null && maxCount > 0)
603             {
604                 int count = 0;
605                 SerializedViewKey previousKey = (SerializedViewKey) key;
606                 do
607                 {
608                   previousKey = _precedence.get(previousKey);
609                   count++;
610                 } while (previousKey != null && count < maxCount);
611                 
612                 if (previousKey != null)
613                 {
614                     SerializedViewKey keyToRemove = (SerializedViewKey) previousKey;
615                     // In theory it should be only one key but just to be sure
616                     // do it in a loop, but in this case if cache old views is on,
617                     // put on that map.
618                     do
619                     {
620                         while (_keys.remove(keyToRemove))
621                         {
622                             // do nothing
623                         }
624 
625                         Object oldView = _serializedViews.remove(keyToRemove);
626                         if (oldView != null && 
627                                 !CACHE_OLD_VIEWS_IN_SESSION_MODE_OFF.equals(getCacheOldViewsInSessionMode(context))) 
628                         {
629                             getOldSerializedViewsMap().put(keyToRemove, oldView);
630                         }
631                     
632                         keyToRemove = _precedence.remove(keyToRemove);
633                     }  while(keyToRemove != null);
634                 }
635             }
636 
637             int views = getNumberOfViewsInSession(context);
638             while (_keys.size() > views)
639             {
640                 key = _keys.remove(0);
641                 
642                 if (maxCount != null && maxCount > 0)
643                 {
644                     SerializedViewKey keyToRemove = (SerializedViewKey) key;
645                     // Note in this case the key to delete is the oldest one, 
646                     // so it could be at least one precedence, but to be safe
647                     // do it with a loop.
648                     do
649                     {
650                         keyToRemove = _precedence.remove(keyToRemove);
651                     } while (keyToRemove != null);
652                 }
653                 
654                 Object oldView = _serializedViews.remove(key);
655                 if (oldView != null && 
656                     !CACHE_OLD_VIEWS_IN_SESSION_MODE_OFF.equals(getCacheOldViewsInSessionMode(context))) 
657                 {
658                     getOldSerializedViewsMap().put(key, oldView);
659                 }
660             }
661         }
662 
663         protected Integer getNumberOfSequentialViewsInSession(FacesContext context)
664         {
665             return WebConfigParamUtils.getIntegerInitParameter(context.getExternalContext(), 
666                     NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION_PARAM);
667         }
668         
669         /**
670          * Reads the amount (default = 20) of views to be stored in session.
671          * @see #NUMBER_OF_VIEWS_IN_SESSION_PARAM
672          * @param context FacesContext for the current request, we are processing
673          * @return Number vf views stored in the session
674          */
675         protected int getNumberOfViewsInSession(FacesContext context)
676         {
677             String value = context.getExternalContext().getInitParameter(
678                     NUMBER_OF_VIEWS_IN_SESSION_PARAM);
679             int views = DEFAULT_NUMBER_OF_VIEWS_IN_SESSION;
680             if (value != null)
681             {
682                 try
683                 {
684                     views = Integer.parseInt(value);
685                     if (views <= 0)
686                     {
687                         log.severe("Configured value for " + NUMBER_OF_VIEWS_IN_SESSION_PARAM
688                                   + " is not valid, must be an value > 0, using default value ("
689                                   + DEFAULT_NUMBER_OF_VIEWS_IN_SESSION);
690                         views = DEFAULT_NUMBER_OF_VIEWS_IN_SESSION;
691                     }
692                 }
693                 catch (Throwable e)
694                 {
695                     log.log(Level.SEVERE, "Error determining the value for " + NUMBER_OF_VIEWS_IN_SESSION_PARAM
696                               + ", expected an integer value > 0, using default value ("
697                               + DEFAULT_NUMBER_OF_VIEWS_IN_SESSION + "): " + e.getMessage(), e);
698                 }
699             }
700             return views;
701         }
702 
703         /**
704          * @return old serialized views map
705          */
706         @SuppressWarnings("unchecked")
707         protected Map<Object, Object> getOldSerializedViewsMap()
708         {
709             FacesContext context = FacesContext.getCurrentInstance();
710             if (_oldSerializedViews == null && context != null)
711             {
712                 String cacheMode = getCacheOldViewsInSessionMode(context); 
713                 if (CACHE_OLD_VIEWS_IN_SESSION_MODE_WEAK.equals(cacheMode))
714                 {
715                     _oldSerializedViews = new ReferenceMap(AbstractReferenceMap.WEAK, AbstractReferenceMap.WEAK, true);
716                 }
717                 else if (CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT_WEAK.equals(cacheMode))
718                 {
719                     _oldSerializedViews = new ReferenceMap(AbstractReferenceMap.SOFT, AbstractReferenceMap.WEAK, true);
720                 }
721                 else if (CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT.equals(cacheMode))
722                 {
723                     _oldSerializedViews = new ReferenceMap(AbstractReferenceMap.SOFT, AbstractReferenceMap.SOFT, true);
724                 }
725                 else if (CACHE_OLD_VIEWS_IN_SESSION_MODE_HARD_SOFT.equals(cacheMode))
726                 {
727                     _oldSerializedViews = new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.SOFT);
728                 }
729             }
730             
731             return _oldSerializedViews;
732         }
733         
734         /**
735          * Reads the value of the <code>org.apache.myfaces.CACHE_OLD_VIEWS_IN_SESSION_MODE</code> context parameter.
736          * 
737          * @since 1.2.5
738          * @param context
739          * @return constant indicating caching mode
740          * @see #CACHE_OLD_VIEWS_IN_SESSION_MODE
741          */
742         protected String getCacheOldViewsInSessionMode(FacesContext context)
743         {
744             String value = context.getExternalContext().getInitParameter(
745                     CACHE_OLD_VIEWS_IN_SESSION_MODE);
746             if (value == null)
747             {
748                 return CACHE_OLD_VIEWS_IN_SESSION_MODE_OFF;
749             }
750             else if (value.equalsIgnoreCase(CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT))
751             {
752                 return CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT;
753             }
754             else if (value.equalsIgnoreCase(CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT_WEAK))
755             {
756                 return CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT_WEAK;
757             }            
758             else if (value.equalsIgnoreCase(CACHE_OLD_VIEWS_IN_SESSION_MODE_WEAK))
759             {
760                 return CACHE_OLD_VIEWS_IN_SESSION_MODE_WEAK;
761             }
762             else if (value.equalsIgnoreCase(CACHE_OLD_VIEWS_IN_SESSION_MODE_HARD_SOFT))
763             {
764                 return CACHE_OLD_VIEWS_IN_SESSION_MODE_HARD_SOFT;
765             }
766             else
767             {
768                 return CACHE_OLD_VIEWS_IN_SESSION_MODE_OFF;
769             }
770         }
771         
772         public Object get(Integer sequence, String viewId)
773         {
774             Object key = new SerializedViewKey(viewId, sequence);
775             Object value = _serializedViews.get(key);
776             if (value == null)
777             {
778                 Map<Object,Object> oldSerializedViewMap = getOldSerializedViewsMap();
779                 if (oldSerializedViewMap != null)
780                 {
781                     value = oldSerializedViewMap.get(key);
782                 }
783             }
784             return value;
785         }
786     }
787 
788     protected static class SerializedViewKey implements Serializable
789     {
790         private static final long serialVersionUID = -1170697124386063642L;
791 
792         private final String _viewId;
793         private final Integer _sequenceId;
794 
795         public SerializedViewKey(String viewId, Integer sequence)
796         {
797             _sequenceId = sequence;
798             _viewId = viewId;
799         }
800 
801         /*
802         public SerializedViewKey(FacesContext context)
803         {
804             _sequenceId = getNextViewSequence(context);
805             _viewId = context.getViewRoot().getViewId();
806         }*/
807 
808         @Override
809         public int hashCode()
810         {
811             final int prime = 31;
812             int result = 1;
813             result = prime * result + ((_sequenceId == null) ? 0 : _sequenceId.hashCode());
814             result = prime * result + ((_viewId == null) ? 0 : _viewId.hashCode());
815             return result;
816         }
817 
818         @Override
819         public boolean equals(Object obj)
820         {
821             if (this == obj)
822             {
823                 return true;
824             }
825             if (obj == null)
826             {
827                 return false;
828             }
829             if (getClass() != obj.getClass())
830             {
831                 return false;
832             }
833             final SerializedViewKey other = (SerializedViewKey) obj;
834             if (_sequenceId == null)
835             {
836                 if (other._sequenceId != null)
837                 {
838                     return false;
839                 }
840             }
841             else if (!_sequenceId.equals(other._sequenceId))
842             {
843                 return false;
844             }
845             if (_viewId == null)
846             {
847                 if (other._viewId != null)
848                 {
849                     return false;
850                 }
851             }
852             else if (!_viewId.equals(other._viewId))
853             {
854                 return false;
855             }
856             return true;
857         }
858 
859     }
860     
861     //------------------------------------- METHOD FROM StateCache ------------------------------------------------
862 
863     @Override
864     public Object saveSerializedView(FacesContext facesContext, Object serializedView)
865     {
866         if (log.isLoggable(Level.FINEST))
867         {
868             log.finest("Processing saveSerializedView - server-side state saving - save state");
869         }
870         //save state in server session
871         saveSerializedViewInServletSession(facesContext, serializedView);
872         
873         if (log.isLoggable(Level.FINEST))
874         {
875             log.finest("Exiting saveSerializedView - server-side state saving - saved state");
876         }
877         
878         return encodeSerializedState(facesContext, serializedView);
879     }
880 
881     @Override
882     public Object restoreSerializedView(FacesContext facesContext, String viewId, Object viewState)
883     {
884         if (log.isLoggable(Level.FINEST))
885         {
886             log.finest("Restoring view from session");
887         }
888 
889         Integer serverStateId = getServerStateId((Object[]) viewState);
890 
891         return (serverStateId == null)
892                 ? null
893                 : getSerializedViewFromServletSession(facesContext, viewId, serverStateId);
894     }
895 
896     public Object encodeSerializedState(FacesContext facesContext, Object serializedView)
897     {
898         Object[] identifier = new Object[2];
899         identifier[JSF_SEQUENCE_INDEX] = Integer.toString(getNextViewSequence(facesContext), Character.MAX_RADIX);
900         return identifier;
901     }
902     
903     @Override
904     public boolean isWriteStateAfterRenderViewRequired(FacesContext facesContext)
905     {
906         return false;
907     }
908 
909     //------------------------------------- Custom methods -----------------------------------------------------
910     
911     private boolean isUseFlashScopePurgeViewsInSession(ExternalContext externalContext)
912     {
913         if (_useFlashScopePurgeViewsInSession == null)
914         {
915             _useFlashScopePurgeViewsInSession = WebConfigParamUtils.getBooleanInitParameter(
916                     externalContext, USE_FLASH_SCOPE_PURGE_VIEWS_IN_SESSION, false);
917         }
918         return _useFlashScopePurgeViewsInSession;
919     }
920     
921     private Integer getNumberOfSequentialViewsInSession(ExternalContext externalContext)
922     {
923         if (!_numberOfSequentialViewsInSessionSet)
924         {
925             _numberOfSequentialViewsInSession = WebConfigParamUtils.getIntegerInitParameter(
926                     externalContext, 
927                     NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION_PARAM);
928             _numberOfSequentialViewsInSessionSet = true;
929         }
930         return _numberOfSequentialViewsInSession;
931     }
932 }