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