You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sd...@apache.org on 2022/05/12 14:04:02 UTC
[ignite-3] branch main updated: IGNITE-16921 Support schema changes concerning inheritance hierarchy (#802)
This is an automated email from the ASF dual-hosted git repository.
sdanilov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new 154447cf6 IGNITE-16921 Support schema changes concerning inheritance hierarchy (#802)
154447cf6 is described below
commit 154447cf6c202b22b11ed98c0191e2f069ebd7c4
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Thu May 12 18:03:57 2022 +0400
IGNITE-16921 Support schema changes concerning inheritance hierarchy (#802)
---
.../network/serialization/BrokenFieldAccessor.java | 11 +-
.../serialization/BrokenSerializationMethods.java | 57 +++++
.../network/serialization/ClassDescriptor.java | 92 +++++++-
.../serialization/ClassDescriptorMerger.java | 57 +++++
...ClassNameMapBackedClassIndexedDescriptors.java} | 10 +-
.../network/serialization/FieldAccessor.java | 2 +-
.../network/serialization/FieldDescriptor.java | 41 +++-
.../network/serialization/MergedLayer.java | 99 +++++++++
.../PerSessionSerializationService.java | 141 ++++++++----
.../serialization/SerializationException.java | 4 +-
.../marshal/DefaultUserObjectMarshaller.java | 13 +-
.../marshal/SchemaMismatchEventSource.java | 11 +
.../marshal/SchemaMismatchHandlers.java | 36 +--
.../marshal/StructuredObjectMarshaller.java | 54 +++--
.../serialization/ClassDescriptorMergerTest.java | 181 +++++++++++++++
...sNameMapBackedClassIndexedDescriptorsTest.java} | 8 +-
...ltUserObjectMarshallerWithSchemaChangeTest.java | 242 ++++++++++++++++++++-
17 files changed, 932 insertions(+), 127 deletions(-)
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/BrokenFieldAccessor.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/BrokenFieldAccessor.java
index 1cf9c6575..41e766d35 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/BrokenFieldAccessor.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/BrokenFieldAccessor.java
@@ -22,11 +22,11 @@ package org.apache.ignite.internal.network.serialization;
*/
class BrokenFieldAccessor implements FieldAccessor {
private final String fieldName;
- private final Class<?> declaringClass;
+ private final String declaringClassName;
- BrokenFieldAccessor(String fieldName, Class<?> declaringClass) {
+ BrokenFieldAccessor(String fieldName, String declaringClassName) {
this.fieldName = fieldName;
- this.declaringClass = declaringClass;
+ this.declaringClassName = declaringClassName;
}
/** {@inheritDoc} */
@@ -137,9 +137,8 @@ class BrokenFieldAccessor implements FieldAccessor {
throw brokenException();
}
- private RuntimeException brokenException() {
- return new IllegalStateException("Field " + declaringClass.getName() + "#" + fieldName
+ private IllegalStateException brokenException() {
+ return new IllegalStateException("Field " + declaringClassName + "#" + fieldName
+ " is missing locally, no accesses to it should be performed, this is a bug");
}
-
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/BrokenSerializationMethods.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/BrokenSerializationMethods.java
new file mode 100644
index 000000000..c1b64adce
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/BrokenSerializationMethods.java
@@ -0,0 +1,57 @@
+/*
+ * 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;
+
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * {@link SpecialSerializationMethods} implementation that always throws.
+ */
+class BrokenSerializationMethods implements SpecialSerializationMethods {
+ private final String missingClassName;
+
+ BrokenSerializationMethods(String missingClassName) {
+ this.missingClassName = missingClassName;
+ }
+
+ @Override
+ public Object writeReplace(Object object) throws SpecialMethodInvocationException {
+ throw brokenException();
+ }
+
+ @Override
+ public Object readResolve(Object object) throws SpecialMethodInvocationException {
+ throw brokenException();
+ }
+
+ @Override
+ public void writeObject(Object object, ObjectOutputStream stream) throws SpecialMethodInvocationException {
+ throw brokenException();
+ }
+
+ @Override
+ public void readObject(Object object, ObjectInputStream stream) throws SpecialMethodInvocationException {
+ throw brokenException();
+ }
+
+ private IllegalStateException brokenException() {
+ return new IllegalStateException("Class " + missingClassName
+ + " is missing locally, no accesses to it should be performed, this is a bug");
+ }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptor.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptor.java
index c108beaee..8f9360faa 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptor.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptor.java
@@ -35,10 +35,17 @@ import org.jetbrains.annotations.Nullable;
* Class descriptor for the user object serialization.
*/
public class ClassDescriptor implements DeclaredType {
+ /**
+ * Class name.
+ */
+ private final String className;
+
/**
* Class. It is local to the current JVM; the descriptor could
* be created on a remote JVM/machine where a class with same name could represent a different class.
+ * Might be absent in a descriptor describing a remote class that is not present locally.
*/
+ @Nullable
private final Class<?> localClass;
/**
@@ -116,6 +123,8 @@ public class ClassDescriptor implements DeclaredType {
private final List<ClassDescriptor> lineage;
+ private final List<MergedLayer> mergedLineage;
+
private final SpecialSerializationMethods serializationMethods;
/**
@@ -133,7 +142,7 @@ public class ClassDescriptor implements DeclaredType {
}
/**
- * Creates a descriptor describing a remote class.
+ * Creates a descriptor describing a remote class (when a local class corresponding to the remote class exists).
*/
public static ClassDescriptor forRemote(
Class<?> localClass,
@@ -151,6 +160,7 @@ public class ClassDescriptor implements DeclaredType {
Objects.requireNonNull(localDescriptor);
return new ClassDescriptor(
+ localClass.getName(),
localClass,
descriptorId,
superClassDescriptor,
@@ -162,10 +172,44 @@ public class ClassDescriptor implements DeclaredType {
fields,
serialization,
ClassDescriptorMerger.mergeFields(localDescriptor.fields(), fields),
+ false,
localDescriptor
);
}
+ /**
+ * Creates a descriptor describing a remote class (when no local class corresponding to the remote class exists).
+ */
+ public static ClassDescriptor forRemote(
+ String className,
+ int descriptorId,
+ @Nullable ClassDescriptor superClassDescriptor,
+ @Nullable ClassDescriptor componentTypeDescriptor,
+ boolean isPrimitive,
+ boolean isArray,
+ boolean isRuntimeEnum,
+ boolean isRuntimeTypeKnownUpfront,
+ List<FieldDescriptor> fields,
+ Serialization serialization
+ ) {
+ return new ClassDescriptor(
+ className,
+ null,
+ descriptorId,
+ superClassDescriptor,
+ componentTypeDescriptor,
+ isPrimitive,
+ isArray,
+ isRuntimeEnum,
+ isRuntimeTypeKnownUpfront,
+ fields,
+ serialization,
+ fields.stream().map(MergedField::remoteOnly).collect(toList()),
+ false,
+ null
+ );
+ }
+
/**
* Constructor for the local class case.
*/
@@ -178,6 +222,7 @@ public class ClassDescriptor implements DeclaredType {
Serialization serialization
) {
this(
+ localClass.getName(),
localClass,
descriptorId,
superClassDescriptor,
@@ -189,6 +234,7 @@ public class ClassDescriptor implements DeclaredType {
fields,
serialization,
fields.stream().map(field -> new MergedField(field, field)).collect(toList()),
+ true,
null
);
}
@@ -197,7 +243,8 @@ public class ClassDescriptor implements DeclaredType {
* Constructor.
*/
private ClassDescriptor(
- Class<?> localClass,
+ String className,
+ @Nullable Class<?> localClass,
int descriptorId,
@Nullable ClassDescriptor superClassDescriptor,
@Nullable ClassDescriptor componentTypeDescriptor,
@@ -208,8 +255,13 @@ public class ClassDescriptor implements DeclaredType {
List<FieldDescriptor> fields,
Serialization serialization,
List<MergedField> mergedFields,
+ boolean thisIsLocal,
@Nullable ClassDescriptor localDescriptor
) {
+ assert localClass != null && (thisIsLocal || localDescriptor != null)
+ || (localClass == null && !thisIsLocal && localDescriptor == null);
+
+ this.className = className;
this.localClass = localClass;
this.descriptorId = descriptorId;
this.superClassDescriptor = superClassDescriptor;
@@ -222,7 +274,7 @@ public class ClassDescriptor implements DeclaredType {
this.fields = List.copyOf(fields);
this.serialization = serialization;
- this.localDescriptor = localDescriptor == null ? this : localDescriptor;
+ this.localDescriptor = thisIsLocal ? this : localDescriptor;
this.mergedFields = List.copyOf(mergedFields);
@@ -233,8 +285,16 @@ public class ClassDescriptor implements DeclaredType {
fieldNullsBitmapIndices = computeFieldNullsBitmapIndices(fields);
lineage = computeLineage(this);
+ if (thisIsLocal) {
+ mergedLineage = lineage.stream().map(layer -> new MergedLayer(layer, layer)).collect(toList());
+ } else if (localDescriptor == null) {
+ mergedLineage = lineage.stream().map(MergedLayer::remoteOnly).collect(toList());
+ } else {
+ mergedLineage = ClassDescriptorMerger.mergeLineages(localDescriptor.lineage(), this.lineage);
+ }
- serializationMethods = new SpecialSerializationMethodsImpl(this);
+ serializationMethods = localClass != null ? new SpecialSerializationMethodsImpl(this)
+ : new BrokenSerializationMethods(className);
}
private static int computePrimitiveFieldsDataSize(List<FieldDescriptor> fields) {
@@ -388,16 +448,21 @@ public class ClassDescriptor implements DeclaredType {
* @return Class' name.
*/
public String className() {
- return localClass.getName();
+ return className;
}
/**
* Returns descriptor's class (represented by a local class). Local means 'on this machine', but the descriptor could
* be created on a remote machine where a class with same name could represent a different class.
*
- * @return Class.
+ * @return local class
+ * @throws IllegalStateException if no local class exists for the described class (this could happen for a remote descriptor)
*/
public Class<?> localClass() {
+ if (localClass == null) {
+ throw new IllegalStateException("No local class exists for '" + className + "'");
+ }
+
return localClass;
}
@@ -558,14 +623,23 @@ public class ClassDescriptor implements DeclaredType {
return descriptorId == BuiltInType.PROXY.descriptorId();
}
+ public boolean hasLocal() {
+ return localDescriptor != null;
+ }
+
/**
* Returns descriptor of the local version of the remote class described by this descriptor
* (or {@code null} if the current descriptor describes a local class).
*
* @return descriptor of the local version of the remote class described by this descriptor
* (or {@code null} if the current descriptor describes a local class)
+ * @throws IllegalStateException if no local counterpart exists for the described class (this could happen for a remote descriptor)
*/
public ClassDescriptor local() {
+ if (localDescriptor == null) {
+ throw new IllegalStateException("No local descriptor exists for '" + className + "'");
+ }
+
return localDescriptor;
}
@@ -585,7 +659,7 @@ public class ClassDescriptor implements DeclaredType {
* @return {@code true} if this descriptor describes same class as the given descriptor
*/
public boolean describesSameClass(ClassDescriptor other) {
- return localClass == other.localClass;
+ return Objects.equals(className, other.className);
}
/**
@@ -799,6 +873,10 @@ public class ClassDescriptor implements DeclaredType {
return lineage;
}
+ public List<MergedLayer> mergedLineage() {
+ return mergedLineage;
+ }
+
/**
* Returns {@code true} if the descriptor describes a String that is represented with Latin-1 internally.
* Needed to apply an optimization.
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorMerger.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorMerger.java
index 93d743fdd..a4da38ed1 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorMerger.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassDescriptorMerger.java
@@ -17,8 +17,12 @@
package org.apache.ignite.internal.network.serialization;
+import static java.util.stream.Collectors.toSet;
+
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
+import java.util.function.Predicate;
/**
* Merges {@link ClassDescriptor} components.
@@ -69,4 +73,57 @@ class ClassDescriptorMerger {
return List.copyOf(mergedFields);
}
+
+ static List<MergedLayer> mergeLineages(List<ClassDescriptor> localLineage, List<ClassDescriptor> remoteLineage) {
+ Set<String> commonClassNames = classNames(localLineage);
+ commonClassNames.retainAll(classNames(remoteLineage));
+
+ Predicate<ClassDescriptor> isCommon = layer -> commonClassNames.contains(layer.className());
+
+ List<MergedLayer> result = new ArrayList<>();
+
+ int localIndex = 0;
+ int remoteIndex = 0;
+
+ while (localIndex < localLineage.size() && remoteIndex < remoteLineage.size()) {
+ while (localIndex < localLineage.size() && !isCommon.test(localLineage.get(localIndex))) {
+ result.add(MergedLayer.localOnly(localLineage.get(localIndex)));
+ localIndex++;
+ }
+ while (remoteIndex < remoteLineage.size() && !isCommon.test(remoteLineage.get(remoteIndex))) {
+ result.add(MergedLayer.remoteOnly(remoteLineage.get(remoteIndex)));
+ remoteIndex++;
+ }
+
+ if (localIndex >= localLineage.size() || remoteIndex >= remoteLineage.size()) {
+ break;
+ }
+
+ // in both lists, we are standing on descriptors with same class name
+ ClassDescriptor localLayer = localLineage.get(localIndex);
+ ClassDescriptor remoteLayer = remoteLineage.get(remoteIndex);
+ result.add(new MergedLayer(localLayer, remoteLayer));
+
+ localIndex++;
+ remoteIndex++;
+ }
+
+ // tails
+ while (localIndex < localLineage.size()) {
+ result.add(MergedLayer.localOnly(localLineage.get(localIndex)));
+ localIndex++;
+ }
+ while (remoteIndex < remoteLineage.size()) {
+ result.add(MergedLayer.remoteOnly(remoteLineage.get(remoteIndex)));
+ remoteIndex++;
+ }
+
+ return List.copyOf(result);
+ }
+
+ private static Set<String> classNames(List<ClassDescriptor> localLineage) {
+ return localLineage.stream()
+ .map(ClassDescriptor::className)
+ .collect(toSet());
+ }
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/MapBackedClassIndexedDescriptors.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassNameMapBackedClassIndexedDescriptors.java
similarity index 76%
rename from modules/network/src/main/java/org/apache/ignite/internal/network/serialization/MapBackedClassIndexedDescriptors.java
rename to modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassNameMapBackedClassIndexedDescriptors.java
index 20ddcef8b..096a97b6b 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/MapBackedClassIndexedDescriptors.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/ClassNameMapBackedClassIndexedDescriptors.java
@@ -21,12 +21,12 @@ import java.util.Map;
import org.jetbrains.annotations.Nullable;
/**
- * A map-backed implementation of {@link ClassIndexedDescriptors}.
+ * A map-backed implementation of {@link ClassIndexedDescriptors}, map stores class names as keys.
*/
-public class MapBackedClassIndexedDescriptors implements ClassIndexedDescriptors {
- private final Map<Class<?>, ClassDescriptor> descriptorsByClass;
+public class ClassNameMapBackedClassIndexedDescriptors implements ClassIndexedDescriptors {
+ private final Map<String, ClassDescriptor> descriptorsByClass;
- public MapBackedClassIndexedDescriptors(Map<Class<?>, ClassDescriptor> descriptorsByClass) {
+ public ClassNameMapBackedClassIndexedDescriptors(Map<String, ClassDescriptor> descriptorsByClass) {
this.descriptorsByClass = descriptorsByClass;
}
@@ -34,6 +34,6 @@ public class MapBackedClassIndexedDescriptors implements ClassIndexedDescriptors
@Override
@Nullable
public ClassDescriptor getDescriptor(Class<?> clazz) {
- return descriptorsByClass.get(clazz);
+ return descriptorsByClass.get(clazz.getName());
}
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/FieldAccessor.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/FieldAccessor.java
index 40717dc5d..5fdfe5328 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/FieldAccessor.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/FieldAccessor.java
@@ -48,7 +48,7 @@ public interface FieldAccessor {
if (field != null) {
return new UnsafeFieldAccessor(field);
} else {
- return new BrokenFieldAccessor(fieldName, declaringClass);
+ return new BrokenFieldAccessor(fieldName, declaringClass.getName());
}
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/FieldDescriptor.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/FieldDescriptor.java
index ef9cb0f21..335e60fcc 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/FieldDescriptor.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/FieldDescriptor.java
@@ -30,7 +30,7 @@ public class FieldDescriptor implements DeclaredType {
/**
* Type of the field (represented by a local class). Local means 'on this machine', but the descriptor could
- * be created on a remote machine where a class with same name could represent a different class..
+ * be created on a remote machine where a class with same name could represent a different class.
*/
private final Class<?> localClass;
@@ -56,7 +56,15 @@ public class FieldDescriptor implements DeclaredType {
* Creates a {@link FieldDescriptor} from a local {@link Field}.
*/
public static FieldDescriptor local(Field field, int typeDescriptorId) {
- return new FieldDescriptor(field, typeDescriptorId);
+ return new FieldDescriptor(
+ field.getName(),
+ field.getType(),
+ typeDescriptorId,
+ false,
+ field.getType().isPrimitive(),
+ Classes.isRuntimeTypeKnownUpfront(field.getType()),
+ FieldAccessor.forField(field)
+ );
}
/**
@@ -80,7 +88,7 @@ public class FieldDescriptor implements DeclaredType {
}
/**
- * Creates a {@link FieldDescriptor} for a remote field.
+ * Creates a {@link FieldDescriptor} for a remote field when the defining class is present locally.
*/
public static FieldDescriptor remote(
String fieldName,
@@ -102,17 +110,26 @@ public class FieldDescriptor implements DeclaredType {
}
/**
- * Constructor.
+ * Creates a {@link FieldDescriptor} for a remote field when the defining class is not present locally.
+ * The resulting FieldDescriptor cannot be used for accessing the field using {@link #accessor()} (because the
+ * returned {@link FieldAccessor} will throw on any access attempt).
*/
- private FieldDescriptor(Field field, int typeDescriptorId) {
- this(
- field.getName(),
- field.getType(),
+ public static FieldDescriptor remote(
+ String fieldName,
+ Class<?> fieldClazz,
+ int typeDescriptorId,
+ boolean unshared,
+ boolean isPrimitive,
+ boolean isRuntimeTypeKnownUpfront,
+ String declaringClassName) {
+ return new FieldDescriptor(
+ fieldName,
+ fieldClazz,
typeDescriptorId,
- false,
- field.getType().isPrimitive(),
- Classes.isRuntimeTypeKnownUpfront(field.getType()),
- FieldAccessor.forField(field)
+ unshared,
+ isPrimitive,
+ isRuntimeTypeKnownUpfront,
+ new BrokenFieldAccessor(fieldName, declaringClassName)
);
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/MergedLayer.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/MergedLayer.java
new file mode 100644
index 000000000..313f34827
--- /dev/null
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/MergedLayer.java
@@ -0,0 +1,99 @@
+/*
+ * 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;
+
+import java.util.Objects;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Contains information about a class layer, both from local and remote classes. Any of the parts (local/remote) might be absent.
+ */
+public class MergedLayer {
+ @Nullable
+ private final ClassDescriptor localLayer;
+ @Nullable
+ private final ClassDescriptor remoteLayer;
+
+ /**
+ * Creates an instance with local layer only.
+ *
+ * @param localLayer the local layer
+ * @return an instance with local layer only
+ */
+ public static MergedLayer localOnly(ClassDescriptor localLayer) {
+ return new MergedLayer(localLayer, null);
+ }
+
+ /**
+ * Creates an instance with remote layer only.
+ *
+ * @param remoteLayer the remote layer
+ * @return an instance with remote layer only
+ */
+ public static MergedLayer remoteOnly(ClassDescriptor remoteLayer) {
+ return new MergedLayer(null, remoteLayer);
+ }
+
+ /**
+ * Constructs a new instance.
+ */
+ public MergedLayer(@Nullable ClassDescriptor localLayer, @Nullable ClassDescriptor remoteLayer) {
+ assert localLayer != null || remoteLayer != null : "Both descriptors are null";
+ assert localLayer == null || remoteLayer == null || Objects.equals(localLayer.className(), remoteLayer.className())
+ : "Descriptors of different classes: " + localLayer.className() + " and " + remoteLayer.className();
+
+ this.localLayer = localLayer;
+ this.remoteLayer = remoteLayer;
+ }
+
+ /**
+ * Returns {@code true} if local layer is present.
+ *
+ * @return {@code true} if local layer is present
+ */
+ public boolean hasLocal() {
+ return localLayer != null;
+ }
+
+ /**
+ * Returns local layer.
+ *
+ * @return local layer
+ */
+ public ClassDescriptor local() {
+ return Objects.requireNonNull(localLayer, "localLayer is null");
+ }
+
+ /**
+ * Returns {@code true} if remote layer is present.
+ *
+ * @return {@code true} if remote layer is present
+ */
+ public boolean hasRemote() {
+ return remoteLayer != null;
+ }
+
+ /**
+ * Returns remote layer.
+ *
+ * @return remote layer
+ */
+ public ClassDescriptor remote() {
+ return Objects.requireNonNull(remoteLayer, "remoteLayer is null");
+ }
+}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/PerSessionSerializationService.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/PerSessionSerializationService.java
index d1ba3f12f..5a1eb0967 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/PerSessionSerializationService.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/PerSessionSerializationService.java
@@ -61,9 +61,9 @@ public class PerSessionSerializationService {
private final Int2ObjectMap<ClassDescriptor> mergedIdToDescriptorMap = new Int2ObjectOpenHashMap<>();
/**
* Map with merged class descriptors. They are the result of the merging of a local and a remote descriptor.
- * The key in this map is the class.
+ * The key in this map is the class name.
*/
- private final Map<Class<?>, ClassDescriptor> mergedClassToDescriptorMap = new HashMap<>();
+ private final Map<String, ClassDescriptor> mergedClassToDescriptorMap = new HashMap<>();
/**
* A collection of the descriptors that were sent to the remote node.
@@ -86,7 +86,7 @@ public class PerSessionSerializationService {
this.serializationService = serializationService;
this.descriptors = new CompositeDescriptorRegistry(
new MapBackedIdIndexedDescriptors(mergedIdToDescriptorMap),
- new MapBackedClassIndexedDescriptors(mergedClassToDescriptorMap),
+ new ClassNameMapBackedClassIndexedDescriptors(mergedClassToDescriptorMap),
serializationService.getLocalDescriptorRegistry()
);
}
@@ -134,14 +134,10 @@ public class PerSessionSerializationService {
* @param descriptorIds Class descriptors.
* @return List of class descriptor network messages.
*/
- @Nullable
public static List<ClassDescriptorMessage> createClassDescriptorsMessages(IntSet descriptorIds, ClassDescriptorRegistry registry) {
- List<ClassDescriptorMessage> messages = descriptorIds.intStream()
- .mapToObj(registry::getDescriptor)
- .filter(descriptor -> {
- int descriptorId = descriptor.descriptorId();
- return !shouldBeBuiltIn(descriptorId);
- })
+ return descriptorIds.intStream()
+ .mapToObj(registry::getRequiredDescriptor)
+ .filter(descriptor -> !shouldBeBuiltIn(descriptor.descriptorId()))
.map(descriptor -> {
List<FieldDescriptorMessage> fields = descriptor.fields().stream()
.map(d -> {
@@ -168,9 +164,8 @@ public class PerSessionSerializationService {
.componentTypeName(descriptor.componentTypeName())
.attributes(classDescriptorAttributeFlags(descriptor))
.build();
- }).collect(toList());
-
- return messages;
+ })
+ .collect(toList());
}
private static byte fieldFlags(FieldDescriptor fieldDescriptor) {
@@ -240,11 +235,10 @@ public class PerSessionSerializationService {
if (knownMergedDescriptor(classMessage.descriptorId())) {
it.remove();
} else if (dependenciesAreMerged(classMessage)) {
- Class<?> localClass = classForName(classMessage.className());
- ClassDescriptor mergedDescriptor = messageToMergedClassDescriptor(classMessage, localClass);
+ ClassDescriptor mergedDescriptor = messageToMergedClassDescriptor(classMessage);
mergedIdToDescriptorMap.put(classMessage.descriptorId(), mergedDescriptor);
- mergedClassToDescriptorMap.put(localClass, mergedDescriptor);
+ mergedClassToDescriptorMap.put(classMessage.className(), mergedDescriptor);
it.remove();
@@ -271,34 +265,27 @@ public class PerSessionSerializationService {
* Converts {@link ClassDescriptorMessage} to a {@link ClassDescriptor} and merges it with a local {@link ClassDescriptor} of the
* same class.
*
- * @param clsMsg ClassDescriptorMessage.
- * @param localClass the local class
+ * @param classMessage ClassDescriptorMessage.
* @return Merged class descriptor.
*/
- private ClassDescriptor messageToMergedClassDescriptor(ClassDescriptorMessage clsMsg, Class<?> localClass) {
- ClassDescriptor localDescriptor = serializationService.getOrCreateLocalDescriptor(localClass);
- return buildRemoteDescriptor(clsMsg, localClass, localDescriptor);
+ private ClassDescriptor messageToMergedClassDescriptor(ClassDescriptorMessage classMessage) {
+ @Nullable Class<?> localClass = maybeClassForName(classMessage.className());
+
+ if (localClass != null) {
+ return buildRemoteDescriptor(classMessage, localClass);
+ } else {
+ return buildRemoteDescriptor(classMessage);
+ }
}
- private ClassDescriptor buildRemoteDescriptor(
- ClassDescriptorMessage classMessage,
- Class<?> localClass,
- ClassDescriptor localDescriptor
- ) {
+ private ClassDescriptor buildRemoteDescriptor(ClassDescriptorMessage classMessage, Class<?> localClass) {
+ ClassDescriptor localDescriptor = serializationService.getOrCreateLocalDescriptor(localClass);
+
List<FieldDescriptor> remoteFields = classMessage.fields().stream()
- .map(fieldMsg -> fieldDescriptorFromMessage(fieldMsg, localClass))
+ .map(fieldMessage -> fieldDescriptorFromMessage(fieldMessage, localClass))
.collect(toList());
- SerializationType serializationType = SerializationType.getByValue(classMessage.serializationType());
-
- var serialization = new Serialization(
- serializationType,
- bitValue(classMessage.serializationFlags(), ClassDescriptorMessage.HAS_WRITE_OBJECT_MASK),
- bitValue(classMessage.serializationFlags(), ClassDescriptorMessage.HAS_READ_OBJECT_MASK),
- bitValue(classMessage.serializationFlags(), ClassDescriptorMessage.HAS_READ_OBJECT_NO_DATA_MASK),
- bitValue(classMessage.serializationFlags(), ClassDescriptorMessage.HAS_WRITE_REPLACE_MASK),
- bitValue(classMessage.serializationFlags(), ClassDescriptorMessage.HAS_READ_RESOLVE_MASK)
- );
+ Serialization serialization = buildSerialization(classMessage);
return ClassDescriptor.forRemote(
localClass,
@@ -315,6 +302,40 @@ public class PerSessionSerializationService {
);
}
+ private ClassDescriptor buildRemoteDescriptor(ClassDescriptorMessage classMessage) {
+ List<FieldDescriptor> remoteFields = classMessage.fields().stream()
+ .map(fieldMessage -> fieldDescriptorFromMessage(fieldMessage, classMessage.className()))
+ .collect(toList());
+
+ Serialization serialization = buildSerialization(classMessage);
+
+ return ClassDescriptor.forRemote(
+ classMessage.className(),
+ classMessage.descriptorId(),
+ remoteSuperClassDescriptor(classMessage),
+ remoteComponentTypeDescriptor(classMessage),
+ bitValue(classMessage.attributes(), ClassDescriptorMessage.IS_PRIMITIVE_MASK),
+ bitValue(classMessage.attributes(), ClassDescriptorMessage.IS_ARRAY_MASK),
+ bitValue(classMessage.attributes(), ClassDescriptorMessage.IS_RUNTIME_ENUM_MASK),
+ bitValue(classMessage.attributes(), ClassDescriptorMessage.IS_RUNTIME_TYPE_KNOWN_UPFRONT_MASK),
+ remoteFields,
+ serialization
+ );
+ }
+
+ private Serialization buildSerialization(ClassDescriptorMessage classMessage) {
+ SerializationType serializationType = SerializationType.getByValue(classMessage.serializationType());
+
+ return new Serialization(
+ serializationType,
+ bitValue(classMessage.serializationFlags(), ClassDescriptorMessage.HAS_WRITE_OBJECT_MASK),
+ bitValue(classMessage.serializationFlags(), ClassDescriptorMessage.HAS_READ_OBJECT_MASK),
+ bitValue(classMessage.serializationFlags(), ClassDescriptorMessage.HAS_READ_OBJECT_NO_DATA_MASK),
+ bitValue(classMessage.serializationFlags(), ClassDescriptorMessage.HAS_WRITE_REPLACE_MASK),
+ bitValue(classMessage.serializationFlags(), ClassDescriptorMessage.HAS_READ_RESOLVE_MASK)
+ );
+ }
+
private boolean bitValue(byte flags, int bitMask) {
return (flags & bitMask) != 0;
}
@@ -335,24 +356,37 @@ public class PerSessionSerializationService {
return remoteClassDescriptor(clsMsg.componentTypeDescriptorId(), clsMsg.componentTypeName());
}
- private FieldDescriptor fieldDescriptorFromMessage(FieldDescriptorMessage fieldMsg, Class<?> declaringClass) {
- int typeDescriptorId = fieldMsg.typeDescriptorId();
+ private FieldDescriptor fieldDescriptorFromMessage(FieldDescriptorMessage fieldMessage, Class<?> declaringClass) {
+ int typeDescriptorId = fieldMessage.typeDescriptorId();
return FieldDescriptor.remote(
- fieldMsg.name(),
- fieldType(typeDescriptorId, fieldMsg.className()),
+ fieldMessage.name(),
+ fieldType(typeDescriptorId, fieldMessage.className()),
typeDescriptorId,
- bitValue(fieldMsg.flags(), FieldDescriptorMessage.UNSHARED_MASK),
- bitValue(fieldMsg.flags(), FieldDescriptorMessage.IS_PRIMITIVE),
- bitValue(fieldMsg.flags(), FieldDescriptorMessage.IS_RUNTIME_TYPE_KNOWN_UPFRONT),
+ bitValue(fieldMessage.flags(), FieldDescriptorMessage.UNSHARED_MASK),
+ bitValue(fieldMessage.flags(), FieldDescriptorMessage.IS_PRIMITIVE),
+ bitValue(fieldMessage.flags(), FieldDescriptorMessage.IS_RUNTIME_TYPE_KNOWN_UPFRONT),
declaringClass
);
}
+ private FieldDescriptor fieldDescriptorFromMessage(FieldDescriptorMessage fieldMessage, String declaringClassName) {
+ int typeDescriptorId = fieldMessage.typeDescriptorId();
+ return FieldDescriptor.remote(
+ fieldMessage.name(),
+ fieldType(typeDescriptorId, fieldMessage.className()),
+ typeDescriptorId,
+ bitValue(fieldMessage.flags(), FieldDescriptorMessage.UNSHARED_MASK),
+ bitValue(fieldMessage.flags(), FieldDescriptorMessage.IS_PRIMITIVE),
+ bitValue(fieldMessage.flags(), FieldDescriptorMessage.IS_RUNTIME_TYPE_KNOWN_UPFRONT),
+ declaringClassName
+ );
+ }
+
private ClassDescriptor remoteClassDescriptor(int descriptorId, String typeName) {
if (shouldBeBuiltIn(descriptorId)) {
return serializationService.getLocalDescriptor(descriptorId);
} else {
- return mergedClassToDescriptorMap.get(classForName(typeName));
+ return mergedClassToDescriptorMap.get(typeName);
}
}
@@ -360,15 +394,26 @@ public class PerSessionSerializationService {
if (shouldBeBuiltIn(descriptorId)) {
return BuiltInType.findByDescriptorId(descriptorId).clazz();
} else {
- return classForName(typeName);
+ return requiredClassForName(typeName);
}
}
- private Class<?> classForName(String className) {
+ private Class<?> requiredClassForName(String className) {
+ Class<?> result = maybeClassForName(className);
+
+ if (result == null) {
+ throw new SerializationException("Class " + className + " is not found");
+ }
+
+ return result;
+ }
+
+ @Nullable
+ private Class<?> maybeClassForName(String className) {
try {
return Class.forName(className, true, classLoader);
} catch (ClassNotFoundException e) {
- throw new SerializationException("Class " + className + " is not found", e);
+ return null;
}
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SerializationException.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SerializationException.java
index 5327d5ab9..a9e0188d7 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SerializationException.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/SerializationException.java
@@ -19,7 +19,7 @@ package org.apache.ignite.internal.network.serialization;
/** Exception that occurs during (de-)serialization. */
public class SerializationException extends RuntimeException {
- public SerializationException(String message, Throwable cause) {
- super(message, cause);
+ public SerializationException(String message) {
+ super(message);
}
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
index 52b4acb31..5e95de818 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshaller.java
@@ -396,12 +396,12 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller, Schema
) throws IOException, UnmarshalException {
int objectId = readObjectId(input);
- Object preInstantiatedObject = preInstantiate(remoteDescriptor, input, context);
- context.registerReference(objectId, preInstantiatedObject, unshared);
+ Object object = preInstantiate(remoteDescriptor, input, context);
+ context.registerReference(objectId, object, unshared);
- fillObjectFrom(input, preInstantiatedObject, remoteDescriptor, context);
+ fillObjectFrom(input, object, remoteDescriptor, context);
- return preInstantiatedObject;
+ return object;
}
private Object preInstantiate(ClassDescriptor remoteDescriptor, IgniteDataInput input, UnmarshallingContext context)
@@ -493,4 +493,9 @@ public class DefaultUserObjectMarshaller implements UserObjectMarshaller, Schema
public <T> void replaceSchemaMismatchHandler(Class<T> layerClass, SchemaMismatchHandler<T> handler) {
schemaMismatchHandlers.registerHandler(layerClass, handler);
}
+
+ @Override
+ public <T> void replaceSchemaMismatchHandler(String layerClassName, SchemaMismatchHandler<T> handler) {
+ schemaMismatchHandlers.registerHandler(layerClassName, handler);
+ }
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/SchemaMismatchEventSource.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/SchemaMismatchEventSource.java
index f032c2703..0a74295d4 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/SchemaMismatchEventSource.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/SchemaMismatchEventSource.java
@@ -31,4 +31,15 @@ public interface SchemaMismatchEventSource {
* @param <T> layer type
*/
<T> void replaceSchemaMismatchHandler(Class<T> layerClass, SchemaMismatchHandler<T> handler);
+
+ /**
+ * Sets the {@link SchemaMismatchHandler} for the given class if not set or replaces the existing one.
+ *
+ * <p>Note that the handlers are per declared class, not per concrete class lineage.
+ *
+ * @param layerClassName the name of the class; for schema changes concerning this class the events will be generated
+ * @param handler the handler that will handle the schema mismatch events
+ * @param <T> layer type
+ */
+ <T> void replaceSchemaMismatchHandler(String layerClassName, SchemaMismatchHandler<T> handler);
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/SchemaMismatchHandlers.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/SchemaMismatchHandlers.java
index a0d627a15..bc5f58561 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/SchemaMismatchHandlers.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/SchemaMismatchHandlers.java
@@ -26,33 +26,41 @@ import java.util.Map;
* A colleciton of {@link SchemaMismatchHandler}s keyed by classes to which they relate.
*/
class SchemaMismatchHandlers {
- private final Map<Class<?>, SchemaMismatchHandler<?>> handlers = new HashMap<>();
+ private final Map<String, SchemaMismatchHandler<?>> handlers = new HashMap<>();
private final SchemaMismatchHandler<Object> defaultHandler = new DefaultSchemaMismatchHandler();
<T> void registerHandler(Class<T> layerClass, SchemaMismatchHandler<T> handler) {
- handlers.put(layerClass, handler);
+ registerHandler(layerClass.getName(), handler);
+ }
+
+ <T> void registerHandler(String layerClassName, SchemaMismatchHandler<T> handler) {
+ handlers.put(layerClassName, handler);
}
- @SuppressWarnings("unchecked")
private SchemaMismatchHandler<Object> handlerFor(Class<?> clazz) {
- SchemaMismatchHandler<Object> handler = (SchemaMismatchHandler<Object>) handlers.get(clazz);
+ return handlerFor(clazz.getName());
+ }
+
+ @SuppressWarnings("unchecked")
+ private SchemaMismatchHandler<Object> handlerFor(String className) {
+ SchemaMismatchHandler<Object> handler = (SchemaMismatchHandler<Object>) handlers.get(className);
if (handler == null) {
handler = defaultHandler;
}
return handler;
}
- void onFieldIgnored(Class<?> layerClass, Object instance, String fieldName, Object fieldValue) throws SchemaMismatchException {
- handlerFor(layerClass).onFieldIgnored(instance, fieldName, fieldValue);
+ void onFieldIgnored(String layerClassName, Object instance, String fieldName, Object fieldValue) throws SchemaMismatchException {
+ handlerFor(layerClassName).onFieldIgnored(instance, fieldName, fieldValue);
}
- void onFieldMissed(Class<?> layerClass, Object instance, String fieldName) throws SchemaMismatchException {
- handlerFor(layerClass).onFieldMissed(instance, fieldName);
+ void onFieldMissed(String layerClassName, Object instance, String fieldName) throws SchemaMismatchException {
+ handlerFor(layerClassName).onFieldMissed(instance, fieldName);
}
- void onFieldTypeChanged(Class<?> layerClass, Object instance, String fieldName, Class<?> remoteType, Object fieldValue)
+ void onFieldTypeChanged(String layerClassName, Object instance, String fieldName, Class<?> remoteType, Object fieldValue)
throws SchemaMismatchException {
- handlerFor(layerClass).onFieldTypeChanged(instance, fieldName, remoteType, fieldValue);
+ handlerFor(layerClassName).onFieldTypeChanged(instance, fieldName, remoteType, fieldValue);
}
void onExternalizableIgnored(Object instance, ObjectInput externalData) throws SchemaMismatchException {
@@ -71,11 +79,11 @@ class SchemaMismatchHandlers {
handlerFor(instance.getClass()).onReadResolveDisappeared(instance);
}
- void onReadObjectIgnored(Class<?> layerClass, Object instance, ObjectInputStream objectData) throws SchemaMismatchException {
- handlerFor(layerClass).onReadObjectIgnored(instance, objectData);
+ void onReadObjectIgnored(String layerClassName, Object instance, ObjectInputStream objectData) throws SchemaMismatchException {
+ handlerFor(layerClassName).onReadObjectIgnored(instance, objectData);
}
- void onReadObjectMissed(Class<?> layerClass, Object instance) throws SchemaMismatchException {
- handlerFor(layerClass).onReadObjectMissed(instance);
+ void onReadObjectMissed(String layerClassName, Object instance) throws SchemaMismatchException {
+ handlerFor(layerClassName).onReadObjectMissed(instance);
}
}
diff --git a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/StructuredObjectMarshaller.java b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/StructuredObjectMarshaller.java
index bc4db595b..e10ec2fb0 100644
--- a/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/StructuredObjectMarshaller.java
+++ b/modules/network/src/main/java/org/apache/ignite/internal/network/serialization/marshal/StructuredObjectMarshaller.java
@@ -27,6 +27,7 @@ import org.apache.ignite.internal.network.serialization.DescriptorRegistry;
import org.apache.ignite.internal.network.serialization.FieldAccessor;
import org.apache.ignite.internal.network.serialization.FieldDescriptor;
import org.apache.ignite.internal.network.serialization.MergedField;
+import org.apache.ignite.internal.network.serialization.MergedLayer;
import org.apache.ignite.internal.network.serialization.SpecialMethodInvocationException;
import org.apache.ignite.internal.network.serialization.marshal.UosObjectInputStream.UosGetField;
import org.apache.ignite.internal.network.serialization.marshal.UosObjectOutputStream.UosPutField;
@@ -211,10 +212,25 @@ class StructuredObjectMarshaller implements DefaultFieldsReaderWriter {
void fillStructuredObjectFrom(IgniteDataInput input, Object object, ClassDescriptor remoteDescriptor, UnmarshallingContext context)
throws IOException, UnmarshalException {
- List<ClassDescriptor> remoteLineage = remoteDescriptor.lineage();
+ List<MergedLayer> lineage = remoteDescriptor.mergedLineage();
- for (ClassDescriptor remoteLayer : remoteLineage) {
- fillStructuredObjectLayerFrom(input, remoteLayer, object, context);
+ for (MergedLayer mergedLayer : lineage) {
+ if (mergedLayer.hasRemote()) {
+ fillStructuredObjectLayerFrom(input, mergedLayer.remote(), object, context);
+ } else if (mergedLayer.hasLocal()) {
+ fireEventsForLocalOnlyLayer(object, mergedLayer.local());
+ }
+ }
+ }
+
+ private void fireEventsForLocalOnlyLayer(Object object, ClassDescriptor localLayer) throws SchemaMismatchException {
+ fireOnFieldMissedOnLayerFields(object, localLayer);
+ schemaMismatchHandlers.onReadObjectMissed(localLayer.className(), object);
+ }
+
+ private void fireOnFieldMissedOnLayerFields(Object object, ClassDescriptor layer) throws SchemaMismatchException {
+ for (FieldDescriptor localField : layer.fields()) {
+ schemaMismatchHandlers.onFieldMissed(layer.className(), object, localField.name());
}
}
@@ -224,17 +240,19 @@ class StructuredObjectMarshaller implements DefaultFieldsReaderWriter {
Object object,
UnmarshallingContext context
) throws IOException, UnmarshalException {
- ClassDescriptor localLayer = remoteLayer.local();
+ boolean hasReadObjectLocally = remoteLayer.hasLocal() && remoteLayer.local().hasReadObject();
- if (remoteLayer.hasWriteObject() && localLayer.hasReadObject()) {
- fillObjectWithReadObjectFrom(input, object, remoteLayer, context);
- } else if (remoteLayer.hasWriteObject() && !localLayer.hasReadObject()) {
- fireReadObjectIgnored(remoteLayer, object, input, context);
+ if (remoteLayer.hasWriteObject()) {
+ if (hasReadObjectLocally) {
+ fillObjectWithReadObjectFrom(input, object, remoteLayer, context);
+ } else {
+ fireReadObjectIgnored(remoteLayer, object, input, context);
+ }
} else {
defaultFillFieldsFrom(input, object, remoteLayer, context);
- if (localLayer.hasReadObject()) {
- schemaMismatchHandlers.onReadObjectMissed(remoteLayer.localClass(), object);
+ if (hasReadObjectLocally) {
+ schemaMismatchHandlers.onReadObjectMissed(remoteLayer.className(), object);
}
}
}
@@ -287,21 +305,21 @@ class StructuredObjectMarshaller implements DefaultFieldsReaderWriter {
IgniteDataInput externalDataInput = new IgniteUnsafeDataInput(writeObjectDataBytes);
try (var oos = new UosObjectInputStream(externalDataInput, valueReader, unsharedReader, this, context)) {
- schemaMismatchHandlers.onReadObjectIgnored(remoteLayer.localClass(), object, oos);
+ schemaMismatchHandlers.onReadObjectIgnored(remoteLayer.className(), object, oos);
}
}
/** {@inheritDoc} */
@Override
- public void defaultFillFieldsFrom(IgniteDataInput input, Object object, ClassDescriptor descriptor, UnmarshallingContext context)
+ public void defaultFillFieldsFrom(IgniteDataInput input, Object object, ClassDescriptor remoteLayer, UnmarshallingContext context)
throws IOException, UnmarshalException {
- @Nullable BitSet nullsBitSet = NullsBitsetReader.readNullsBitSet(input, descriptor);
+ @Nullable BitSet nullsBitSet = NullsBitsetReader.readNullsBitSet(input, remoteLayer);
- for (MergedField mergedField : descriptor.mergedFields()) {
+ for (MergedField mergedField : remoteLayer.mergedFields()) {
if (mergedField.hasRemote()) {
- fillFieldWithNullSkippedCheckFrom(input, object, mergedField, descriptor, nullsBitSet, context);
+ fillFieldWithNullSkippedCheckFrom(input, object, mergedField, remoteLayer, nullsBitSet, context);
} else {
- schemaMismatchHandlers.onFieldMissed(descriptor.localClass(), object, mergedField.name());
+ schemaMismatchHandlers.onFieldMissed(remoteLayer.className(), object, mergedField.name());
}
}
}
@@ -365,13 +383,13 @@ class StructuredObjectMarshaller implements DefaultFieldsReaderWriter {
private void fireFieldIgnored(ClassDescriptor layerDescriptor, Object object, MergedField mergedField, Object fieldValue)
throws SchemaMismatchException {
- schemaMismatchHandlers.onFieldIgnored(layerDescriptor.localClass(), object, mergedField.name(), fieldValue);
+ schemaMismatchHandlers.onFieldIgnored(layerDescriptor.className(), object, mergedField.name(), fieldValue);
}
private void fireFieldTypeChanged(ClassDescriptor layerDescriptor, Object object, MergedField mergedField, Object fieldValue)
throws SchemaMismatchException {
schemaMismatchHandlers.onFieldTypeChanged(
- layerDescriptor.localClass(),
+ layerDescriptor.className(),
object,
mergedField.name(),
mergedField.remote().localClass(),
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/ClassDescriptorMergerTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/ClassDescriptorMergerTest.java
index b206fae0d..1db4fc90f 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/ClassDescriptorMergerTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/ClassDescriptorMergerTest.java
@@ -17,10 +17,15 @@
package org.apache.ignite.internal.network.serialization;
+import static java.util.stream.Collectors.toUnmodifiableList;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Test;
@@ -137,6 +142,182 @@ class ClassDescriptorMergerTest {
assertThat(mergedFields.get(1).hasRemote(), is(false));
}
+ @Test
+ void mergesIdenticalLineages() {
+ List<ClassDescriptor> local = descriptorsForClasses("A", "B");
+ List<ClassDescriptor> remote = List.copyOf(local);
+
+ List<MergedLayer> mergedLineage = ClassDescriptorMerger.mergeLineages(local, remote);
+
+ assertThat(mergedLineage, hasSize(2));
+
+ assertMergedLayerIsFull(mergedLineage.get(0), "A");
+ assertMergedLayerIsFull(mergedLineage.get(1), "B");
+ }
+
+ private List<ClassDescriptor> descriptorsForClasses(String... classNames) {
+ return Arrays.stream(classNames)
+ .map(this::descriptorForClass)
+ .collect(toUnmodifiableList());
+ }
+
+ private ClassDescriptor descriptorForClass(String className) {
+ ClassDescriptor descriptor = mock(ClassDescriptor.class);
+
+ when(descriptor.className()).thenReturn(className);
+
+ return descriptor;
+ }
+
+ private void assertMergedLayerIsFull(MergedLayer mergedLayer, String expectedClassName) {
+ assertThat(mergedLayer.hasLocal(), is(true));
+ assertThat(mergedLayer.local().className(), is(expectedClassName));
+ assertThat(mergedLayer.hasRemote(), is(true));
+ assertThat(mergedLayer.remote().className(), is(expectedClassName));
+ }
+
+ @Test
+ void mergesLocalLineageExcessInTheBeginningToLocalOnlyPrefix() {
+ List<ClassDescriptor> local = descriptorsForClasses("A", "B");
+ List<ClassDescriptor> remote = descriptorsForClasses("B");
+
+ List<MergedLayer> mergedLineage = ClassDescriptorMerger.mergeLineages(local, remote);
+
+ assertThat(mergedLineage, hasSize(2));
+
+ assertMergedLayerIsLocalOnly(mergedLineage.get(0), "A");
+ assertMergedLayerIsFull(mergedLineage.get(1), "B");
+ }
+
+ private void assertMergedLayerIsLocalOnly(MergedLayer mergedLayer, String expectedClassName) {
+ assertThat(mergedLayer.hasLocal(), is(true));
+ assertThat(mergedLayer.local().className(), is(expectedClassName));
+ assertThat(mergedLayer.hasRemote(), is(false));
+ NullPointerException ex = assertThrows(NullPointerException.class, mergedLayer::remote);
+ assertThat(ex.getMessage(), is("remoteLayer is null"));
+ }
+
+ @Test
+ void mergesLocalLineageExcessInTheEndToLocalOnlyPostfix() {
+ List<ClassDescriptor> local = descriptorsForClasses("A", "B");
+ List<ClassDescriptor> remote = descriptorsForClasses("A");
+
+ List<MergedLayer> mergedLineage = ClassDescriptorMerger.mergeLineages(local, remote);
+
+ assertThat(mergedLineage, hasSize(2));
+
+ assertMergedLayerIsFull(mergedLineage.get(0), "A");
+ assertMergedLayerIsLocalOnly(mergedLineage.get(1), "B");
+ }
+
+ @Test
+ void mergesRemoteLineageExcessInTheBeginningToRemoteOnlyPrefix() {
+ List<ClassDescriptor> local = descriptorsForClasses("B");
+ List<ClassDescriptor> remote = descriptorsForClasses("A", "B");
+
+ List<MergedLayer> mergedLineage = ClassDescriptorMerger.mergeLineages(local, remote);
+
+ assertThat(mergedLineage, hasSize(2));
+
+ assertMergedLayerIsRemoteOnly(mergedLineage.get(0), "A");
+ assertMergedLayerIsFull(mergedLineage.get(1), "B");
+ }
+
+ private void assertMergedLayerIsRemoteOnly(MergedLayer mergedLayer, String expectedClassName) {
+ assertThat(mergedLayer.hasRemote(), is(true));
+ assertThat(mergedLayer.remote().className(), is(expectedClassName));
+ assertThat(mergedLayer.hasLocal(), is(false));
+ NullPointerException ex = assertThrows(NullPointerException.class, mergedLayer::local);
+ assertThat(ex.getMessage(), is("localLayer is null"));
+ }
+
+ @Test
+ void mergesRemoteLineageExcessInTheEndToRemoteOnlyPostfix() {
+ List<ClassDescriptor> local = descriptorsForClasses("A");
+ List<ClassDescriptor> remote = descriptorsForClasses("A", "B");
+
+ List<MergedLayer> mergedLineage = ClassDescriptorMerger.mergeLineages(local, remote);
+
+ assertThat(mergedLineage, hasSize(2));
+
+ assertMergedLayerIsFull(mergedLineage.get(0), "A");
+ assertMergedLayerIsRemoteOnly(mergedLineage.get(1), "B");
+ }
+
+ @Test
+ void mergesLocalLineageExcessInTheMiddleToLocalOnlyMiddle() {
+ List<ClassDescriptor> local = descriptorsForClasses("A", "B", "C");
+ List<ClassDescriptor> remote = descriptorsForClasses("A", "C");
+
+ List<MergedLayer> mergedLineage = ClassDescriptorMerger.mergeLineages(local, remote);
+
+ assertThat(mergedLineage, hasSize(3));
+
+ assertMergedLayerIsFull(mergedLineage.get(0), "A");
+ assertMergedLayerIsLocalOnly(mergedLineage.get(1), "B");
+ assertMergedLayerIsFull(mergedLineage.get(2), "C");
+ }
+
+ @Test
+ void mergesRemoteLineageExcessInTheMiddleToRemoteOnlyMiddle() {
+ List<ClassDescriptor> local = descriptorsForClasses("A", "C");
+ List<ClassDescriptor> remote = descriptorsForClasses("A", "B", "C");
+
+ List<MergedLayer> mergedLineage = ClassDescriptorMerger.mergeLineages(local, remote);
+
+ assertThat(mergedLineage, hasSize(3));
+
+ assertMergedLayerIsFull(mergedLineage.get(0), "A");
+ assertMergedLayerIsRemoteOnly(mergedLineage.get(1), "B");
+ assertMergedLayerIsFull(mergedLineage.get(2), "C");
+ }
+
+ @Test
+ void mergesBothLineagesExcessInTheBeginningToCorrespondingPrefix() {
+ List<ClassDescriptor> local = descriptorsForClasses("A", "B");
+ List<ClassDescriptor> remote = descriptorsForClasses("X", "Y", "B");
+
+ List<MergedLayer> mergedLineage = ClassDescriptorMerger.mergeLineages(local, remote);
+
+ assertThat(mergedLineage, hasSize(4));
+
+ assertMergedLayerIsLocalOnly(mergedLineage.get(0), "A");
+ assertMergedLayerIsRemoteOnly(mergedLineage.get(1), "X");
+ assertMergedLayerIsRemoteOnly(mergedLineage.get(2), "Y");
+ assertMergedLayerIsFull(mergedLineage.get(3), "B");
+ }
+
+ @Test
+ void mergesBothLineagesExcessInTheEndToCorrespondingPostfix() {
+ List<ClassDescriptor> local = descriptorsForClasses("A", "B");
+ List<ClassDescriptor> remote = descriptorsForClasses("A", "X", "Y");
+
+ List<MergedLayer> mergedLineage = ClassDescriptorMerger.mergeLineages(local, remote);
+
+ assertThat(mergedLineage, hasSize(4));
+
+ assertMergedLayerIsFull(mergedLineage.get(0), "A");
+ assertMergedLayerIsLocalOnly(mergedLineage.get(1), "B");
+ assertMergedLayerIsRemoteOnly(mergedLineage.get(2), "X");
+ assertMergedLayerIsRemoteOnly(mergedLineage.get(3), "Y");
+ }
+
+ @Test
+ void mergesBothLineagesExcessInTheMiddleToCorrespondingPostfix() {
+ List<ClassDescriptor> local = descriptorsForClasses("A", "B", "C");
+ List<ClassDescriptor> remote = descriptorsForClasses("A", "X", "Y", "C");
+
+ List<MergedLayer> mergedLineage = ClassDescriptorMerger.mergeLineages(local, remote);
+
+ assertThat(mergedLineage, hasSize(5));
+
+ assertMergedLayerIsFull(mergedLineage.get(0), "A");
+ assertMergedLayerIsLocalOnly(mergedLineage.get(1), "B");
+ assertMergedLayerIsRemoteOnly(mergedLineage.get(2), "X");
+ assertMergedLayerIsRemoteOnly(mergedLineage.get(3), "Y");
+ assertMergedLayerIsFull(mergedLineage.get(4), "C");
+ }
+
private static class FieldHost {
@SuppressWarnings("unused")
private int test1;
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/MapBackedClassIndexedDescriptorsTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/ClassNameMapBackedClassIndexedDescriptorsTest.java
similarity index 85%
rename from modules/network/src/test/java/org/apache/ignite/internal/network/serialization/MapBackedClassIndexedDescriptorsTest.java
rename to modules/network/src/test/java/org/apache/ignite/internal/network/serialization/ClassNameMapBackedClassIndexedDescriptorsTest.java
index e8244c9fc..9c04cd824 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/MapBackedClassIndexedDescriptorsTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/ClassNameMapBackedClassIndexedDescriptorsTest.java
@@ -27,27 +27,27 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import java.util.Map;
import org.junit.jupiter.api.Test;
-class MapBackedClassIndexedDescriptorsTest {
+class ClassNameMapBackedClassIndexedDescriptorsTest {
private final ClassDescriptorRegistry unrelatedRegistry = new ClassDescriptorRegistry();
@Test
void retrievesKnownDescriptorByClass() {
ClassDescriptor descriptor = unrelatedRegistry.getRequiredDescriptor(String.class);
- var descriptors = new MapBackedClassIndexedDescriptors(Map.of(String.class, descriptor));
+ var descriptors = new ClassNameMapBackedClassIndexedDescriptors(Map.of(String.class.getName(), descriptor));
assertThat(descriptors.getDescriptor(String.class), is(descriptor));
}
@Test
void doesNotFindAnythingByClassWhenMapDoesNotContainTheClassDescriptor() {
- var descriptors = new MapBackedClassIndexedDescriptors(emptyMap());
+ var descriptors = new ClassNameMapBackedClassIndexedDescriptors(emptyMap());
assertThat(descriptors.getDescriptor(String.class), is(nullValue()));
}
@Test
void throwsWhenQueriedAboutUnknownDescriptorByClass() {
- var descriptors = new MapBackedClassIndexedDescriptors(emptyMap());
+ var descriptors = new ClassNameMapBackedClassIndexedDescriptors(emptyMap());
Throwable thrownEx = assertThrows(IllegalStateException.class, () -> descriptors.getRequiredDescriptor(String.class));
assertThat(thrownEx.getMessage(), startsWith("Did not find a descriptor by class"));
diff --git a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSchemaChangeTest.java b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSchemaChangeTest.java
index 5cdb58e91..5fcd157db 100644
--- a/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSchemaChangeTest.java
+++ b/modules/network/src/test/java/org/apache/ignite/internal/network/serialization/marshal/DefaultUserObjectMarshallerWithSchemaChangeTest.java
@@ -28,6 +28,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -45,15 +46,19 @@ import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.util.List;
import java.util.Map;
+import java.util.function.Function;
+import java.util.function.UnaryOperator;
import java.util.stream.Stream;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.implementation.StubMethod;
import org.apache.ignite.internal.network.serialization.ClassDescriptor;
import org.apache.ignite.internal.network.serialization.ClassDescriptorFactory;
import org.apache.ignite.internal.network.serialization.ClassDescriptorRegistry;
+import org.apache.ignite.internal.network.serialization.ClassNameMapBackedClassIndexedDescriptors;
import org.apache.ignite.internal.network.serialization.CompositeDescriptorRegistry;
import org.apache.ignite.internal.network.serialization.FieldDescriptor;
-import org.apache.ignite.internal.network.serialization.MapBackedClassIndexedDescriptors;
import org.apache.ignite.internal.network.serialization.MapBackedIdIndexedDescriptors;
import org.apache.ignite.internal.testframework.IgniteTestUtils;
import org.jetbrains.annotations.Nullable;
@@ -72,6 +77,8 @@ import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class DefaultUserObjectMarshallerWithSchemaChangeTest {
private static final byte[] INT_42_BYTES_IN_LITTLE_ENDIAN = {42, 0, 0, 0};
+ private static final String NON_LEAF_CLASS_NAME = "test.NonLeaf";
+ private static final String LEAF_CLASS_NAME = "test.Leaf";
private final ClassDescriptorRegistry localRegistry = new ClassDescriptorRegistry();
private final ClassDescriptorFactory localFactory = new ClassDescriptorFactory(localRegistry);
@@ -103,9 +110,9 @@ public class DefaultUserObjectMarshallerWithSchemaChangeTest {
verify(schemaMismatchHandler).onFieldIgnored(unmarshalled, "addedRemotely", extraField.value);
}
- private Class<?> addFieldTo(Class<?> localClass, String fieldName, Class<?> fieldType) {
+ private Class<?> addFieldTo(Class<?> baseClass, String fieldName, Class<?> fieldType) {
return new ByteBuddy()
- .redefine(localClass)
+ .redefine(baseClass)
.defineField(fieldName, fieldType, Visibility.PRIVATE)
.make()
.load(getClass().getClassLoader(), CHILD_FIRST)
@@ -205,6 +212,16 @@ public class DefaultUserObjectMarshallerWithSchemaChangeTest {
return unmarshalNotNullLocally(marshalled, localClass, remoteClass);
}
+ private Object marshalRemotelyAndUnmarshalLocally(
+ Object remoteInstance,
+ Class<?> localClass,
+ Class<?> remoteClass,
+ Function<ClassDescriptor, ClassDescriptor> reconstructSuperDescriptor
+ ) throws MarshalException, UnmarshalException {
+ MarshalledObject marshalled = remoteMarshaller.marshal(remoteInstance);
+ return unmarshalNotNullLocally(marshalled, localClass, remoteClass, reconstructSuperDescriptor);
+ }
+
private <T> T unmarshalNotNullLocally(MarshalledObject marshalled, Class<?> localClass, Class<?> remoteClass)
throws UnmarshalException {
T unmarshalled = unmarshalLocally(marshalled, localClass, remoteClass);
@@ -212,9 +229,30 @@ public class DefaultUserObjectMarshallerWithSchemaChangeTest {
return unmarshalled;
}
+ private <T> T unmarshalNotNullLocally(
+ MarshalledObject marshalled,
+ Class<?> localClass,
+ Class<?> remoteClass,
+ Function<ClassDescriptor, ClassDescriptor> reconstructSuperDescriptor
+ ) throws UnmarshalException {
+ T unmarshalled = unmarshalLocally(marshalled, localClass, remoteClass, reconstructSuperDescriptor);
+ assertThat(unmarshalled, is(notNullValue()));
+ return unmarshalled;
+ }
+
@Nullable
private <T> T unmarshalLocally(MarshalledObject marshalled, Class<?> localClass, Class<?> remoteClass)
throws UnmarshalException {
+ return unmarshalLocally(marshalled, localClass, remoteClass, Function.identity());
+ }
+
+ @Nullable
+ private <T> T unmarshalLocally(
+ MarshalledObject marshalled,
+ Class<?> localClass,
+ Class<?> remoteClass,
+ Function<ClassDescriptor, ClassDescriptor> reconstructSuperDescriptor
+ ) throws UnmarshalException {
localFactory.create(localClass);
ClassDescriptor localDescriptor = localRegistry.getRequiredDescriptor(localClass);
@@ -223,7 +261,7 @@ public class DefaultUserObjectMarshallerWithSchemaChangeTest {
ClassDescriptor reconstructedRemoteDescriptor = ClassDescriptor.forRemote(
localDescriptor.localClass(),
remoteDescriptor.descriptorId(),
- remoteDescriptor.superClassDescriptor(),
+ reconstructSuperDescriptor.apply(remoteDescriptor.superClassDescriptor()),
remoteDescriptor.componentTypeDescriptor(),
remoteDescriptor.isPrimitive(),
remoteDescriptor.isArray(),
@@ -238,7 +276,9 @@ public class DefaultUserObjectMarshallerWithSchemaChangeTest {
new MapBackedIdIndexedDescriptors(
Int2ObjectMaps.singleton(reconstructedRemoteDescriptor.descriptorId(), reconstructedRemoteDescriptor)
),
- new MapBackedClassIndexedDescriptors(Map.of(reconstructedRemoteDescriptor.localClass(), reconstructedRemoteDescriptor)),
+ new ClassNameMapBackedClassIndexedDescriptors(
+ Map.of(reconstructedRemoteDescriptor.localClass().getName(), reconstructedRemoteDescriptor)
+ ),
localRegistry
);
@@ -278,6 +318,196 @@ public class DefaultUserObjectMarshallerWithSchemaChangeTest {
}
}
+ @Test
+ void whenClassIsMergedIntoItsSubclassLocallyThenItsFieldsShouldNotBeFilledOnUnmarshalling() throws Exception {
+ Object unmarshalled = marshalRemotelyAndUnmarshalWithSuperclassDisappearingLocally();
+
+ assertThat(IgniteTestUtils.getFieldValue(unmarshalled, unmarshalled.getClass(), "value1"), is(0));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ void whenClassIsMergedIntoItsSubclassLocallyThenItsFieldsShouldTriggerFieldMissedAndIgnoredEventsOnUnmarshalling() throws Exception {
+ SchemaMismatchHandler<Object> nonLeafLayerHandler = mock(SchemaMismatchHandler.class);
+ SchemaMismatchHandler<Object> leafLayerHandler = mock(SchemaMismatchHandler.class);
+
+ localMarshaller.replaceSchemaMismatchHandler(LEAF_CLASS_NAME, leafLayerHandler);
+ localMarshaller.replaceSchemaMismatchHandler(NON_LEAF_CLASS_NAME, nonLeafLayerHandler);
+
+ Object unmarshalled = marshalRemotelyAndUnmarshalWithSuperclassDisappearingLocally();
+
+ verify(leafLayerHandler).onFieldMissed(unmarshalled, "value1");
+ verify(nonLeafLayerHandler).onFieldIgnored(unmarshalled, "value1", 1);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ void whenClassWithWriteObjectMethodIsMergedIntoItsSubclassLocallyThenReadObjectIgnoredEventShouldBeTriggeredOnUnmarshalling()
+ throws Exception {
+ SchemaMismatchHandler<Object> nonLeafLayerHandler = mock(SchemaMismatchHandler.class);
+
+ localMarshaller.replaceSchemaMismatchHandler(NON_LEAF_CLASS_NAME, nonLeafLayerHandler);
+
+ Object unmarshalled = marshalRemotelyAndUnmarshalWithSuperclassDisappearingLocally(this::withEmptyWriteObjectMethod);
+
+ verify(nonLeafLayerHandler).onReadObjectIgnored(eq(unmarshalled), any());
+ }
+
+ private DynamicType.Builder<Object> withEmptyWriteObjectMethod(DynamicType.Builder<Object> builder) {
+ return builder
+ .implement(Serializable.class)
+ .defineMethod("writeObject", void.class, Visibility.PRIVATE)
+ .withParameters(ObjectOutputStream.class)
+ .intercept(StubMethod.INSTANCE);
+ }
+
+ private Object marshalRemotelyAndUnmarshalWithSuperclassDisappearingLocally()
+ throws MarshalException, ReflectiveOperationException, UnmarshalException {
+ return marshalRemotelyAndUnmarshalWithSuperclassDisappearingLocally(UnaryOperator.identity());
+ }
+
+ private Object marshalRemotelyAndUnmarshalWithSuperclassDisappearingLocally(
+ UnaryOperator<DynamicType.Builder<Object>> remoteNonLeafClassCustomizer
+ ) throws ReflectiveOperationException, MarshalException, UnmarshalException {
+
+ DynamicType.Builder<Object> remoteNonLeafClassBuilder = new ByteBuddy()
+ .subclass(Object.class)
+ .name(NON_LEAF_CLASS_NAME)
+ .defineField("value1", int.class, Visibility.PRIVATE);
+
+ Class<?> remoteNonLeafClass = remoteNonLeafClassCustomizer.apply(remoteNonLeafClassBuilder)
+ .make()
+ .load(getClass().getClassLoader(), CHILD_FIRST)
+ .getLoaded();
+ Class<?> remoteLeafClass = new ByteBuddy()
+ .subclass(remoteNonLeafClass)
+ .name(LEAF_CLASS_NAME)
+ .defineField("value2", int.class, Visibility.PRIVATE)
+ .make()
+ .load(remoteNonLeafClass.getClassLoader())
+ .getLoaded();
+
+ Class<?> localLeafClass = new ByteBuddy()
+ .subclass(Object.class)
+ .name(LEAF_CLASS_NAME)
+ .defineField("value1", int.class, Visibility.PRIVATE)
+ .defineField("value2", int.class, Visibility.PRIVATE)
+ .make()
+ .load(getClass().getClassLoader(), CHILD_FIRST)
+ .getLoaded();
+
+ Object remoteInstance = instantiate(remoteLeafClass);
+ IgniteTestUtils.setFieldValue(remoteInstance, remoteInstance.getClass().getSuperclass(), "value1", 1);
+ IgniteTestUtils.setFieldValue(remoteInstance, remoteLeafClass, "value2", 2);
+
+ return marshalRemotelyAndUnmarshalLocally(
+ remoteInstance,
+ localLeafClass,
+ remoteLeafClass,
+ this::toRemoteDescriptorWithoutLocalClass
+ );
+ }
+
+ private ClassDescriptor toRemoteDescriptorWithoutLocalClass(ClassDescriptor superDescriptor) {
+ return ClassDescriptor.forRemote(
+ superDescriptor.className(),
+ superDescriptor.descriptorId(),
+ superDescriptor.superClassDescriptor(),
+ superDescriptor.componentTypeDescriptor(),
+ superDescriptor.isPrimitive(),
+ superDescriptor.isArray(),
+ superDescriptor.isRuntimeEnum(),
+ superDescriptor.isRuntimeTypeKnownUpfront(),
+ superDescriptor.fields(),
+ superDescriptor.serialization()
+ );
+ }
+
+ @Test
+ void whenSuperclassIsSplitFromClassLocallyThenSuperclassFieldsShouldNotBeFilledOnUnmarshalling() throws Exception {
+ Object unmarshalled = marshalRemotelyAndUnmarshalWithSuperclassAppearingLocally();
+
+ assertThat(IgniteTestUtils.getFieldValue(unmarshalled, unmarshalled.getClass().getSuperclass(), "value1"), is(0));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ void whenSuperclassIsSplitFromClassLocallyThenSuperclassFieldsShouldTriggerFieldMissedAndIgnoredEventsOnUnmarshalling()
+ throws Exception {
+ SchemaMismatchHandler<Object> nonLeafLayerHandler = mock(SchemaMismatchHandler.class);
+ SchemaMismatchHandler<Object> leafLayerHandler = mock(SchemaMismatchHandler.class);
+
+ localMarshaller.replaceSchemaMismatchHandler(LEAF_CLASS_NAME, leafLayerHandler);
+ localMarshaller.replaceSchemaMismatchHandler(NON_LEAF_CLASS_NAME, nonLeafLayerHandler);
+
+ Object unmarshalled = marshalRemotelyAndUnmarshalWithSuperclassAppearingLocally();
+
+ verify(leafLayerHandler).onFieldIgnored(unmarshalled, "value1", 1);
+ verify(nonLeafLayerHandler).onFieldMissed(unmarshalled, "value1");
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ void whenSuperclassWithReadObjectMethodIsSplitFromClassLocallyThenReadObjectMissedEventShouldBeTriggeredOnUnmarshalling()
+ throws Exception {
+ SchemaMismatchHandler<Object> nonLeafLayerHandler = mock(SchemaMismatchHandler.class);
+
+ localMarshaller.replaceSchemaMismatchHandler(NON_LEAF_CLASS_NAME, nonLeafLayerHandler);
+
+ Object unmarshalled = marshalRemotelyAndUnmarshalWithSuperclassAppearingLocally(this::withEmptyReadObjectMethod);
+
+ verify(nonLeafLayerHandler).onReadObjectMissed(eq(unmarshalled));
+ }
+
+ private DynamicType.Builder<Object> withEmptyReadObjectMethod(DynamicType.Builder<Object> builder) {
+ return builder
+ .implement(Serializable.class)
+ .defineMethod("readObject", void.class, Visibility.PRIVATE)
+ .withParameters(ObjectInputStream.class)
+ .intercept(StubMethod.INSTANCE);
+ }
+
+ private Object marshalRemotelyAndUnmarshalWithSuperclassAppearingLocally()
+ throws ReflectiveOperationException, MarshalException, UnmarshalException {
+ return marshalRemotelyAndUnmarshalWithSuperclassAppearingLocally(UnaryOperator.identity());
+ }
+
+ private Object marshalRemotelyAndUnmarshalWithSuperclassAppearingLocally(
+ UnaryOperator<DynamicType.Builder<Object>> localNonLeafClassCustomizer
+ ) throws ReflectiveOperationException, MarshalException, UnmarshalException {
+
+ Class<?> remoteLeafClass = new ByteBuddy()
+ .subclass(Object.class)
+ .name(LEAF_CLASS_NAME)
+ .defineField("value1", int.class, Visibility.PRIVATE)
+ .defineField("value2", int.class, Visibility.PRIVATE)
+ .make()
+ .load(getClass().getClassLoader(), CHILD_FIRST)
+ .getLoaded();
+
+ DynamicType.Builder<Object> localNonLeafClassBuilder = new ByteBuddy()
+ .subclass(Object.class)
+ .name(NON_LEAF_CLASS_NAME)
+ .defineField("value1", int.class, Visibility.PRIVATE);
+ Class<?> localNonLeafClass = localNonLeafClassCustomizer.apply(localNonLeafClassBuilder)
+ .make()
+ .load(getClass().getClassLoader(), CHILD_FIRST)
+ .getLoaded();
+ Class<?> localLeafClass = new ByteBuddy()
+ .subclass(localNonLeafClass)
+ .name(LEAF_CLASS_NAME)
+ .defineField("value2", int.class, Visibility.PRIVATE)
+ .make()
+ .load(localNonLeafClass.getClassLoader())
+ .getLoaded();
+
+ Object remoteInstance = instantiate(remoteLeafClass);
+ IgniteTestUtils.setFieldValue(remoteInstance, remoteInstance.getClass(), "value1", 1);
+ IgniteTestUtils.setFieldValue(remoteInstance, remoteInstance.getClass(), "value2", 2);
+
+ return marshalRemotelyAndUnmarshalLocally(remoteInstance, localLeafClass, remoteLeafClass);
+ }
+
@ParameterizedTest
@MethodSource("extraFieldsForGetField")
void getFieldReturnsDefaultValueWhenRemoteClassHasExtraField(ExtraFieldForGetField extraField) throws Exception {
@@ -589,7 +819,7 @@ public class DefaultUserObjectMarshallerWithSchemaChangeTest {
private void writeObject(ObjectOutputStream stream) throws IOException {
stream.putFields();
- stream.writeFields();;
+ stream.writeFields();
}
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {