Skip to content
Permalink
Browse files
IGNITE-16258 Support Serializable lambdas marshalling in User Object …
…Serialization
  • Loading branch information
rpuch committed Jan 19, 2022
1 parent 783a975 commit 4f8f7aef8b475381cd23bc2865a3986472679d40
Showing 15 changed files with 330 additions and 142 deletions.
@@ -79,7 +79,8 @@ public enum BuiltInType {
LINKED_HASH_MAP(41, LinkedHashMap.class),
BIT_SET(42, BitSet.class),
NULL(43, Null.class),
REFERENCE(44, DummyPlaceholder.class)
REFERENCE(44, DummyPlaceholder.class),
CLASS(45, Class.class)
;

/**
@@ -19,79 +19,77 @@

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.Objects;
import org.jetbrains.annotations.Nullable;

/**
* Encapsulates special serialization methods like writeReplace()/readResolve() for convenient invocation.
* Encapsulates special serialization methods like writeReplace()/readResolve() and so on for convenient invocation.
*/
class SpecialSerializationMethodsImpl implements SpecialSerializationMethods {
/** MethodHandle that can be used to invoke writeReplace() on the target class. */
/** Method that can be used to invoke writeReplace() on the target class. */
@Nullable
private final MethodHandle writeReplaceHandle;
private final Method writeReplace;

/** MethodHandle that can be used to invoke readResolve() on the target class. */
/** Method that can be used to invoke readResolve() on the target class. */
@Nullable
private final MethodHandle readResolveHandle;
private final Method readResolve;

/** MethodHandle that can be used to invoke writeObject() on the target class. */
/** Method that can be used to invoke writeObject() on the target class. */
@Nullable
private final MethodHandle writeObjectHandle;
private final Method writeObject;

/** MethodHandle that can be used to invoke readObject() on the target class. */
/** Method that can be used to invoke readObject() on the target class. */
@Nullable
private final MethodHandle readObjectHandle;
private final Method readObject;

/**
* Creates a new instance from the provided descriptor.
*
* @param descriptor class descriptor on which class to operate
*/
public SpecialSerializationMethodsImpl(ClassDescriptor descriptor) {
writeReplaceHandle = descriptor.hasWriteReplace() ? writeReplaceHandle(descriptor) : null;
readResolveHandle = descriptor.hasReadResolve() ? readResolveHandle(descriptor) : null;
writeObjectHandle = descriptor.hasWriteObject() ? writeObjectHandle(descriptor) : null;
readObjectHandle = descriptor.hasReadObject() ? readObjectHandle(descriptor) : null;
writeReplace = descriptor.hasWriteReplace() ? writeReplaceInvoker(descriptor) : null;
readResolve = descriptor.hasReadResolve() ? readResolveInvoker(descriptor) : null;
writeObject = descriptor.hasWriteObject() ? writeObjectInvoker(descriptor) : null;
readObject = descriptor.hasReadObject() ? readObjectInvoker(descriptor) : null;
}

private static MethodHandle writeReplaceHandle(ClassDescriptor descriptor) {
private static Method writeReplaceInvoker(ClassDescriptor descriptor) {
try {
return MethodHandles.privateLookupIn(descriptor.clazz(), MethodHandles.lookup())
.findVirtual(descriptor.clazz(), "writeReplace", MethodType.methodType(Object.class))
.asType(MethodType.methodType(Object.class, Object.class));
Method method = descriptor.clazz().getDeclaredMethod("writeReplace");
method.setAccessible(true);
return method;
} catch (ReflectiveOperationException e) {
throw new ReflectionException("Cannot find writeReplace() in " + descriptor.clazz(), e);
}
}

private static MethodHandle readResolveHandle(ClassDescriptor descriptor) {
private static Method readResolveInvoker(ClassDescriptor descriptor) {
try {
return MethodHandles.privateLookupIn(descriptor.clazz(), MethodHandles.lookup())
.findVirtual(descriptor.clazz(), "readResolve", MethodType.methodType(Object.class))
.asType(MethodType.methodType(Object.class, Object.class));
Method method = descriptor.clazz().getDeclaredMethod("readResolve");
method.setAccessible(true);
return method;
} catch (ReflectiveOperationException e) {
throw new ReflectionException("Cannot find readResolve() in " + descriptor.clazz(), e);
}
}

private static MethodHandle writeObjectHandle(ClassDescriptor descriptor) {
private static Method writeObjectInvoker(ClassDescriptor descriptor) {
try {
return MethodHandles.privateLookupIn(descriptor.clazz(), MethodHandles.lookup())
.findVirtual(descriptor.clazz(), "writeObject", MethodType.methodType(void.class, ObjectOutputStream.class))
.asType(MethodType.methodType(void.class, Object.class, ObjectOutputStream.class));
Method method = descriptor.clazz().getDeclaredMethod("writeObject", ObjectOutputStream.class);
method.setAccessible(true);
return method;
} catch (ReflectiveOperationException e) {
throw new ReflectionException("Cannot find writeObject() in " + descriptor.clazz(), e);
}
}

private static MethodHandle readObjectHandle(ClassDescriptor descriptor) {
private static Method readObjectInvoker(ClassDescriptor descriptor) {
try {
return MethodHandles.privateLookupIn(descriptor.clazz(), MethodHandles.lookup())
.findVirtual(descriptor.clazz(), "readObject", MethodType.methodType(void.class, ObjectInputStream.class))
.asType(MethodType.methodType(void.class, Object.class, ObjectInputStream.class));
Method method = descriptor.clazz().getDeclaredMethod("readObject", ObjectInputStream.class);
method.setAccessible(true);
return method;
} catch (ReflectiveOperationException e) {
throw new ReflectionException("Cannot find readObject() in " + descriptor.clazz(), e);
}
@@ -100,55 +98,47 @@ private static MethodHandle readObjectHandle(ClassDescriptor descriptor) {
/** {@inheritDoc} */
@Override
public Object writeReplace(Object object) throws SpecialMethodInvocationException {
Objects.requireNonNull(writeReplaceHandle);
Objects.requireNonNull(writeReplace);

try {
return writeReplaceHandle.invokeExact(object);
} catch (Error e) {
throw e;
} catch (Throwable e) {
return writeReplace.invoke(object, (Object[]) null);
} catch (ReflectiveOperationException e) {
throw new SpecialMethodInvocationException("writeReplace() invocation failed on " + object, e);
}
}

/** {@inheritDoc} */
@Override
public Object readResolve(Object object) throws SpecialMethodInvocationException {
Objects.requireNonNull(readResolveHandle);
Objects.requireNonNull(readResolve);

try {
return readResolveHandle.invokeExact(object);
} catch (Error e) {
throw e;
} catch (Throwable e) {
return readResolve.invoke(object, (Object[]) null);
} catch (ReflectiveOperationException e) {
throw new SpecialMethodInvocationException("readResolve() invocation failed on " + object, e);
}
}

/** {@inheritDoc} */
@Override
public void writeObject(Object object, ObjectOutputStream stream) throws SpecialMethodInvocationException {
Objects.requireNonNull(writeObjectHandle);
Objects.requireNonNull(writeObject);

try {
writeObjectHandle.invokeExact(object, stream);
} catch (Error e) {
throw e;
} catch (Throwable e) {
writeObject.invoke(object, stream);
} catch (ReflectiveOperationException e) {
throw new SpecialMethodInvocationException("writeObject() invocation failed on " + object, e);
}
}

/** {@inheritDoc} */
@Override
public void readObject(Object object, ObjectInputStream stream) throws SpecialMethodInvocationException {
Objects.requireNonNull(readObjectHandle);
Objects.requireNonNull(readObject);

try {
readObjectHandle.invokeExact(object, stream);
} catch (Error e) {
throw e;
} catch (Throwable e) {
readObject.invoke(object, stream);
} catch (ReflectiveOperationException e) {
throw new SpecialMethodInvocationException("readObject() invocation failed on " + object, e);
}
}
@@ -83,7 +83,7 @@ <T> T[] preInstantiateGenericRefArray(DataInput input) throws IOException, Unmar

<T> void fillGenericRefArray(DataInputStream input, T[] array, ValueReader<T> elementReader, UnmarshallingContext context)
throws IOException, UnmarshalException {
BuiltInMarshalling.fillGenericRefArray(input, array, elementReader, context);
BuiltInMarshalling.fillGenericRefArrayFrom(input, array, elementReader, context);
}

void writeBuiltInCollection(Collection<?> object, ClassDescriptor descriptor, DataOutputStream output, MarshallingContext context)
@@ -229,11 +229,11 @@ static char[] readCharArray(DataInput input) throws IOException {
}

static void writeBigDecimal(BigDecimal object, DataOutput output) throws IOException {
output.writeUTF(object.toString());
writeString(object.toString(), output);
}

static BigDecimal readBigDecimal(DataInput input) throws IOException {
return new BigDecimal(input.readUTF());
return new BigDecimal(readString(input));
}

static void writeEnum(Enum<?> object, DataOutput output) throws IOException {
@@ -245,40 +245,45 @@ static void writeEnum(Enum<?> object, DataOutput output) throws IOException {

assert enumClass.getSuperclass() == Enum.class;

output.writeUTF(enumClass.getName());
output.writeUTF(object.name());
writeClass(enumClass, output);
writeString(object.name(), output);
}

@SuppressWarnings("unchecked")
static <T extends Enum<T>> Enum<?> readEnum(DataInput input) throws IOException, UnmarshalException {
String enumClassName = input.readUTF();
Class<T> enumClass = enumClass(enumClassName);
Class<T> enumClass = (Class<T>) readClass(input);
return Enum.valueOf(enumClass, input.readUTF());
}

private static <T extends Enum<T>> Class<T> enumClass(String className) throws UnmarshalException {
return classByName(className, "enum");
}

@NotNull
private static <T> Class<T> classByName(String className, String classKind) throws UnmarshalException {
private static <T> Class<T> classByName(String className) throws UnmarshalException {
try {
// TODO: what classloader to use?
@SuppressWarnings("unchecked") Class<T> castedClass = (Class<T>) Class.forName(className);
return castedClass;
} catch (ClassNotFoundException e) {
throw new UnmarshalException("Can not load " + classKind + " class: " + className, e);
throw new UnmarshalException("Can not load a class: " + className, e);
}
}

static <T> void writeRefArray(T[] array, DataOutputStream output, ValueWriter<T> valueWriter, MarshallingContext context)
static void writeClass(Class<?> classToWrite, DataOutput output) throws IOException {
writeString(classToWrite.getName(), output);
}

static Class<?> readClass(DataInput input) throws IOException, UnmarshalException {
String className = readString(input);
return classByName(className);
}

private static <T> void writeRefArray(T[] array, DataOutputStream output, ValueWriter<T> valueWriter, MarshallingContext context)
throws IOException, MarshalException {
writeLength(array.length, output);
for (T object : array) {
valueWriter.write(object, output, context);
}
}

static <T> T[] readRefArray(
private static <T> T[] readRefArray(
DataInputStream input,
IntFunction<T[]> arrayFactory,
ValueReader<T> valueReader,
@@ -301,8 +306,7 @@ private static <T> void fillRefArrayFrom(DataInputStream input, T[] array, Value

@SuppressWarnings("unchecked")
private static <T> IntFunction<T[]> readTypeAndCreateArrayFactory(DataInput input) throws IOException, UnmarshalException {
String componentClassName = input.readUTF();
Class<T> componentType = classByName(componentClassName, "component");
Class<T> componentType = (Class<T>) readClass(input);
return len -> (T[]) Array.newInstance(componentType, len);
}

@@ -312,7 +316,7 @@ static <T> T[] preInstantiateGenericRefArray(DataInput input) throws IOException
return arrayFactory.apply(length);
}

static <T> void fillGenericRefArray(DataInputStream input, T[] array, ValueReader<T> elementReader, UnmarshallingContext context)
static <T> void fillGenericRefArrayFrom(DataInputStream input, T[] array, ValueReader<T> elementReader, UnmarshallingContext context)
throws IOException, UnmarshalException {
fillRefArrayFrom(input, array, elementReader, context);
}
@@ -335,13 +339,13 @@ static BigDecimal[] readBigDecimalArray(DataInputStream input, UnmarshallingCont
}

static void writeEnumArray(Enum<?>[] array, DataOutputStream output, MarshallingContext context) throws IOException, MarshalException {
output.writeUTF(array.getClass().getComponentType().getName());
writeClass(array.getClass().getComponentType(), output);
writeRefArray(array, output, enumWriter, context);
}

@SuppressWarnings("unchecked")
static Enum<?>[] readEnumArray(DataInputStream input, UnmarshallingContext context) throws IOException, UnmarshalException {
String enumClassName = input.readUTF();
Class<? extends Enum<?>> enumClass = enumClass(enumClassName);
Class<? extends Enum<?>> enumClass = (Class<? extends Enum<?>>) readClass(input);
return readRefArray(input, len -> (Enum<?>[]) Array.newInstance(enumClass, len), enumReader, context);
}

@@ -69,6 +69,7 @@ private static Map<Class<?>, BuiltInMarshaller<?>> createBuiltInMarshallers() {
addSingle(map, Enum[].class, BuiltInMarshalling::writeEnumArray, BuiltInMarshalling::readEnumArray);
addSingle(map, BitSet.class, BuiltInMarshalling::writeBitSet, BuiltInMarshalling::readBitSet);
addSingle(map, Null.class, (obj, output) -> {}, input -> null);
addSingle(map, Class.class, BuiltInMarshalling::writeClass, BuiltInMarshalling::readClass);

return Map.copyOf(map);
}
@@ -0,0 +1,48 @@
/*
* 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.ignite.internal.network.serialization.marshal;

import java.io.Serializable;

/**
* Utilities to work with classes.
*/
class Classes {
static boolean isSerializable(Class<?> objectClass) {
return Serializable.class.isAssignableFrom(objectClass);
}

static boolean isLambda(Class<?> objectClass) {
return !objectClass.isPrimitive() && !objectClass.isArray()
&& !objectClass.isAnonymousClass() && !objectClass.isLocalClass()
&& objectClass.isSynthetic()
&& classCannotBeLoadedByName(objectClass);
}

private static boolean classCannotBeLoadedByName(Class<?> objectClass) {
try {
Class.forName(objectClass.getName());
return false;
} catch (ClassNotFoundException e) {
return true;
}
}

private Classes() {
}
}
@@ -134,10 +134,14 @@ private void throwIfMarshallingNotSupported(@Nullable Object object) {

Class<?> objectClass = object.getClass();
if (isInnerClass(objectClass)) {
throw new IllegalArgumentException("Non-static inner class instances are not supported for marshalling: " + objectClass);
throw new MarshallingNotSupportedException("Non-static inner class instances are not supported for marshalling: "
+ objectClass);
}
if (isCapturingClosure(objectClass)) {
throw new IllegalArgumentException("Capturing nested class instances are not supported for marshalling: " + object);
throw new MarshallingNotSupportedException("Capturing nested class instances are not supported for marshalling: " + object);
}
if (Classes.isLambda(objectClass) && !Classes.isSerializable(objectClass)) {
throw new MarshallingNotSupportedException("Non-serializable lambda instances are not supported for marshalling: " + object);
}
}

0 comments on commit 4f8f7ae

Please sign in to comment.