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.trinidad.bean.util;
20  
21  import java.io.ByteArrayOutputStream;
22  import java.io.IOException;
23  import java.io.ObjectOutputStream;
24  import java.io.Serializable;
25  
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.HashMap;
29  import java.util.HashSet;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Set;
33  
34  import java.util.concurrent.ConcurrentMap;
35  
36  import javax.faces.component.StateHolder;
37  import javax.faces.context.ExternalContext;
38  import javax.faces.context.FacesContext;
39  
40  import org.apache.myfaces.trinidad.bean.FacesBean;
41  import org.apache.myfaces.trinidad.bean.PropertyKey;
42  import org.apache.myfaces.trinidad.bean.PropertyMap;
43  import org.apache.myfaces.trinidad.context.RequestContext;
44  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
45  
46  
47  /**
48   * Utilities for handling state persistance.
49   */
50  public final class StateUtils
51  {
52    static
53    {
54      // initialize checks for serialization verification
55      boolean checkPropertyStateSerialization = false;
56      boolean checkComponentStateSerialization = false;
57      boolean checkComponentTreeStateSerialization = false;
58      boolean checkSessionSerialization = false;
59      boolean checkApplicationSerialization = false;
60  
61      String checkSerializationProperty;
62  
63      // determine if we need to aggressively check the serialization of components
64      // we wrap the check in a try/catch in case of weird ecurity managers
65      try
66      {
67        checkSerializationProperty = 
68                         System.getProperty("org.apache.myfaces.trinidad.CHECK_STATE_SERIALIZATION");
69      }
70      catch (Throwable t)
71      {
72        checkSerializationProperty = null;
73      }
74  
75      if (checkSerializationProperty != null)
76      {
77        checkSerializationProperty = checkSerializationProperty.toUpperCase();
78        
79        // comma-separated list with allowed whitespace
80        String[] paramArray = checkSerializationProperty.split(",");
81        
82        Set<String> serializationFlags = new HashSet<String>(Arrays.asList(paramArray));
83        
84        if (!serializationFlags.contains("NONE"))
85        {
86          if (serializationFlags.contains("ALL"))
87          {
88            checkPropertyStateSerialization = true;
89            checkComponentStateSerialization = true;
90            checkComponentTreeStateSerialization = true;
91            checkSessionSerialization = true;
92            checkApplicationSerialization = true;
93          }
94          else
95          {
96            checkPropertyStateSerialization = serializationFlags.contains("PROPERTY");
97            checkComponentStateSerialization = serializationFlags.contains("COMPONENT");
98            checkComponentTreeStateSerialization = serializationFlags.contains("TREE");       
99            checkSessionSerialization = serializationFlags.contains("SESSION");
100           checkApplicationSerialization = serializationFlags.contains("APPLICATION");
101         }
102       }
103     }
104 
105     _CHECK_PROPERTY_STATE_SERIALIZATION = checkPropertyStateSerialization;
106     _CHECK_COMPONENT_STATE_SERIALIZATION = checkComponentStateSerialization;
107     _CHECK_COMPONENT_TREE_STATE_SERIALIZATION = checkComponentTreeStateSerialization;
108     _CHECK_SESSION_SERIALIZATION = checkSessionSerialization;
109     _CHECK_APPLICATION_SERIALIZATION = checkApplicationSerialization;
110   }
111 
112   private static final boolean _CHECK_COMPONENT_TREE_STATE_SERIALIZATION;
113   private static final boolean _CHECK_COMPONENT_STATE_SERIALIZATION;
114   private static final boolean _CHECK_PROPERTY_STATE_SERIALIZATION;
115   private static final boolean _CHECK_SESSION_SERIALIZATION;
116   private static final boolean _CHECK_APPLICATION_SERIALIZATION;
117 
118   /**
119    * Returns <code>true</code> if properties should be checked for
120    * serializability when when generating the view's state object.
121    * <p>
122    * By default property state serialization checking is off.  It can be
123    * enabled by setting the system property
124    * <code>org.apache.myfaces.trinidad.CHECK_STATE_SERIALIZATION</code>
125    * to <code>all</code> or, more rately, adding <code>property</code> to the
126    * comma-separated list of serialization checks to perform.
127    * <p>
128    * As property serialization checking is expensive, it is usually
129    * only enabled after component tree serialization checking has detected
130    * a problem
131    * @return
132    * @see #checkComponentStateSerialization
133    * @see #checkComponentTreeStateSerialization
134    */
135   private static boolean _checkPropertyStateSerialization()
136   {
137     return _CHECK_PROPERTY_STATE_SERIALIZATION;
138   }
139 
140   /**
141    * Returns <code>true</code> if components should be checked for
142    * serializability when when generating the view's state object.
143    * <p>
144    * By default component state serialization checking is off.  It can be
145    * enabled by setting the system property
146    * <code>org.apache.myfaces.trinidad.CHECK_STATE_SERIALIZATION</code>
147    * to <code>all</code> or, more rarely, adding <code>component</code> to the
148    * comma-separated list of serialization checks to perform.
149    * <p>
150    * As property serialization checking is expensive, it is usually
151    * only enabled after component tree serialization checking has detected
152    * a problem.  In addition, since component serialization checking only
153    * detects the problem component, it is usually combined with
154    * property state serialization checking either by specifying <code>all</code>.
155    * @return
156    * @see #checkComponentTreeStateSerialization
157    */
158   public static boolean checkComponentStateSerialization(FacesContext context)
159   {
160     return _CHECK_COMPONENT_STATE_SERIALIZATION;
161   }
162 
163   /**
164    * Returns <code>true</code> if the component tree should be checked for
165    * serializability when when generating the view's state object.
166    * <p>
167    * By default component tree state serialization checking is off.  It can be
168    * enabled by setting the system property
169    * <code>org.apache.myfaces.trinidad.CHECK_STATE_SERIALIZATION</code>
170    * to <code>all</code> or, more commonly, adding <code>tree</code> to the
171    * comma-separated list of serialization checks to perform.
172    * <p>
173    * Because unserializable objects defeat fail-over, it is important to
174    * check for serializability when testing applications.  While component
175    * tree state serializability checking isn't cheap, it is much faster to
176    * initially only enable checking of the component tree and then switch
177    * to <code>all</code> testing to determine the problem component and 
178    * property when the component tree testing determines a problem.
179    * @return
180    * @see #checkComponentStateSerialization
181    */
182   public static boolean checkComponentTreeStateSerialization(FacesContext context)
183   {
184     return _CHECK_COMPONENT_TREE_STATE_SERIALIZATION;
185   }
186 
187   /**
188    * Returns <code>true</code> if Object written to the ExternalContext's Session Map should be
189    * checked for Serializability when <code>put</code> is called.
190    * <p>
191    * Configuring this property allows this aspect of high-availability to be tested without
192    * configuring the server to run in high-availability mode.
193    * <p>
194    * By default session serialization checking is off.  It can be
195    * enabled by setting the system property
196    * <code>org.apache.myfaces.trinidad.CHECK_STATE_SERIALIZATION</code>
197    * to <code>all</code> or, more commonly, adding <code>session</code> to the
198    * comma-separated list of serialization checks to perform.
199    * @return
200    * @see #checkComponentStateSerialization
201    * @see #checkComponentTreeStateSerialization
202    * @see #checkApplicationSerialization
203    */
204   public static boolean checkSessionSerialization(ExternalContext extContext)
205   {
206     return _CHECK_SESSION_SERIALIZATION;
207   }
208 
209   /**
210    * Returns <code>true</code> if Object written to the ExternalContext's Application Map should be
211    * checked for Serializability when <code>put</code> is called.
212    * <p>
213    * Configuring this property allows this aspect of high-availability to be tested without
214    * configuring the server to run in high-availability mode.
215    * <p>
216    * By default application serialization checking is off.  It can be
217    * enabled by setting the system property
218    * <code>org.apache.myfaces.trinidad.CHECK_STATE_SERIALIZATION</code>
219    * to <code>all</code> or, more commonly, adding <code>application</code> to the
220    * comma-separated list of serialization checks to perform.
221    * @return
222    * @see #checkComponentStateSerialization
223    * @see #checkComponentTreeStateSerialization
224    * @see #checkSessionSerialization
225    */
226   public static boolean checkApplicationSerialization(ExternalContext extContext)
227   {
228     return _CHECK_APPLICATION_SERIALIZATION;
229   }
230 
231   /**
232    * Persists a property key.
233    */
234   static public Object saveKey(PropertyKey key)
235   {
236     int index = key.getIndex();
237     if (index < 0)
238       return key.getName();
239 
240     if (index < 128)
241       return Byte.valueOf((byte) index);
242 
243     return Integer.valueOf(index);
244   }
245 
246 
247   /**
248    * Restores a persisted PropertyKey.
249    */
250   static public PropertyKey restoreKey(FacesBean.Type type, Object value)
251   {
252     PropertyKey key;
253     if (value instanceof Number)
254     {
255       key = type.findKey(((Number) value).intValue());
256       if (key == null)
257         throw new IllegalStateException(_LOG.getMessage(
258           "INVALID_INDEX"));
259     }
260     else
261     {
262       key = type.findKey((String) value);
263       if (key == null)
264         key = PropertyKey.createPropertyKey((String) value);
265     }
266 
267     return key;
268   }
269 
270   /**
271    * Generic (unoptimized) version of PropertyMap state saving.
272    */
273   static public Object saveState(
274     PropertyMap    map,
275     FacesContext   context,
276     boolean        useStateHolder)
277   {
278     int size = map.size();
279     if (size == 0)
280       return null;
281 
282     Object[] values = new Object[2 * size];
283     int i = 0;
284     for(Map.Entry<PropertyKey, Object> entry : map.entrySet())
285     {
286       PropertyKey key = entry.getKey();
287       if (key.isTransient())
288         continue;
289 
290       Object value = entry.getValue();
291 
292       values[i] = saveKey(key);
293       if (_LOG.isFinest())
294       {
295         _LOG.finest("SAVE {" + key + "=" + value + "}");
296       }
297 
298       Object saveValue;
299       
300       if (useStateHolder)
301         saveValue = saveStateHolder(context, value);
302       else
303         saveValue = key.saveValue(context, value);
304 
305       // aggressively check the serializability of the value
306       if (_checkPropertyStateSerialization())
307       {
308         try
309         {
310           new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(saveValue);
311         }
312         catch (IOException e)
313         {          
314           throw new RuntimeException(_LOG.getMessage("UNSERIALIZABLE_PROPERTY_VALUE",
315                                                      new Object[]{saveValue, key, map}),
316                                      e);
317         }
318       }
319       
320       values[i + 1] = saveValue;
321       
322       i+=2;
323     }
324 
325     return values;
326   }
327 
328 
329   /**
330    * Generic (unoptimized) version of PropertyMap state restoring.
331    */
332   static public void restoreState(
333     PropertyMap    map,
334     FacesContext   context,
335     FacesBean.Type type,
336     Object         state,
337     boolean        useStateHolder)
338   {
339     if (state == null)
340       return;
341 
342     Object[] values = (Object[]) state;
343     int size = values.length / 2;
344     for (int i = 0; i < size; i++)
345     {
346       Object savedKey = values[i * 2];
347       if (savedKey == null)
348         continue;
349 
350       Object savedValue = values[i * 2 + 1];
351       PropertyKey key = restoreKey(type, savedKey);
352       Object value;
353 
354       if (useStateHolder)
355         value = restoreStateHolder(context, savedValue);
356       else
357         value = key.restoreValue(context, savedValue);
358 
359       if (_LOG.isFinest())
360       {
361         _LOG.finest("RESTORE {" + key + "=" + value + "}");
362       }
363 
364       map.put(key, value);
365     }
366   }
367 
368   /**
369    * Saves an object that may implement StateHolder.
370    */
371   static public Object saveStateHolder(
372     FacesContext context,
373     Object       value)
374   {
375     if (value == null)
376       return null;
377 
378     Saver saver = null;
379     if (value instanceof StateHolder)
380     {
381       if (((StateHolder) value).isTransient())
382         return null;
383 
384       saver = new SHSaver();
385     }
386     else if (value instanceof Serializable)
387     {
388       return value;
389     }
390     else
391     {
392       saver = new Saver();
393     }
394 
395     if (saver != null)
396       saver.saveState(context, value);
397 
398     return saver;
399   }
400 
401   /**
402    * Restores an object that was saved using saveStateHolder()
403    */
404   static public Object restoreStateHolder(
405     FacesContext context,
406     Object       savedValue)
407   {
408     if (!(savedValue instanceof Saver))
409       return savedValue;
410 
411     return ((Saver) savedValue).restoreState(context);
412   }
413 
414 
415 
416   /**
417    * Saves a List whose elements may implement StateHolder.
418    */
419   @SuppressWarnings("unchecked")
420   static public Object saveList(
421     FacesContext context,
422     Object       value)
423   {
424     if (value == null)
425       return null;
426 
427     List<Object> list = (List<Object>) value;
428     int size = list.size();
429     if (size == 0)
430       return null;
431 
432     Object[] array = new Object[size];
433     // 2006-08-01: -= Simon Lessard =-
434     //             Inefficient loop if the list implementation
435     //             ever change to a linked data structure. Use
436     //             iterators instead
437     //for (int i = 0; i < size; i++)
438     //  array[i] = saveStateHolder(context, list.get(i));
439     int index = 0;
440     for(Object object : list)
441     {
442       array[index++] = saveStateHolder(context, object);
443     }
444 
445     return array;
446   }
447 
448   /**
449    * Restores a List whose elements may implement StateHolder.
450    */
451   static public Object restoreList(
452     FacesContext context,
453     Object       savedValue)
454   {
455     if (savedValue == null)
456       return null;
457 
458     Object[] array = (Object[]) savedValue;
459     int length = array.length;
460     if (length == 0)
461       return null;
462 
463     List<Object> list = new ArrayList<Object>(length);
464     for(Object state : array)
465     {
466       Object restored = restoreStateHolder(context, state);
467       if (restored != null)
468       {
469         list.add(restored);
470       }
471     }
472 
473     return list;
474   }
475 
476 
477 
478   /**
479    * Instance used to save generic instances;  simply saves
480    * the class name.
481    */
482   static private class Saver implements Serializable
483   {
484     public void saveState(FacesContext context, Object saved)
485     {
486       _name = saved.getClass().getName();
487     }
488 
489     public Object restoreState(FacesContext context)
490     {
491       // we don't need to use concurrent map methods like putIfAbsent. If someone happens to
492       // add a name/value pair again it's fine because as the doc for put in HashMap says
493       // "If the map previously contained a mapping for this key, the old value is replaced."
494       ConcurrentMap<String, Object> appMap = 
495                            RequestContext.getCurrentInstance().getApplicationScopedConcurrentMap();
496       
497 
498       Map<String, Class> classMap = (Map<String, Class>) appMap.get(_CLASS_MAP_KEY);
499       
500       if (classMap == null)
501       {    
502         // the classMap doesn't need to worry about synchronization, 
503         // if the Class is loaded twice that's fine. 
504         Map<String, Class> newClassMap = new HashMap<String, Class>();
505         Map<String, Class> oldClassMap = 
506                               (Map<String, Class>) appMap.putIfAbsent(_CLASS_MAP_KEY, newClassMap);
507         
508         if (oldClassMap != null)
509           classMap = oldClassMap;
510         else
511           classMap = newClassMap;
512       }
513             
514       Class clazz = classMap.get(_name);
515 
516       if (clazz == null)
517       {
518         try
519         {
520           ClassLoader cl = _getClassLoader();
521           clazz = cl.loadClass(_name);
522           classMap.put(_name, clazz);
523         }
524         catch (Throwable t)
525         {
526           _LOG.severe(t);
527           return null;
528         }
529       }
530 
531       try
532       {
533         return clazz.newInstance();
534       }
535       catch (Throwable t)
536       {
537         _LOG.severe(t);
538         return null;
539       }
540     }
541 
542     private String _name;
543     private static final long serialVersionUID = 1L;
544   }
545 
546 
547   /**
548    * Instance used to save StateHolder objects.
549    */
550   static private class SHSaver extends Saver
551   {
552     @Override
553     public void saveState(FacesContext context, Object value)
554     {
555       super.saveState(context, value);
556       _save = ((StateHolder) value).saveState(context);
557     }
558 
559     @Override
560     public Object restoreState(FacesContext context)
561     {
562       Object o = super.restoreState(context);
563       if (o != null)
564         ((StateHolder) o).restoreState(context, _save);
565 
566       return o;
567     }
568 
569     private Object _save;
570     private static final long serialVersionUID = 1L;
571   }
572 
573 
574   //
575   // Pick a ClassLoader
576   //
577   static private ClassLoader _getClassLoader()
578   {
579     ClassLoader cl = Thread.currentThread().getContextClassLoader();
580     if (cl == null)
581       cl = StateUtils.class.getClassLoader();
582 
583     return cl;
584   }
585 
586   static private final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(StateUtils.class);
587 
588   private static final String _CLASS_MAP_KEY = 
589                                            "org.apache.myfaces.trinidad.bean.util.CLASS_MAP_KEY";
590 
591 
592 }
593 
594 
595 
596