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

com.expediagroup.rhapsody.kafka.avro.util.AvroDeserialization Maven / Gradle / Ivy

/**
 * Copyright 2019 Expedia, Inc.
 *
 * Licensed 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 com.expediagroup.rhapsody.kafka.avro.util;

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;

import org.apache.avro.Schema;
import org.apache.avro.reflect.ReflectData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.expediagroup.rhapsody.util.FieldResolution;
import com.expediagroup.rhapsody.util.Instantiation;
import com.expediagroup.rhapsody.util.TypeResolution;
import com.expediagroup.rhapsody.util.ValueResolution;

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 generic Data Field: genericField={} schema={} e={}", 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(writerField -> generateReaderReferenceSchemaField(referenceData, fieldsByName.get(writerField.name()), writerField, 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(nonNullFieldWriterSchema -> generateReaderReferenceSchema(fieldReferenceData, nonNullFieldWriterSchema, typeSchemaLoader))
            .orElseGet(() -> typeSchemaLoader.apply(fieldReferenceData.getClass()));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy