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