
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