/* * 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 java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Objects; import org.apache.flink.annotation.Internal; import org.apache.flink.annotation.VisibleForTesting; import org.apache.flink.api.common.ExecutionConfig; import org.apache.flink.api.common.typeutils.CompatibilityResult; import org.apache.flink.api.common.typeutils.GenericTypeSerializerConfigSnapshot; import org.apache.flink.api.common.typeutils.TypeSerializer; import org.apache.flink.api.common.typeutils.TypeSerializerConfigSnapshot; import org.apache.flink.api.common.typeutils.TypeSerializerUtil; import org.apache.flink.api.java.typeutils.TypeExtractor; import org.apache.flink.core.memory.DataInputView; import org.apache.flink.core.memory.DataOutputView; import org.apache.flink.util.Preconditions; import static org.apache.flink.util.Preconditions.checkNotNull; @Internal public final class PojoSerializer<T> extends TypeSerializer<T> { // Flags for the header private static byte IS_NULL = 1; private static byte NO_SUBCLASS = 2; private static byte IS_SUBCLASS = 4; private static byte IS_TAGGED_SUBCLASS = 8; private static final long serialVersionUID = 1L; // -------------------------------------------------------------------------------------------- // PojoSerializer parameters // -------------------------------------------------------------------------------------------- /** The POJO type class. */ private final Class<T> clazz; /** * Fields of the POJO and their serializers. * * <p>The fields are kept as a separate transient member, with their serialization * handled with the {@link #readObject(ObjectInputStream)} and {@link #writeObject(ObjectOutputStream)} * methods. * * <p>These may be reconfigured in {@link #ensureCompatibility(TypeSerializerConfigSnapshot)}. */ private transient Field[] fields; private TypeSerializer<Object>[] fieldSerializers; private final int numFields; /** * Registered subclasses and their serializers. * Each subclass to their registered class tag is maintained as a separate map ordered by the class tag. * * <p>These may be reconfigured in {@link #ensureCompatibility(TypeSerializerConfigSnapshot)}. */ private LinkedHashMap<Class<?>, Integer> registeredClasses; private TypeSerializer<?>[] registeredSerializers; /** * Cache of non-registered subclasses to their serializers, created on-the-fly. * * <p>This cache is persisted and will be repopulated with reconfigured serializers * in {@link #ensureCompatibility(TypeSerializerConfigSnapshot)}. */ private transient HashMap<Class<?>, TypeSerializer<?>> subclassSerializerCache; // -------------------------------------------------------------------------------------------- /** * Configuration of the current execution. * * <p>Nested serializers created using this will have the most up-to-date configuration, * and can be resolved for backwards compatibility with previous configuration * snapshots in {@link #ensureCompatibility(TypeSerializerConfigSnapshot)}. */ private final ExecutionConfig executionConfig; private transient ClassLoader cl; @SuppressWarnings("unchecked") public PojoSerializer( Class<T> clazz, TypeSerializer<?>[] fieldSerializers, Field[] fields, ExecutionConfig executionConfig) { this.clazz = checkNotNull(clazz); this.fieldSerializers = (TypeSerializer<Object>[]) checkNotNull(fieldSerializers); this.fields = checkNotNull(fields); this.numFields = fieldSerializers.length; this.executionConfig = checkNotNull(executionConfig); for (int i = 0; i < numFields; i++) { this.fields[i].setAccessible(true); } cl = Thread.currentThread().getContextClassLoader(); // We only want those classes that are not our own class and are actually sub-classes. LinkedHashSet<Class<?>> registeredSubclasses = getRegisteredSubclassesFromExecutionConfig(clazz, executionConfig); this.registeredClasses = createRegisteredSubclassTags(registeredSubclasses); this.registeredSerializers = createRegisteredSubclassSerializers(registeredSubclasses, executionConfig); this.subclassSerializerCache = new HashMap<>(); } @Override public boolean isImmutableType() { return false; } @Override public PojoSerializer<T> duplicate() { boolean stateful = false; TypeSerializer<?>[] duplicateFieldSerializers = new TypeSerializer[fieldSerializers.length]; for (int i = 0; i < fieldSerializers.length; i++) { duplicateFieldSerializers[i] = fieldSerializers[i].duplicate(); if (duplicateFieldSerializers[i] != fieldSerializers[i]) { // at least one of them is stateful stateful = true; } } if (stateful) { return new PojoSerializer<T>(clazz, duplicateFieldSerializers, fields, executionConfig); } else { return this; } } @Override public T createInstance() { if (clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers())) { return null; } try { T t = clazz.newInstance(); initializeFields(t); return t; } catch (Exception e) { throw new RuntimeException("Cannot instantiate class.", e); } } protected void initializeFields(T t) { for (int i = 0; i < numFields; i++) { try { fields[i].set(t, fieldSerializers[i].createInstance()); } catch (IllegalAccessException e) { throw new RuntimeException("Cannot initialize fields.", e); } } } @Override @SuppressWarnings({"unchecked", "rawtypes"}) public T copy(T from) { if (from == null) { return null; } Class<?> actualType = from.getClass(); if (actualType == clazz) { T target; try { target = (T) from.getClass().newInstance(); } catch (Throwable t) { throw new RuntimeException("Cannot instantiate class.", t); } // no subclass try { for (int i = 0; i < numFields; i++) { Object value = fields[i].get(from); if (value != null) { Object copy = fieldSerializers[i].copy(value); fields[i].set(target, copy); } else { fields[i].set(target, null); } } } catch (IllegalAccessException e) { throw new RuntimeException("Error during POJO copy, this should not happen since we check the fields before."); } return target; } else { // subclass TypeSerializer subclassSerializer = getSubclassSerializer(actualType); return (T) subclassSerializer.copy(from); } } @Override @SuppressWarnings({"unchecked", "rawtypes"}) public T copy(T from, T reuse) { if (from == null) { return null; } Class<?> actualType = from.getClass(); if (reuse == null || actualType != reuse.getClass()) { // cannot reuse, do a non-reuse copy return copy(from); } if (actualType == clazz) { try { for (int i = 0; i < numFields; i++) { Object value = fields[i].get(from); if (value != null) { Object reuseValue = fields[i].get(reuse); Object copy; if(reuseValue != null) { copy = fieldSerializers[i].copy(value, reuseValue); } else { copy = fieldSerializers[i].copy(value); } fields[i].set(reuse, copy); } else { fields[i].set(reuse, null); } } } catch (IllegalAccessException e) { throw new RuntimeException("Error during POJO copy, this should not happen since we check the fields before.", e); } } else { TypeSerializer subclassSerializer = getSubclassSerializer(actualType); reuse = (T) subclassSerializer.copy(from, reuse); } return reuse; } @Override public int getLength() { return -1; } @Override @SuppressWarnings({"unchecked", "rawtypes"}) public void serialize(T value, DataOutputView target) throws IOException { int flags = 0; // handle null values if (value == null) { flags |= IS_NULL; target.writeByte(flags); return; } Integer subclassTag = -1; Class<?> actualClass = value.getClass(); TypeSerializer subclassSerializer = null; if (clazz != actualClass) { subclassTag = registeredClasses.get(actualClass); if (subclassTag != null) { flags |= IS_TAGGED_SUBCLASS; subclassSerializer = registeredSerializers[subclassTag]; } else { flags |= IS_SUBCLASS; subclassSerializer = getSubclassSerializer(actualClass); } } else { flags |= NO_SUBCLASS; } target.writeByte(flags); // if its a registered subclass, write the class tag id, otherwise write the full classname if ((flags & IS_SUBCLASS) != 0) { target.writeUTF(actualClass.getName()); } else if ((flags & IS_TAGGED_SUBCLASS) != 0) { target.writeByte(subclassTag); } // if its a subclass, use the corresponding subclass serializer, // otherwise serialize each field with our field serializers if ((flags & NO_SUBCLASS) != 0) { try { for (int i = 0; i < numFields; i++) { Object o = fields[i].get(value); if (o == null) { target.writeBoolean(true); // null field handling } else { target.writeBoolean(false); fieldSerializers[i].serialize(o, target); } } } catch (IllegalAccessException e) { throw new RuntimeException("Error during POJO copy, this should not happen since we check the fields before.", e); } } else { // subclass if (subclassSerializer != null) { subclassSerializer.serialize(value, target); } } } @Override @SuppressWarnings({"unchecked", "rawtypes"}) public T deserialize(DataInputView source) throws IOException { int flags = source.readByte(); if((flags & IS_NULL) != 0) { return null; } T target; Class<?> actualSubclass = null; TypeSerializer subclassSerializer = null; if ((flags & IS_SUBCLASS) != 0) { String subclassName = source.readUTF(); try { actualSubclass = Class.forName(subclassName, true, cl); } catch (ClassNotFoundException e) { throw new RuntimeException("Cannot instantiate class.", e); } subclassSerializer = getSubclassSerializer(actualSubclass); target = (T) subclassSerializer.createInstance(); // also initialize fields for which the subclass serializer is not responsible initializeFields(target); } else if ((flags & IS_TAGGED_SUBCLASS) != 0) { int subclassTag = source.readByte(); subclassSerializer = registeredSerializers[subclassTag]; target = (T) subclassSerializer.createInstance(); // also initialize fields for which the subclass serializer is not responsible initializeFields(target); } else { target = createInstance(); } if ((flags & NO_SUBCLASS) != 0) { try { for (int i = 0; i < numFields; i++) { boolean isNull = source.readBoolean(); if (isNull) { fields[i].set(target, null); } else { Object field = fieldSerializers[i].deserialize(source); fields[i].set(target, field); } } } catch (IllegalAccessException e) { throw new RuntimeException("Error during POJO copy, this should not happen since we check the fields before.", e); } } else { if (subclassSerializer != null) { target = (T) subclassSerializer.deserialize(target, source); } } return target; } @Override @SuppressWarnings({"unchecked", "rawtypes"}) public T deserialize(T reuse, DataInputView source) throws IOException { // handle null values int flags = source.readByte(); if((flags & IS_NULL) != 0) { return null; } Class<?> subclass = null; TypeSerializer subclassSerializer = null; if ((flags & IS_SUBCLASS) != 0) { String subclassName = source.readUTF(); try { subclass = Class.forName(subclassName, true, cl); } catch (ClassNotFoundException e) { throw new RuntimeException("Cannot instantiate class.", e); } subclassSerializer = getSubclassSerializer(subclass); if (reuse == null || subclass != reuse.getClass()) { // cannot reuse reuse = (T) subclassSerializer.createInstance(); // also initialize fields for which the subclass serializer is not responsible initializeFields(reuse); } } else if ((flags & IS_TAGGED_SUBCLASS) != 0) { int subclassTag = source.readByte(); subclassSerializer = registeredSerializers[subclassTag]; if (reuse == null || ((PojoSerializer)subclassSerializer).clazz != reuse.getClass()) { // cannot reuse reuse = (T) subclassSerializer.createInstance(); // also initialize fields for which the subclass serializer is not responsible initializeFields(reuse); } } else { if (reuse == null || clazz != reuse.getClass()) { reuse = createInstance(); } } if ((flags & NO_SUBCLASS) != 0) { try { for (int i = 0; i < numFields; i++) { boolean isNull = source.readBoolean(); if (isNull) { fields[i].set(reuse, null); } else { Object field; Object reuseField = fields[i].get(reuse); if(reuseField != null) { field = fieldSerializers[i].deserialize(reuseField, source); } else { field = fieldSerializers[i].deserialize(source); } fields[i].set(reuse, field); } } } catch (IllegalAccessException e) { throw new RuntimeException("Error during POJO copy, this should not happen since we check the fields before.", e); } } else { if (subclassSerializer != null) { reuse = (T) subclassSerializer.deserialize(reuse, source); } } return reuse; } @Override public void copy(DataInputView source, DataOutputView target) throws IOException { // copy the flags int flags = source.readByte(); target.writeByte(flags); if ((flags & IS_NULL) != 0) { // is a null value, nothing further to copy return; } TypeSerializer<?> subclassSerializer = null; if ((flags & IS_SUBCLASS) != 0) { String className = source.readUTF(); target.writeUTF(className); try { Class<?> subclass = Class.forName(className, true, Thread.currentThread() .getContextClassLoader()); subclassSerializer = getSubclassSerializer(subclass); } catch (ClassNotFoundException e) { throw new RuntimeException("Cannot instantiate class.", e); } } else if ((flags & IS_TAGGED_SUBCLASS) != 0) { int subclassTag = source.readByte(); target.writeByte(subclassTag); subclassSerializer = registeredSerializers[subclassTag]; } if ((flags & NO_SUBCLASS) != 0) { for (int i = 0; i < numFields; i++) { boolean isNull = source.readBoolean(); target.writeBoolean(isNull); if (!isNull) { fieldSerializers[i].copy(source, target); } } } else { if (subclassSerializer != null) { subclassSerializer.copy(source, target); } } } @Override public int hashCode() { return 31 * (31 * Arrays.hashCode(fieldSerializers) + Arrays.hashCode(registeredSerializers)) + Objects.hash(clazz, numFields, registeredClasses); } @Override public boolean equals(Object obj) { if (obj instanceof PojoSerializer) { PojoSerializer<?> other = (PojoSerializer<?>) obj; return other.canEqual(this) && clazz == other.clazz && Arrays.equals(fieldSerializers, other.fieldSerializers) && Arrays.equals(registeredSerializers, other.registeredSerializers) && numFields == other.numFields && registeredClasses.equals(other.registeredClasses); } else { return false; } } @Override public boolean canEqual(Object obj) { return obj instanceof PojoSerializer; } // -------------------------------------------------------------------------------------------- // Serializer configuration snapshotting & compatibility // -------------------------------------------------------------------------------------------- @Override public PojoSerializerConfigSnapshot<T> snapshotConfiguration() { return buildConfigSnapshot( clazz, registeredClasses, registeredSerializers, fields, fieldSerializers, subclassSerializerCache); } @SuppressWarnings("unchecked") @Override public CompatibilityResult<T> ensureCompatibility(TypeSerializerConfigSnapshot configSnapshot) { if (configSnapshot instanceof PojoSerializerConfigSnapshot) { final PojoSerializerConfigSnapshot<T> config = (PojoSerializerConfigSnapshot<T>) configSnapshot; if (clazz.equals(config.getTypeClass())) { if (this.numFields == config.getFieldToSerializerConfigSnapshot().size()) { CompatibilityResult<?> compatResult; // ----------- check field order and compatibility of field serializers ----------- // reordered fields and their serializers; // this won't be applied to this serializer until all compatibility checks have been completed final Field[] reorderedFields = new Field[this.numFields]; final TypeSerializer<Object>[] reorderedFieldSerializers = (TypeSerializer<Object>[]) new TypeSerializer<?>[this.numFields]; int i = 0; for (Map.Entry<Field, TypeSerializerConfigSnapshot> fieldToConfigSnapshotEntry : config.getFieldToSerializerConfigSnapshot().entrySet()) { int fieldIndex = findField(fieldToConfigSnapshotEntry.getKey()); if (fieldIndex != -1) { reorderedFields[i] = fieldToConfigSnapshotEntry.getKey(); compatResult = fieldSerializers[fieldIndex].ensureCompatibility(fieldToConfigSnapshotEntry.getValue()); if (compatResult.isRequiresMigration()) { return CompatibilityResult.requiresMigration(); } else { reorderedFieldSerializers[i] = fieldSerializers[fieldIndex]; } } else { return CompatibilityResult.requiresMigration(); } i++; } // ---- check subclass registration order and compatibility of registered serializers ---- // reordered subclass registrations and their serializers; // this won't be applied to this serializer until all compatibility checks have been completed final LinkedHashMap<Class<?>, Integer> reorderedRegisteredSubclassesToClasstags; final TypeSerializer<?>[] reorderedRegisteredSubclassSerializers; final LinkedHashMap<Class<?>, TypeSerializerConfigSnapshot> previousRegistrations = config.getRegisteredSubclassesToSerializerConfigSnapshots(); // the reconfigured list of registered subclasses will be the previous registered // subclasses in the original order with new subclasses appended at the end LinkedHashSet<Class<?>> reorderedRegisteredSubclasses = new LinkedHashSet<>(); reorderedRegisteredSubclasses.addAll(previousRegistrations.keySet()); reorderedRegisteredSubclasses.addAll( getRegisteredSubclassesFromExecutionConfig(clazz, executionConfig)); // re-establish the registered class tags and serializers reorderedRegisteredSubclassesToClasstags = createRegisteredSubclassTags(reorderedRegisteredSubclasses); reorderedRegisteredSubclassSerializers = createRegisteredSubclassSerializers( reorderedRegisteredSubclasses, executionConfig); i = 0; for (TypeSerializerConfigSnapshot previousRegisteredSerializerConfig : previousRegistrations.values()) { // check compatibility of subclass serializer compatResult = reorderedRegisteredSubclassSerializers[i].ensureCompatibility(previousRegisteredSerializerConfig); if (compatResult.isRequiresMigration()) { return CompatibilityResult.requiresMigration(); } i++; } // ------------------ ensure compatibility of non-registered subclass serializers ------------------ // the rebuilt cache for non-registered subclass serializers; // this won't be applied to this serializer until all compatibility checks have been completed HashMap<Class<?>, TypeSerializer<?>> rebuiltCache = new HashMap<>(); for (Map.Entry<Class<?>, TypeSerializerConfigSnapshot> previousCachedEntry : config.getNonRegisteredSubclassesToSerializerConfigSnapshots().entrySet()) { TypeSerializer<?> cachedSerializer = createSubclassSerializer(previousCachedEntry.getKey()); // check compatibility of cached subclass serializer compatResult = cachedSerializer.ensureCompatibility(previousCachedEntry.getValue()); if (compatResult.isRequiresMigration()) { return CompatibilityResult.requiresMigration(); } else { rebuiltCache.put(previousCachedEntry.getKey(), cachedSerializer); } } // completed compatibility checks; up to this point, we can just reconfigure // the serializer so that it is compatible and migration is not required this.fields = reorderedFields; this.fieldSerializers = reorderedFieldSerializers; this.registeredClasses = reorderedRegisteredSubclassesToClasstags; this.registeredSerializers = reorderedRegisteredSubclassSerializers; this.subclassSerializerCache = rebuiltCache; return CompatibilityResult.compatible(); } } } return CompatibilityResult.requiresMigration(); } public static final class PojoSerializerConfigSnapshot<T> extends GenericTypeSerializerConfigSnapshot<T> { private static final int VERSION = 1; /** * Ordered map of POJO fields to the configuration snapshots of their corresponding serializers. * * <p>Ordering of the fields is kept so that new Pojo serializers for previous data * may reorder the fields in case they are different. The order of the fields need to * stay the same for binary compatibility, as the field order is part of the serialization format. */ private LinkedHashMap<Field, TypeSerializerConfigSnapshot> fieldToSerializerConfigSnapshot; /** * Ordered map of registered subclasses to the configuration snapshots of their corresponding serializers. * * <p>Ordering of the registered subclasses is kept so that new Pojo serializers for previous data * may retain the same class tag used for registered subclasses. Newly registered subclasses that * weren't present before should be appended with the next available class tag. */ private LinkedHashMap<Class<?>, TypeSerializerConfigSnapshot> registeredSubclassesToSerializerConfigSnapshots; /** * Configuration snapshots of previously cached non-registered subclass serializers. * * <p>This is kept so that new Pojo serializers may eagerly repopulate their * cache with reconfigured subclass serializers. */ private HashMap<Class<?>, TypeSerializerConfigSnapshot> nonRegisteredSubclassesToSerializerConfigSnapshots; /** This empty nullary constructor is required for deserializing the configuration. */ public PojoSerializerConfigSnapshot() {} public PojoSerializerConfigSnapshot( Class<T> pojoType, LinkedHashMap<Field, TypeSerializerConfigSnapshot> fieldToSerializerConfigSnapshot, LinkedHashMap<Class<?>, TypeSerializerConfigSnapshot> registeredSubclassesToSerializerConfigSnapshots, HashMap<Class<?>, TypeSerializerConfigSnapshot> nonRegisteredSubclassesToSerializerConfigSnapshots) { super(pojoType); this.fieldToSerializerConfigSnapshot = Preconditions.checkNotNull(fieldToSerializerConfigSnapshot); this.registeredSubclassesToSerializerConfigSnapshots = Preconditions.checkNotNull(registeredSubclassesToSerializerConfigSnapshots); this.nonRegisteredSubclassesToSerializerConfigSnapshots = Preconditions.checkNotNull(nonRegisteredSubclassesToSerializerConfigSnapshots); } @Override public void write(DataOutputView out) throws IOException { super.write(out); // --- write fields and their serializers, in order out.writeInt(fieldToSerializerConfigSnapshot.size()); for (Map.Entry<Field, TypeSerializerConfigSnapshot> entry : fieldToSerializerConfigSnapshot.entrySet()) { out.writeUTF(entry.getKey().getName()); TypeSerializerUtil.writeSerializerConfigSnapshot(out, entry.getValue()); } // --- write registered subclasses and their serializers, in registration order out.writeInt(registeredSubclassesToSerializerConfigSnapshots.size()); for (Map.Entry<Class<?>, TypeSerializerConfigSnapshot> entry : registeredSubclassesToSerializerConfigSnapshots.entrySet()) { out.writeUTF(entry.getKey().getName()); TypeSerializerUtil.writeSerializerConfigSnapshot(out, entry.getValue()); } // --- write snapshot of non-registered subclass serializer cache out.writeInt(nonRegisteredSubclassesToSerializerConfigSnapshots.size()); for (Map.Entry<Class<?>, TypeSerializerConfigSnapshot> entry : nonRegisteredSubclassesToSerializerConfigSnapshots.entrySet()) { out.writeUTF(entry.getKey().getName()); TypeSerializerUtil.writeSerializerConfigSnapshot(out, entry.getValue()); } } @Override public void read(DataInputView in) throws IOException { super.read(in); // --- read fields and their serializers, in order int numFields = in.readInt(); this.fieldToSerializerConfigSnapshot = new LinkedHashMap<>(numFields); String fieldName; Field field; for (int i = 0; i < numFields; i++) { fieldName = in.readUTF(); // search all superclasses for the field Class<?> clazz = getTypeClass(); field = null; while (clazz != null) { try { field = clazz.getDeclaredField(fieldName); field.setAccessible(true); break; } catch (NoSuchFieldException e) { clazz = clazz.getSuperclass(); } } if (field == null) { // the field no longer exists in the POJO throw new IOException("Can't find field " + fieldName + " in POJO class " + getTypeClass().getName()); } else { fieldToSerializerConfigSnapshot.put( field, TypeSerializerUtil.readSerializerConfigSnapshot(in, getUserCodeClassLoader())); } } // --- read registered subclasses and their serializers, in registration order int numRegisteredSubclasses = in.readInt(); this.registeredSubclassesToSerializerConfigSnapshots = new LinkedHashMap<>(numRegisteredSubclasses); String registeredSubclassname; Class<?> registeredSubclass; for (int i = 0; i < numRegisteredSubclasses; i++) { registeredSubclassname = in.readUTF(); try { registeredSubclass = Class.forName(registeredSubclassname, true, getUserCodeClassLoader()); } catch (ClassNotFoundException e) { throw new IOException("Cannot find requested class " + registeredSubclassname + " in classpath.", e); } this.registeredSubclassesToSerializerConfigSnapshots.put( registeredSubclass, TypeSerializerUtil.readSerializerConfigSnapshot(in, getUserCodeClassLoader())); } // --- read snapshot of non-registered subclass serializer cache int numCachedSubclassSerializers = in.readInt(); this.nonRegisteredSubclassesToSerializerConfigSnapshots = new HashMap<>(numCachedSubclassSerializers); String cachedSubclassname; Class<?> cachedSubclass; for (int i = 0; i < numCachedSubclassSerializers; i++) { cachedSubclassname = in.readUTF(); try { cachedSubclass = Class.forName(cachedSubclassname, true, getUserCodeClassLoader()); } catch (ClassNotFoundException e) { throw new IOException("Cannot find requested class " + cachedSubclassname + " in classpath.", e); } this.nonRegisteredSubclassesToSerializerConfigSnapshots.put( cachedSubclass, TypeSerializerUtil.readSerializerConfigSnapshot(in, getUserCodeClassLoader())); } } @Override public int getVersion() { return VERSION; } public LinkedHashMap<Field, TypeSerializerConfigSnapshot> getFieldToSerializerConfigSnapshot() { return fieldToSerializerConfigSnapshot; } public LinkedHashMap<Class<?>, TypeSerializerConfigSnapshot> getRegisteredSubclassesToSerializerConfigSnapshots() { return registeredSubclassesToSerializerConfigSnapshots; } public HashMap<Class<?>, TypeSerializerConfigSnapshot> getNonRegisteredSubclassesToSerializerConfigSnapshots() { return nonRegisteredSubclassesToSerializerConfigSnapshots; } @Override public boolean equals(Object obj) { return super.equals(obj) && (obj instanceof PojoSerializerConfigSnapshot) && fieldToSerializerConfigSnapshot.equals(((PojoSerializerConfigSnapshot) obj).getFieldToSerializerConfigSnapshot()) && registeredSubclassesToSerializerConfigSnapshots.equals(((PojoSerializerConfigSnapshot) obj).getRegisteredSubclassesToSerializerConfigSnapshots()) && nonRegisteredSubclassesToSerializerConfigSnapshots.equals(((PojoSerializerConfigSnapshot) obj).nonRegisteredSubclassesToSerializerConfigSnapshots); } @Override public int hashCode() { return super.hashCode() + Objects.hash( fieldToSerializerConfigSnapshot, registeredSubclassesToSerializerConfigSnapshots, nonRegisteredSubclassesToSerializerConfigSnapshots); } } // -------------------------------------------------------------------------------------------- private void writeObject(ObjectOutputStream out) throws IOException, ClassNotFoundException { out.defaultWriteObject(); out.writeInt(fields.length); for (Field field: fields) { FieldSerializer.serializeField(field, out); } } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); int numFields = in.readInt(); fields = new Field[numFields]; for (int i = 0; i < numFields; i++) { fields[i] = FieldSerializer.deserializeField(in); } cl = Thread.currentThread().getContextClassLoader(); subclassSerializerCache = new HashMap<Class<?>, TypeSerializer<?>>(); } // -------------------------------------------------------------------------------------------- // Utilities // -------------------------------------------------------------------------------------------- /** * Extracts the subclasses of the base POJO class registered in the execution config. */ private static LinkedHashSet<Class<?>> getRegisteredSubclassesFromExecutionConfig( Class<?> basePojoClass, ExecutionConfig executionConfig) { LinkedHashSet<Class<?>> subclassesInRegistrationOrder = new LinkedHashSet<>(executionConfig.getRegisteredPojoTypes().size()); for (Class<?> registeredClass : executionConfig.getRegisteredPojoTypes()) { if (registeredClass.equals(basePojoClass)) { continue; } if (!basePojoClass.isAssignableFrom(registeredClass)) { continue; } subclassesInRegistrationOrder.add(registeredClass); } return subclassesInRegistrationOrder; } /** * Builds map of registered subclasses to their class tags. * Class tags will be integers starting from 0, assigned incrementally with the order of provided subclasses. */ private static LinkedHashMap<Class<?>, Integer> createRegisteredSubclassTags(LinkedHashSet<Class<?>> registeredSubclasses) { final LinkedHashMap<Class<?>, Integer> classToTag = new LinkedHashMap<>(); int id = 0; for (Class<?> registeredClass : registeredSubclasses) { classToTag.put(registeredClass, id); id ++; } return classToTag; } /** * Creates an array of serializers for provided list of registered subclasses. * Order of returned serializers will correspond to order of provided subclasses. */ private static TypeSerializer<?>[] createRegisteredSubclassSerializers( LinkedHashSet<Class<?>> registeredSubclasses, ExecutionConfig executionConfig) { final TypeSerializer<?>[] subclassSerializers = new TypeSerializer[registeredSubclasses.size()]; int i = 0; for (Class<?> registeredClass : registeredSubclasses) { subclassSerializers[i] = TypeExtractor.createTypeInfo(registeredClass).createSerializer(executionConfig); i++; } return subclassSerializers; } /** * Fetches cached serializer for a non-registered subclass; * also creates the serializer if it doesn't exist yet. * * This method is also exposed to package-private access * for testing purposes. */ TypeSerializer<?> getSubclassSerializer(Class<?> subclass) { TypeSerializer<?> result = subclassSerializerCache.get(subclass); if (result == null) { result = createSubclassSerializer(subclass); subclassSerializerCache.put(subclass, result); } return result; } private TypeSerializer<?> createSubclassSerializer(Class<?> subclass) { TypeSerializer<?> serializer = TypeExtractor.createTypeInfo(subclass).createSerializer(executionConfig); if (serializer instanceof PojoSerializer) { PojoSerializer<?> subclassSerializer = (PojoSerializer<?>) serializer; subclassSerializer.copyBaseFieldOrder(this); } return serializer; } /** * Finds and returns the order (0-based) of a POJO field. * Returns -1 if the field does not exist for this POJO. */ private int findField(Field f) { int foundIndex = 0; for (Field field : fields) { if (f.equals(field)) { return foundIndex; } foundIndex++; } return -1; } private void copyBaseFieldOrder(PojoSerializer<?> baseSerializer) { // do nothing for now, but in the future, adapt subclass serializer to have same // ordering as base class serializer so that binary comparison on base class fields // can work } /** * Build and return a snapshot of the serializer's parameters and currently cached serializers. */ private static <T> PojoSerializerConfigSnapshot<T> buildConfigSnapshot( Class<T> pojoType, LinkedHashMap<Class<?>, Integer> registeredSubclassesToTags, TypeSerializer<?>[] registeredSubclassSerializers, Field[] fields, TypeSerializer<?>[] fieldSerializers, HashMap<Class<?>, TypeSerializer<?>> nonRegisteredSubclassSerializerCache) { final LinkedHashMap<Field, TypeSerializerConfigSnapshot> fieldToSerializerConfigSnapshots = new LinkedHashMap<>(fields.length); for (int i = 0; i < fields.length; i++) { fieldToSerializerConfigSnapshots.put(fields[i], fieldSerializers[i].snapshotConfiguration()); } final LinkedHashMap<Class<?>, TypeSerializerConfigSnapshot> registeredSubclassesToSerializerConfigSnapshots = new LinkedHashMap<>(registeredSubclassesToTags.size()); for (Map.Entry<Class<?>, Integer> entry : registeredSubclassesToTags.entrySet()) { registeredSubclassesToSerializerConfigSnapshots.put( entry.getKey(), registeredSubclassSerializers[entry.getValue()].snapshotConfiguration()); } final HashMap<Class<?>, TypeSerializerConfigSnapshot> nonRegisteredSubclassesToSerializerConfigSnapshots = new LinkedHashMap<>(nonRegisteredSubclassSerializerCache.size()); for (Map.Entry<Class<?>, TypeSerializer<?>> entry : nonRegisteredSubclassSerializerCache.entrySet()) { nonRegisteredSubclassesToSerializerConfigSnapshots.put(entry.getKey(), entry.getValue().snapshotConfiguration()); } return new PojoSerializerConfigSnapshot<>( pojoType, fieldToSerializerConfigSnapshots, registeredSubclassesToSerializerConfigSnapshots, nonRegisteredSubclassesToSerializerConfigSnapshots); } // -------------------------------------------------------------------------------------------- // Test utilities // -------------------------------------------------------------------------------------------- @VisibleForTesting Field[] getFields() { return fields; } @VisibleForTesting TypeSerializer<?>[] getFieldSerializers() { return fieldSerializers; } @VisibleForTesting LinkedHashMap<Class<?>, Integer> getRegisteredClasses() { return registeredClasses; } @VisibleForTesting TypeSerializer<?>[] getRegisteredSerializers() { return registeredSerializers; } @VisibleForTesting HashMap<Class<?>, TypeSerializer<?>> getSubclassSerializerCache() { return subclassSerializerCache; } }