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.ReferenceMap;
22  import org.apache.commons.collections.map.AbstractReferenceMap;
23  import org.apache.commons.lang.builder.EqualsBuilder;
24  import org.apache.commons.lang.builder.HashCodeBuilder;
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.apache.myfaces.application.MyfacesStateManager;
28  import org.apache.myfaces.application.TreeStructureManager;
29  import org.apache.myfaces.renderkit.MyfacesResponseStateManager;
30  import org.apache.myfaces.shared_impl.renderkit.ViewSequenceUtils;
31  import org.apache.myfaces.shared_impl.util.MyFacesObjectInputStream;
32  
33  import javax.faces.FactoryFinder;
34  import javax.faces.application.StateManager;
35  import javax.faces.component.NamingContainer;
36  import javax.faces.component.UIComponent;
37  import javax.faces.component.UIViewRoot;
38  import javax.faces.context.ExternalContext;
39  import javax.faces.context.FacesContext;
40  import javax.faces.render.RenderKit;
41  import javax.faces.render.RenderKitFactory;
42  import javax.faces.render.ResponseStateManager;
43  import java.io.*;
44  import java.util.*;
45  import java.util.zip.GZIPInputStream;
46  import java.util.zip.GZIPOutputStream;
47  
48  /**
49   * Default StateManager implementation for use when views are defined
50   * via tags in JSP pages.
51   *
52   * @author Thomas Spiegl (latest modification by $Author: lu4242 $)
53   * @author Manfred Geiler
54   * @version $Revision: 694453 $ $Date: 2008-09-11 15:12:45 -0500 (Thu, 11 Sep 2008) $
55   */
56  public class JspStateManagerImpl
57      extends MyfacesStateManager {
58      private static final Log log = LogFactory.getLog(JspStateManagerImpl.class);
59      private static final String SERIALIZED_VIEW_SESSION_ATTR
60          = JspStateManagerImpl.class.getName() + ".SERIALIZED_VIEW";
61      private static final String SERIALIZED_VIEW_REQUEST_ATTR
62          = JspStateManagerImpl.class.getName() + ".SERIALIZED_VIEW";
63      private static final String RESTORED_SERIALIZED_VIEW_REQUEST_ATTR
64          = JspStateManagerImpl.class.getName() + ".RESTORED_SERIALIZED_VIEW";
65  
66      /**
67       * Only applicable if state saving method is "server" (= default).
68       * Defines the amount (default = 20) of the latest views are stored in session.
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      private static final String NUMBER_OF_VIEWS_IN_SESSION_PARAM = "org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION";
75  
76      /**
77       * Default value for <code>org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION</code> context parameter.
78       */
79      /**
80       * Default value for <code>org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION</code> context parameter.
81       */
82      private static final int DEFAULT_NUMBER_OF_VIEWS_IN_SESSION = 20;
83  
84      /**
85       * Only applicable if state saving method is "server" (= default).
86       * If <code>true</code> (default) the state will be serialized to a byte stream before it is written to the session.
87       * If <code>false</code> the state will not be serialized to a byte stream.
88       */
89      private static final String SERIALIZE_STATE_IN_SESSION_PARAM = "org.apache.myfaces.SERIALIZE_STATE_IN_SESSION";
90  
91      /**
92       * 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).
93       * If <code>true</code> (default) the serialized state will be compressed before it is written to the session.
94       * If <code>false</code> the state will not be compressed.
95       */
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     private static final String CACHE_OLD_VIEWS_IN_SESSION_MODE = "org.apache.myfaces.CACHE_OLD_VIEWS_IN_SESSION_MODE";
132     
133     /**
134      * This option uses an hard-soft ReferenceMap, but it could cause a 
135      * memory leak, because the keys are not removed by any method
136      * (MYFACES-1660). So use with caution.
137      */
138     private static final String CACHE_OLD_VIEWS_IN_SESSION_MODE_HARD_SOFT = "hard-soft";
139     
140     private static final String CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT = "soft";
141     
142     private static final String CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT_WEAK = "soft-weak";
143     
144     private static final String CACHE_OLD_VIEWS_IN_SESSION_MODE_WEAK = "weak";
145     
146     private static final String CACHE_OLD_VIEWS_IN_SESSION_MODE_OFF = "off";
147 
148     private static final int UNCOMPRESSED_FLAG = 0;
149     private static final int COMPRESSED_FLAG = 1;
150 
151     private RenderKitFactory _renderKitFactory = null;
152 
153     public JspStateManagerImpl() {
154         if (log.isTraceEnabled()) log.trace("New JspStateManagerImpl instance created");
155     }
156 
157     protected Object getComponentStateToSave(FacesContext facesContext) {
158         if (log.isTraceEnabled()) log.trace("Entering getComponentStateToSave");
159 
160         UIViewRoot viewRoot = facesContext.getViewRoot();
161         if (viewRoot.isTransient()) {
162             return null;
163         }
164 
165         Object serializedComponentStates = viewRoot.processSaveState(facesContext);
166         //Locale is a state attribute of UIViewRoot and need not be saved explicitly
167         if (log.isTraceEnabled()) log.trace("Exiting getComponentStateToSave");
168         return serializedComponentStates;
169     }
170 
171     /**
172      * Return an object which contains info about the UIComponent type
173      * of each node in the view tree. This allows an identical UIComponent
174      * tree to be recreated later, though all the components will have
175      * just default values for their members.
176      */
177     protected Object getTreeStructureToSave(FacesContext facesContext) {
178         if (log.isTraceEnabled()) log.trace("Entering getTreeStructureToSave");
179         UIViewRoot viewRoot = facesContext.getViewRoot();
180         if (viewRoot.isTransient()) {
181             return null;
182         }
183         TreeStructureManager tsm = new TreeStructureManager();
184         Object retVal = tsm.buildTreeStructureToSave(viewRoot);
185         if (log.isTraceEnabled()) log.trace("Exiting getTreeStructureToSave");
186         return retVal;
187     }
188 
189     /**
190      * Given a tree of UIComponent objects created the default constructor
191      * for each node, retrieve saved state info (from either the client or
192      * the server) and walk the tree restoring the members of each node
193      * from the saved state information.
194      */
195     protected void restoreComponentState(FacesContext facesContext,
196                                          UIViewRoot uiViewRoot,
197                                          String renderKitId) {
198         if (log.isTraceEnabled()) log.trace("Entering restoreComponentState");
199 
200         //===========================================
201         // first, locate the saved state information
202         //===========================================
203 
204         Object serializedComponentStates;
205         if (isSavingStateInClient(facesContext)) {
206             RenderKit renderKit = getRenderKitFactory().getRenderKit(facesContext, renderKitId);
207             ResponseStateManager responseStateManager = renderKit.getResponseStateManager();
208             serializedComponentStates = responseStateManager.getComponentStateToRestore(facesContext);
209             if (serializedComponentStates == null) {
210                 log.error("No serialized component state found in client request!");
211                 // mark UIViewRoot invalid by resetting view id
212                 uiViewRoot.setViewId(null);
213                 return;
214             }
215         }
216         else {
217             String viewId = uiViewRoot.getViewId();
218             String sequenceStr = getSequenceString(facesContext, renderKitId, viewId);
219             SerializedView serializedView = getSerializedViewFromServletSession(facesContext,
220                                                                                 viewId,
221                                                                                 sequenceStr);
222             if (serializedView == null) {
223                 log.error("No serialized view found in server session!");
224                 // mark UIViewRoot invalid by resetting view id
225                 uiViewRoot.setViewId(null);
226                 return;
227             }
228             serializedComponentStates = serializedView.getState();
229             if (serializedComponentStates == null) {
230                 log.error("No serialized component state found in server session!");
231                 return;
232             }
233         }
234 
235         if (uiViewRoot.getRenderKitId() == null) {
236             //Just to be sure...
237             uiViewRoot.setRenderKitId(renderKitId);
238         }
239 
240         // now ask the view root component to restore its state
241         uiViewRoot.processRestoreState(facesContext, serializedComponentStates);
242 
243         if (log.isTraceEnabled()) log.trace("Exiting restoreComponentState");
244     }
245 
246     /**
247      * See getTreeStructureToSave.
248      */
249     protected UIViewRoot restoreTreeStructure(FacesContext facesContext,
250                                               String viewId,
251                                               String renderKitId) {
252         if (log.isTraceEnabled()) log.trace("Entering restoreTreeStructure");
253 
254         UIViewRoot uiViewRoot;
255         if (isSavingStateInClient(facesContext)) {
256             //reconstruct tree structure from request
257             RenderKit rk = getRenderKitFactory().getRenderKit(facesContext, renderKitId);
258             ResponseStateManager responseStateManager = rk.getResponseStateManager();
259             Object treeStructure = responseStateManager.getTreeStructureToRestore(facesContext, viewId);
260             if (treeStructure == null) {
261                 if (log.isDebugEnabled())
262                     log.debug("Exiting restoreTreeStructure - No tree structure state found in client request");
263                 return null;
264             }
265 
266             TreeStructureManager tsm = new TreeStructureManager();
267             uiViewRoot = tsm.restoreTreeStructure((TreeStructureManager.TreeStructComponent) treeStructure);
268             if (log.isTraceEnabled()) log.trace("Tree structure restored from client request");
269         }
270         else {
271             String sequenceStr = getSequenceString(facesContext, renderKitId, viewId);
272             //reconstruct tree structure from ServletSession
273             SerializedView serializedView = getSerializedViewFromServletSession(facesContext,
274                                                                                 viewId,
275                                                                                 sequenceStr);
276             if (serializedView == null) {
277                 if (log.isDebugEnabled())
278                     log.debug("Exiting restoreTreeStructure - No serialized view found in server session!");
279                 return null;
280             }
281 
282             Object treeStructure = serializedView.getStructure();
283             if (treeStructure == null) {
284                 if (log.isDebugEnabled())
285                     log.debug("Exiting restoreTreeStructure - No tree structure state found in server session, former UIViewRoot must have been transient");
286                 return null;
287             }
288 
289             TreeStructureManager tsm = new TreeStructureManager();
290             uiViewRoot = tsm.restoreTreeStructure((TreeStructureManager.TreeStructComponent) serializedView.getStructure());
291             if (log.isTraceEnabled()) log.trace("Tree structure restored from server session");
292         }
293 
294         if (log.isTraceEnabled()) log.trace("Exiting restoreTreeStructure");
295         return uiViewRoot;
296     }
297 
298     private String getSequenceString(FacesContext facesContext, String renderKitId, String viewId) {
299         RenderKit rk = getRenderKitFactory().getRenderKit(facesContext, renderKitId);
300 
301         if(rk==null) //first access of the renderkit in a typical application - at this point, renderkit needs to be available. if it isn't we'll throw an exception
302             throw new NullPointerException("RenderKit for renderKitId : "+renderKitId + " on viewId : " + viewId + " could not be retrieved. Please specify a valid renderkit-id.");
303 
304         ResponseStateManager responseStateManager = rk.getResponseStateManager();
305         String sequenceStr = (String) responseStateManager.getTreeStructureToRestore(facesContext, viewId);
306         return sequenceStr;
307     }
308 
309     public UIViewRoot restoreView(FacesContext facescontext, String viewId, String renderKitId) {
310         if (log.isTraceEnabled()) log.trace("Entering restoreView");
311 
312         UIViewRoot uiViewRoot = restoreTreeStructure(facescontext, viewId, renderKitId);
313         if (uiViewRoot != null) {
314             uiViewRoot.setViewId(viewId);
315             restoreComponentState(facescontext, uiViewRoot, renderKitId);
316             String restoredViewId = uiViewRoot.getViewId();
317             if (restoredViewId == null || !(restoredViewId.equals(viewId))) {
318                 if (log.isTraceEnabled()) log.trace("Exiting restoreView - restored view is null.");
319                 return null;
320             }
321         }
322 
323         if (log.isTraceEnabled()) log.trace("Exiting restoreView");
324 
325         return uiViewRoot;
326     }
327 
328     public SerializedView saveSerializedView(FacesContext facesContext) throws IllegalStateException {
329         if (log.isTraceEnabled()) log.trace("Entering saveSerializedView");
330 
331         checkForDuplicateIds(facesContext, facesContext.getViewRoot(), new HashSet());
332 
333         if (log.isTraceEnabled()) log.trace("Processing saveSerializedView - Checked for duplicate Ids");
334 
335         ExternalContext externalContext = facesContext.getExternalContext();
336 
337         // SerializedView already created before within this request?
338         SerializedView serializedView = (SerializedView) externalContext.getRequestMap()
339             .get(SERIALIZED_VIEW_REQUEST_ATTR);
340         if (serializedView == null) {
341             if (log.isTraceEnabled()) log.trace("Processing saveSerializedView - create new serialized view");
342 
343             // first call to saveSerializedView --> create SerializedView
344             Object treeStruct = getTreeStructureToSave(facesContext);
345             Object compStates = getComponentStateToSave(facesContext);
346             serializedView = new StateManager.SerializedView(treeStruct, compStates);
347             externalContext.getRequestMap().put(SERIALIZED_VIEW_REQUEST_ATTR,
348                                                 serializedView);
349 
350             if (log.isTraceEnabled()) log.trace("Processing saveSerializedView - new serialized view created");
351         }
352 
353         if (!isSavingStateInClient(facesContext)) {
354             if (log.isTraceEnabled())
355                 log.trace("Processing saveSerializedView - server-side state saving - save state");
356             //save state in server session
357             saveSerializedViewInServletSession(facesContext, serializedView);
358 
359             if (log.isTraceEnabled()) log.trace("Exiting saveSerializedView - server-side state saving - saved state");
360             Integer sequence = ViewSequenceUtils.getViewSequence(facesContext);
361             return new SerializedView(sequence.toString(), null);
362         }
363 
364         if (log.isTraceEnabled()) log.trace("Exiting saveSerializedView - client-side state saving");
365 
366         return serializedView;
367     }
368 
369     private static void checkForDuplicateIds(FacesContext context,
370                                              UIComponent component,
371                                              Set ids) {
372         String id = component.getId();
373         if (id != null && !ids.add(id)) {
374             throw new IllegalStateException("Client-id : " + id +
375                 " is duplicated in the faces tree. Component : " + component.getClientId(context) + ", path: " +
376                 getPathToComponent(component));
377         }
378         if (component instanceof NamingContainer)
379         {
380             ids = new HashSet();
381         }
382         Iterator it = component.getFacetsAndChildren();
383         while (it.hasNext()) {
384             UIComponent kid = (UIComponent) it.next();
385             checkForDuplicateIds(context, kid, ids);
386         }
387     }
388 
389     private static String getPathToComponent(UIComponent component) {
390         StringBuffer buf = new StringBuffer();
391 
392         if (component == null) {
393             buf.append("{Component-Path : ");
394             buf.append("[null]}");
395             return buf.toString();
396         }
397 
398         getPathToComponent(component, buf);
399 
400         buf.insert(0, "{Component-Path : ");
401         buf.append("}");
402 
403         return buf.toString();
404     }
405 
406     private static void getPathToComponent(UIComponent component, StringBuffer buf) {
407         if (component == null)
408             return;
409 
410         StringBuffer intBuf = new StringBuffer();
411 
412         intBuf.append("[Class: ");
413         intBuf.append(component.getClass().getName());
414         if (component instanceof UIViewRoot) {
415             intBuf.append(",ViewId: ");
416             intBuf.append(((UIViewRoot) component).getViewId());
417         }
418         else {
419             intBuf.append(",Id: ");
420             intBuf.append(component.getId());
421         }
422         intBuf.append("]");
423 
424         buf.insert(0, intBuf.toString());
425 
426         if (component != null) {
427             getPathToComponent(component.getParent(), buf);
428         }
429     }
430 
431     public void writeState(FacesContext facesContext,
432                            SerializedView serializedView) throws IOException {
433         if (log.isTraceEnabled()) log.trace("Entering writeState");
434 
435         if (log.isTraceEnabled())
436             log.trace("Processing writeState - either client-side (full state) or server-side (partial information; e.g. sequence)");
437         if (serializedView != null) {
438             UIViewRoot uiViewRoot = facesContext.getViewRoot();
439             //save state in response (client-side: full state; server-side: sequence)
440             RenderKit renderKit = getRenderKitFactory().getRenderKit(facesContext, uiViewRoot.getRenderKitId());
441             renderKit.getResponseStateManager().writeState(facesContext, serializedView);
442 
443             if (log.isTraceEnabled()) log.trace("Exiting writeState");
444         }
445     }
446 
447     /**
448      * MyFaces extension
449      *
450      * @param facesContext
451      * @param serializedView
452      * @throws IOException
453      */
454     public void writeStateAsUrlParams(FacesContext facesContext,
455                                       SerializedView serializedView) throws IOException {
456         if (log.isTraceEnabled()) log.trace("Entering writeStateAsUrlParams");
457 
458         if (isSavingStateInClient(facesContext)) {
459             if (log.isTraceEnabled())
460                 log.trace("Processing writeStateAsUrlParams - client-side state saving writing state");
461 
462             UIViewRoot uiViewRoot = facesContext.getViewRoot();
463             //save state in response (client)
464             RenderKit renderKit = getRenderKitFactory().getRenderKit(facesContext, uiViewRoot.getRenderKitId());
465             ResponseStateManager responseStateManager = renderKit.getResponseStateManager();
466             if (responseStateManager instanceof MyfacesResponseStateManager) {
467                 ((MyfacesResponseStateManager) responseStateManager).writeStateAsUrlParams(facesContext,
468                                                                                            serializedView);
469             }
470             else {
471                 log.error("ResponseStateManager of render kit " + uiViewRoot.getRenderKitId() + " is no MyfacesResponseStateManager and does not support saving state in url parameters.");
472             }
473         }
474 
475         if (log.isTraceEnabled()) log.trace("Exiting writeStateAsUrlParams");
476     }
477 
478     //helpers
479 
480     protected RenderKitFactory getRenderKitFactory() {
481         if (_renderKitFactory == null) {
482             _renderKitFactory = (RenderKitFactory) FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
483         }
484         return _renderKitFactory;
485     }
486 
487     protected void saveSerializedViewInServletSession(FacesContext context,
488                                                       SerializedView serializedView) {
489         Map sessionMap = context.getExternalContext().getSessionMap();
490         SerializedViewCollection viewCollection = (SerializedViewCollection) sessionMap
491             .get(SERIALIZED_VIEW_SESSION_ATTR);
492         if (viewCollection == null) {
493             viewCollection = new SerializedViewCollection();
494             sessionMap.put(SERIALIZED_VIEW_SESSION_ATTR, viewCollection);
495         }
496         viewCollection.add(context, serializeView(context, serializedView));
497         // replace the value to notify the container about the change
498         sessionMap.put(SERIALIZED_VIEW_SESSION_ATTR, viewCollection);
499     }
500 
501     protected SerializedView getSerializedViewFromServletSession(FacesContext context, String viewId, String sequenceStr) {
502         ExternalContext externalContext = context.getExternalContext();
503         Map requestMap = externalContext.getRequestMap();
504         SerializedView serializedView = null;
505         if (requestMap.containsKey(RESTORED_SERIALIZED_VIEW_REQUEST_ATTR)) {
506             serializedView = (SerializedView) requestMap.get(RESTORED_SERIALIZED_VIEW_REQUEST_ATTR);
507         }
508         else {
509             SerializedViewCollection viewCollection = (SerializedViewCollection) externalContext
510                 .getSessionMap().get(SERIALIZED_VIEW_SESSION_ATTR);
511             if (viewCollection != null) {
512                 Integer sequence = null;
513                 if (sequenceStr == null) {
514                     // use latest sequence
515                     sequence = ViewSequenceUtils.getCurrentSequence(context);
516                 }
517                 else {
518                     sequence = new Integer(sequenceStr);
519                 }
520                 if (sequence != null) {
521                     Object state = viewCollection.get(sequence, viewId);
522                     if (state != null) {
523                         serializedView = deserializeView(state);
524                     }
525                 }
526             }
527             requestMap.put(RESTORED_SERIALIZED_VIEW_REQUEST_ATTR, serializedView);
528             ViewSequenceUtils.nextViewSequence(context);
529         }
530         return serializedView;
531     }
532 
533     protected Object serializeView(FacesContext context, SerializedView serializedView) {
534         if (log.isTraceEnabled()) log.trace("Entering serializeView");
535 
536         if (isSerializeStateInSession(context)) {
537             if (log.isTraceEnabled()) log.trace("Processing serializeView - serialize state in session");
538 
539             ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
540             try {
541                 OutputStream os = baos;
542                 if (isCompressStateInSession(context)) {
543                     if (log.isTraceEnabled()) log.trace("Processing serializeView - serialize compressed");
544 
545                     os.write(COMPRESSED_FLAG);
546                     os = new GZIPOutputStream(os, 1024);
547                 }
548                 else {
549                     if (log.isTraceEnabled()) log.trace("Processing serializeView - serialize uncompressed");
550 
551                     os.write(UNCOMPRESSED_FLAG);
552                 }
553                 ObjectOutputStream out = new ObjectOutputStream(os);
554                 out.writeObject(serializedView.getStructure());
555                 out.writeObject(serializedView.getState());
556                 out.close();
557                 baos.close();
558 
559                 if (log.isTraceEnabled()) log.trace("Exiting serializeView - serialized. Bytes : " + baos.size());
560                 return baos.toByteArray();
561             }
562             catch (IOException e) {
563                 log.error("Exiting serializeView - Could not serialize state: " + e.getMessage(), e);
564                 return null;
565             }
566         }
567         else {
568             if (log.isTraceEnabled()) log.trace("Exiting serializeView - do not serialize state in session.");
569             return new Object[]{serializedView.getStructure(), serializedView.getState()};
570         }
571     }
572 
573     /**
574      * Reads the value of the <code>org.apache.myfaces.SERIALIZE_STATE_IN_SESSION</code> context parameter.
575      *
576      * @param context <code>FacesContext</code> for the request we are processing.
577      * @return boolean true, if the server state should be serialized in the session
578      * @see SERIALIZE_STATE_IN_SESSION_PARAM
579      */
580     protected boolean isSerializeStateInSession(FacesContext context) {
581         String value = context.getExternalContext().getInitParameter(
582             SERIALIZE_STATE_IN_SESSION_PARAM);
583         boolean serialize = DEFAULT_SERIALIZE_STATE_IN_SESSION;
584         if (value != null) {
585             serialize = new Boolean(value).booleanValue();
586         }
587         return serialize;
588     }
589 
590     /**
591      * Reads the value of the <code>org.apache.myfaces.COMPRESS_STATE_IN_SESSION</code> context parameter.
592      *
593      * @param context <code>FacesContext</code> for the request we are processing.
594      * @return boolean true, if the server state steam should be compressed
595      * @see COMPRESS_SERVER_STATE_PARAM
596      */
597     protected boolean isCompressStateInSession(FacesContext context) {
598         String value = context.getExternalContext().getInitParameter(
599             COMPRESS_SERVER_STATE_PARAM);
600         boolean compress = DEFAULT_COMPRESS_SERVER_STATE_PARAM;
601         if (value != null) {
602             compress = new Boolean(value).booleanValue();
603         }
604         return compress;
605     }
606 
607     protected SerializedView deserializeView(Object state) {
608         if (log.isTraceEnabled()) log.trace("Entering deserializeView");
609 
610         if (state instanceof byte[]) {
611             if (log.isTraceEnabled())
612                 log.trace("Processing deserializeView - deserializing serialized state. Bytes : " + ((byte[]) state).length);
613 
614             try {
615                 ByteArrayInputStream bais = new ByteArrayInputStream((byte[]) state);
616                 InputStream is = bais;
617                 if (is.read() == COMPRESSED_FLAG) {
618                     is = new GZIPInputStream(is);
619                 }
620                 ObjectInputStream in = new MyFacesObjectInputStream(
621                     is);
622                 return new SerializedView(in.readObject(), in.readObject());
623             }
624             catch (IOException e) {
625                 log.error("Exiting deserializeView - Could not deserialize state: " + e.getMessage(), e);
626                 return null;
627             }
628             catch (ClassNotFoundException e) {
629                 log.error("Exiting deserializeView - Could not deserialize state: " + e.getMessage(), e);
630                 return null;
631             }
632         }
633         else if (state instanceof Object[]) {
634             if (log.isTraceEnabled()) log.trace("Exiting deserializeView - state not serialized.");
635 
636             Object[] value = (Object[]) state;
637             return new SerializedView(value[0], value[1]);
638         }
639         else if (state == null) {
640             log.error("Exiting deserializeView - this method should not be called with a null-state.");
641             return null;
642         }
643         else {
644             log.error("Exiting deserializeView - this method should not be called with a state of type : " + state.getClass());
645             return null;
646         }
647     }
648 
649     protected static class SerializedViewCollection implements Serializable {
650         private static final long serialVersionUID = -3734849062185115847L;
651 
652         private final List _keys = new ArrayList(DEFAULT_NUMBER_OF_VIEWS_IN_SESSION);
653         private final Map _serializedViews = new HashMap();
654 
655         // old views will be hold as soft references which will be removed by 
656         // the garbage collector if free memory is low
657         private transient Map _oldSerializedViews = null;
658 
659         public synchronized void add(FacesContext context, Object state) {
660             Object key = new SerializedViewKey(context);
661             _serializedViews.put(key, state);
662 
663             while (_keys.remove(key)) ;
664             _keys.add(key);
665 
666             int views = getNumberOfViewsInSession(context);
667             while (_keys.size() > views) {
668                 key = _keys.remove(0);
669                 Object oldView = _serializedViews.remove(key);
670                 if (oldView != null && 
671                     !CACHE_OLD_VIEWS_IN_SESSION_MODE_OFF.equals(getCacheOldViewsInSessionMode(context))) 
672                 {
673                     getOldSerializedViewsMap().put(key, oldView);
674                 }
675             }
676         }
677 
678         /**
679          * Reads the amount (default = 20) of views to be stored in session.
680          *
681          * @param context FacesContext for the current request, we are processing
682          * @return Number vf views stored in the session
683          * @see NUMBER_OF_VIEWS_IN_SESSION_PARAM
684          */
685         protected int getNumberOfViewsInSession(FacesContext context) {
686             String value = context.getExternalContext().getInitParameter(
687                 NUMBER_OF_VIEWS_IN_SESSION_PARAM);
688             int views = DEFAULT_NUMBER_OF_VIEWS_IN_SESSION;
689             if (value != null) {
690                 try {
691                     views = Integer.parseInt(value);
692                     if (views <= 0) {
693                         log.error("Configured value for " + NUMBER_OF_VIEWS_IN_SESSION_PARAM
694                             + " is not valid, must be an value > 0, using default value ("
695                             + DEFAULT_NUMBER_OF_VIEWS_IN_SESSION);
696                         views = DEFAULT_NUMBER_OF_VIEWS_IN_SESSION;
697                     }
698                 }
699                 catch (Throwable e) {
700                     log.error("Error determining the value for " + NUMBER_OF_VIEWS_IN_SESSION_PARAM
701                         + ", expected an integer value > 0, using default value ("
702                         + DEFAULT_NUMBER_OF_VIEWS_IN_SESSION + "): " + e.getMessage(), e);
703                 }
704             }
705             return views;
706         }
707 
708         /**
709          * @return old serialized views map
710          */
711         protected Map getOldSerializedViewsMap() {
712             FacesContext context = FacesContext.getCurrentInstance();
713             if (_oldSerializedViews == null && context != null)
714             {
715                 String cacheMode = getCacheOldViewsInSessionMode(context); 
716                 if (CACHE_OLD_VIEWS_IN_SESSION_MODE_WEAK.equals(cacheMode))
717                 {
718                     _oldSerializedViews = new ReferenceMap(AbstractReferenceMap.WEAK, AbstractReferenceMap.WEAK, true);
719                 }
720                 else if (CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT_WEAK.equals(cacheMode))
721                 {
722                     _oldSerializedViews = new ReferenceMap(AbstractReferenceMap.SOFT, AbstractReferenceMap.WEAK, true);
723                 }
724                 else if (CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT.equals(cacheMode))
725                 {
726                     _oldSerializedViews = new ReferenceMap(AbstractReferenceMap.SOFT, AbstractReferenceMap.SOFT, true);
727                 }
728                 else if (CACHE_OLD_VIEWS_IN_SESSION_MODE_HARD_SOFT.equals(cacheMode))
729                 {
730                     _oldSerializedViews = new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.SOFT);
731                 }
732             }
733             return _oldSerializedViews;
734         }
735         
736         /**
737          * Reads the value of the <code>org.apache.myfaces.CACHE_OLD_VIEWS_IN_SESSION_MODE</code> context parameter.
738          * 
739          * @since 1.1.7
740          * @param context
741          * @return constant indicating caching mode
742          * @see CACHE_OLD_VIEWS_IN_SESSION_MODE
743          */
744         protected String getCacheOldViewsInSessionMode(FacesContext context) {
745             String value = context.getExternalContext().getInitParameter(
746                     CACHE_OLD_VIEWS_IN_SESSION_MODE);
747             if (value == null)
748             {
749                 return CACHE_OLD_VIEWS_IN_SESSION_MODE_OFF;
750             }
751             else if (value.equalsIgnoreCase(CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT))
752             {
753                 return CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT;
754             }
755             else if (value.equalsIgnoreCase(CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT_WEAK))
756             {
757                 return CACHE_OLD_VIEWS_IN_SESSION_MODE_SOFT_WEAK;
758             }            
759             else if (value.equalsIgnoreCase(CACHE_OLD_VIEWS_IN_SESSION_MODE_WEAK))
760             {
761                 return CACHE_OLD_VIEWS_IN_SESSION_MODE_WEAK;
762             }
763             else if (value.equalsIgnoreCase(CACHE_OLD_VIEWS_IN_SESSION_MODE_HARD_SOFT))
764             {
765                 return CACHE_OLD_VIEWS_IN_SESSION_MODE_HARD_SOFT;
766             }
767             else
768             {
769                 return CACHE_OLD_VIEWS_IN_SESSION_MODE_OFF;
770             }
771         }
772         
773         public Object get(Integer sequence, String viewId) {
774             Object key = new SerializedViewKey(viewId, sequence);
775             Object value = _serializedViews.get(key);
776             if (value == null)
777             {
778                 Map oldSerializedViewMap = getOldSerializedViewsMap();
779                 if (oldSerializedViewMap != null)
780                 {
781                     value = oldSerializedViewMap.get(key);
782                 }
783             }
784             return value;
785         }
786     }
787 
788     protected static class SerializedViewKey implements Serializable {
789         private static final long serialVersionUID = -1170697124386063642L;
790 
791         private final String _viewId;
792         private final Integer _sequenceId;
793 
794         public SerializedViewKey(String viewId, Integer sequence) {
795             _sequenceId = sequence;
796             _viewId = viewId;
797         }
798 
799         public SerializedViewKey(FacesContext context) {
800             _sequenceId = ViewSequenceUtils.getViewSequence(context);
801             _viewId = context.getViewRoot().getViewId();
802         }
803 
804         public boolean equals(Object obj) {
805             if (obj == null) {
806                 return false;
807             }
808             if (obj == this) {
809                 return true;
810             }
811             if (obj instanceof SerializedViewKey) {
812                 SerializedViewKey other = (SerializedViewKey) obj;
813                 return new EqualsBuilder().append(other._viewId, _viewId).append(other._sequenceId,
814                                                                                  _sequenceId).isEquals();
815             }
816             return false;
817         }
818 
819         public int hashCode() {
820             return new HashCodeBuilder().append(_viewId).append(_sequenceId).toHashCode();
821         }
822     }
823 }