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.application.viewstate;
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.security.AccessController;
29  import java.security.PrivilegedActionException;
30  import java.security.PrivilegedExceptionAction;
31  import java.util.Map;
32  import java.util.logging.Level;
33  import java.util.logging.Logger;
34  import java.util.zip.GZIPInputStream;
35  import java.util.zip.GZIPOutputStream;
36  
37  import javax.faces.context.ExternalContext;
38  import javax.faces.context.FacesContext;
39  import org.apache.myfaces.application.StateCache;
40  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
41  import org.apache.myfaces.shared.renderkit.RendererUtils;
42  import org.apache.myfaces.shared.util.MyFacesObjectInputStream;
43  import org.apache.myfaces.shared.util.WebConfigParamUtils;
44  
45  class ServerSideStateCacheImpl extends StateCache<Object, Object>
46  {
47      private static final Logger log = Logger.getLogger(ServerSideStateCacheImpl.class.getName());
48      
49      public static final String SERIALIZED_VIEW_SESSION_ATTR= 
50          ServerSideStateCacheImpl.class.getName() + ".SERIALIZED_VIEW";
51      
52      public static final String RESTORED_SERIALIZED_VIEW_REQUEST_ATTR = 
53          ServerSideStateCacheImpl.class.getName() + ".RESTORED_SERIALIZED_VIEW";
54  
55      public static final String RESTORED_VIEW_KEY_REQUEST_ATTR = 
56          ServerSideStateCacheImpl.class.getName() + ".RESTORED_VIEW_KEY";
57      
58      /**
59       * Defines the amount (default = 20) of the latest views are stored in session.
60       * 
61       * <p>Only applicable if state saving method is "server" (= default).
62       * </p>
63       * 
64       */
65      @JSFWebConfigParam(defaultValue="20",since="1.1", classType="java.lang.Integer", group="state", tags="performance")
66      public static final String NUMBER_OF_VIEWS_IN_SESSION_PARAM = "org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION";
67  
68      /**
69       * Indicates the amount of views (default is not active) that should be stored in session between sequential
70       * POST or POST-REDIRECT-GET if org.apache.myfaces.USE_FLASH_SCOPE_PURGE_VIEWS_IN_SESSION is true.
71       * 
72       * <p>Only applicable if state saving method is "server" (= default). For example, if this param has value = 2 and 
73       * in your custom webapp there is a form that is clicked 3 times, only 2 views
74       * will be stored and the third one (the one stored the first time) will be
75       * removed from session, even if the view can
76       * store more sessions org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION.
77       * This feature becomes useful for multi-window applications.
78       * where without this feature a window can swallow all view slots so
79       * the other ones will throw ViewExpiredException.</p>
80       */
81      @JSFWebConfigParam(since="2.0.6", classType="java.lang.Integer", group="state", tags="performance")
82      public static final String NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION_PARAM
83              = "org.apache.myfaces.NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION";
84      
85      /**
86       * Default value for <code>org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION</code> context parameter.
87       */
88      public static final int DEFAULT_NUMBER_OF_VIEWS_IN_SESSION = 20;
89  
90      /**
91       * Indicate if the state should be serialized before save it on the session. 
92       * <p>
93       * Only applicable if state saving method is "server" (= default).
94       * If <code>true</code> (default) the state will be serialized to a byte stream before it is written to the session.
95       * If <code>false</code> the state will not be serialized to a byte stream.
96       * </p>
97       */
98      @JSFWebConfigParam(defaultValue="true",since="1.1", expectedValues="true,false", group="state", tags="performance")
99      public static final String SERIALIZE_STATE_IN_SESSION_PARAM = "org.apache.myfaces.SERIALIZE_STATE_IN_SESSION";
100 
101     /**
102      * Indicates that the serialized state will be compressed before it is written to the session. By default true.
103      * 
104      * Only applicable if state saving method is "server" (= default) and if
105      * <code>org.apache.myfaces.SERIALIZE_STATE_IN_SESSION</code> is <code>true</code> (= default).
106      * If <code>true</code> (default) the serialized state will be compressed before it is written to the session.
107      * If <code>false</code> the state will not be compressed.
108      */
109     @JSFWebConfigParam(defaultValue="true",since="1.1", expectedValues="true,false", group="state", tags="performance")
110     public static final String COMPRESS_SERVER_STATE_PARAM = "org.apache.myfaces.COMPRESS_STATE_IN_SESSION";
111 
112     /**
113      * Default value for <code>org.apache.myfaces.COMPRESS_STATE_IN_SESSION</code> context parameter.
114      */
115     public static final boolean DEFAULT_COMPRESS_SERVER_STATE_PARAM = true;
116 
117     /**
118      * Default value for <code>org.apache.myfaces.SERIALIZE_STATE_IN_SESSION</code> context parameter.
119      */
120     public static final boolean DEFAULT_SERIALIZE_STATE_IN_SESSION = true;
121 
122     /**
123      * Define the way of handle old view references(views removed from session), making possible to
124      * store it in a cache, so the state manager first try to get the view from the session. If is it
125      * not found and soft or weak ReferenceMap is used, it try to get from it.
126      * <p>
127      * Only applicable if state saving method is "server" (= default).
128      * </p>
129      * <p>
130      * The gc is responsible for remove the views, according to the rules used for soft, weak or phantom
131      * references. If a key in soft and weak mode is garbage collected, its values are purged.
132      * </p>
133      * <p>
134      * By default no cache is used, so views removed from session became phantom references.
135      * </p>
136      * <ul> 
137      * <li> off, no: default, no cache is used</li> 
138      * <li> hard-soft: use an ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.SOFT)</li>
139      * <li> soft: use an ReferenceMap(AbstractReferenceMap.SOFT, AbstractReferenceMap.SOFT, true) </li>
140      * <li> soft-weak: use an ReferenceMap(AbstractReferenceMap.SOFT, AbstractReferenceMap.WEAK, true) </li>
141      * <li> weak: use an ReferenceMap(AbstractReferenceMap.WEAK, AbstractReferenceMap.WEAK, true) </li>
142      * </ul>
143      * 
144      */
145     @JSFWebConfigParam(defaultValue="off", expectedValues="off, no, hard-soft, soft, soft-weak, weak",
146                        since="1.2.5", group="state", tags="performance")
147     public static final String CACHE_OLD_VIEWS_IN_SESSION_MODE = "org.apache.myfaces.CACHE_OLD_VIEWS_IN_SESSION_MODE";
148     
149     /**
150      * This option uses an hard-soft ReferenceMap, but it could cause a 
151      * memory leak, because the keys are not removed by any method
152      * (MYFACES-1660). So use with caution.
153      */
154     public static final String CACHE_OLD_VIEWS_IN_SESSION_MODE_HARD_SOFT = "hard-soft";
155     
156     public static final String CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT = "soft";
157     
158     public static final String CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT_WEAK = "soft-weak";
159     
160     public static final String CACHE_OLD_VIEWS_IN_SESSION_MODE_WEAK = "weak";
161     
162     public static final String CACHE_OLD_VIEWS_IN_SESSION_MODE_OFF = "off";
163 
164     /**
165      * Allow use flash scope to keep track of the views used in session and the previous ones,
166      * so server side state saving can delete old views even if POST-REDIRECT-GET pattern is used.
167      * 
168      * <p>
169      * Only applicable if state saving method is "server" (= default).
170      * The default value is false.</p>
171      */
172     @JSFWebConfigParam(since="2.0.6", defaultValue="false", expectedValues="true, false", group="state")
173     public static final String USE_FLASH_SCOPE_PURGE_VIEWS_IN_SESSION
174             = "org.apache.myfaces.USE_FLASH_SCOPE_PURGE_VIEWS_IN_SESSION";
175 
176     public static final String RANDOM_KEY_IN_VIEW_STATE_SESSION_TOKEN_NONE = "none";
177     public static final String RANDOM_KEY_IN_VIEW_STATE_SESSION_TOKEN_SECURE_RANDOM = "secureRandom";
178     public static final String RANDOM_KEY_IN_VIEW_STATE_SESSION_TOKEN_RANDOM = "random";
179     
180     /**
181      * Adds a random key to the generated view state session token.
182      */
183     @JSFWebConfigParam(since="2.1.9, 2.0.15", expectedValues="secureRandom, random, none", 
184             defaultValue="none", group="state")
185     public static final String RANDOM_KEY_IN_VIEW_STATE_SESSION_TOKEN_PARAM
186             = "org.apache.myfaces.RANDOM_KEY_IN_VIEW_STATE_SESSION_TOKEN";
187     public static final String RANDOM_KEY_IN_VIEW_STATE_SESSION_TOKEN_PARAM_DEFAULT = 
188             RANDOM_KEY_IN_VIEW_STATE_SESSION_TOKEN_NONE;
189 
190     /**
191      * Set the default length of the random key added to the view state session token.
192      * By default is 8. 
193      */
194     @JSFWebConfigParam(since="2.1.9, 2.0.15", defaultValue="8", group="state")
195     public static final String RANDOM_KEY_IN_VIEW_STATE_SESSION_TOKEN_LENGTH_PARAM 
196             = "org.apache.myfaces.RANDOM_KEY_IN_VIEW_STATE_SESSION_TOKEN_LENGTH";
197     public static final int RANDOM_KEY_IN_VIEW_STATE_SESSION_TOKEN_LENGTH_PARAM_DEFAULT = 8;
198 
199     /**
200      * Sets the random class to initialize the secure random id generator. 
201      * By default it uses java.security.SecureRandom
202      */
203     @JSFWebConfigParam(since="2.1.9, 2.0.15", group="state")
204     public static final String RANDOM_KEY_IN_VIEW_STATE_SESSION_TOKEN_SECURE_RANDOM_CLASS_PARAM
205             = "org.apache.myfaces.RANDOM_KEY_IN_VIEW_STATE_SESSION_TOKEN_SECURE_RANDOM_CLASS";
206     
207     /**
208      * Sets the random provider to initialize the secure random id generator.
209      */
210     @JSFWebConfigParam(since="2.1.9, 2.0.15", group="state")
211     public static final String RANDOM_KEY_IN_VIEW_STATE_SESSION_TOKEN_SECURE_RANDOM_PROVIDER_PARAM
212             = "org.apache.myfaces.RANDOM_KEY_IN_VIEW_STATE_SESSION_TOKEN_SECURE_RANDOM_PROVIDER";
213     
214     /**
215      * Sets the random algorithm to initialize the secure random id generator. 
216      * By default is SHA1PRNG
217      */
218     @JSFWebConfigParam(since="2.1.9, 2.0.15", defaultValue="SHA1PRNG", group="state")
219     public static final String RANDOM_KEY_IN_VIEW_STATE_SESSION_TOKEN_SECURE_RANDOM_ALGORITM_PARAM 
220             = "org.apache.myfaces.RANDOM_KEY_IN_VIEW_STATE_SESSION_TOKEN_SECURE_RANDOM_ALGORITM";
221     
222     
223     public static final int UNCOMPRESSED_FLAG = 0;
224     public static final int COMPRESSED_FLAG = 1;
225 
226     private Boolean _useFlashScopePurgeViewsInSession = null;
227     
228     private Integer _numberOfSequentialViewsInSession = null;
229     private boolean _numberOfSequentialViewsInSessionSet = false;
230 
231     private SessionViewStorageFactory sessionViewStorageFactory;
232 
233     public ServerSideStateCacheImpl()
234     {
235         FacesContext facesContext = FacesContext.getCurrentInstance();
236         String randomMode = WebConfigParamUtils.getStringInitParameter(facesContext.getExternalContext(),
237                 RANDOM_KEY_IN_VIEW_STATE_SESSION_TOKEN_PARAM, 
238                 RANDOM_KEY_IN_VIEW_STATE_SESSION_TOKEN_PARAM_DEFAULT);
239         if (RANDOM_KEY_IN_VIEW_STATE_SESSION_TOKEN_SECURE_RANDOM.equals(randomMode))
240         {
241             sessionViewStorageFactory = new RandomSessionViewStorageFactory(
242                     new SecureRandomKeyFactory(facesContext));
243         }
244         else if (RANDOM_KEY_IN_VIEW_STATE_SESSION_TOKEN_RANDOM.equals(randomMode))
245         {
246             sessionViewStorageFactory = new RandomSessionViewStorageFactory(
247                     new RandomKeyFactory(facesContext));
248         }
249         else
250         {
251             sessionViewStorageFactory = new CounterSessionViewStorageFactory(new CounterKeyFactory());
252         }
253     }
254     
255     //------------------------------------- METHODS COPIED FROM JspStateManagerImpl--------------------------------
256 
257     protected Object getServerStateId(FacesContext facesContext, Object state)
258     {
259       if (state != null)
260       {
261           return getKeyFactory(facesContext).decode((String) state);
262       }
263       return null;
264     }
265 
266     protected void saveSerializedViewInServletSession(FacesContext context,
267                                                       Object serializedView)
268     {
269         Map<String, Object> sessionMap = context.getExternalContext().getSessionMap();
270         SerializedViewCollection viewCollection = (SerializedViewCollection) sessionMap
271                 .get(SERIALIZED_VIEW_SESSION_ATTR);
272         if (viewCollection == null)
273         {
274             viewCollection = getSessionViewStorageFactory().createSerializedViewCollection(context);
275             sessionMap.put(SERIALIZED_VIEW_SESSION_ATTR, viewCollection);
276         }
277 
278         Map<Object,Object> attributeMap = context.getAttributes();
279         
280         SerializedViewKey key = null;
281         if (getNumberOfSequentialViewsInSession(context.getExternalContext()) != null &&
282             getNumberOfSequentialViewsInSession(context.getExternalContext()) > 0)
283         {
284             key = (SerializedViewKey) attributeMap.get(RESTORED_VIEW_KEY_REQUEST_ATTR);
285             
286             if (key == null )
287             {
288                 if (isUseFlashScopePurgeViewsInSession(context.getExternalContext()) && 
289                     Boolean.TRUE.equals(context.getExternalContext().getRequestMap()
290                             .get("oam.Flash.REDIRECT.PREVIOUSREQUEST")))
291                 {
292                     key = (SerializedViewKey)
293                             context.getExternalContext().getFlash().get(RESTORED_VIEW_KEY_REQUEST_ATTR);
294                 }
295             }
296         }
297         
298         SerializedViewKey nextKey = getSessionViewStorageFactory().createSerializedViewKey(
299                 context, context.getViewRoot().getViewId(), getNextViewSequence(context));
300         viewCollection.add(context, serializeView(context, serializedView), nextKey, key);
301 
302         // replace the value to notify the container about the change
303         sessionMap.put(SERIALIZED_VIEW_SESSION_ATTR, viewCollection);
304     }
305 
306     protected Object getSerializedViewFromServletSession(FacesContext context, String viewId, Object sequence)
307     {
308         ExternalContext externalContext = context.getExternalContext();
309         Map<Object, Object> attributeMap = context.getAttributes();
310         Object serializedView = null;
311         if (attributeMap.containsKey(RESTORED_SERIALIZED_VIEW_REQUEST_ATTR))
312         {
313             serializedView = attributeMap.get(RESTORED_SERIALIZED_VIEW_REQUEST_ATTR);
314         }
315         else
316         {
317             SerializedViewCollection viewCollection = (SerializedViewCollection) externalContext
318                     .getSessionMap().get(SERIALIZED_VIEW_SESSION_ATTR);
319             if (viewCollection != null)
320             {
321                 if (sequence != null)
322                 {
323                     Object state = viewCollection.get(
324                             getSessionViewStorageFactory().createSerializedViewKey(
325                             context, viewId, sequence));
326                     if (state != null)
327                     {
328                         serializedView = deserializeView(state);
329                     }
330                 }
331             }
332             attributeMap.put(RESTORED_SERIALIZED_VIEW_REQUEST_ATTR, serializedView);
333             
334             if (getNumberOfSequentialViewsInSession(externalContext) != null &&
335                 getNumberOfSequentialViewsInSession(externalContext) > 0)
336             {
337                 SerializedViewKey key = getSessionViewStorageFactory().
338                         createSerializedViewKey(context, viewId, sequence);
339                 attributeMap.put(RESTORED_VIEW_KEY_REQUEST_ATTR, key);
340                 
341                 if (isUseFlashScopePurgeViewsInSession(externalContext))
342                 {
343                     externalContext.getFlash().put(RESTORED_VIEW_KEY_REQUEST_ATTR, key);
344                     externalContext.getFlash().keep(RESTORED_VIEW_KEY_REQUEST_ATTR);
345                 }
346             }
347 
348             nextViewSequence(context);
349         }
350         return serializedView;
351     }
352 
353     public Object getNextViewSequence(FacesContext context)
354     {
355         Object sequence = context.getAttributes().get(RendererUtils.SEQUENCE_PARAM);
356         if (sequence == null)
357         {
358             sequence = nextViewSequence(context);
359             context.getAttributes().put(RendererUtils.SEQUENCE_PARAM, sequence);
360         }
361         return sequence;
362     }
363 
364     public Object nextViewSequence(FacesContext facescontext)
365     {
366         Object sequence = getKeyFactory(facescontext).generateKey(facescontext);
367         facescontext.getAttributes().put(RendererUtils.SEQUENCE_PARAM, sequence);
368         return sequence;
369     }
370 
371     protected Object serializeView(FacesContext context, Object serializedView)
372     {
373         if (log.isLoggable(Level.FINEST))
374         {
375             log.finest("Entering serializeView");
376         }
377 
378         if(isSerializeStateInSession(context))
379         {
380             if (log.isLoggable(Level.FINEST))
381             {
382                 log.finest("Processing serializeView - serialize state in session");
383             }
384 
385             ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
386             try
387             {
388                 OutputStream os = baos;
389                 if(isCompressStateInSession(context))
390                 {
391                     if (log.isLoggable(Level.FINEST))
392                     {
393                         log.finest("Processing serializeView - serialize compressed");
394                     }
395 
396                     os.write(COMPRESSED_FLAG);
397                     os = new GZIPOutputStream(os, 1024);
398                 }
399                 else
400                 {
401                     if (log.isLoggable(Level.FINEST))
402                     {
403                         log.finest("Processing serializeView - serialize uncompressed");
404                     }
405 
406                     os.write(UNCOMPRESSED_FLAG);
407                 }
408 
409                 ObjectOutputStream out = new ObjectOutputStream(os);
410                 
411                 out.writeObject(serializedView);
412                 out.close();
413                 baos.close();
414 
415                 if (log.isLoggable(Level.FINEST))
416                 {
417                     log.finest("Exiting serializeView - serialized. Bytes : " + baos.size());
418                 }
419                 return baos.toByteArray();
420             }
421             catch (IOException e)
422             {
423                 log.log(Level.SEVERE, "Exiting serializeView - Could not serialize state: " + e.getMessage(), e);
424                 return null;
425             }
426         }
427 
428 
429         if (log.isLoggable(Level.FINEST))
430         {
431             log.finest("Exiting serializeView - do not serialize state in session.");
432         }
433 
434         return serializedView;
435 
436     }
437 
438     /**
439      * Reads the value of the <code>org.apache.myfaces.SERIALIZE_STATE_IN_SESSION</code> context parameter.
440      * @see #SERIALIZE_STATE_IN_SESSION_PARAM
441      * @param context <code>FacesContext</code> for the request we are processing.
442      * @return boolean true, if the server state should be serialized in the session
443      */
444     protected boolean isSerializeStateInSession(FacesContext context)
445     {
446         String value = context.getExternalContext().getInitParameter(
447                 SERIALIZE_STATE_IN_SESSION_PARAM);
448         boolean serialize = DEFAULT_SERIALIZE_STATE_IN_SESSION;
449         if (value != null)
450         {
451            serialize = Boolean.valueOf(value);
452         }
453         return serialize;
454     }
455 
456     /**
457      * Reads the value of the <code>org.apache.myfaces.COMPRESS_STATE_IN_SESSION</code> context parameter.
458      * @see #COMPRESS_SERVER_STATE_PARAM
459      * @param context <code>FacesContext</code> for the request we are processing.
460      * @return boolean true, if the server state steam should be compressed
461      */
462     protected boolean isCompressStateInSession(FacesContext context)
463     {
464         String value = context.getExternalContext().getInitParameter(
465                 COMPRESS_SERVER_STATE_PARAM);
466         boolean compress = DEFAULT_COMPRESS_SERVER_STATE_PARAM;
467         if (value != null)
468         {
469            compress = Boolean.valueOf(value);
470         }
471         return compress;
472     }
473 
474     protected Object deserializeView(Object state)
475     {
476         if (log.isLoggable(Level.FINEST))
477         {
478             log.finest("Entering deserializeView");
479         }
480 
481         if(state instanceof byte[])
482         {
483             if (log.isLoggable(Level.FINEST))
484             {
485                 log.finest("Processing deserializeView - deserializing serialized state. Bytes : "
486                            + ((byte[]) state).length);
487             }
488 
489             try
490             {
491                 ByteArrayInputStream bais = new ByteArrayInputStream((byte[]) state);
492                 InputStream is = bais;
493                 if(is.read() == COMPRESSED_FLAG)
494                 {
495                     is = new GZIPInputStream(is);
496                 }
497                 ObjectInputStream ois = null;
498                 try
499                 {
500                     final ObjectInputStream in = new MyFacesObjectInputStream(is);
501                     ois = in;
502                     Object object = null;
503                     if (System.getSecurityManager() != null) 
504                     {
505                         object = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() 
506                         {
507                             public Object run() throws PrivilegedActionException, IOException, ClassNotFoundException
508                             {
509                                 //return new Object[] {in.readObject(), in.readObject()};
510                                 return in.readObject();
511                             }
512                         });
513                     }
514                     else
515                     {
516                         //object = new Object[] {in.readObject(), in.readObject()};
517                         object = in.readObject();
518                     }
519                     return object;
520                 }
521                 finally
522                 {
523                     if (ois != null)
524                     {
525                         ois.close();
526                         ois = null;
527                     }
528                 }
529             }
530             catch (PrivilegedActionException e) 
531             {
532                 log.log(Level.SEVERE, "Exiting deserializeView - Could not deserialize state: " + e.getMessage(), e);
533                 return null;
534             }
535             catch (IOException e)
536             {
537                 log.log(Level.SEVERE, "Exiting deserializeView - Could not deserialize state: " + e.getMessage(), e);
538                 return null;
539             }
540             catch (ClassNotFoundException e)
541             {
542                 log.log(Level.SEVERE, "Exiting deserializeView - Could not deserialize state: " + e.getMessage(), e);
543                 return null;
544             }
545         }
546         else if (state instanceof Object[])
547         {
548             if (log.isLoggable(Level.FINEST))
549             {
550                 log.finest("Exiting deserializeView - state not serialized.");
551             }
552 
553             return state;
554         }
555         else if(state == null)
556         {
557             log.severe("Exiting deserializeView - this method should not be called with a null-state.");
558             return null;
559         }
560         else
561         {
562             log.severe("Exiting deserializeView - this method should not be called with a state of type : "
563                        + state.getClass());
564             return null;
565         }
566     }
567     
568     //------------------------------------- METHOD FROM StateCache ------------------------------------------------
569 
570     @Override
571     public Object saveSerializedView(FacesContext facesContext, Object serializedView)
572     {
573         if (log.isLoggable(Level.FINEST))
574         {
575             log.finest("Processing saveSerializedView - server-side state saving - save state");
576         }
577         //save state in server session
578         saveSerializedViewInServletSession(facesContext, serializedView);
579         
580         if (log.isLoggable(Level.FINEST))
581         {
582             log.finest("Exiting saveSerializedView - server-side state saving - saved state");
583         }
584         
585         return encodeSerializedState(facesContext, serializedView);
586     }
587 
588     @Override
589     public Object restoreSerializedView(FacesContext facesContext, String viewId, Object viewState)
590     {
591         if (log.isLoggable(Level.FINEST))
592         {
593             log.finest("Restoring view from session");
594         }
595 
596         Object serverStateId = getServerStateId(facesContext, viewState);
597 
598         return (serverStateId == null)
599                 ? null
600                 : getSerializedViewFromServletSession(facesContext, viewId, serverStateId);
601     }
602 
603     public Object encodeSerializedState(FacesContext facesContext, Object serializedView)
604     {
605         return getKeyFactory(facesContext).encode(getNextViewSequence(facesContext));
606     }
607     
608     @Override
609     public boolean isWriteStateAfterRenderViewRequired(FacesContext facesContext)
610     {
611         return false;
612     }
613 
614     //------------------------------------- Custom methods -----------------------------------------------------
615     
616     private boolean isUseFlashScopePurgeViewsInSession(ExternalContext externalContext)
617     {
618         if (_useFlashScopePurgeViewsInSession == null)
619         {
620             _useFlashScopePurgeViewsInSession = WebConfigParamUtils.getBooleanInitParameter(
621                     externalContext, USE_FLASH_SCOPE_PURGE_VIEWS_IN_SESSION, false);
622         }
623         return _useFlashScopePurgeViewsInSession;
624     }
625     
626     private Integer getNumberOfSequentialViewsInSession(ExternalContext externalContext)
627     {
628         if (!_numberOfSequentialViewsInSessionSet)
629         {
630             _numberOfSequentialViewsInSession = WebConfigParamUtils.getIntegerInitParameter(
631                     externalContext, 
632                     NUMBER_OF_SEQUENTIAL_VIEWS_IN_SESSION_PARAM);
633             _numberOfSequentialViewsInSessionSet = true;
634         }
635         return _numberOfSequentialViewsInSession;
636     }
637     
638     protected KeyFactory getKeyFactory(FacesContext facesContext)
639     {
640         //return keyFactory;
641         return sessionViewStorageFactory.getKeyFactory();
642     }
643     
644     protected SessionViewStorageFactory getSessionViewStorageFactory()
645     {
646         return sessionViewStorageFactory;
647     }
648 }