/* * Licensed to the Apache Software Foundation (ASF) under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional information regarding * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. You may obtain a * copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package org.apache.geode.internal.util; import static org.assertj.core.api.Assertions.*; import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import org.apache.bcel.Constants; import org.apache.bcel.classfile.Field; import org.apache.bcel.classfile.JavaClass; import org.apache.bcel.generic.ClassGen; import org.apache.bcel.generic.ConstantPoolGen; import org.apache.bcel.generic.FieldGen; import org.apache.bcel.generic.InstructionFactory; import org.apache.bcel.generic.InstructionHandle; import org.apache.bcel.generic.InstructionList; import org.apache.bcel.generic.MethodGen; import org.apache.bcel.generic.Type; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; import org.apache.geode.test.junit.categories.UnitTest; /** * Unit tests for {@link BlobHelper} with Thread Context ClassLoader. * * @since GemFire 2.0.2 */ @Category(UnitTest.class) public class BlobHelperWithThreadContextClassLoaderTest { private static final String CLASS_NAME_SERIALIZABLE_IMPL = "org.apache.geode.internal.util.SerializableImpl"; private static final String CLASS_NAME_SERIALIZABLE_IMPL_WITH_VALUE = "org.apache.geode.internal.util.SerializableImplWithValue"; private static final String VALUE = "value"; private static final String SET_VALUE = "setValue"; private static final String GET_VALUE = "getValue"; private ClassLoader oldCCL; @Before public void setUp() throws MalformedURLException { this.oldCCL = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(new GeneratingClassLoader(this.oldCCL)); } @After public void tearDown() { Thread.currentThread().setContextClassLoader(this.oldCCL); } @Test public void tcclLoadsSerializableImpl() throws Exception { Class loadedClass = Class.forName(CLASS_NAME_SERIALIZABLE_IMPL, true, Thread.currentThread().getContextClassLoader()); assertThat(loadedClass).isNotNull(); assertThat(loadedClass.getName()).isEqualTo(CLASS_NAME_SERIALIZABLE_IMPL); Object instance = loadedClass.newInstance(); assertThat(instance).isNotNull(); assertThat(Serializable.class.isInstance(loadedClass)); assertThat(loadedClass.getInterfaces()).contains(Serializable.class); } @Test public void tcclLoadsSerializableImplWithValue() throws Exception { Class loadedClass = Class.forName(CLASS_NAME_SERIALIZABLE_IMPL_WITH_VALUE, true, Thread.currentThread().getContextClassLoader()); assertThat(loadedClass).isNotNull(); assertThat(loadedClass.getName()).isEqualTo(CLASS_NAME_SERIALIZABLE_IMPL_WITH_VALUE); Object instance = loadedClass.newInstance(); assertThat(instance).isNotNull(); assertThat(loadedClass.getSuperclass().getName()).isEqualTo(CLASS_NAME_SERIALIZABLE_IMPL); assertThat(Serializable.class.isInstance(loadedClass)); assertThat(Valuable.class.isInstance(loadedClass)); assertThat(loadedClass.getInterfaces()).contains(Valuable.class); Method setter = loadedClass.getMethod("setValue", Object.class); assertThat(setter).isNotNull(); } /** * Tests serializing an object loaded with the current context class loader (whose parent is the * loader that loads GemFire and test classes). */ @Test public void handlesClassFromOtherClassLoader() throws Exception { Class loadedClass = Class.forName(CLASS_NAME_SERIALIZABLE_IMPL, true, Thread.currentThread().getContextClassLoader()); Object instance = loadedClass.newInstance(); byte[] bytes = BlobHelper.serializeToBlob(instance); Object object = BlobHelper.deserializeBlob(bytes); assertThat(object).isNotNull(); assertThat(object.getClass().getName()).isEqualTo(CLASS_NAME_SERIALIZABLE_IMPL); assertThat(Serializable.class.isInstance(object)); Class deserializedClass = object.getClass(); assertThat(deserializedClass.getInterfaces()).contains(Serializable.class); } /** * Tests that the deserialized object has the correct state */ @Test public void handlesObjectWithStateFromOtherClassLoader() throws Exception { Class loadedClass = Class.forName(CLASS_NAME_SERIALIZABLE_IMPL_WITH_VALUE, true, Thread.currentThread().getContextClassLoader()); Constructor ctor = loadedClass.getConstructor(new Class[] {Object.class}); Valuable instance = (Valuable) ctor.newInstance(new Object[] {123}); assertThat(instance.getValue()).isEqualTo(123); byte[] bytes = BlobHelper.serializeToBlob(instance); Valuable object = (Valuable) BlobHelper.deserializeBlob(bytes); assertThat(object.getValue()).isEqualTo(instance.getValue()); } /** * Custom class loader which uses BCEL to dynamically generate SerializableImpl or * SerializableImplWithValue. */ private static class GeneratingClassLoader extends ClassLoader { private static final String GENERATED = "<generated>"; private final Map<String, Class<?>> classDefinitions; public GeneratingClassLoader(ClassLoader parent) { super(parent); this.classDefinitions = new HashMap<>(); } public GeneratingClassLoader() { this(null); // no parent } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { Class<?> definedClass = null; synchronized (this.classDefinitions) { definedClass = getClass(name); if (definedClass == null) { definedClass = generate(name); this.classDefinitions.put(name, definedClass); } } return definedClass; } @Override protected URL findResource(String name) { return null; } @Override protected Enumeration<URL> findResources(String name) throws IOException { return null; } private Class<?> generate(String name) throws ClassNotFoundException { if (CLASS_NAME_SERIALIZABLE_IMPL.equals(name)) { return generateSerializableImpl(); } else if (CLASS_NAME_SERIALIZABLE_IMPL_WITH_VALUE.equals(name)) { return generateSerializableImplWithValue(); } else { return null; // throw new Error("Unable to generate " + name); } } /** * <pre> * public class SerializableImpl implements Serializable { * * public SerializableImpl() {} * * } * </pre> */ private Class<?> generateSerializableImpl() throws ClassNotFoundException { ClassGen cg = new ClassGen(CLASS_NAME_SERIALIZABLE_IMPL, Object.class.getName(), GENERATED, Constants.ACC_PUBLIC | Constants.ACC_SUPER, new String[] {Serializable.class.getName()}); cg.addEmptyConstructor(Constants.ACC_PUBLIC); JavaClass jClazz = cg.getJavaClass(); byte[] bytes = jClazz.getBytes(); return defineClass(jClazz.getClassName(), bytes, 0, bytes.length); } /** * <pre> * public class SerializableImplWithValue extends SerializableImpl implements Valuable { * * private Object value; * * public SerializableImplWithValue() {} * * public SerializableImplWithValue(Object value) { * this.value = value; * } * * public Object getValue() { * return this.value; * } * * public void setValue(Object value) { * this.value = value; * } * } * </pre> * * @see Valuable */ private Class<?> generateSerializableImplWithValue() throws ClassNotFoundException { ClassGen cg = new ClassGen(CLASS_NAME_SERIALIZABLE_IMPL_WITH_VALUE, CLASS_NAME_SERIALIZABLE_IMPL, GENERATED, Constants.ACC_PUBLIC | Constants.ACC_SUPER, new String[] {Valuable.class.getName()}); ConstantPoolGen cp = cg.getConstantPool(); InstructionFactory fac = new InstructionFactory(cg, cp); // field FieldGen fg = new FieldGen(Constants.ACC_PRIVATE, Type.OBJECT, VALUE, cp); Field field = fg.getField(); cg.addField(field); // empty constructor cg.addEmptyConstructor(Constants.ACC_PUBLIC); // constructor with arg InstructionList ctor = new InstructionList(); MethodGen ctorMethod = new MethodGen(Constants.ACC_PUBLIC, Type.VOID, new Type[] {Type.OBJECT}, new String[] {"arg0"}, "<init>", "org.apache.geode.internal.util.bcel.SerializableImplWithValue", ctor, cp); ctorMethod.setMaxStack(2); InstructionHandle ctor_ih_0 = ctor.append(fac.createLoad(Type.OBJECT, 0)); ctor.append(fac.createInvoke(CLASS_NAME_SERIALIZABLE_IMPL, "<init>", Type.VOID, Type.NO_ARGS, Constants.INVOKESPECIAL)); InstructionHandle ctor_ih_4 = ctor.append(fac.createLoad(Type.OBJECT, 0)); ctor.append(fac.createLoad(Type.OBJECT, 1)); ctor.append(fac.createFieldAccess(CLASS_NAME_SERIALIZABLE_IMPL_WITH_VALUE, "value", Type.OBJECT, Constants.PUTFIELD)); InstructionHandle ctor_ih_9 = ctor.append(fac.createReturn(Type.VOID)); cg.addMethod(ctorMethod.getMethod()); ctor.dispose(); // getter InstructionList getter = new InstructionList(); MethodGen getterMethod = new MethodGen(Constants.ACC_PUBLIC, Type.OBJECT, null, null, GET_VALUE, CLASS_NAME_SERIALIZABLE_IMPL_WITH_VALUE, getter, cp); getterMethod.setMaxStack(1); InstructionHandle getter_ih_0 = getter.append(fac.createLoad(Type.OBJECT, 0)); InstructionHandle getter_ih_1 = getter.append(fac.createGetField(cg.getClassName(), field.getName(), Type.getType(field.getSignature()))); InstructionHandle getter_ih_4 = getter.append(fac.createReturn(Type.OBJECT)); cg.addMethod(getterMethod.getMethod()); getter.dispose(); // setter InstructionList setter = new InstructionList(); MethodGen setterMethod = new MethodGen(Constants.ACC_PUBLIC, Type.VOID, new Type[] {Type.OBJECT}, new String[] {field.getName()}, SET_VALUE, CLASS_NAME_SERIALIZABLE_IMPL_WITH_VALUE, setter, cp); setterMethod.setMaxStack(2); InstructionHandle setter_ih_0 = setter.append(fac.createLoad(Type.OBJECT, 0)); InstructionHandle setter_ih_1 = setter.append(fac.createLoad(Type.OBJECT, 1)); InstructionHandle setter_ih_2 = setter.append(fac.createPutField(cg.getClassName(), field.getName(), Type.getType(field.getSignature()))); InstructionHandle setter_ih_0_ih_5 = setter.append(fac.createReturn(Type.VOID)); cg.addMethod(setterMethod.getMethod()); setter.dispose(); JavaClass jClazz = cg.getJavaClass(); byte[] bytes = jClazz.getBytes(); return defineClass(jClazz.getClassName(), bytes, 0, bytes.length); } private Class<?> getClass(String name) { synchronized (this.classDefinitions) { return this.classDefinitions.get(name); } } } }