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

codec.SpecificRecordCodec.kt Maven / Gradle / Ivy

package io.toolisticon.kotlin.avro.codec

import io.toolisticon.kotlin.avro.AvroKotlin
import io.toolisticon.kotlin.avro.repository.AvroSchemaResolver
import io.toolisticon.kotlin.avro.codec.AvroCodec.Converter
import io.toolisticon.kotlin.avro.codec.AvroCodec.SingleObjectDecoder
import io.toolisticon.kotlin.avro.codec.AvroCodec.SingleObjectEncoder
import io.toolisticon.kotlin.avro.codec.SpecificRecordCodec.DecoderSpecificRecordClassResolver
import io.toolisticon.kotlin.avro.model.wrapper.AvroSchema
import io.toolisticon.kotlin.avro.value.SingleObjectEncodedBytes
import org.apache.avro.Schema
import org.apache.avro.generic.GenericRecord
import org.apache.avro.specific.SpecificData
import org.apache.avro.specific.SpecificRecordBase
import org.apache.avro.util.ClassUtils

object SpecificRecordCodec {

  fun  avroSchema(recordType: Class): AvroSchema = AvroSchema(schema = recordType.getDeclaredField("SCHEMA\$").get(null) as Schema)
  fun  specificData(recordType: Class): SpecificData =
    recordType.getDeclaredField("MODEL\$").apply { isAccessible = true }.get(null) as SpecificData

  /**
   * Resolver for a concrete class used by decoding of Avro single object into Avro specific record.
   */
  fun interface DecoderSpecificRecordClassResolver : (AvroSchema) -> Class

  /**
   * Default implementation using [Class.forName].
   */
  @Suppress("UNCHECKED_CAST")
  val reflectionBasedDecoderSpecificRecordClassResolver = DecoderSpecificRecordClassResolver {
    ClassUtils.forName(it.canonicalName.fqn) as Class
  }

  @JvmStatic
  fun  encodeSingleObject(record: T): SingleObjectEncodedBytes {
    return specificRecordSingleObjectEncoder().encode(record)
  }

  @Suppress("UNCHECKED_CAST")
  @JvmStatic
  fun  decodeSingleObject(
    bytes: SingleObjectEncodedBytes,
    schemaResolver: AvroSchemaResolver
  ): T = specificRecordSingleObjectDecoder(schemaResolver).decode(bytes) as T

  /**
   * [Converter] from [SpecificRecordBase] to [GenericRecord].
   */
  @JvmStatic
  fun specificRecordToGenericRecordConverter() = Converter { specific ->
    val data = AvroCodec.genericData(specific.specificData)

    data.deepCopy(specific.schema, specific) as GenericRecord
  }

  /**
   * [Converter] from [GenericRecord] to [SpecificRecordBase].
   */
  @JvmStatic
  fun genericRecordToSpecificRecordConverter(readerType: Class<*>? = null) = Converter { generic ->
    val writerSchema = generic.schema

    // TODO if caller does not provide expected type, we use the writer schema to derive the class ... this could be wrong.
    val readerClass: Class<*> = AvroKotlin.specificData.getClass(writerSchema)

    val readerSpecificData = SpecificData.getForClass(readerClass)

    readerSpecificData.deepCopy(writerSchema, generic) as SpecificRecordBase
  }

  @JvmStatic
  fun specificRecordSingleObjectDecoder(writerSchemaResolver: AvroSchemaResolver): SingleObjectDecoder =
    SpecificRecordSingleObjectDecoder(writerSchemaResolver)


  @JvmStatic
  fun specificRecordSingleObjectEncoder(): SingleObjectEncoder = SpecificRecordSingleObjectEncoder()
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy