Skip to content
Permalink
Browse files
IGNITE-16921 Support schema changes concerning inheritance hierarchy (#…
  • Loading branch information
rpuch committed May 12, 2022
1 parent 62c80ec commit 154447cf6c202b22b11ed98c0191e2f069ebd7c4
Showing 17 changed files with 932 additions and 127 deletions.
@@ -22,11 +22,11 @@
*/
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 @@ public void setBoolean(Object target, boolean fieldValue) {
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");
}

}
@@ -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");
}
}
@@ -35,10 +35,17 @@
* 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 static ClassDescriptor forLocal(
}

/**
* 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 static ClassDescriptor forRemote(
Objects.requireNonNull(localDescriptor);

return new ClassDescriptor(
localClass.getName(),
localClass,
descriptorId,
superClassDescriptor,
@@ -162,10 +172,44 @@ public static ClassDescriptor forRemote(
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 @@ private ClassDescriptor(
Serialization serialization
) {
this(
localClass.getName(),
localClass,
descriptorId,
superClassDescriptor,
@@ -189,6 +234,7 @@ private ClassDescriptor(
fields,
serialization,
fields.stream().map(field -> new MergedField(field, field)).collect(toList()),
true,
null
);
}
@@ -197,7 +243,8 @@ private ClassDescriptor(
* Constructor.
*/
private ClassDescriptor(
Class<?> localClass,
String className,
@Nullable Class<?> localClass,
int descriptorId,
@Nullable ClassDescriptor superClassDescriptor,
@Nullable ClassDescriptor componentTypeDescriptor,
@@ -208,8 +255,13 @@ private ClassDescriptor(
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 @@ private ClassDescriptor(
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 @@ private ClassDescriptor(
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 List<FieldDescriptor> fields() {
* @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 boolean isProxy() {
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 SpecialSerializationMethods serializationMethods() {
* @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 List<ClassDescriptor> lineage() {
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.
@@ -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 @@ static List<MergedField> mergeFields(List<FieldDescriptor> localFields, List<Fie

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());
}
}

0 comments on commit 154447c

Please sign in to comment.