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.jsp;
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.lang.reflect.Method;
30  import java.security.AccessController;
31  import java.security.PrivilegedActionException;
32  import java.security.PrivilegedExceptionAction;
33  import java.util.ArrayList;
34  import java.util.Arrays;
35  import java.util.HashMap;
36  import java.util.HashSet;
37  import java.util.Iterator;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.Set;
41  import java.util.logging.Level;
42  import java.util.logging.Logger;
43  import java.util.zip.GZIPInputStream;
44  import java.util.zip.GZIPOutputStream;
45  
46  import javax.faces.FactoryFinder;
47  import javax.faces.component.NamingContainer;
48  import javax.faces.component.UIComponent;
49  import javax.faces.component.UIViewRoot;
50  import javax.faces.context.ExternalContext;
51  import javax.faces.context.FacesContext;
52  import javax.faces.render.RenderKit;
53  import javax.faces.render.RenderKitFactory;
54  import javax.faces.render.ResponseStateManager;
55  import javax.faces.view.StateManagementStrategy;
56  import javax.faces.view.ViewDeclarationLanguage;
57  
58  import org.apache.commons.collections.map.AbstractReferenceMap;
59  import org.apache.commons.collections.map.ReferenceMap;
60  import org.apache.myfaces.application.MyfacesStateManager;
61  import org.apache.myfaces.application.TreeStructureManager;
62  import org.apache.myfaces.renderkit.MyfacesResponseStateManager;
63  import org.apache.myfaces.shared.renderkit.RendererUtils;
64  import org.apache.myfaces.shared.util.MyFacesObjectInputStream;
65  
66  /**
67   * Default StateManager implementation for use when views are defined
68   * via tags in JSP pages.
69   *
70   * @author Thomas Spiegl (latest modification by $Author: bommel $)
71   * @author Manfred Geiler
72   * @version $Revision: 1187700 $ $Date: 2011-10-22 07:19:37 -0500 (Sat, 22 Oct 2011) $
73   */
74  public class JspStateManagerImpl extends MyfacesStateManager
75  {
76      //private static final Log log = LogFactory.getLog(JspStateManagerImpl.class);
77      private static final Logger log = Logger.getLogger(JspStateManagerImpl.class.getName());
78      
79      private static final String SERIALIZED_VIEW_SESSION_ATTR= 
80          JspStateManagerImpl.class.getName() + ".SERIALIZED_VIEW";
81      
82      private static final String SERIALIZED_VIEW_REQUEST_ATTR = 
83          JspStateManagerImpl.class.getName() + ".SERIALIZED_VIEW";
84      
85      private static final String RESTORED_SERIALIZED_VIEW_REQUEST_ATTR = 
86          JspStateManagerImpl.class.getName() + ".RESTORED_SERIALIZED_VIEW";
87  
88      /**
89       * Only applicable if state saving method is "server" (= default).
90       * Defines the amount (default = 20) of the latest views are stored in session.
91       */
92      private static final String NUMBER_OF_VIEWS_IN_SESSION_PARAM = "org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION";
93  
94      /**
95       * Default value for <code>org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION</code> context parameter.
96       */
97      private static final int DEFAULT_NUMBER_OF_VIEWS_IN_SESSION = 20;
98  
99      /**
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      */
104     private static final String SERIALIZE_STATE_IN_SESSION_PARAM = "org.apache.myfaces.SERIALIZE_STATE_IN_SESSION";
105 
106     /**
107      * Only applicable if state saving method is "server" (= default) and if <code>org.apache.myfaces.SERIALIZE_STATE_IN_SESSION</code> is <code>true</code> (= default).
108      * If <code>true</code> (default) the serialized state will be compressed before it is written to the session.
109      * If <code>false</code> the state will not be compressed.
110      */
111     private static final String COMPRESS_SERVER_STATE_PARAM = "org.apache.myfaces.COMPRESS_STATE_IN_SESSION";
112 
113     /**
114      * Default value for <code>org.apache.myfaces.COMPRESS_STATE_IN_SESSION</code> context parameter.
115      */
116     private static final boolean DEFAULT_COMPRESS_SERVER_STATE_PARAM = true;
117 
118     /**
119      * Default value for <code>org.apache.myfaces.SERIALIZE_STATE_IN_SESSION</code> context parameter.
120      */
121     private static final boolean DEFAULT_SERIALIZE_STATE_IN_SESSION = true;
122 
123     /**
124      * Define the way of handle old view references(views removed from session), making possible to
125      * store it in a cache, so the state manager first try to get the view from the session. If is it
126      * not found and soft or weak ReferenceMap is used, it try to get from it.
127      * <p>
128      * Only applicable if state saving method is "server" (= default).
129      * </p>
130      * <p>
131      * The gc is responsible for remove the views, according to the rules used for soft, weak or phantom
132      * references. If a key in soft and weak mode is garbage collected, its values are purged.
133      * </p>
134      * <p>
135      * By default no cache is used, so views removed from session became phantom references.
136      * </p>
137      * <ul> 
138      * <li> off, no: default, no cache is used</li> 
139      * <li> hard-soft: use an ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.SOFT)</li>
140      * <li> soft: use an ReferenceMap(AbstractReferenceMap.SOFT, AbstractReferenceMap.SOFT, true) </li>
141      * <li> soft-weak: use an ReferenceMap(AbstractReferenceMap.SOFT, AbstractReferenceMap.WEAK, true) </li>
142      * <li> weak: use an ReferenceMap(AbstractReferenceMap.WEAK, AbstractReferenceMap.WEAK, true) </li>
143      * </ul>
144      * 
145      */
146     private static final String CACHE_OLD_VIEWS_IN_SESSION_MODE = "org.apache.myfaces.CACHE_OLD_VIEWS_IN_SESSION_MODE";
147     
148     /**
149      * This option uses an hard-soft ReferenceMap, but it could cause a 
150      * memory leak, because the keys are not removed by any method
151      * (MYFACES-1660). So use with caution.
152      */
153     private static final String CACHE_OLD_VIEWS_IN_SESSION_MODE_HARD_SOFT = "hard-soft";
154     
155     private static final String CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT = "soft";
156     
157     private static final String CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT_WEAK = "soft-weak";
158     
159     private static final String CACHE_OLD_VIEWS_IN_SESSION_MODE_WEAK = "weak";
160     
161     private static final String CACHE_OLD_VIEWS_IN_SESSION_MODE_OFF = "off";
162 
163     private static final int UNCOMPRESSED_FLAG = 0;
164     private static final int COMPRESSED_FLAG = 1;
165 
166     private static final int JSF_SEQUENCE_INDEX = 0;
167 
168     private RenderKitFactory _renderKitFactory = null;
169 
170     public JspStateManagerImpl()
171     {
172         if (log.isLoggable(Level.FINEST)) log.finest("New JspStateManagerImpl instance created");
173     }
174 
175     @Override
176     protected Object getComponentStateToSave(FacesContext facesContext)
177     {
178         if (log.isLoggable(Level.FINEST)) log.finest("Entering getComponentStateToSave");
179 
180         UIViewRoot viewRoot = facesContext.getViewRoot();
181         if (viewRoot.isTransient())
182         {
183             return null;
184         }
185 
186         Object serializedComponentStates = viewRoot.processSaveState(facesContext);
187         //Locale is a state attribute of UIViewRoot and need not be saved explicitly
188         if (log.isLoggable(Level.FINEST)) log.finest("Exiting getComponentStateToSave");
189         return serializedComponentStates;
190     }
191 
192     /**
193      * Return an object which contains info about the UIComponent type
194      * of each node in the view tree. This allows an identical UIComponent
195      * tree to be recreated later, though all the components will have
196      * just default values for their members.
197      */
198     @Override
199     protected Object getTreeStructureToSave(FacesContext facesContext)
200     {
201         if (log.isLoggable(Level.FINEST)) log.finest("Entering getTreeStructureToSave");
202         UIViewRoot viewRoot = facesContext.getViewRoot();
203         if (viewRoot.isTransient())
204         {
205             return null;
206         }
207         TreeStructureManager tsm = new TreeStructureManager();
208         Object retVal = tsm.buildTreeStructureToSave(viewRoot);
209         if (log.isLoggable(Level.FINEST)) log.finest("Exiting getTreeStructureToSave");
210         return retVal;
211     }
212 
213     /**
214      * Given a tree of UIComponent objects created the default constructor
215      * for each node, retrieve saved state info (from either the client or
216      * the server) and walk the tree restoring the members of each node
217      * from the saved state information.
218      */
219     @Override
220     protected void restoreComponentState(FacesContext facesContext,
221                                          UIViewRoot uiViewRoot,
222                                          String renderKitId)
223     {
224         if (log.isLoggable(Level.FINEST)) log.finest("Entering restoreComponentState");
225 
226         //===========================================
227         // first, locate the saved state information
228         //===========================================
229 
230         RenderKit renderKit = getRenderKitFactory().getRenderKit(facesContext, renderKitId);
231         ResponseStateManager responseStateManager = renderKit.getResponseStateManager();
232 
233         Object serializedComponentStates;
234         if (isSavingStateInClient(facesContext))
235         {
236             if (isLegacyResponseStateManager(responseStateManager))
237             {
238                 serializedComponentStates = responseStateManager.getComponentStateToRestore(facesContext);
239             }
240             else
241             {
242                 serializedComponentStates = responseStateManager.getState(facesContext, uiViewRoot.getViewId());
243             }
244             if (serializedComponentStates == null)
245             {
246                 log.severe("No serialized component state found in client request!");
247                 // mark UIViewRoot invalid by resetting view id
248                 uiViewRoot.setViewId(null);
249                 return;
250             }
251         }
252         else
253         {
254             Integer serverStateId = getServerStateId((Object[]) responseStateManager.getState(facesContext, uiViewRoot.getViewId()));
255 
256             Object[] stateObj = (Object[])( (serverStateId == null)? null : getSerializedViewFromServletSession(facesContext, uiViewRoot.getViewId(), serverStateId) );
257             if (stateObj == null)
258             {
259                  log.severe("No serialized view found in server session!");
260                 // mark UIViewRoot invalid by resetting view id
261                 uiViewRoot.setViewId(null);
262                 return;
263             }
264             SerializedView serializedView = new SerializedView(stateObj[0], stateObj[1]);
265             serializedComponentStates = serializedView.getState();
266             if (serializedComponentStates == null)
267             {
268                 log.severe("No serialized component state found in server session!");
269                 return;
270             }
271         }
272 
273         if (uiViewRoot.getRenderKitId() == null)
274         {
275             //Just to be sure...
276             uiViewRoot.setRenderKitId(renderKitId);
277         }
278 
279         // now ask the view root component to restore its state
280         uiViewRoot.processRestoreState(facesContext, serializedComponentStates);
281 
282         if (log.isLoggable(Level.FINEST)) log.finest("Exiting restoreComponentState");
283     }
284 
285       protected Integer getServerStateId(Object[] state)
286       {
287         if (state != null)
288         {
289             Object serverStateId = state[JSF_SEQUENCE_INDEX];
290             if (serverStateId != null)
291             {
292                 return Integer.valueOf((String) serverStateId, Character.MAX_RADIX);
293             }
294         }
295         return null;
296     }
297 
298     /**
299      * See getTreeStructureToSave.
300      */
301     @Override
302     protected UIViewRoot restoreTreeStructure(FacesContext facesContext,
303                                               String viewId,
304                                               String renderKitId)
305     {
306         if (log.isLoggable(Level.FINEST)) log.finest("Entering restoreTreeStructure");
307 
308         RenderKit rk = getRenderKitFactory().getRenderKit(facesContext, renderKitId);
309         ResponseStateManager responseStateManager = rk.getResponseStateManager();
310 
311         UIViewRoot uiViewRoot;
312         if (isSavingStateInClient(facesContext))
313         {
314             //reconstruct tree structure from request
315             Object treeStructure = responseStateManager.getTreeStructureToRestore(facesContext, viewId);
316             if (treeStructure == null)
317             {
318                 if (log.isLoggable(Level.FINE)) log.fine("Exiting restoreTreeStructure - No tree structure state found in client request");
319                 return null;
320             }
321 
322             TreeStructureManager tsm = new TreeStructureManager();
323             uiViewRoot = tsm.restoreTreeStructure(treeStructure);
324             if (log.isLoggable(Level.FINEST)) log.finest("Tree structure restored from client request");
325         }
326         else
327         {
328             //reconstruct tree structure from ServletSession
329             Integer serverStateId = getServerStateId((Object[]) responseStateManager.getState(facesContext, viewId));
330 
331             Object[] stateObj = (Object[])( (serverStateId == null)? null : getSerializedViewFromServletSession(facesContext, viewId, serverStateId) );
332             if (stateObj == null)
333             {
334                 if (log.isLoggable(Level.FINE)) log.fine("Exiting restoreTreeStructure - No serialized view found in server session!");
335                 return null;
336             }
337 
338             SerializedView serializedView = new SerializedView(stateObj[0], stateObj[1]);
339             Object treeStructure = serializedView.getStructure();
340             if (treeStructure == null)
341             {
342                 if (log.isLoggable(Level.FINE)) log.fine("Exiting restoreTreeStructure - No tree structure state found in server session, former UIViewRoot must have been transient");
343                 return null;
344             }
345 
346             TreeStructureManager tsm = new TreeStructureManager();
347             uiViewRoot = tsm.restoreTreeStructure(serializedView.getStructure());
348             if (log.isLoggable(Level.FINEST)) log.finest("Tree structure restored from server session");
349         }
350 
351         if (log.isLoggable(Level.FINEST)) log.finest("Exiting restoreTreeStructure");
352         return uiViewRoot;
353     }
354 
355     @Override
356     public UIViewRoot restoreView(FacesContext facesContext, String viewId, String renderKitId)
357     {
358         if (log.isLoggable(Level.FINEST)) log.finest("Entering restoreView - viewId: "+viewId+" ; renderKitId: "+renderKitId);
359 
360         UIViewRoot uiViewRoot = null;
361         
362         ViewDeclarationLanguage vdl = facesContext.getApplication().
363             getViewHandler().getViewDeclarationLanguage(facesContext,viewId);
364         StateManagementStrategy sms = null; 
365         if (vdl != null)
366         {
367             sms = vdl.getStateManagementStrategy(facesContext, viewId);
368         }
369         
370         if (sms != null)
371         {
372             if (log.isLoggable(Level.FINEST)) log.finest("Redirect to StateManagementStrategy: "+sms.getClass().getName());
373             
374             uiViewRoot = sms.restoreView(facesContext, viewId, renderKitId);
375         }
376         else
377         {
378             RenderKit renderKit = getRenderKitFactory().getRenderKit(facesContext, renderKitId);
379             ResponseStateManager responseStateManager = renderKit.getResponseStateManager();
380 
381             Object state;
382             if (isSavingStateInClient(facesContext))
383             {
384                 if (log.isLoggable(Level.FINEST)) log.finest("Restoring view from client");
385 
386                 state = responseStateManager.getState(facesContext, viewId);
387             }
388             else
389             {
390                 if (log.isLoggable(Level.FINEST)) log.finest("Restoring view from session");
391 
392                 Integer serverStateId = getServerStateId((Object[]) responseStateManager.getState(facesContext, viewId));
393 
394                 state = (serverStateId == null) ? null : getSerializedViewFromServletSession(facesContext, viewId, serverStateId);
395             }
396 
397             if (state != null) {
398                 Object[] stateArray = (Object[])state;
399                 TreeStructureManager tsm = new TreeStructureManager();
400                 uiViewRoot = tsm.restoreTreeStructure(stateArray[0]);
401 
402                 if (uiViewRoot != null) {
403                     facesContext.setViewRoot (uiViewRoot);
404                     uiViewRoot.processRestoreState(facesContext, stateArray[1]);
405                 }
406             }            
407         }
408         if (log.isLoggable(Level.FINEST)) log.finest("Exiting restoreView - "+viewId);
409 
410         return uiViewRoot;
411     }
412 
413     /**
414      * Wrap the original method and redirect to VDL StateManagementStrategy when
415      * necessary
416      */
417     @Override
418     public Object saveView(FacesContext facesContext)
419     {
420         UIViewRoot uiViewRoot = facesContext.getViewRoot();
421         
422         String viewId = uiViewRoot.getViewId();
423         ViewDeclarationLanguage vdl = facesContext.getApplication().
424             getViewHandler().getViewDeclarationLanguage(facesContext,viewId);
425         if (vdl != null)
426         {
427             StateManagementStrategy sms = vdl.getStateManagementStrategy(facesContext, viewId);
428             
429             if (sms != null)
430             {
431                 if (log.isLoggable(Level.FINEST)) log.finest("Calling saveView of StateManagementStrategy: "+sms.getClass().getName());
432                 
433                 return sms.saveView(facesContext);
434             }
435         }
436 
437         // In StateManagementStrategy.saveView there is a check for transient at
438         // start, but the same applies for VDL without StateManagementStrategy,
439         // so this should be checked before call parent (note that parent method
440         // does not do this check).
441         if (uiViewRoot.isTransient())
442         {
443             return null;
444         }
445 
446         return super.saveView(facesContext);
447     }
448     
449     @Override
450     public SerializedView saveSerializedView(FacesContext facesContext) throws IllegalStateException
451     {
452         if (log.isLoggable(Level.FINEST)) log.finest("Entering saveSerializedView");
453 
454         checkForDuplicateIds(facesContext, facesContext.getViewRoot(), new HashSet<String>());
455 
456         if (log.isLoggable(Level.FINEST)) log.finest("Processing saveSerializedView - Checked for duplicate Ids");
457 
458         ExternalContext externalContext = facesContext.getExternalContext();
459 
460         // SerializedView already created before within this request?
461         Object serializedView = externalContext.getRequestMap()
462                                                             .get(SERIALIZED_VIEW_REQUEST_ATTR);
463         if (serializedView == null)
464         {
465             if (log.isLoggable(Level.FINEST)) log.finest("Processing saveSerializedView - create new serialized view");
466 
467             // first call to saveSerializedView --> create SerializedView
468             Object treeStruct = getTreeStructureToSave(facesContext);
469             Object compStates = getComponentStateToSave(facesContext);
470             serializedView = new Object[] {treeStruct, compStates};
471             externalContext.getRequestMap().put(SERIALIZED_VIEW_REQUEST_ATTR,
472                                                 serializedView);
473 
474             if (log.isLoggable(Level.FINEST)) log.finest("Processing saveSerializedView - new serialized view created");
475         }
476 
477         Object[] serializedViewArray = (Object[]) serializedView;
478 
479         if (!isSavingStateInClient(facesContext))
480         {
481             if (log.isLoggable(Level.FINEST)) log.finest("Processing saveSerializedView - server-side state saving - save state");
482             //save state in server session
483             saveSerializedViewInServletSession(facesContext, serializedView);
484 
485             if (log.isLoggable(Level.FINEST)) log.finest("Exiting saveSerializedView - server-side state saving - saved state");
486             return new SerializedView(serializedViewArray[0], new Object[0]);
487         }
488 
489         if (log.isLoggable(Level.FINEST)) log.finest("Exiting saveSerializedView - client-side state saving");
490 
491         return new SerializedView(serializedViewArray[0], serializedViewArray[1]);
492     }
493 
494     private static void checkForDuplicateIds(FacesContext context,
495                                              UIComponent component,
496                                              Set<String> ids)
497     {
498         String id = component.getId();
499         if (id != null && !ids.add(id))
500         {
501             throw new IllegalStateException("Client-id : " + id +
502                                             " is duplicated in the faces tree. Component : " + 
503                                             component.getClientId(context)+", path: " +
504                                             getPathToComponent(component));
505         }
506         
507         if (component instanceof NamingContainer)
508         {
509             ids = new HashSet<String>();
510         }
511         
512         Iterator<UIComponent> it = component.getFacetsAndChildren();
513         while (it.hasNext())
514         {
515             UIComponent kid = it.next();
516             checkForDuplicateIds(context, kid, ids);
517         }
518     }
519 
520     private static String getPathToComponent(UIComponent component)
521     {
522         StringBuffer buf = new StringBuffer();
523 
524         if(component == null)
525         {
526             buf.append("{Component-Path : ");
527             buf.append("[null]}");
528             return buf.toString();
529         }
530 
531         getPathToComponent(component,buf);
532 
533         buf.insert(0,"{Component-Path : ");
534         buf.append("}");
535 
536         return buf.toString();
537     }
538 
539     private static void getPathToComponent(UIComponent component, StringBuffer buf)
540     {
541         if(component == null)
542             return;
543 
544         StringBuffer intBuf = new StringBuffer();
545 
546         intBuf.append("[Class: ");
547         intBuf.append(component.getClass().getName());
548         if(component instanceof UIViewRoot)
549         {
550             intBuf.append(",ViewId: ");
551             intBuf.append(((UIViewRoot) component).getViewId());
552         }
553         else
554         {
555             intBuf.append(",Id: ");
556             intBuf.append(component.getId());
557         }
558         intBuf.append("]");
559 
560         buf.insert(0,intBuf.toString());
561 
562         getPathToComponent(component.getParent(),buf);
563     }
564 
565     @Override
566     public void writeState(FacesContext facesContext,
567                            SerializedView serializedView) throws IOException
568     {
569         if (log.isLoggable(Level.FINEST)) log.finest("Entering writeState");
570 
571         UIViewRoot uiViewRoot = facesContext.getViewRoot();
572         //save state in response (client)
573         RenderKit renderKit = getRenderKitFactory().getRenderKit(facesContext, uiViewRoot.getRenderKitId());
574         ResponseStateManager responseStateManager = renderKit.getResponseStateManager();
575 
576         if (isLegacyResponseStateManager(responseStateManager))
577         {
578             responseStateManager.writeState(facesContext, serializedView);
579         }
580         else if (!isSavingStateInClient(facesContext))
581         {
582             Object[] state = new Object[2];
583             state[JSF_SEQUENCE_INDEX] = Integer.toString(getNextViewSequence(facesContext), Character.MAX_RADIX);
584             responseStateManager.writeState(facesContext, state);
585         }
586         else
587         {
588             Object[] state = new Object[2];
589             state[0] = serializedView.getStructure();
590             state[1] = serializedView.getState();
591             responseStateManager.writeState(facesContext, state);
592         }
593 
594         if (log.isLoggable(Level.FINEST)) log.finest("Exiting writeState");
595 
596     }
597 
598     @Override
599     public String getViewState(FacesContext facesContext)
600     {
601         UIViewRoot uiViewRoot = facesContext.getViewRoot();
602         String viewId = uiViewRoot.getViewId();
603         ViewDeclarationLanguage vdl = facesContext.getApplication().getViewHandler().getViewDeclarationLanguage(facesContext,viewId);
604         if (vdl != null)
605         {
606             StateManagementStrategy sms = vdl.getStateManagementStrategy(facesContext, viewId);
607             
608             if (sms != null)
609             {
610                 if (log.isLoggable(Level.FINEST)) log.finest("Calling saveView of StateManagementStrategy from getViewState: "+sms.getClass().getName());
611                 
612                 return facesContext.getRenderKit().getResponseStateManager().getViewState(facesContext, saveView(facesContext));
613             }
614         }
615         Object[] savedState = (Object[]) saveView(facesContext);
616         
617         if (!isSavingStateInClient(facesContext))
618         {
619             Object[] state = new Object[2];
620             state[JSF_SEQUENCE_INDEX] = Integer.toString(getNextViewSequence(facesContext), Character.MAX_RADIX);
621             return facesContext.getRenderKit().getResponseStateManager().getViewState(facesContext, state);
622         }
623         else
624         {
625             return facesContext.getRenderKit().getResponseStateManager().getViewState(facesContext, savedState);
626         }
627     }
628 
629     /**
630      * MyFaces extension
631      * @param facesContext
632      * @param serializedView
633      * @throws IOException
634      */
635     @Override
636     public void writeStateAsUrlParams(FacesContext facesContext,
637                                       SerializedView serializedView) throws IOException
638     {
639         if (log.isLoggable(Level.FINEST)) log.finest("Entering writeStateAsUrlParams");
640 
641         if (isSavingStateInClient(facesContext))
642         {
643             if (log.isLoggable(Level.FINEST)) log.finest("Processing writeStateAsUrlParams - client-side state saving writing state");
644 
645             UIViewRoot uiViewRoot = facesContext.getViewRoot();
646             //save state in response (client)
647             RenderKit renderKit = getRenderKitFactory().getRenderKit(facesContext, uiViewRoot.getRenderKitId());
648             ResponseStateManager responseStateManager = renderKit.getResponseStateManager();
649             if (responseStateManager instanceof MyfacesResponseStateManager)
650             {
651                 ((MyfacesResponseStateManager)responseStateManager).writeStateAsUrlParams(facesContext,
652                                                                                           serializedView);
653             }
654             else
655             {
656                 log.severe("ResponseStateManager of render kit " + uiViewRoot.getRenderKitId() + " is no MyfacesResponseStateManager and does not support saving state in url parameters.");
657             }
658         }
659 
660         if (log.isLoggable(Level.FINEST)) log.finest("Exiting writeStateAsUrlParams");
661     }
662 
663     //helpers
664 
665     protected RenderKitFactory getRenderKitFactory()
666     {
667         if (_renderKitFactory == null)
668         {
669             _renderKitFactory = (RenderKitFactory)FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
670         }
671         return _renderKitFactory;
672     }
673 
674     protected void saveSerializedViewInServletSession(FacesContext context,
675                                                       Object serializedView)
676     {
677         Map<String, Object> sessionMap = context.getExternalContext().getSessionMap();
678         SerializedViewCollection viewCollection = (SerializedViewCollection) sessionMap
679                 .get(SERIALIZED_VIEW_SESSION_ATTR);
680         if (viewCollection == null)
681         {
682             viewCollection = new SerializedViewCollection();
683             sessionMap.put(SERIALIZED_VIEW_SESSION_ATTR, viewCollection);
684         }
685         viewCollection.add(context, serializeView(context, serializedView));
686         // replace the value to notify the container about the change
687         sessionMap.put(SERIALIZED_VIEW_SESSION_ATTR, viewCollection);
688     }
689 
690     protected Object getSerializedViewFromServletSession(FacesContext context, String viewId, Integer sequence)
691     {
692         ExternalContext externalContext = context.getExternalContext();
693         Map<String, Object> requestMap = externalContext.getRequestMap();
694         Object serializedView = null;
695         if (requestMap.containsKey(RESTORED_SERIALIZED_VIEW_REQUEST_ATTR))
696         {
697             serializedView = requestMap.get(RESTORED_SERIALIZED_VIEW_REQUEST_ATTR);
698         }
699         else
700         {
701             SerializedViewCollection viewCollection = (SerializedViewCollection) externalContext
702                     .getSessionMap().get(SERIALIZED_VIEW_SESSION_ATTR);
703             if (viewCollection != null)
704             {
705                 /*
706                 String sequenceStr = externalContext.getRequestParameterMap().get(
707                        RendererUtils.SEQUENCE_PARAM);
708                 Integer sequence = null;
709                 if (sequenceStr == null)
710                 {
711                     // use latest sequence
712                     Map map = externalContext.getSessionMap();
713                     sequence = (Integer) map.get(RendererUtils.SEQUENCE_PARAM);
714                 }
715                 else
716                 {
717                     sequence = new Integer(sequenceStr);
718                 }
719                 */
720                 if (sequence != null)
721                 {
722                     Object state = viewCollection.get(sequence, viewId);
723                     if (state != null)
724                     {
725                         serializedView = deserializeView(state);
726                     }
727                 }
728             }
729             requestMap.put(RESTORED_SERIALIZED_VIEW_REQUEST_ATTR, serializedView);
730             nextViewSequence(context);
731         }
732         return serializedView;
733     }
734 
735     protected int getNextViewSequence(FacesContext context)
736     {
737         ExternalContext externalContext = context.getExternalContext();
738 
739         if (!externalContext.getRequestMap().containsKey(RendererUtils.SEQUENCE_PARAM))
740         {
741             nextViewSequence(context);
742         }
743 
744         Integer sequence = (Integer) externalContext.getRequestMap().get(RendererUtils.SEQUENCE_PARAM);
745         return sequence.intValue();
746     }
747 
748     protected void nextViewSequence(FacesContext facescontext)
749     {
750         ExternalContext externalContext = facescontext.getExternalContext();
751         Object sessionObj = externalContext.getSession(true);
752         synchronized(sessionObj) // synchronized to increase sequence if multiple requests
753                                  // are handled at the same time for the session
754         {
755             Map<String, Object> map = externalContext.getSessionMap();
756             Integer sequence = (Integer) map.get(RendererUtils.SEQUENCE_PARAM);
757             if(sequence == null || sequence.intValue() == Integer.MAX_VALUE)
758             {
759                 sequence = Integer.valueOf(1);
760             }
761             else
762             {
763                 sequence = Integer.valueOf(sequence.intValue() + 1);
764             }
765             map.put(RendererUtils.SEQUENCE_PARAM, sequence);
766             externalContext.getRequestMap().put(RendererUtils.SEQUENCE_PARAM, sequence);
767         }
768     }
769 
770     protected Object serializeView(FacesContext context, Object serializedView)
771     {
772         if (log.isLoggable(Level.FINEST)) log.finest("Entering serializeView");
773 
774         if(isSerializeStateInSession(context))
775         {
776             if (log.isLoggable(Level.FINEST)) log.finest("Processing serializeView - serialize state in session");
777 
778             ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
779             try
780             {
781                 OutputStream os = baos;
782                 if(isCompressStateInSession(context))
783                 {
784                     if (log.isLoggable(Level.FINEST)) log.finest("Processing serializeView - serialize compressed");
785 
786                     os.write(COMPRESSED_FLAG);
787                     os = new GZIPOutputStream(os, 1024);
788                 }
789                 else
790                 {
791                     if (log.isLoggable(Level.FINEST)) log.finest("Processing serializeView - serialize uncompressed");
792 
793                     os.write(UNCOMPRESSED_FLAG);
794                 }
795 
796                 Object[] stateArray = (Object[]) serializedView;
797 
798                 ObjectOutputStream out = new ObjectOutputStream(os);
799                 out.writeObject(stateArray[0]);
800                 out.writeObject(stateArray[1]);
801                 out.close();
802                 baos.close();
803 
804                 if (log.isLoggable(Level.FINEST)) log.finest("Exiting serializeView - serialized. Bytes : "+baos.size());
805                 return baos.toByteArray();
806             }
807             catch (IOException e)
808             {
809                 log.log(Level.SEVERE, "Exiting serializeView - Could not serialize state: " + e.getMessage(), e);
810                 return null;
811             }
812         }
813 
814 
815         if (log.isLoggable(Level.FINEST))
816             log.finest("Exiting serializeView - do not serialize state in session.");
817 
818         return serializedView;
819 
820     }
821 
822     /**
823      * Reads the value of the <code>org.apache.myfaces.SERIALIZE_STATE_IN_SESSION</code> context parameter.
824      * @see SERIALIZE_STATE_IN_SESSION_PARAM
825      * @param context <code>FacesContext</code> for the request we are processing.
826      * @return boolean true, if the server state should be serialized in the session
827      */
828     protected boolean isSerializeStateInSession(FacesContext context)
829     {
830         String value = context.getExternalContext().getInitParameter(
831                 SERIALIZE_STATE_IN_SESSION_PARAM);
832         boolean serialize = DEFAULT_SERIALIZE_STATE_IN_SESSION;
833         if (value != null)
834         {
835            serialize = Boolean.valueOf(value);
836         }
837         return serialize;
838     }
839 
840     /**
841      * Reads the value of the <code>org.apache.myfaces.COMPRESS_STATE_IN_SESSION</code> context parameter.
842      * @see COMPRESS_SERVER_STATE_PARAM
843      * @param context <code>FacesContext</code> for the request we are processing.
844      * @return boolean true, if the server state steam should be compressed
845      */
846     protected boolean isCompressStateInSession(FacesContext context)
847     {
848         String value = context.getExternalContext().getInitParameter(
849                 COMPRESS_SERVER_STATE_PARAM);
850         boolean compress = DEFAULT_COMPRESS_SERVER_STATE_PARAM;
851         if (value != null)
852         {
853            compress = Boolean.valueOf(value);
854         }
855         return compress;
856     }
857 
858     protected Object deserializeView(Object state)
859     {
860         if (log.isLoggable(Level.FINEST)) log.finest("Entering deserializeView");
861 
862         if(state instanceof byte[])
863         {
864             if (log.isLoggable(Level.FINEST)) log.finest("Processing deserializeView - deserializing serialized state. Bytes : "+((byte[]) state).length);
865 
866             try
867             {
868                 ByteArrayInputStream bais = new ByteArrayInputStream((byte[]) state);
869                 InputStream is = bais;
870                 if(is.read() == COMPRESSED_FLAG)
871                 {
872                     is = new GZIPInputStream(is);
873                 }
874                 ObjectInputStream ois = null;
875                 try
876                 {
877                     final ObjectInputStream in = new MyFacesObjectInputStream(is);
878                     ois = in;
879                     Object object = null;
880                     if (System.getSecurityManager() != null) 
881                     {
882                         object = AccessController.doPrivileged(new PrivilegedExceptionAction<Object []>() 
883                         {
884                             public Object[] run() throws PrivilegedActionException, IOException, ClassNotFoundException
885                             {
886                                 return new Object[] {in.readObject(), in.readObject()};                                    
887                             }
888                         });
889                     }
890                     else
891                     {
892                         object = new Object[] {in.readObject(), in.readObject()};
893                     }
894                     return object;
895                 }
896                 finally
897                 {
898                     if (ois != null)
899                     {
900                         ois.close();
901                         ois = null;
902                     }
903                 }
904             }
905             catch (PrivilegedActionException e) 
906             {
907                 log.log(Level.SEVERE, "Exiting deserializeView - Could not deserialize state: " + e.getMessage(), e);
908                 return null;
909             }
910             catch (IOException e)
911             {
912                 log.log(Level.SEVERE, "Exiting deserializeView - Could not deserialize state: " + e.getMessage(), e);
913                 return null;
914             }
915             catch (ClassNotFoundException e)
916             {
917                 log.log(Level.SEVERE, "Exiting deserializeView - Could not deserialize state: " + e.getMessage(), e);
918                 return null;
919             }
920         }
921         else if (state instanceof Object[])
922         {
923             if (log.isLoggable(Level.FINEST)) log.finest("Exiting deserializeView - state not serialized.");
924 
925             return state;
926         }
927         else if(state == null)
928         {
929             log.severe("Exiting deserializeView - this method should not be called with a null-state.");
930             return null;
931         }
932         else
933         {
934             log.severe("Exiting deserializeView - this method should not be called with a state of type : "+state.getClass());
935             return null;
936         }
937     }
938 
939     private boolean isLegacyResponseStateManager(ResponseStateManager instance) {
940 
941         Method[] methods = instance.getClass().getMethods();
942         for (Method m : methods)
943         {
944             if (m.getName().equals("getState") &&
945                     Arrays.equals(m.getParameterTypes(),new Class[] {FacesContext.class, String.class}))
946             {
947                  return false;
948             }
949         }
950 
951         return true;
952     }
953 
954     protected static class SerializedViewCollection implements Serializable
955     {
956         private static final long serialVersionUID = -3734849062185115847L;
957 
958         private final List<Object> _keys = new ArrayList<Object>(DEFAULT_NUMBER_OF_VIEWS_IN_SESSION);
959         private final Map<Object, Object> _serializedViews = new HashMap<Object, Object>();
960 
961         // old views will be hold as soft references which will be removed by
962         // the garbage collector if free memory is low
963         private transient Map<Object, Object> _oldSerializedViews = null;
964 
965         public synchronized void add(FacesContext context, Object state)
966         {
967             Object key = new SerializedViewKey(context);
968             _serializedViews.put(key, state);
969 
970             while (_keys.remove(key));
971             _keys.add(key);
972 
973             int views = getNumberOfViewsInSession(context);
974             while (_keys.size() > views)
975             {
976                 key = _keys.remove(0);
977                 Object oldView = _serializedViews.remove(key);
978                 if (oldView != null && 
979                     !CACHE_OLD_VIEWS_IN_SESSION_MODE_OFF.equals(getCacheOldViewsInSessionMode(context))) 
980                 {
981                     getOldSerializedViewsMap().put(key, oldView);
982                 }
983             }
984         }
985 
986         /**
987          * Reads the amount (default = 20) of views to be stored in session.
988          * @see NUMBER_OF_VIEWS_IN_SESSION_PARAM
989          * @param context FacesContext for the current request, we are processing
990          * @return Number vf views stored in the session
991          */
992         protected int getNumberOfViewsInSession(FacesContext context)
993         {
994             String value = context.getExternalContext().getInitParameter(
995                     NUMBER_OF_VIEWS_IN_SESSION_PARAM);
996             int views = DEFAULT_NUMBER_OF_VIEWS_IN_SESSION;
997             if (value != null)
998             {
999                 try
1000                 {
1001                     views = Integer.parseInt(value);
1002                     if (views <= 0)
1003                     {
1004                         log.severe("Configured value for " + NUMBER_OF_VIEWS_IN_SESSION_PARAM
1005                                   + " is not valid, must be an value > 0, using default value ("
1006                                   + DEFAULT_NUMBER_OF_VIEWS_IN_SESSION);
1007                         views = DEFAULT_NUMBER_OF_VIEWS_IN_SESSION;
1008                     }
1009                 }
1010                 catch (Throwable e)
1011                 {
1012                     log.log(Level.SEVERE, "Error determining the value for " + NUMBER_OF_VIEWS_IN_SESSION_PARAM
1013                               + ", expected an integer value > 0, using default value ("
1014                               + DEFAULT_NUMBER_OF_VIEWS_IN_SESSION + "): " + e.getMessage(), e);
1015                 }
1016             }
1017             return views;
1018         }
1019 
1020         /**
1021          * @return old serialized views map
1022          */
1023         @SuppressWarnings("unchecked")
1024         protected Map<Object, Object> getOldSerializedViewsMap()
1025         {
1026             FacesContext context = FacesContext.getCurrentInstance();
1027             if (_oldSerializedViews == null && context != null)
1028             {
1029                 String cacheMode = getCacheOldViewsInSessionMode(context); 
1030                 if (CACHE_OLD_VIEWS_IN_SESSION_MODE_WEAK.equals(cacheMode))
1031                 {
1032                     _oldSerializedViews = new ReferenceMap(AbstractReferenceMap.WEAK, AbstractReferenceMap.WEAK, true);
1033                 }
1034                 else if (CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT_WEAK.equals(cacheMode))
1035                 {
1036                     _oldSerializedViews = new ReferenceMap(AbstractReferenceMap.SOFT, AbstractReferenceMap.WEAK, true);
1037                 }
1038                 else if (CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT.equals(cacheMode))
1039                 {
1040                     _oldSerializedViews = new ReferenceMap(AbstractReferenceMap.SOFT, AbstractReferenceMap.SOFT, true);
1041                 }
1042                 else if (CACHE_OLD_VIEWS_IN_SESSION_MODE_HARD_SOFT.equals(cacheMode))
1043                 {
1044                     _oldSerializedViews = new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.SOFT);
1045                 }
1046             }
1047             
1048             return _oldSerializedViews;
1049         }
1050         
1051         /**
1052          * Reads the value of the <code>org.apache.myfaces.CACHE_OLD_VIEWS_IN_SESSION_MODE</code> context parameter.
1053          * 
1054          * @since 1.2.5
1055          * @param context
1056          * @return constant indicating caching mode
1057          * @see CACHE_OLD_VIEWS_IN_SESSION_MODE
1058          */
1059         protected String getCacheOldViewsInSessionMode(FacesContext context)
1060         {
1061             String value = context.getExternalContext().getInitParameter(
1062                     CACHE_OLD_VIEWS_IN_SESSION_MODE);
1063             if (value == null)
1064             {
1065                 return CACHE_OLD_VIEWS_IN_SESSION_MODE_OFF;
1066             }
1067             else if (value.equalsIgnoreCase(CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT))
1068             {
1069                 return CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT;
1070             }
1071             else if (value.equalsIgnoreCase(CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT_WEAK))
1072             {
1073                 return CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT_WEAK;
1074             }            
1075             else if (value.equalsIgnoreCase(CACHE_OLD_VIEWS_IN_SESSION_MODE_WEAK))
1076             {
1077                 return CACHE_OLD_VIEWS_IN_SESSION_MODE_WEAK;
1078             }
1079             else if (value.equalsIgnoreCase(CACHE_OLD_VIEWS_IN_SESSION_MODE_HARD_SOFT))
1080             {
1081                 return CACHE_OLD_VIEWS_IN_SESSION_MODE_HARD_SOFT;
1082             }
1083             else
1084             {
1085                 return CACHE_OLD_VIEWS_IN_SESSION_MODE_OFF;
1086             }
1087         }
1088         
1089         public Object get(Integer sequence, String viewId)
1090         {
1091             Object key = new SerializedViewKey(viewId, sequence);
1092             Object value = _serializedViews.get(key);
1093             if (value == null)
1094             {
1095                 Map<Object,Object> oldSerializedViewMap = getOldSerializedViewsMap();
1096                 if (oldSerializedViewMap != null)
1097                 {
1098                     value = oldSerializedViewMap.get(key);
1099                 }
1100             }
1101             return value;
1102         }
1103     }
1104 
1105     protected static class SerializedViewKey implements Serializable
1106     {
1107         private static final long serialVersionUID = -1170697124386063642L;
1108 
1109         private final String _viewId;
1110         private final Integer _sequenceId;
1111 
1112         public SerializedViewKey(String viewId, Integer sequence)
1113         {
1114             _sequenceId = sequence;
1115             _viewId = viewId;
1116         }
1117 
1118         public SerializedViewKey(FacesContext context)
1119         {
1120             _sequenceId = RendererUtils.getViewSequence(context);
1121             _viewId = context.getViewRoot().getViewId();
1122         }
1123 
1124         @Override
1125         public int hashCode()
1126         {
1127             final int PRIME = 31;
1128             int result = 1;
1129             result = PRIME * result + ((_sequenceId == null) ? 0 : _sequenceId.hashCode());
1130             result = PRIME * result + ((_viewId == null) ? 0 : _viewId.hashCode());
1131             return result;
1132         }
1133 
1134         @Override
1135         public boolean equals(Object obj)
1136         {
1137             if (this == obj)
1138                 return true;
1139             if (obj == null)
1140                 return false;
1141             if (getClass() != obj.getClass())
1142                 return false;
1143             final SerializedViewKey other = (SerializedViewKey) obj;
1144             if (_sequenceId == null)
1145             {
1146                 if (other._sequenceId != null)
1147                     return false;
1148             }
1149             else if (!_sequenceId.equals(other._sequenceId))
1150                 return false;
1151             if (_viewId == null)
1152             {
1153                 if (other._viewId != null)
1154                     return false;
1155             }
1156             else if (!_viewId.equals(other._viewId))
1157                 return false;
1158             return true;
1159         }
1160 
1161     }
1162 }