/*
* 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.flink.api.java.typeutils.runtime;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import org.apache.flink.annotation.Internal;
import org.apache.flink.api.common.ExecutionConfig;
import org.apache.flink.api.common.typeutils.GenericTypeSerializerConfigSnapshot;
import org.apache.flink.api.java.typeutils.runtime.kryo.KryoSerializer;
import org.apache.flink.core.io.IOReadableWritable;
import org.apache.flink.core.memory.DataInputView;
import org.apache.flink.core.memory.DataOutputView;
import org.apache.flink.util.InstantiationUtil;
import org.apache.flink.util.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InvalidClassException;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Configuration snapshot base class for serializers that use Kryo for serialization.
*
* <p>The configuration captures the order of Kryo serializer registrations, so that new
* Kryo serializers can determine how to reconfigure their registration order to retain
* backwards compatibility.
*
* @param <T> the data type that the Kryo serializer handles.
*/
@Internal
public abstract class KryoRegistrationSerializerConfigSnapshot<T> extends GenericTypeSerializerConfigSnapshot<T> {
private static final Logger LOG = LoggerFactory.getLogger(KryoRegistrationSerializerConfigSnapshot.class);
/** Map of class tag to the registration, with ordering. */
private LinkedHashMap<String, KryoRegistration> kryoRegistrations;
/** This empty nullary constructor is required for deserializing the configuration. */
public KryoRegistrationSerializerConfigSnapshot() {}
public KryoRegistrationSerializerConfigSnapshot(
Class<T> typeClass,
LinkedHashMap<String, KryoRegistration> kryoRegistrations) {
super(typeClass);
this.kryoRegistrations = Preconditions.checkNotNull(kryoRegistrations);
}
@Override
public void write(DataOutputView out) throws IOException {
super.write(out);
out.writeInt(kryoRegistrations.size());
for (Map.Entry<String, KryoRegistration> kryoRegistrationEntry : kryoRegistrations.entrySet()) {
out.writeUTF(kryoRegistrationEntry.getKey());
new KryoRegistrationSerializationProxy<>(kryoRegistrationEntry.getValue()).write(out);
}
}
@Override
public void read(DataInputView in) throws IOException {
super.read(in);
int numKryoRegistrations = in.readInt();
kryoRegistrations = new LinkedHashMap<>(numKryoRegistrations);
KryoRegistrationSerializationProxy proxy;
for (int i = 0; i < numKryoRegistrations; i++) {
String classTag = in.readUTF();
proxy = new KryoRegistrationSerializationProxy(getUserCodeClassLoader());
proxy.read(in);
kryoRegistrations.put(classTag, proxy.kryoRegistration);
}
}
public LinkedHashMap<String, KryoRegistration> getKryoRegistrations() {
return kryoRegistrations;
}
// --------------------------------------------------------------------------------------------
private static class KryoRegistrationSerializationProxy<RC> implements IOReadableWritable {
private ClassLoader userCodeClassLoader;
private KryoRegistration kryoRegistration;
public KryoRegistrationSerializationProxy(ClassLoader userCodeClassLoader) {
this.userCodeClassLoader = Preconditions.checkNotNull(userCodeClassLoader);
}
public KryoRegistrationSerializationProxy(KryoRegistration kryoRegistration) {
this.kryoRegistration = Preconditions.checkNotNull(kryoRegistration);
}
@Override
public void write(DataOutputView out) throws IOException {
out.writeUTF(kryoRegistration.getRegisteredClass().getName());
final KryoRegistration.SerializerDefinitionType serializerDefinitionType = kryoRegistration.getSerializerDefinitionType();
out.writeInt(serializerDefinitionType.ordinal());
switch (serializerDefinitionType) {
case UNSPECIFIED:
// nothing else to write
break;
case CLASS:
out.writeUTF(kryoRegistration.getSerializerClass().getName());
break;
case INSTANCE:
InstantiationUtil.serializeObject(new DataOutputViewStream(out), kryoRegistration.getSerializableSerializerInstance());
break;
default:
// this should not happen; adding as a guard for the future
throw new IllegalStateException(
"Unrecognized Kryo registration serializer definition type: " + serializerDefinitionType);
}
}
@SuppressWarnings("unchecked")
@Override
public void read(DataInputView in) throws IOException {
String registeredClassname = in.readUTF();
Class<RC> registeredClass;
try {
registeredClass = (Class<RC>) Class.forName(registeredClassname, true, userCodeClassLoader);
} catch (ClassNotFoundException e) {
LOG.warn("Cannot find registered class " + registeredClassname + " for Kryo serialization in classpath;" +
" using a dummy class as a placeholder.", e);
registeredClass = (Class) DummyRegisteredClass.class;
}
final KryoRegistration.SerializerDefinitionType serializerDefinitionType =
KryoRegistration.SerializerDefinitionType.values()[in.readInt()];
switch (serializerDefinitionType) {
case UNSPECIFIED:
kryoRegistration = new KryoRegistration(registeredClass);
break;
case CLASS:
String serializerClassname = in.readUTF();
Class serializerClass;
try {
serializerClass = Class.forName(serializerClassname, true, userCodeClassLoader);
} catch (ClassNotFoundException e) {
LOG.warn("Cannot find registered Kryo serializer class for class " + registeredClassname +
" in classpath; using a dummy Kryo serializer that should be replaced as soon as" +
" a new Kryo serializer for the class is present", e);
serializerClass = DummyKryoSerializerClass.class;
}
kryoRegistration = new KryoRegistration(registeredClass, serializerClass);
break;
case INSTANCE:
ExecutionConfig.SerializableSerializer<? extends Serializer<RC>> serializerInstance;
try {
serializerInstance = InstantiationUtil.deserializeObject(new DataInputViewStream(in), userCodeClassLoader);
} catch (ClassNotFoundException e) {
LOG.warn("Cannot find registered Kryo serializer class for class " + registeredClassname +
" in classpath; using a dummy Kryo serializer that should be replaced as soon as" +
" a new Kryo serializer for the class is present", e);
serializerInstance = new ExecutionConfig.SerializableSerializer<>(new DummyKryoSerializerClass<RC>());
} catch (InvalidClassException e) {
LOG.warn("The registered Kryo serializer class for class " + registeredClassname +
" has changed and is no longer valid; using a dummy Kryo serializer that should be replaced" +
" as soon as a new Kryo serializer for the class is present.", e);
serializerInstance = new ExecutionConfig.SerializableSerializer<>(new DummyKryoSerializerClass<RC>());
}
kryoRegistration = new KryoRegistration(registeredClass, serializerInstance);
break;
default:
// this should not happen; adding as a guard for the future
throw new IllegalStateException(
"Unrecognized Kryo registration serializer definition type: " + serializerDefinitionType);
}
}
}
/**
* Placeholder dummy for a previously registered class that can no longer be found in classpath on restore.
*/
public static class DummyRegisteredClass {}
/**
* Placeholder dummmy for a previously registered Kryo serializer that is no longer valid or in classpath on restore.
*/
public static class DummyKryoSerializerClass<RC> extends Serializer<RC> implements Serializable {
private static final long serialVersionUID = -6172780797425739308L;
@Override
public void write(Kryo kryo, Output output, Object o) {
throw new UnsupportedOperationException(
"This exception indicates that you're trying to write a data type" +
" that no longer has a valid Kryo serializer registered for it.");
}
@Override
public Object read(Kryo kryo, Input input, Class aClass) {
throw new UnsupportedOperationException(
"This exception indicates that you're trying to read a data type" +
" that no longer has a valid Kryo serializer registered for it.");
}
}
@Override
public boolean equals(Object obj) {
return super.equals(obj)
&& kryoRegistrations.equals(((KryoSerializer.KryoSerializerConfigSnapshot) obj).getKryoRegistrations());
}
@Override
public int hashCode() {
return super.hashCode() + kryoRegistrations.hashCode();
}
}