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