
com.atlan.serde.AssetDeserializer Maven / Gradle / Ivy
// Generated by delombok at Thu Oct 10 18:56:33 UTC 2024
/* SPDX-License-Identifier: Apache-2.0
Copyright 2022 Atlan Pte. Ltd. */
package com.atlan.serde;
import com.atlan.AtlanClient;
import com.atlan.cache.ReflectionCache;
import com.atlan.exception.AtlanException;
import com.atlan.exception.NotFoundException;
import com.atlan.model.assets.*;
import com.atlan.model.core.AtlanTag;
import com.atlan.model.core.CustomMetadataAttributes;
import com.atlan.util.JacksonUtils;
import com.atlan.util.StringUtils;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import java.io.IOException;
import java.lang.reflect.*;
import java.util.*;
/**
* Deserialization of all {@link Asset} objects, down through the entire inheritance hierarchy.
* This custom deserialization is necessary to flatten some specific aspects of complexity in Atlan's payloads:
*
* - The nested
attributes
and relationshipAttributes
structures.
* - The possibility that the same (relationship) attribute could appear in either of these nested structures.
* - Handling the extension of properties as you traverse down the inheritance structures, without needing to also extend these nested structures through inheritance.
* - Automatically translating the nested
businessAttributes
structure into custom metadata, including translating from Atlan's internal hashed-string representations into human-readable names.
*
*/
public class AssetDeserializer extends StdDeserializer {
@java.lang.SuppressWarnings("all")
@lombok.Generated
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AssetDeserializer.class);
private static final long serialVersionUID = 2L;
private final AtlanClient client;
public AssetDeserializer(AtlanClient client) {
this(Asset.class, client);
}
public AssetDeserializer(Class> t, AtlanClient client) {
super(t);
this.client = client;
}
/**
* {@inheritDoc}
*/
@Override
public Object deserializeWithType(JsonParser parser, DeserializationContext context, TypeDeserializer typeDeserializer) throws IOException {
return deserialize(parser, context);
}
/**
* {@inheritDoc}
*/
@Override
public Asset deserialize(JsonParser parser, DeserializationContext context) throws IOException {
return deserialize(parser.getCodec().readTree(parser));
}
// Suppress deprecation notice on use of atlanTagNames builder
/**
* Actually do the work of deserializing an asset.
*
* @param root of the parsed JSON tree
* @return the deserialized asset
* @throws IOException on any issues parsing the JSON
*/
@SuppressWarnings("deprecation")
Asset deserialize(JsonNode root) throws IOException {
JsonNode attributes = root.get("attributes");
JsonNode relationshipGuid = root.get("relationshipGuid");
JsonNode relationshipAttributes = root.get("relationshipAttributes");
JsonNode businessAttributes = root.get("businessAttributes");
JsonNode classificationNames = root.get("classificationNames");
Asset.AssetBuilder, ?> builder;
Class> assetClass;
JsonNode typeNameJson = root.get("typeName");
String typeName = null;
if (typeNameJson == null || typeNameJson.isNull()) {
builder = IndistinctAsset._internal();
assetClass = IndistinctAsset.class;
} else {
typeName = root.get("typeName").asText();
try {
assetClass = Serde.getAssetClassForType(typeName);
Method method = assetClass.getMethod("_internal");
Object result = method.invoke(null);
builder = (Asset.AssetBuilder, ?>) result;
} catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
log.warn("Unable to dynamically retrieve asset for typeName {}, falling back to an IndistinctAsset.", typeName, e);
builder = IndistinctAsset._internal();
assetClass = IndistinctAsset.class;
}
}
// Start by deserializing all the non-attribute properties (defined at Asset-level)
// Include reference attributes, for related entities
builder.typeName(JacksonUtils.deserializeString(root, "typeName")).guid(JacksonUtils.deserializeString(root, "guid")).displayText(JacksonUtils.deserializeString(root, "displayText")).entityStatus(JacksonUtils.deserializeString(root, "entityStatus")).relationshipType(JacksonUtils.deserializeString(root, "relationshipType")).relationshipGuid(JacksonUtils.deserializeString(root, "relationshipGuid")).relationshipStatus(JacksonUtils.deserializeObject(client, root, "relationshipStatus", new TypeReference<>() {
})).uniqueAttributes(JacksonUtils.deserializeObject(client, root, "uniqueAttributes", new TypeReference<>() {
})).status(JacksonUtils.deserializeObject(client, root, "status", new TypeReference<>() {
})).createdBy(JacksonUtils.deserializeString(root, "createdBy")).updatedBy(JacksonUtils.deserializeString(root, "updatedBy")).createTime(JacksonUtils.deserializeLong(root, "createTime")).updateTime(JacksonUtils.deserializeLong(root, "updateTime")).deleteHandler(JacksonUtils.deserializeString(root, "deleteHandler")).isIncomplete(JacksonUtils.deserializeBoolean(root, "isIncomplete")).depth(JacksonUtils.deserializeLong(root, "depth"));
Set atlanTags = JacksonUtils.deserializeObject(client, root, "classifications", new TypeReference<>() {
});
if (atlanTags != null) {
builder.atlanTags(atlanTags);
}
TreeSet meaningNames = JacksonUtils.deserializeObject(client, root, "meaningNames", new TypeReference<>() {
});
if (meaningNames != null) {
builder.meaningNames(meaningNames);
}
TreeSet meanings = JacksonUtils.deserializeObject(client, root, "meanings", new TypeReference<>() {
});
if (meanings != null) {
builder.meanings(meanings);
}
TreeSet pendingTasks = JacksonUtils.deserializeObject(client, root, "pendingTasks", new TypeReference<>() {
});
if (pendingTasks != null) {
builder.pendingTasks(pendingTasks);
}
List immediateUpstream = JacksonUtils.deserializeObject(client, root, "immediateUpstream", new TypeReference<>() {
});
if (immediateUpstream != null) {
builder.immediateUpstream(immediateUpstream);
}
List immediateDownstream = JacksonUtils.deserializeObject(client, root, "immediateDownstream", new TypeReference<>() {
});
if (immediateDownstream != null) {
builder.immediateDownstream(immediateDownstream);
}
TreeMap customAttributes = JacksonUtils.deserializeObject(client, root, "customAttributes", new TypeReference<>() {
});
Class> builderClass = builder.getClass();
Map leftOverAttributes = new HashMap<>();
// If the same attribute appears in both 'attributes' and 'relationshipAttributes'
// then retain the 'relationshipAttributes' (more information) and skip the 'attributes'
// copy of the same
Set processedAttributes = new HashSet<>();
// Only process relationshipAttributes if this is a full asset, not a relationship
// reference. (If it is a relationship reference, the relationshipGuid will be non-null.)
if (relationshipGuid == null || relationshipGuid.isNull()) {
if (relationshipAttributes != null && !relationshipAttributes.isNull()) {
Iterator itr = relationshipAttributes.fieldNames();
while (itr.hasNext()) {
String relnKey = itr.next();
String deserializeName = ReflectionCache.getDeserializedName(assetClass, relnKey);
Method method = ReflectionCache.getSetter(builderClass, deserializeName);
if (method != null) {
try {
Object value = Serde.deserialize(client, relationshipAttributes.get(relnKey), method, deserializeName);
boolean set = ReflectionCache.setValue(builder, deserializeName, value);
if (set) {
processedAttributes.add(deserializeName);
}
} catch (NoSuchMethodException e) {
throw new IOException("Missing fromValue method for enum.", e);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new IOException("Failed to deserialize through reflection.", e);
}
}
}
}
}
if (attributes != null && !attributes.isNull()) {
Iterator itr = attributes.fieldNames();
while (itr.hasNext()) {
String attrKey = itr.next();
String deserializeName = ReflectionCache.getDeserializedName(assetClass, attrKey);
// Only proceed with deserializing the 'attributes' copy of an attribute if
// it was not already deserialized as a more complete relationship (above)
if (!processedAttributes.contains(deserializeName)) {
Method method = ReflectionCache.getSetter(builderClass, deserializeName);
if (method != null) {
try {
Object value = Serde.deserialize(client, attributes.get(attrKey), method, deserializeName);
ReflectionCache.setValue(builder, deserializeName, value);
} catch (NoSuchMethodException e) {
throw new IOException("Missing fromValue method for enum.", e);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new IOException("Failed to deserialize through reflection.", e);
}
} else {
// If the setter was not found, still retain it for later processing
// (this is where custom attributes will end up for search results)
leftOverAttributes.put(attrKey, attributes.get(attrKey));
}
}
}
}
// Custom (metadata) attributes can come from two places, only one of which should ever have data...
Map cm = null;
// 1. For search results, they're embedded in `attributes` in the form . (custom metadata)
// or just __customAttributes (source-specific custom attributes)
if (!leftOverAttributes.isEmpty()) {
// Translate these into custom metadata structure
try {
cm = client.getCustomMetadataCache().getCustomMetadataFromSearchResult(leftOverAttributes);
} catch (AtlanException e) {
throw new IOException(e);
}
if (leftOverAttributes.containsKey("__customAttributes")) {
JsonNode caSearch = leftOverAttributes.get("__customAttributes");
if (caSearch != null && !caSearch.isNull()) {
customAttributes = Serde.allInclusiveMapper.readValue(caSearch.asText(), new TypeReference<>() {
});
}
}
}
// Note that these are source-provided custom attributes, not custom METADATA attributes (different things)
if (customAttributes != null) {
builder.customAttributes(customAttributes);
}
// 2. For asset retrievals, they're all in a `businessAttributes` dict (custom metadata)
// or directly under `customAttributes` (source-specific custom attributes, handled above)
if (businessAttributes != null) {
// Translate these into custom metadata structure
try {
cm = client.getCustomMetadataCache().getCustomMetadataFromBusinessAttributes(businessAttributes);
} catch (AtlanException e) {
throw new IOException(e);
}
}
Set clsNames = null;
if (classificationNames != null && classificationNames.isArray()) {
clsNames = new HashSet<>();
// Translate these IDs in to human-readable names
for (JsonNode element : classificationNames) {
String tagId = element.asText();
String name = null;
try {
name = client.getAtlanTagCache().getNameForId(tagId);
} catch (NotFoundException e) {
log.debug("Unable to find tag with ID {}, deserializing as {}.", tagId, Serde.DELETED_AUDIT_OBJECT, e);
} catch (AtlanException e) {
throw new IOException(e);
}
if (name == null) {
// Note: the name could be null here either because it was not found in the
// first place (NotFoundException), or because it is already tracked as deleted
// (in which case it'll come back from getNameForId as null rather than a NFE).
name = Serde.DELETED_AUDIT_OBJECT;
}
clsNames.add(name);
}
}
// Special cases to wrap-up:
// Decode the Readme's description after deserialization
if (typeName != null && typeName.equals("Readme")) {
builder.description(StringUtils.decodeContent(builder.build().getDescription()));
}
if (cm != null) {
builder.customMetadataSets(cm);
}
if (clsNames != null) {
builder.atlanTagNames(clsNames);
}
Asset result = builder.build();
result.setRawJsonObject(root);
return result;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy