/* * Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved. * * Licensed 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 com.hazelcast.nio.serialization; import com.hazelcast.config.GlobalSerializerConfig; import com.hazelcast.config.SerializationConfig; import com.hazelcast.config.SerializerConfig; import com.hazelcast.core.Member; import com.hazelcast.core.MemberLeftException; import com.hazelcast.core.PartitioningStrategy; import com.hazelcast.instance.MemberImpl; import com.hazelcast.instance.SimpleMemberImpl; import com.hazelcast.internal.serialization.impl.DefaultSerializationServiceBuilder; import com.hazelcast.internal.serialization.impl.HeapData; import com.hazelcast.internal.serialization.impl.JavaDefaultSerializers.JavaSerializer; import com.hazelcast.nio.Address; import com.hazelcast.nio.IOUtil; import com.hazelcast.nio.ObjectDataInput; import com.hazelcast.nio.ObjectDataOutput; import com.hazelcast.nio.serialization.SerializationConcurrencyTest.Person; import com.hazelcast.spi.serialization.SerializationService; import com.hazelcast.test.HazelcastSerialClassRunner; import com.hazelcast.test.HazelcastTestSupport; import com.hazelcast.test.annotation.QuickTest; import com.hazelcast.util.UuidUtil; import com.hazelcast.version.MemberVersion; import com.hazelcast.version.Version; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.Externalizable; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInput; import java.io.ObjectInputStream; import java.io.ObjectOutput; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.Properties; import java.util.Random; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import static com.hazelcast.instance.BuildInfoProvider.BUILD_INFO; import static java.util.Arrays.asList; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @RunWith(HazelcastSerialClassRunner.class) @Category(QuickTest.class) public class SerializationTest extends HazelcastTestSupport { @Test public void testGlobalSerializer_withOverrideJavaSerializable() { GlobalSerializerConfig globalSerializerConfig = new GlobalSerializerConfig(); globalSerializerConfig.setOverrideJavaSerialization(true); final AtomicInteger writeCounter = new AtomicInteger(); final AtomicInteger readCounter = new AtomicInteger(); final JavaSerializer javaSerializer = new JavaSerializer(true, false); SerializationConfig serializationConfig = new SerializationConfig().setGlobalSerializerConfig( globalSerializerConfig.setImplementation(new StreamSerializer<Object>() { @Override public void write(ObjectDataOutput out, Object v) throws IOException { writeCounter.incrementAndGet(); if (v instanceof Serializable) { out.writeBoolean(true); javaSerializer.write(out, v); } else if (v instanceof DummyValue) { out.writeBoolean(false); out.writeUTF(((DummyValue) v).s); out.writeInt(((DummyValue) v).k); } } @Override public Object read(ObjectDataInput in) throws IOException { readCounter.incrementAndGet(); boolean java = in.readBoolean(); if (java) { return javaSerializer.read(in); } return new DummyValue(in.readUTF(), in.readInt()); } public int getTypeId() { return 123; } public void destroy() { } })); SerializationService ss1 = new DefaultSerializationServiceBuilder().setConfig(serializationConfig).build(); DummyValue value = new DummyValue("test", 111); Data data1 = ss1.toData(value); Data data2 = ss1.toData(new Foo()); Assert.assertNotNull(data1); Assert.assertNotNull(data2); assertEquals(2, writeCounter.get()); SerializationService ss2 = new DefaultSerializationServiceBuilder().setConfig(serializationConfig).build(); Object o1 = ss2.toObject(data1); Object o2 = ss2.toObject(data2); Assert.assertEquals(value, o1); Assert.assertNotNull(o2); assertEquals(2, readCounter.get()); } @Test public void testGlobalSerializer_withoutOverrideJavaSerializable() { GlobalSerializerConfig globalSerializerConfig = new GlobalSerializerConfig(); globalSerializerConfig.setOverrideJavaSerialization(false); final AtomicInteger writeCounter = new AtomicInteger(); final AtomicInteger readCounter = new AtomicInteger(); SerializationConfig serializationConfig = new SerializationConfig().setGlobalSerializerConfig( globalSerializerConfig.setImplementation(new StreamSerializer<Object>() { @Override public void write(ObjectDataOutput out, Object v) throws IOException { writeCounter.incrementAndGet(); out.writeUTF(((DummyValue) v).s); out.writeInt(((DummyValue) v).k); } @Override public Object read(ObjectDataInput in) throws IOException { readCounter.incrementAndGet(); return new DummyValue(in.readUTF(), in.readInt()); } public int getTypeId() { return 123; } public void destroy() { } })); SerializationService ss1 = new DefaultSerializationServiceBuilder().setConfig(serializationConfig).build(); DummyValue value = new DummyValue("test", 111); Data data1 = ss1.toData(value); Data data2 = ss1.toData(new Foo()); Assert.assertNotNull(data1); Assert.assertNotNull(data2); assertEquals(1, writeCounter.get()); SerializationService ss2 = new DefaultSerializationServiceBuilder().setConfig(serializationConfig).build(); Object o1 = ss2.toObject(data1); Object o2 = ss2.toObject(data2); Assert.assertEquals(value, o1); Assert.assertNotNull(o2); assertEquals(1, readCounter.get()); } private static class DummyValue { String s; int k; private DummyValue(String s, int k) { this.s = s; this.k = k; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } DummyValue that = (DummyValue) o; if (k != that.k) { return false; } if (s != null ? !s.equals(that.s) : that.s != null) { return false; } return true; } @Override public int hashCode() { int result = s != null ? s.hashCode() : 0; result = 31 * result + k; return result; } } @Test public void testEmptyData() { SerializationConfig serializationConfig = new SerializationConfig().addSerializerConfig( new SerializerConfig().setTypeClass(SingletonValue.class) .setImplementation(new StreamSerializer<SingletonValue>() { @Override public void write(ObjectDataOutput out, SingletonValue v) throws IOException { } @Override public SingletonValue read(ObjectDataInput in) throws IOException { return new SingletonValue(); } @Override public int getTypeId() { return 123; } @Override public void destroy() { } })); SerializationService ss1 = new DefaultSerializationServiceBuilder().setConfig(serializationConfig).build(); Data data = ss1.toData(new SingletonValue()); Assert.assertNotNull(data); SerializationService ss2 = new DefaultSerializationServiceBuilder().setConfig(serializationConfig).build(); Object o = ss2.toObject(data); Assert.assertEquals(new SingletonValue(), o); } private static class SingletonValue { public boolean equals(Object obj) { return obj instanceof SingletonValue; } } @Test public void testNullData() { Data data = new HeapData(); SerializationService ss = new DefaultSerializationServiceBuilder().build(); assertNull(ss.toObject(data)); } /** * issue #1265 */ @Test public void testSharedJavaSerialization() { SerializationService ss = new DefaultSerializationServiceBuilder().setEnableSharedObject(true).build(); Data data = ss.toData(new Foo()); Foo foo = (Foo) ss.toObject(data); assertTrue("Objects are not identical!", foo == foo.getBar().getFoo()); } @Test public void testLinkedListSerialization() { SerializationService ss = new DefaultSerializationServiceBuilder().build(); LinkedList<Person> linkedList = new LinkedList<Person>(); linkedList.add(new Person(35, 180, 100, "Orhan", null)); linkedList.add(new Person(12, 120, 60, "Osman", null)); Data data = ss.toData(linkedList); LinkedList deserialized = ss.toObject(data); assertTrue("Objects are not identical!", linkedList.equals(deserialized)); } @Test public void testArrayListSerialization() { SerializationService ss = new DefaultSerializationServiceBuilder().build(); ArrayList<Person> arrayList = new ArrayList<Person>(); arrayList.add(new Person(35, 180, 100, "Orhan", null)); arrayList.add(new Person(12, 120, 60, "Osman", null)); Data data = ss.toData(arrayList); ArrayList deserialized = ss.toObject(data); assertTrue("Objects are not identical!", arrayList.equals(deserialized)); } @Test public void testArraySerialization() { SerializationService ss = new DefaultSerializationServiceBuilder().build(); byte[] array = new byte[1024]; new Random().nextBytes(array); Data data = ss.toData(array); byte[] deserialized = ss.toObject(data); assertArrayEquals(array, deserialized); } @Test public void testPartitionHash() { PartitioningStrategy partitionStrategy = new PartitioningStrategy() { @Override public Object getPartitionKey(Object key) { return key.hashCode(); } }; SerializationService ss = new DefaultSerializationServiceBuilder().build(); String obj = String.valueOf(System.nanoTime()); Data dataWithPartitionHash = ss.toData(obj, partitionStrategy); Data dataWithOutPartitionHash = ss.toData(obj); assertTrue(dataWithPartitionHash.hasPartitionHash()); assertNotEquals(dataWithPartitionHash.hashCode(), dataWithPartitionHash.getPartitionHash()); assertFalse(dataWithOutPartitionHash.hasPartitionHash()); assertEquals(dataWithOutPartitionHash.hashCode(), dataWithOutPartitionHash.getPartitionHash()); } /** * issue #1265 */ @Test public void testUnsharedJavaSerialization() { SerializationService ss = new DefaultSerializationServiceBuilder().setEnableSharedObject(false).build(); Data data = ss.toData(new Foo()); Foo foo = ss.toObject(data); Assert.assertFalse("Objects should not be identical!", foo == foo.getBar().getFoo()); } private static class Foo implements Serializable { public Bar bar; public Foo() { this.bar = new Bar(); } public Bar getBar() { return bar; } private class Bar implements Serializable { public Foo getFoo() { return Foo.this; } } } /** * Ensures that SerializationService correctly handles compressed Serializables, * using a Properties object as a test case. */ @Test public void testCompressionOnSerializables() { SerializationService serializationService = new DefaultSerializationServiceBuilder() .setEnableCompression(true) .build(); long key = 1, value = 5000; Properties properties = new Properties(); properties.put(key, value); Data data = serializationService.toData(properties); Properties output = serializationService.toObject(data); assertEquals(value, output.get(key)); } /** * Ensures that SerializationService correctly handles compressed Serializables, * using a test-specific object as a test case. */ @Test public void testCompressionOnExternalizables() { SerializationService serializationService = new DefaultSerializationServiceBuilder() .setEnableCompression(true) .build(); String test = "test"; ExternalizableString ex = new ExternalizableString(test); Data data = serializationService.toData(ex); ExternalizableString actual = serializationService.toObject(data); assertEquals(test, actual.value); } private static class ExternalizableString implements Externalizable { String value; public ExternalizableString() { } public ExternalizableString(String value) { this.value = value; } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeUTF(value); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { value = in.readUTF(); } } @Test public void testMemberLeftException_usingMemberImpl() throws Exception { String uuid = UuidUtil.newUnsecureUuidString(); String host = "127.0.0.1"; int port = 5000; Member member = new MemberImpl(new Address(host, port), MemberVersion.of("3.8.0"), false, uuid); testMemberLeftException(uuid, host, port, member); } @Test public void testMemberLeftException_usingSimpleMember() throws Exception { String uuid = UuidUtil.newUnsecureUuidString(); String host = "127.0.0.1"; int port = 5000; Member member = new SimpleMemberImpl(MemberVersion.of("3.8.0"), uuid, new InetSocketAddress(host, port)); testMemberLeftException(uuid, host, port, member); } @Test public void testMemberLeftException_withLiteMemberImpl() throws Exception { String uuid = UuidUtil.newUnsecureUuidString(); String host = "127.0.0.1"; int port = 5000; Member member = new MemberImpl(new Address(host, port), MemberVersion.of("3.8.0"), false, uuid, null, true); testMemberLeftException(uuid, host, port, member); } @Test public void testMemberLeftException_withLiteSimpleMemberImpl() throws Exception { String uuid = UuidUtil.newUnsecureUuidString(); String host = "127.0.0.1"; int port = 5000; Member member = new SimpleMemberImpl(MemberVersion.of("3.8.0"), uuid, new InetSocketAddress(host, port), true); testMemberLeftException(uuid, host, port, member); } private void testMemberLeftException(String uuid, String host, int port, Member member) throws Exception { MemberLeftException exception = new MemberLeftException(member); ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bout); out.writeObject(exception); ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray()); ObjectInputStream in = new ObjectInputStream(bin); MemberLeftException exception2 = (MemberLeftException) in.readObject(); MemberImpl member2 = (MemberImpl) exception2.getMember(); assertEquals(uuid, member2.getUuid()); assertEquals(host, member2.getAddress().getHost()); assertEquals(port, member2.getAddress().getPort()); assertEquals(member.isLiteMember(), member2.isLiteMember()); assertEquals(member.getVersion(), member2.getVersion()); } @Test public void testInternallySupportedClassExtended() { SerializationService ss = new DefaultSerializationServiceBuilder().build(); TheClassThatExtendArrayList obj = new TheClassThatExtendArrayList(); Data data = ss.toData(obj); Object obj2 = ss.toObject(data); assertEquals(obj2.getClass(), TheClassThatExtendArrayList.class); } static class TheClassThatExtendArrayList<E> extends ArrayList<E> implements DataSerializable { @Override public void writeData(ObjectDataOutput out) throws IOException { out.writeInt(size()); for (Object item : this) { out.writeObject(item); } } @Override @SuppressWarnings("unchecked") public void readData(ObjectDataInput in) throws IOException { int size = in.readInt(); for (int k = 0; k < size; k++) { add((E) in.readObject()); } } } @Test public void testDynamicProxySerialization_withConfiguredClassLoader() { ClassLoader current = getClass().getClassLoader(); DynamicProxyTestClassLoader cl = new DynamicProxyTestClassLoader(current); SerializationService ss = new DefaultSerializationServiceBuilder().setClassLoader(cl).build(); IObjectA oa = (IObjectA) Proxy.newProxyInstance(current, new Class[]{IObjectA.class}, DummyInvocationHandler.INSTANCE); Data data = ss.toData(oa); Object o = ss.toObject(data); Assert.assertSame("configured classloader is not used", cl, o.getClass().getClassLoader()); try { IObjectA.class.cast(o); Assert.fail("the serialized object should not be castable"); } catch (ClassCastException expected) { // expected } } @Test public void testDynamicProxySerialization_withContextClassLoader() { ClassLoader oldContextLoader = Thread.currentThread().getContextClassLoader(); try { ClassLoader current = getClass().getClassLoader(); DynamicProxyTestClassLoader cl = new DynamicProxyTestClassLoader(current); Thread.currentThread().setContextClassLoader(cl); SerializationService ss = new DefaultSerializationServiceBuilder().setClassLoader(cl).build(); IObjectA oa = (IObjectA) Proxy.newProxyInstance(current, new Class[]{IObjectA.class}, DummyInvocationHandler.INSTANCE); Data data = ss.toData(oa); Object o = ss.toObject(data); Assert.assertSame("context classloader is not used", cl, o.getClass().getClassLoader()); try { IObjectA.class.cast(o); Assert.fail("the serialized object should not be castable"); } catch (ClassCastException expected) { // expected } } finally { Thread.currentThread().setContextClassLoader(oldContextLoader); } } @Test public void testNonPublicDynamicProxySerialization_withClassLoaderMess() { ClassLoader current = getClass().getClassLoader(); DynamicProxyTestClassLoader cl1 = new DynamicProxyTestClassLoader(current, IPrivateObjectB.class.getName()); DynamicProxyTestClassLoader cl2 = new DynamicProxyTestClassLoader(cl1, IPrivateObjectC.class.getName()); SerializationService ss = new DefaultSerializationServiceBuilder().setClassLoader(cl2).build(); Object ocd = Proxy.newProxyInstance(current, new Class[]{IPrivateObjectB.class, IPrivateObjectC.class}, DummyInvocationHandler.INSTANCE); Data data = ss.toData(ocd); try { ss.toObject(data); Assert.fail("the object should not be deserializable"); } catch (IllegalAccessError expected) { // expected } } @Test public void testVersionedDataSerializable_outputHasMemberVersion() { SerializationService ss = new DefaultSerializationServiceBuilder().build(); VersionedDataSerializable object = new VersionedDataSerializable(); ss.toData(object); assertEquals("ObjectDataOutput.getVersion should be equal to member version", Version.of(BUILD_INFO.getVersion()), object.getVersion()); } @Test public void testVersionedDataSerializable_inputHasMemberVersion() { SerializationService ss = new DefaultSerializationServiceBuilder().build(); VersionedDataSerializable object = new VersionedDataSerializable(); VersionedDataSerializable otherObject = ss.toObject(ss.toData(object)); assertEquals("ObjectDataInput.getVersion should be equal to member version", Version.of(BUILD_INFO.getVersion()), otherObject.getVersion()); } private static final class DynamicProxyTestClassLoader extends ClassLoader { private static final Set<String> WELL_KNOWN_TEST_CLASSES = new HashSet<String>(asList(IObjectA.class.getName(), IPrivateObjectB.class.getName(), IPrivateObjectC.class.getName())); private final Set<String> wellKnownClasses = new HashSet<String>(); private DynamicProxyTestClassLoader(ClassLoader parent, String... classesToLoad) { super(parent); if (classesToLoad.length == 0) { wellKnownClasses.addAll(WELL_KNOWN_TEST_CLASSES); } else { wellKnownClasses.addAll(asList(classesToLoad)); } } @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { if (!WELL_KNOWN_TEST_CLASSES.contains(name)) { return super.loadClass(name, resolve); } synchronized (this) { // first, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { c = findClass(name); } if (resolve) { resolveClass(c); } return c; } } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { if (!wellKnownClasses.contains(name)) { return super.findClass(name); } String path = name.replace('.', '/') + ".class"; InputStream in = null; try { in = getParent().getResourceAsStream(path); ByteArrayOutputStream bout = new ByteArrayOutputStream(); byte[] buf = new byte[1024]; int read; while ((read = in.read(buf)) != -1) { bout.write(buf, 0, read); } byte[] code = bout.toByteArray(); return defineClass(name, code, 0, code.length); } catch (IOException e) { return super.findClass(name); } finally { IOUtil.closeResource(in); } } } public static final class DummyInvocationHandler implements InvocationHandler, Serializable { private static final long serialVersionUID = 3459316091095397098L; private static final DummyInvocationHandler INSTANCE = new DummyInvocationHandler(); @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } } @SuppressWarnings("unused") public interface IObjectA { void doA(); } @SuppressWarnings("unused") interface IPrivateObjectB { void doC(); } @SuppressWarnings("unused") interface IPrivateObjectC { void doD(); } }