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

com.atlan.pkg.serde.RowDeserializer.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.assets.Asset.AssetBuilder
import com.atlan.model.core.CustomMetadataAttributes
import com.atlan.pkg.PackageContext
import com.atlan.pkg.serde.RowSerde.CM_HEADING_DELIMITER
import com.atlan.pkg.serde.cell.AssetRefXformer
import com.atlan.pkg.serde.csv.CSVXformer
import com.atlan.serde.Serde
import com.atlan.util.AssetBatch
import mu.KLogger
import java.util.concurrent.ThreadLocalRandom

/**
 * Class to generally deserialize an asset object from a row of tabular data.
 * Note: at least the qualifiedName and type of the asset must be present in every row.
 *
 * @param ctx context in which the package is running
 * @param heading the list of field names, in the order they appear as columns in the tabular data
 * @param row values for each field in a single row, representing a single asset
 * @param typeIdx the numeric index for the type in the list of columns
 * @param qnIdx the numeric index for the qualifiedName in the list of columns
 * @param typeName the name of the type of asset on the row (calculated from typeIdx and row, if not specified)
 * @param qualifiedName the qualifiedName of the asset on the row (calculated from qnIdx and row, if not specified)
 * @param logger through which to record any problems
 * @param skipColumns columns to skip, i.e. that need to be processed in a later pass
 */
class RowDeserializer(
    private val ctx: PackageContext<*>,
    val heading: List,
    val row: List,
    private val typeIdx: Int = -1,
    private val qnIdx: Int = -1,
    val typeName: String = CSVXformer.trimWhitespace(row.getOrElse(typeIdx) { "" }),
    val qualifiedName: String = CSVXformer.trimWhitespace(row.getOrElse(qnIdx) { "" }),
    private val logger: KLogger,
    private val skipColumns: Set,
) {
    /**
     * Actually deserialize the provided inputs into a builder for an asset object.
     *
     * @return the builders, for the primary asset object (already-populated with the metadata from the row of tabular data)
     *         and any related asset builders (for example, for READMEs, Links, or other assets that were denormalized in the tabular form)
     */
    fun getAssets(): RowDeserialization? {
        if (typeName.isBlank() || qualifiedName.isBlank()) {
            logger.warn("No qualifiedName or typeName found on row, cannot deserialize: {}", row)
        } else {
            val builder =
                FieldSerde.getBuilderForType(typeName)
                    .guid("-" + ThreadLocalRandom.current().nextLong(0, Long.MAX_VALUE - 1))
                    .qualifiedName(qualifiedName)
            return getAssets(builder)
        }
        return null
    }

    /**
     * Actually deserialize the provided inputs into a builder for an asset object.
     *
     * @param builder for the primary asset represented by the row
     * @return the builders, for the primary asset object (already-populated with the metadata from the row of tabular data)
     *         and any related asset builders (for example, for READMEs, Links, or other assets that were denormalized in the tabular form)
     */
    @Suppress("UNCHECKED_CAST")
    fun getAssets(builder: AssetBuilder<*, *>): RowDeserialization? {
        val partial = builder.build()
        if (partial.typeName.isNullOrBlank() || partial.qualifiedName.isNullOrBlank()) {
            logger.warn("No qualifiedName or typeName found in builder, cannot deserialize: {}", row)
        } else {
            val deserialization = RowDeserialization(AssetBatch.AssetIdentity(partial.typeName, partial.qualifiedName), builder)
            val customMetadataMap = mutableMapOf>()
            for (i in heading.indices) {
                val fieldName = heading[i]
                if (!skipColumns.contains(fieldName)) {
                    if (fieldName.isNotEmpty()) {
                        val value = getValue(fieldName)
                        if (fieldName.contains(CM_HEADING_DELIMITER)) {
                            // Custom metadata field...
                            val tokens = fieldName.split(CM_HEADING_DELIMITER)
                            val setName = tokens[0]
                            val attrName = tokens[1]
                            if (!customMetadataMap.containsKey(setName)) {
                                customMetadataMap[setName] = CustomMetadataAttributes.builder()
                            }
                            customMetadataMap[setName]!!.attribute(attrName, value)
                        } else if (value != null) {
                            // "Normal" field...
                            val setter = ReflectionCache.getSetter(builder.javaClass, fieldName)
                            if (setter != null) {
                                if (AssetRefXformer.requiresHandling(fieldName, value)) {
                                    if (value is Collection<*>) {
                                        deserialization.related[fieldName] = value as Collection
                                    } else {
                                        deserialization.related[fieldName] = listOf(value as Asset)
                                    }
                                } else {
                                    // Only set the value on the asset directly if it does not require
                                    // special handling, otherwise leave it to the special handling
                                    // to set the value (later)
                                    ReflectionCache.setValue(builder, fieldName, value)
                                }
                            }
                        }
                    }
                }
            }
            if (customMetadataMap.isNotEmpty()) {
                for ((key, value) in customMetadataMap) {
                    builder.customMetadata(key, value.build())
                }
            }
            return deserialization
        }
        return null
    }

    /**
     * Retrieve the decoded cell value for the specified field -- AFTER it has been transformed.
     *
     * @param fieldName of the field to retrieve
     * @return the decoded value for that cell
     */
    fun getValue(fieldName: String): Any? {
        val rValue = getRawValue(fieldName)
        return if (fieldName.contains(CM_HEADING_DELIMITER)) {
            // Custom metadata field...
            val cache = ctx.client.customMetadataCache
            val tokens = fieldName.split(CM_HEADING_DELIMITER)
            val setName = tokens[0]
            val attrName = tokens[1]
            val attrId = cache.getAttrIdForName(setName, attrName)
            if (attrId != null) {
                val attrDef = cache.getAttributeDef(attrId)
                return FieldSerde.getCustomMetadataValueFromString(attrDef, rValue)
            } else {
                // If we cannot translate via the attribute def, return it as-is
                rValue
            }
        } else {
            // "Normal" field...
            val setter = ReflectionCache.getSetter(Serde.getBuilderClassForType(typeName), fieldName)
            if (setter != null) {
                FieldSerde.getValueFromCell(ctx, rValue, setter, logger)
            } else {
                // If this isn't a "real" field, return it as a simple string
                rValue
            }
        }
    }

    /**
     * Retrieve the raw cell value for the specified field -- without ANY decoding applied to it.
     *
     * @param fieldName of the field to retrieve
     * @return the raw, un-decoded value for that cell
     */
    fun getRawValue(fieldName: String): String {
        if (fieldName.isNotBlank()) {
            val i = heading.indexOf(fieldName)
            if (i >= 0) {
                return CSVXformer.trimWhitespace(row[i])
            }
        }
        return ""
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy