1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
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
64
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
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
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
434
435
436
437
438
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
492
493
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
503
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
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