All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.atleon.avro.AvroDeserialization Maven / Gradle / Ivy

package io.atleon.avro;

import io.atleon.util.FieldResolution;
import io.atleon.util.Instantiation;
import io.atleon.util.TypeResolution;
import io.atleon.util.ValueResolution;
import org.apache.avro.Schema;
import org.apache.avro.reflect.ReflectData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;

public final class AvroDeserialization {

    private static final Logger LOGGER = LoggerFactory.getLogger(AvroDeserialization.class);

    private AvroDeserialization() {

    }

    public static Object instantiateReferenceData(Schema writerSchema) {
        Object referenceData = Instantiation.one(ReflectData.get().getClass(writerSchema));
        if (TypeResolution.isGenericClass(referenceData.getClass())) {
            consumeGenericFields(referenceData.getClass(), writerSchema, (genericField, fieldSchema) ->
                instantiateGenericReferenceDataField(referenceData, genericField, fieldSchema));
        }
        return referenceData;
    }

    public static void consumeGenericFields(
        Class referenceClass,
        Schema schema,
        BiConsumer genericFieldConsumer
    ) {
        Set genericParameters = TypeResolution.getAllTypeParameters(referenceClass);
        Map fieldsByName = FieldResolution.getAllFieldsByName(referenceClass);
        for (Schema.Field schemaField : schema.getFields()) {
            Field dataField = fieldsByName.get(schemaField.name());
            if (dataField != null && genericParameters.contains(dataField.getGenericType())) {
                genericFieldConsumer.accept(dataField, schemaField.schema());
            }
        }
    }

    public static Schema generateReaderReferenceSchema(
        Object referenceData,
        Schema writerSchema,
        Function typeSchemaLoader
    ) {
        return AvroSchemas.isRecord(writerSchema) && TypeResolution.isGenericClass(referenceData.getClass())
            ? generateReaderReferenceRecordSchema(referenceData, writerSchema, typeSchemaLoader)
            : typeSchemaLoader.apply(referenceData.getClass());
    }

    private static void instantiateGenericReferenceDataField(Object referenceData, Field genericField, Schema fieldSchema) {
        AvroSchemas.reduceNonNull(fieldSchema).ifPresent(nonNullFieldSchema ->
            instantiateNonNullGenericReferenceDataField(referenceData, genericField, nonNullFieldSchema));
    }

    private static void instantiateNonNullGenericReferenceDataField(
        Object referenceData,
        Field genericField,
        Schema nonNullFieldSchema
    ) {
        try {
            Object fieldData = instantiateReferenceData(nonNullFieldSchema);
            genericField.setAccessible(true);
            genericField.set(referenceData, fieldData);
        } catch (Exception e) {
            LOGGER.warn("Failed to instantiate: genericField={} schema={}", genericField, nonNullFieldSchema, e);
        }
    }

    private static Schema generateReaderReferenceRecordSchema(
        Object referenceData,
        Schema writerSchema,
        Function typeSchemaLoader
    ) {
        Class referenceDataClass = referenceData.getClass();
        Map fieldsByName = FieldResolution.getAllFieldsByName(referenceDataClass);
        List schemaFields = writerSchema.getFields().stream()
            .filter(writerField -> fieldsByName.containsKey(writerField.name()))
            .map(it -> generateReaderReferenceSchemaField(referenceData, fieldsByName.get(it.name()), it, typeSchemaLoader))
            .collect(Collectors.toList());
        return Schema.createRecord(
            referenceDataClass.getCanonicalName(),
            writerSchema.getDoc(),
            null,
            writerSchema.isError(),
            schemaFields
        );
    }

    private static Schema.Field generateReaderReferenceSchemaField(
        Object referenceData,
        Field dataField,
        Schema.Field writerField,
        Function typeSchemaLoader
    ) {
        Object fieldReferenceData = ValueResolution.getFieldValue(referenceData, dataField);
        Schema fieldReaderSchema = fieldReferenceData == null
            ? typeSchemaLoader.apply(dataField.getGenericType())
            : generateFieldReaderReferenceSchema(fieldReferenceData, writerField.schema(), typeSchemaLoader);
        Schema nullSafeFieldReaderSchema = AvroSchemas.isNullable(writerField.schema())
            ? AvroSchemas.makeNullable(fieldReaderSchema)
            : fieldReaderSchema;
        return new Schema.Field(
            writerField.name(),
            nullSafeFieldReaderSchema,
            writerField.doc(),
            writerField.defaultVal(),
            writerField.order()
        );
    }

    private static Schema generateFieldReaderReferenceSchema(
        Object fieldReferenceData,
        Schema fieldWriterSchema,
        Function typeSchemaLoader
    ) {
        return AvroSchemas.reduceNonNull(fieldWriterSchema)
            .map(nonNullSchema -> generateReaderReferenceSchema(fieldReferenceData, nonNullSchema, typeSchemaLoader))
            .orElseGet(() -> typeSchemaLoader.apply(fieldReferenceData.getClass()));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy