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

com.atlan.pkg.serde.FieldSerde.kt Maven / Gradle / Ivy

There is a newer version: 3.1.2
Show newest version
/* SPDX-License-Identifier: Apache-2.0
   Copyright 2023 Atlan Pte. Ltd. */
package com.atlan.pkg.serde

import com.atlan.cache.ReflectionCache
import com.atlan.model.assets.Asset
import com.atlan.model.fields.AtlanField
import com.atlan.model.fields.CustomMetadataField
import com.atlan.model.typedefs.AttributeDef
import com.atlan.pkg.PackageContext
import com.atlan.pkg.serde.cell.CellXformer
import com.atlan.pkg.serde.cell.TimestampXformer
import com.atlan.serde.Serde
import mu.KLogger
import java.lang.reflect.Method
import java.util.concurrent.ThreadLocalRandom
import java.util.concurrent.atomic.AtomicBoolean

/**
 * Static object to (de)serialize a single field's value for a given asset.
 */
object FieldSerde {
    val FAIL_ON_ERRORS = AtomicBoolean(true)

    /**
     * Serialize a single field's value from an asset object.
     *
     * @param ctx context in which the custom package is running
     * @param asset from which to serialize the value
     * @param field attribute within the asset to serialize
     * @param logger through which to record any errors
     * @return the serialized form of that field's value, from that asset
     */
    fun getValueForField(
        ctx: PackageContext<*>,
        asset: Asset,
        field: AtlanField,
        logger: KLogger,
    ): String {
        val value: Any?
        val dates: Boolean
        if (field is CustomMetadataField) {
            value = asset.getCustomMetadata(field.setName, field.attributeName)
            val attrId = ctx.client.customMetadataCache.getAttrIdForName(field.setName, field.attributeName)
            val attrDef = ctx.client.customMetadataCache.getAttributeDef(attrId)
            dates = attrDef.typeName.lowercase() == "date"
        } else {
            val deserializedName = ReflectionCache.getDeserializedName(asset.javaClass, field.atlanFieldName)
            value = ReflectionCache.getValue(asset, deserializedName)
            // TODO: confirm whether we should use this field name or the deserializedName
            dates = ReflectionCache.isDate(asset.javaClass, field.atlanFieldName)
        }
        return try {
            CellXformer.encode(ctx, value, asset.guid, dates)
        } catch (e: Exception) {
            if (FAIL_ON_ERRORS.get()) {
                throw e
            } else {
                logger.warn("Unable to encode value for field -- skipping {}: {}", field, asset)
                logger.debug("Full stack trace:", e)
            }
            ""
        }
    }

    /**
     * Deserialize a single field's value from a row of tabular data.
     *
     * @param ctx context in which the custom package is running
     * @param value the single field's value
     * @param setter the method on the asset that will be used to set the value onto the object
     * @param logger through which to record any errors
     * @return the deserialized form of that field's value
     */
    fun getValueFromCell(
        ctx: PackageContext<*>,
        value: String,
        setter: Method,
        logger: KLogger,
    ): Any? {
        val paramClass = ReflectionCache.getParameterOfMethod(setter)
        var innerClass: Class<*>? = null
        val fieldName = setter.name
        if (Collection::class.java.isAssignableFrom(paramClass) || Map::class.java.isAssignableFrom(paramClass)) {
            val paramType = ReflectionCache.getParameterizedTypeOfMethod(setter)
            innerClass = ReflectionCache.getClassOfParameterizedType(paramType)
        }
        return try {
            val assetClass = setter.declaringClass.enclosingClass
            CellXformer.decode(ctx, assetClass, value, paramClass, innerClass, fieldName)
        } catch (e: Exception) {
            if (FAIL_ON_ERRORS.get()) {
                throw e
            } else {
                logger.warn("Unable to decode value from field -- skipping {}: {}", setter.name, value)
                logger.debug("Full stack trace:", e)
            }
            null
        }
    }

    /**
     * Deserialize a single field's value from a row of tabular data, when that field
     * is stored as custom metadata.
     *
     * @param attrDef attribute definition of the field
     * @param value the single field's value
     * @return the deserialized form of that field's value
     */
    fun getCustomMetadataValueFromString(
        attrDef: AttributeDef,
        value: String?,
    ): Any? {
        return if (value.isNullOrEmpty()) {
            null
        } else if (attrDef.options?.multiValueSelect == true) {
            getMultiValuedCustomMetadata(attrDef, value)
        } else {
            getSingleValuedCustomMetadata(attrDef, value)
        }
    }

    private fun getMultiValuedCustomMetadata(
        attrDef: AttributeDef,
        value: String?,
    ): List {
        return if (value.isNullOrEmpty()) {
            listOf()
        } else {
            value.split(CellXformer.LIST_DELIMITER).map { getSingleValuedCustomMetadata(attrDef, it.trim()).toString() }
        }
    }

    private fun getSingleValuedCustomMetadata(
        attrDef: AttributeDef,
        value: String?,
    ): Any? {
        if (value == null) {
            return null
        }
        return when (attrDef.basicType.lowercase()) {
            "boolean" -> CellXformer.decodeBoolean(value)
            "int" -> CellXformer.decodeInt(value)
            "date" -> TimestampXformer.decode(value, "unused")
            "long" -> CellXformer.decodeLong(value)
            "float" -> CellXformer.decodeDouble(value)
            else -> CellXformer.decodeString(value)
        }
    }

    /**
     * Get a builder for the provided asset type.
     *
     * @param typeName name of the asset type for which to get a builder
     * @return a builder for that asset type
     */
    fun getBuilderForType(typeName: String): Asset.AssetBuilder<*, *> {
        val assetClass = Serde.getAssetClassForType(typeName)
        val method = assetClass.getMethod("_internal")
        return (method.invoke(null) as Asset.AssetBuilder<*, *>)
            .guid("-" + ThreadLocalRandom.current().nextLong(0, Long.MAX_VALUE - 1))
    }

    /**
     * Get a relationship by qualifiedName for the provided asset type.
     *
     * @param typeName name of the asset type for which to get a relationship
     * @param qualifiedName of the asset for which to get a relationship
     * @return a relationship reference for that asset
     */
    fun getRefByQualifiedName(
        typeName: String,
        qualifiedName: String,
    ): Asset {
        val assetClass = Serde.getAssetClassForType(typeName)
        val method = assetClass.getMethod("refByQualifiedName", String::class.java)
        return (method.invoke(null, qualifiedName) as Asset)
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy