001: /*
002: * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright
003: * notice. All rights reserved.
004: */
005: package com.tctest;
006:
007: import org.apache.commons.io.IOUtils;
008:
009: import com.tc.exception.TCRuntimeException;
010: import com.tc.object.BaseDSOTestCase;
011: import com.tc.object.TestClientObjectManager;
012: import com.tc.object.bytecode.Manageable;
013: import com.tc.object.bytecode.TransparentAccess;
014: import com.tc.object.config.DSOClientConfigHelper;
015: import com.tc.object.loaders.IsolationClassLoader;
016: import com.tc.object.tools.BootJar;
017: import com.tc.object.tx.MockTransactionManager;
018: import com.tc.process.LinkedJavaProcess;
019: import com.tc.process.StreamCollector;
020: import com.tc.util.DebugUtil;
021: import com.tc.util.runtime.Vm;
022:
023: import java.io.ByteArrayInputStream;
024: import java.io.ByteArrayOutputStream;
025: import java.io.File;
026: import java.io.FileInputStream;
027: import java.io.FileOutputStream;
028: import java.io.IOException;
029: import java.io.InputStream;
030: import java.io.ObjectInputStream;
031: import java.io.ObjectOutputStream;
032: import java.io.ObjectStreamClass;
033: import java.io.Serializable;
034: import java.lang.reflect.Constructor;
035: import java.lang.reflect.Field;
036: import java.lang.reflect.InvocationTargetException;
037: import java.lang.reflect.Method;
038: import java.lang.reflect.Modifier;
039: import java.util.ArrayList;
040: import java.util.Arrays;
041: import java.util.Collection;
042: import java.util.Collections;
043: import java.util.Date;
044: import java.util.HashMap;
045: import java.util.HashSet;
046: import java.util.Hashtable;
047: import java.util.IdentityHashMap;
048: import java.util.Iterator;
049: import java.util.LinkedList;
050: import java.util.List;
051: import java.util.Map;
052: import java.util.Set;
053: import java.util.TreeMap;
054: import java.util.TreeSet;
055:
056: /*
057: * TODO:: More tests needed. 1) Populated collections needs to be serialized and deserialized. 2) Serialized
058: * instrumented version of collections/objects should be deserializable by uninstrumented versions and vice versa
059: */
060: public class SerializationTest extends BaseDSOTestCase {
061: private static final Set reentrantReadWriteLockInnerClassNames = new HashSet();
062:
063: private static final Set disabled = new HashSet();
064:
065: static {
066: disabled.add("java.util.concurrent.ConcurrentHashMap");
067: disabled.add("java.util.concurrent.LinkedBlockingQueue");
068: disabled
069: .add("java.util.concurrent.locks.ReentrantReadWriteLock$DsoLock");
070: disabled
071: .add("com.tcclient.util.concurrent.locks.ConditionObject");
072: disabled
073: .add("com.tcclient.util.concurrent.locks.ConditionObject$SyncCondition");
074:
075: reentrantReadWriteLockInnerClassNames
076: .add("java.util.concurrent.locks.ReentrantReadWriteLock$ReadLock");
077: reentrantReadWriteLockInnerClassNames
078: .add("java.util.concurrent.locks.ReentrantReadWriteLock$WriteLock");
079: }
080:
081: public void testSerialization() throws Exception {
082: DebugUtil.DEBUG = true;
083: BootJar bj = BootJar.getDefaultBootJarForReading();
084:
085: Set specs = bj.getAllPreInstrumentedClasses();
086: for (Iterator iter = specs.iterator(); iter.hasNext();) {
087: String className = (String) iter.next();
088: checkSerialization(className);
089: }
090: DebugUtil.DEBUG = false;
091: }
092:
093: public void testCollectionInnerClasses() throws Exception {
094: validateSerialization(Collections.EMPTY_LIST);
095: validateSerialization(Collections.EMPTY_SET);
096: validateSerialization(Collections.EMPTY_MAP);
097:
098: // this tests Collections$UnmodifiableList
099: validateSerialization(Collections
100: .unmodifiableList(new LinkedList()));
101: // this tests Collections$UnmodifiableRandomAccessList
102: validateSerialization(Collections
103: .unmodifiableList(new ArrayList()));
104:
105: validateSerialization(Collections
106: .unmodifiableCollection(new LinkedList()));
107: validateSerialization(Collections
108: .synchronizedList(new LinkedList()));
109: validateSerialization(Collections
110: .synchronizedList(new ArrayList()));
111: validateSerialization(Collections
112: .synchronizedMap(new HashMap()));
113: validateSerialization(Collections
114: .synchronizedSet(new HashSet()));
115: validateSerialization(Collections
116: .synchronizedCollection(new LinkedList()));
117: }
118:
119: public void testIfTheTestIsRunningWithBootJar() throws Exception {
120: assertTrue(isHashMapDSOInstrumented());
121: }
122:
123: public void testSubclassOfMapSerialization() throws Exception {
124: validateSubclassOfMapSerialization(MyHashMap.class.getName());
125:
126: validateSubclassOfMapSerialization(MyHashtable.class.getName());
127: }
128:
129: public void testSubclassofCollectionSerialization()
130: throws Exception {
131: validateSubclassOfCollectionSerialization(MyArrayList.class
132: .getName());
133: validateSubclassOfCollectionSerialization(MyHashSet.class
134: .getName());
135: }
136:
137: private void validateSubclassOfMapSerialization(String mapclassName)
138: throws Exception {
139: ClassLoader originalLoader = Thread.currentThread()
140: .getContextClassLoader();
141:
142: try {
143: DSOClientConfigHelper config = createClientConfigHelper();
144: config.addIncludePattern(SerializationTest.class.getName()
145: + "$*");
146:
147: TestClientObjectManager testClientObjectManager = new TestClientObjectManager();
148: MockTransactionManager testTransactionManager = new MockTransactionManager();
149: IsolationClassLoader classLoader = new IsolationClassLoader(
150: config, testClientObjectManager,
151: testTransactionManager);
152: classLoader.init();
153: Thread.currentThread().setContextClassLoader(classLoader);
154:
155: Class clazz = classLoader.loadClass(mapclassName);
156: Map o = (Map) clazz.newInstance();
157: o.put("key1", "value1");
158: o.put("key2", "value2");
159: assertTrue(o instanceof Manageable);
160: assertTrue(o instanceof TransparentAccess);
161: validateSerializationForSubclass(o, classLoader);
162: } finally {
163: Thread.currentThread()
164: .setContextClassLoader(originalLoader);
165: }
166: }
167:
168: private void validateSubclassOfCollectionSerialization(
169: String mapclassName) throws Exception {
170: ClassLoader originalLoader = Thread.currentThread()
171: .getContextClassLoader();
172:
173: try {
174: DSOClientConfigHelper config = createClientConfigHelper();
175: config.addIncludePattern(SerializationTest.class.getName()
176: + "$*");
177:
178: TestClientObjectManager testClientObjectManager = new TestClientObjectManager();
179: MockTransactionManager testTransactionManager = new MockTransactionManager();
180: IsolationClassLoader classLoader = new IsolationClassLoader(
181: config, testClientObjectManager,
182: testTransactionManager);
183: classLoader.init();
184: Thread.currentThread().setContextClassLoader(classLoader);
185:
186: Class clazz = classLoader.loadClass(mapclassName);
187: Collection o = (Collection) clazz.newInstance();
188: o.add("value1");
189: o.add("value2");
190: assertTrue(o instanceof Manageable);
191: assertTrue(o instanceof TransparentAccess);
192: validateSerializationForSubclass(o, classLoader);
193: } finally {
194: Thread.currentThread()
195: .setContextClassLoader(originalLoader);
196: }
197: }
198:
199: private static boolean isHashMapDSOInstrumented() {
200: Class c = HashMap.class;
201: Class[] interfaces = c.getInterfaces();
202: for (int i = 0; i < interfaces.length; i++) {
203: if (interfaces[i].getName().equals(
204: Manageable.class.getName())) {
205: return true;
206: }
207: }
208:
209: return false;
210: }
211:
212: private boolean handleSpecificSerializationTestIfNeeded(
213: String className) throws Exception {
214: if (reentrantReadWriteLockInnerClassNames.contains(className)) {
215: checkSerializationForReentrantReadWriteLockInnerClass(className);
216: return true;
217: }
218: return false;
219: }
220:
221: private void checkSerialization(String className) throws Exception {
222: boolean handleSpecificSerializationTest = handleSpecificSerializationTestIfNeeded(className);
223: if (handleSpecificSerializationTest) {
224: return;
225: }
226:
227: if (className.startsWith("com.tc.")
228: || (className.indexOf('$') > 0)) {
229: // System.err.println("Skipping class " + className);
230: return;
231: }
232:
233: Class klass = Class.forName(className);
234:
235: int access = klass.getModifiers();
236: if (Modifier.isAbstract(access) || !Modifier.isPublic(access)) {
237: // System.err.println("Skipping " + klass);
238: return;
239: }
240:
241: Constructor cstr;
242: try {
243: cstr = klass.getConstructor(new Class[] {});
244: } catch (Exception e) {
245: // System.err.println("No default cstr for " + klass);
246: return;
247: }
248:
249: Object o = cstr.newInstance(new Object[] {});
250:
251: if (!(o instanceof Serializable)) {
252: System.err.println("Skipping non-serializable " + klass);
253: return;
254: }
255:
256: validateSerialization(o);
257:
258: if (canValidateExternal(o)) {
259: validateExternalSerialization(o);
260: }
261: }
262:
263: private void checkSerializationForReentrantReadWriteLockInnerClass(
264: String innerClassName) throws Exception {
265: Object lock = getReentrantReadWriteLockInnerClassInstance(innerClassName);
266:
267: validateSerialization(lock);
268:
269: if (canValidateExternal(lock)) {
270: validateExternalSerialization(lock);
271: }
272: }
273:
274: private Object getReentrantReadWriteLockInnerClassInstance(
275: String innerClassName) throws Exception {
276: Class reentrantReadWriteLockClass = Class
277: .forName("java.util.concurrent.locks.ReentrantReadWriteLock");
278: Constructor cstr = reentrantReadWriteLockClass
279: .getConstructor(new Class[] {});
280: Object reentrantReadWriteLock = cstr
281: .newInstance(new Object[] {});
282:
283: Class syncClass = Class
284: .forName("java.util.concurrent.locks.ReentrantReadWriteLock$Sync");
285:
286: Object sync = getReentrantReadWriteLockField(
287: reentrantReadWriteLock, "sync");
288:
289: Class innerClass = Class.forName(innerClassName);
290: cstr = innerClass.getDeclaredConstructor(new Class[] {
291: reentrantReadWriteLockClass, syncClass });
292: cstr.setAccessible(true);
293: Object innerObject = cstr.newInstance(new Object[] {
294: reentrantReadWriteLock, sync });
295:
296: return innerObject;
297: }
298:
299: private boolean canValidateExternal(Object o) {
300: // Serialization of IdentityHashMap is busted on 1.4.x (http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4821217)
301: if (o.getClass() == IdentityHashMap.class && Vm.isJDK14()) {
302: System.err.println("Skipping " + o.getClass()
303: + " due to Java bug 4821217");
304: return false;
305: }
306: return true;
307: }
308:
309: private void validateSerialization(Object o) throws Exception {
310: System.out.println("TESTING " + o.getClass());
311: assertTrue(o instanceof Serializable);
312:
313: deserialize(serialize(o), o.getClass().getClassLoader());
314: }
315:
316: private void validateSerializationForSubclass(Object o,
317: ClassLoader loader) throws Exception {
318: System.out.println("TESTING " + o.getClass());
319: assertTrue(o instanceof Serializable);
320:
321: deserialize(serialize(o), loader);
322: }
323:
324: private void validateExternalSerialization(Object o)
325: throws Exception {
326: // TODO:: This needs to be fixed.
327: if (disabled.contains(o.getClass().getName())) {
328: System.out.println("SKIPPING External Serialization : "
329: + o.getClass());
330: return;
331: }
332: System.out.println("TESTING External Serialization : "
333: + o.getClass());
334: populateData(o);
335: Object n = externallySerialize(o);
336: verify(o, n);
337: }
338:
339: private void verify(Object o, Object n) {
340: Class co = o.getClass();
341: Class cn = n.getClass();
342: if (co.getName().equals(cn.getName())) {
343: if (o instanceof IdentityHashMap) {
344: // Special check
345: verifyIdentifyHashMap((IdentityHashMap) o,
346: (IdentityHashMap) n);
347: } else if (!equalsMethodPresent(co)) {
348: verifyStringifiedVersion(o, n);
349: } else {
350: assertEquals(o, n);
351: }
352: } else {
353: System.err.println("FATAL : Error trying serialize " + o);
354: System.err.println("FATAL : Recd " + n);
355: throw new AssertionError(n);
356: }
357: }
358:
359: private boolean equalsMethodPresent(Class c) {
360: Class oc = Object.class;
361: Class[] params = new Class[] { oc };
362: while (c != oc) {
363: try {
364: c.getDeclaredMethod("equals", params);
365: // Got it
366: break;
367: } catch (SecurityException e) {
368: throw new AssertionError(e);
369: } catch (NoSuchMethodException e) {
370: // try the super class
371: c = c.getSuperclass();
372: }
373: }
374: return c != oc;
375: }
376:
377: private void verifyStringifiedVersion(Object o, Object n) {
378: String so = String.valueOf(o);
379: String sn = String.valueOf(n);
380: if (so != null && so.startsWith(o.getClass().getName() + "@")) {
381: // This is a result from Object.toString()
382: assertTrue(sn.startsWith(o.getClass().getName() + "@"));
383: } else {
384: assertEquals(so, sn);
385: }
386: }
387:
388: private void verifyIdentifyHashMap(IdentityHashMap map1,
389: IdentityHashMap map2) {
390: Map m1 = new HashMap(map1);
391: Map m2 = new HashMap(map2);
392: assertEquals(m1, m2);
393: }
394:
395: private static Object deserialize(byte[] data, ClassLoader loader)
396: throws IOException, ClassNotFoundException {
397: // We need to use a subclass of ObjectInputStream because for when deserializing MyHashMap, we
398: // need to use the instrumented version of MyHashMap and that could be achieved by loading it
399: // using the same classloader that serialize it, i.e., IsolationClassLoader.
400: ObjectInputStream ois = new MyObjectInputStream(
401: new ByteArrayInputStream(data), loader);
402: Object rv = ois.readObject();
403: verifyRun(rv);
404: ois.close();
405: return rv;
406: }
407:
408: private static void verifyRun(Object obj) {
409: String className = obj.getClass().getName();
410: if (reentrantReadWriteLockInnerClassNames.contains(className)) {
411: invokeLockUnlockMethod(obj);
412: } else if ("java.util.concurrent.locks.ReentrantReadWriteLock"
413: .equals(className)) {
414: Object readLock = getReentrantReadWriteLockField(obj,
415: "readerLock");
416: invokeLockUnlockMethod(readLock);
417: Object writeLock = getReentrantReadWriteLockField(obj,
418: "writerLock");
419: invokeLockUnlockMethod(writeLock);
420: } else if ("java.util.concurrent.locks.ReentrantLock"
421: .equals(className)) {
422: invokeLockUnlockMethod(obj);
423: }
424: }
425:
426: private static Object getReentrantReadWriteLockField(Object obj,
427: String fieldName) {
428: try {
429: Field f = obj.getClass().getDeclaredField(fieldName);
430: f.setAccessible(true);
431: return f.get(obj);
432: } catch (SecurityException e) {
433: throw new TCRuntimeException(e);
434: } catch (NoSuchFieldException e) {
435: throw new TCRuntimeException(e);
436: } catch (IllegalArgumentException e) {
437: throw new TCRuntimeException(e);
438: } catch (IllegalAccessException e) {
439: throw new TCRuntimeException(e);
440: }
441: }
442:
443: private static void invokeLockUnlockMethod(Object obj) {
444: try {
445: Method lockMethod = obj.getClass().getDeclaredMethod(
446: "lock", new Class[] {});
447: Method unlockMethod = obj.getClass().getDeclaredMethod(
448: "unlock", new Class[] {});
449: lockMethod.invoke(obj, new Object[] {});
450: unlockMethod.invoke(obj, new Object[] {});
451: } catch (SecurityException e) {
452: throw new TCRuntimeException(e);
453: } catch (NoSuchMethodException e) {
454: throw new TCRuntimeException(e);
455: } catch (IllegalArgumentException e) {
456: throw new TCRuntimeException(e);
457: } catch (IllegalAccessException e) {
458: throw new TCRuntimeException(e);
459: } catch (InvocationTargetException e) {
460: throw new TCRuntimeException(e);
461: }
462: }
463:
464: private static byte[] serialize(Object obj) throws IOException {
465: ByteArrayOutputStream baos = new ByteArrayOutputStream();
466: ObjectOutputStream oos = new ObjectOutputStream(baos);
467: oos.writeObject(obj);
468: oos.close();
469: return baos.toByteArray();
470: }
471:
472: private Object externallySerialize(Object m) throws Exception {
473: File base = getTempFile(m.getClass().getName());
474: File out = new File(base.getAbsolutePath() + ".out");
475: File in = new File(base.getAbsolutePath() + ".in");
476:
477: FileOutputStream fos = new FileOutputStream(out);
478: fos.write(serialize(m));
479: fos.close();
480:
481: LinkedJavaProcess process = new LinkedJavaProcess(
482: ExternalSerializer.class.getName(), new String[] {
483: out.getAbsolutePath(), in.getAbsolutePath() });
484: process.start();
485:
486: process.STDIN().close();
487: StreamCollector stdout = new StreamCollector(process.STDOUT());
488: stdout.start();
489: StreamCollector stderr = new StreamCollector(process.STDERR());
490: stderr.start();
491:
492: int exitCode = process.waitFor();
493:
494: stdout.join();
495: stderr.join();
496:
497: if (exitCode != 0) {
498: fail("exit code was " + exitCode + "\n\nSTDOUT:\n"
499: + stdout.toString() + "\nSTDERR:\n"
500: + stderr.toString());
501: }
502:
503: ByteArrayOutputStream recvBytes = new ByteArrayOutputStream();
504: FileInputStream fis = new FileInputStream(in);
505: IOUtils.copy(fis, recvBytes);
506: fis.close();
507:
508: return deserialize(recvBytes.toByteArray(), this .getClass()
509: .getClassLoader());
510: }
511:
512: private Object populateData(Object o) {
513: if (o instanceof TreeMap) {
514: populateTreeMap((TreeMap) o);
515: } else if (o instanceof TreeSet) {
516: populateTreeSet((TreeSet) o);
517: } else if (o instanceof Map) {
518: populateMap((Map) o);
519: } else if (o instanceof Set) {
520: populateSet((Set) o);
521: } else if (o instanceof List) {
522: populateList((List) o);
523: }
524: return o;
525: }
526:
527: private void populateTreeSet(TreeSet set) {
528: set.add("Saravanan Subbiah");
529: set.add("Tim Eck");
530: set.add("Cindy Fisher");
531: set.add("Steve Harris");
532: }
533:
534: private void populateTreeMap(TreeMap map) {
535: map.put("Saravanan", "Subbiah");
536: map.put("Tim", "Eck");
537: map.put("Cindy", "Fisher");
538: map.put("Steve", "Harris");
539: }
540:
541: private void populateMap(Map m) {
542: m.put("Hello", "Saro");
543: m.put(new Integer(99), new Long(88));
544: m.put(new Date(), new Double(454.4545));
545: }
546:
547: private void populateSet(Set set) {
548: set.add("Hello Saro");
549: set.add(new Integer(343));
550: set.add(new Long(33434343));
551: set.add(new Date());
552: set.add(new Double(34343.23));
553: }
554:
555: private void populateList(List list) {
556: list.add("Hey you ");
557: list.add(new Integer(34343));
558: list.add(new Long(33434343));
559: list.add(new Date());
560: list.add(new Double(34343.23));
561: }
562:
563: public static class ExternalSerializer {
564:
565: public static void main(String args[]) {
566: if (isHashMapDSOInstrumented()) {
567: throw new AssertionError(
568: "HashMap is instrumented in the external verifier!");
569: }
570:
571: ExternalSerializer e = new ExternalSerializer();
572: try {
573: e.execute(args);
574: } catch (Throwable t) {
575: t.printStackTrace();
576: System.exit(1);
577: }
578: System.exit(0);
579: }
580:
581: private void execute(String[] args) throws Exception {
582: if (args.length != 2) {
583: throw new AssertionError("Bad args: "
584: + Arrays.asList(args));
585: }
586:
587: File readFrom = new File(args[0]);
588: File writeTo = new File(args[1]);
589:
590: ByteArrayOutputStream readBytes = new ByteArrayOutputStream();
591: FileInputStream in = new FileInputStream(readFrom);
592: IOUtils.copy(in, readBytes);
593: in.close();
594:
595: Object o = deserialize(readBytes.toByteArray(), this
596: .getClass().getClassLoader());
597:
598: FileOutputStream out = new FileOutputStream(writeTo);
599: out.write(serialize(o));
600: out.close();
601: }
602: }
603:
604: public static class MyHashMap extends HashMap {
605: private Object key;
606: private Object value;
607:
608: public MyHashMap() {
609: super ();
610: }
611:
612: public Object getKey() {
613: return key;
614: }
615:
616: public Object getValue() {
617: return value;
618: }
619: }
620:
621: public static class MyHashtable extends Hashtable {
622: private Object key;
623: private Object value;
624:
625: public MyHashtable() {
626: super ();
627: }
628:
629: public Object getKey() {
630: return key;
631: }
632:
633: public Object getValue() {
634: return value;
635: }
636: }
637:
638: public static class MyArrayList extends ArrayList {
639: private Object index;
640:
641: public MyArrayList() {
642: super ();
643: }
644:
645: public Object getIndex() {
646: return index;
647: }
648: }
649:
650: public static class MyHashSet extends HashSet {
651: private Object index;
652:
653: public MyHashSet() {
654: super ();
655: }
656:
657: public Object getIndex() {
658: return index;
659: }
660: }
661:
662: private static class MyObjectInputStream extends ObjectInputStream {
663: private static final Set useCustomLoaderClasses = new HashSet();
664: private ClassLoader loader;
665:
666: static {
667: useCustomLoaderClasses.add(MyHashMap.class.getName());
668: useCustomLoaderClasses.add(MyHashtable.class.getName());
669: useCustomLoaderClasses.add(MyArrayList.class.getName());
670: useCustomLoaderClasses.add(MyHashSet.class.getName());
671: }
672:
673: public MyObjectInputStream(ClassLoader loader)
674: throws IOException {
675: super ();
676: this .loader = loader;
677: }
678:
679: public MyObjectInputStream(InputStream inputStream,
680: ClassLoader loader) throws IOException {
681: super (inputStream);
682: this .loader = loader;
683: }
684:
685: protected Class resolveClass(ObjectStreamClass desc)
686: throws IOException, ClassNotFoundException {
687: if (useCustomLoaderClasses.contains(desc.getName())) {
688: return loader.loadClass(desc.getName());
689: } else {
690: return super.resolveClass(desc);
691: }
692: }
693: }
694:
695: }
|