
com.atlan.model.search.Suggestions Maven / Gradle / Ivy
// Generated by delombok at Thu Oct 10 18:56:33 UTC 2024
/* SPDX-License-Identifier: Apache-2.0
Copyright 2023 Atlan Pte. Ltd. */
package com.atlan.model.search;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import com.atlan.Atlan;
import com.atlan.AtlanClient;
import com.atlan.exception.AtlanException;
import com.atlan.exception.NotFoundException;
import com.atlan.model.assets.Asset;
import com.atlan.model.core.AssetMutationResponse;
import com.atlan.model.core.AtlanTag;
import com.atlan.model.fields.AtlanField;
import com.atlan.serde.Serde;
import com.atlan.util.ParallelBatch;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
/**
* Suggestion abstraction mechanism, to simplify finding suggestions for metadata enrichment
* for a given asset. This works purely by looking at other assets with the same name (and type)
* that have this metadata populated, and rank-ordering any such metadata by how frequently it
* occurs across other assets of the same type with the same name.
*/
public class Suggestions {
private static final String AGG_DESCRIPTION = "group_by_description";
private static final String AGG_USER_DESCRIPTION = "group_by_userDescription";
private static final String AGG_OWNER_USERS = "group_by_ownerUsers";
private static final String AGG_OWNER_GROUPS = "group_by_ownerGroups";
private static final String AGG_ATLAN_TAGS = "group_by_tags";
private static final String AGG_TERMS = "group_by_terms";
public enum TYPE {
/**
* System-level description suggestions.
*/
SystemDescription, /**
* User-provided description suggestions.
*/
UserDescription, /**
* Suggestions for individual users who could be owners.
*/
IndividualOwners, /**
* Suggestions for groups who could be owners.
*/
GroupOwners, /**
* Suggestions for Atlan tags to assign to the asset.
*/
Tags, /**
* Suggestions for terms to assign to the asset.
*/
Terms;
}
/**
* Build a suggestion finder for the provided asset.
*
* @param asset for which to find suggestions
* @return the start of a suggestion finder for the provided asset, against the default tenant
*/
public static SuggestionsBuilder finder(Asset asset) {
return finder(Atlan.getDefaultClient(), asset);
}
/**
* Build a suggestion finder against the provided Atlan tenant for the provided asset.
*
* @param client connectivity to an Atlan tenant
* @param asset for which to find suggestions
* @return the start of a suggestion finder for the provided asset, against the specified tenant
*/
public static SuggestionsBuilder finder(AtlanClient client, Asset asset) {
return _internal().client(client).asset(asset).includeArchived(false).maxSuggestions(5);
}
/**
* Client through which to find suggestions.
*/
AtlanClient client;
/**
* Asset for which to find suggestions.
*/
Asset asset;
/**
* Whether to include archived assets as part of suggestions (true) or not (false, default).
*/
Boolean includeArchived;
/**
* Which type(s) of suggestions to include in the search and results.
*/
Collection includes;
/**
* Maximum number of suggestions to return (default: 5).
*/
Integer maxSuggestions;
/**
* By default, we will only look for suggestions on other assets with exactly the same
* type. You may want to expand this, for example, for suggested metadata for tables you might also want to look
* at Views. You can add any additional types here you want to consider where an asset with the same name as
* this asset is likely have similar metadata (and thus be valid for providing suggestions).
*/
Collection withOtherTypes;
/**
* By default, we will only match on the name (exactly) of the provided asset.
* You may want to expand this, for example, to look for assets with the same name as well as with
* some other context, for example, looking only at columns with the same name that are also in parent
* tables that have the same name. (Columns like 'ID' may otherwise be insufficiently unique to have
* very useful suggestions.)
*/
Collection wheres;
/**
* By default, we will only match on the name (exactly) of the provided asset.
* You may want to expand this, for example, to look for assets with the same name as well as without
* some other context, for example, looking only at columns with the same name that are not in a particular
* schema (e.g. one used purely for testing purposes).
*/
Collection whereNots;
public static class SuggestionsBuilder {
@java.lang.SuppressWarnings("all")
@lombok.Generated
private AtlanClient client;
@java.lang.SuppressWarnings("all")
@lombok.Generated
private Asset asset;
@java.lang.SuppressWarnings("all")
@lombok.Generated
private Boolean includeArchived;
@java.lang.SuppressWarnings("all")
@lombok.Generated
private java.util.ArrayList includes;
@java.lang.SuppressWarnings("all")
@lombok.Generated
private Integer maxSuggestions;
@java.lang.SuppressWarnings("all")
@lombok.Generated
private java.util.ArrayList withOtherTypes;
@java.lang.SuppressWarnings("all")
@lombok.Generated
private java.util.ArrayList wheres;
@java.lang.SuppressWarnings("all")
@lombok.Generated
private java.util.ArrayList whereNots;
/**
* Find the requested suggestions and return the results
* (but do not make any changes to the asset itself).
*
* @throws AtlanException on any issue interacting with the APIs
*/
public SuggestionResponse get() throws AtlanException {
List allTypes = new ArrayList<>();
allTypes.add(asset.getTypeName());
if (withOtherTypes != null && !withOtherTypes.isEmpty()) {
allTypes.addAll(withOtherTypes);
}
FluentSearch.FluentSearchBuilder, ?> builder = // We only care about the aggregations, not results
client.assets.select(includeArchived).where(Asset.TYPE_NAME.in(allTypes)).where(Asset.NAME.eq(asset.getName())).pageSize(0).minSomes(1);
if (wheres != null && !wheres.isEmpty()) {
for (Query condition : wheres) {
builder.where(condition);
}
}
if (whereNots != null && !whereNots.isEmpty()) {
for (Query condition : whereNots) {
builder.whereNot(condition);
}
}
for (TYPE include : includes) {
switch (include) {
case SystemDescription:
builder.whereSome(Asset.DESCRIPTION.hasAnyValue()).aggregate(AGG_DESCRIPTION, Asset.DESCRIPTION.bucketBy(maxSuggestions, true));
break;
case UserDescription:
builder.whereSome(Asset.USER_DESCRIPTION.hasAnyValue()).aggregate(AGG_USER_DESCRIPTION, Asset.USER_DESCRIPTION.bucketBy(maxSuggestions, true));
break;
case IndividualOwners:
builder.whereSome(Asset.OWNER_USERS.hasAnyValue()).aggregate(AGG_OWNER_USERS, Asset.OWNER_USERS.bucketBy(maxSuggestions));
break;
case GroupOwners:
builder.whereSome(Asset.OWNER_GROUPS.hasAnyValue()).aggregate(AGG_OWNER_GROUPS, Asset.OWNER_GROUPS.bucketBy(maxSuggestions));
break;
case Tags:
builder.whereSome(Asset.ATLAN_TAGS.hasAnyValue()).aggregate(AGG_ATLAN_TAGS, Asset.ATLAN_TAGS.bucketBy(maxSuggestions));
break;
case Terms:
builder.whereSome(Asset.ASSIGNED_TERMS.hasAnyValue()).aggregate(AGG_TERMS, Asset.ASSIGNED_TERMS.bucketBy(maxSuggestions));
break;
default:
}
// Do nothing -- unknown type
}
IndexSearchRequest request = builder.toRequest();
IndexSearchResponse response = request.search(client);
Map aggregations = response.getAggregations();
SuggestionResponse.SuggestionResponseBuilder responseBuilder = SuggestionResponse.builder();
for (TYPE include : includes) {
switch (include) {
case SystemDescription:
responseBuilder.systemDescriptions(getDescriptions(aggregations.get(AGG_DESCRIPTION), Asset.DESCRIPTION));
break;
case UserDescription:
responseBuilder.userDescriptions(getDescriptions(aggregations.get(AGG_USER_DESCRIPTION), Asset.USER_DESCRIPTION));
break;
case IndividualOwners:
responseBuilder.ownerUsers(getOthers(aggregations.get(AGG_OWNER_USERS)));
break;
case GroupOwners:
responseBuilder.ownerGroups(getOthers(aggregations.get(AGG_OWNER_GROUPS)));
break;
case Tags:
responseBuilder.atlanTags(getTags(client, aggregations.get(AGG_ATLAN_TAGS)));
break;
case Terms:
responseBuilder.assignedTerms(getTerms(aggregations.get(AGG_TERMS)));
break;
}
}
return responseBuilder.build();
}
/**
* Find the requested suggestions and apply the top suggestions as
* changes to the asset.
* Note: this will NOT validate whether there is any existing value for what
* you are setting, so will clobber any existing value with the suggestion.
* If you want to be certain you are only updating empty values, you should ensure
* you are only building a finder for suggestions for values that do not already
* exist on the asset in question.
*
* @throws AtlanException on any issue interacting with the APIs
*/
public AssetMutationResponse apply() throws AtlanException {
return apply(false);
}
/**
* Find the requested suggestions and apply the top suggestions as
* changes to the asset.
* Note: this will NOT validate whether there is any existing value for what
* you are setting, so will clobber any existing value with the suggestion.
* If you want to be certain you are only updating empty values, you should ensure
* you are only building a finder for suggestions for values that do not already
* exist on the asset in question.
*
* @param allowMultiple if true, allow multiple suggestions to be applied to the asset (up to maxSuggestions requested), i.e. for owners, terms and tags
* @throws AtlanException on any issue interacting with the APIs
*/
public AssetMutationResponse apply(boolean allowMultiple) throws AtlanException {
Apply result = _apply(allowMultiple);
return result.getAsset().save(result.getIncludesTags());
}
/**
* Find the requested suggestions and apply the top suggestions as
* changes to the asset within the provided batch.
* Note: this will NOT validate whether there is any existing value for what
* you are setting, so will clobber any existing value with the suggestion. Also,
* to ensure tags are applied you MUST set your provided batch up to replace tags
* BEFORE using it here.
* If you want to be certain you are only updating empty values, you should ensure
* you are only building a finder for suggestions for values that do not already
* exist on the asset in question.
*
* @throws AtlanException on any issue interacting with the APIs
*/
public AssetMutationResponse apply(ParallelBatch batch) throws AtlanException {
return apply(batch, false);
}
/**
* Find the requested suggestions and apply the top suggestions as
* changes to the asset within the provided batch.
* Note: this will NOT validate whether there is any existing value for what
* you are setting, so will clobber any existing value with the suggestion.
* If you want to be certain you are only updating empty values, you should ensure
* you are only building a finder for suggestions for values that do not already
* exist on the asset in question.
*
* @param allowMultiple if true, allow multiple suggestions to be applied to the asset (up to maxSuggestions requested), i.e. for owners, terms and tags
* @throws AtlanException on any issue interacting with the APIs
*/
public AssetMutationResponse apply(ParallelBatch batch, boolean allowMultiple) throws AtlanException {
return batch.add(_apply(allowMultiple).getAsset());
}
private Apply _apply(boolean allowMultiple) throws AtlanException {
SuggestionResponse response = get();
Asset.AssetBuilder, ?> builder = asset.trimToRequired();
String descriptionToApply = getDescriptionToApply(response);
// Note: only ever set the description over a user-provided description (never the system-sourced
// description)
builder.userDescription(descriptionToApply);
if (response.getOwnerGroups() != null && !response.getOwnerGroups().isEmpty()) {
if (allowMultiple) {
builder.ownerGroups(response.getOwnerGroups().stream().map(SuggestionResponse.SuggestedItem::getValue).collect(Collectors.toSet()));
} else {
builder.ownerGroup(response.getOwnerGroups().get(0).getValue());
}
}
if (response.getOwnerUsers() != null && !response.getOwnerUsers().isEmpty()) {
if (allowMultiple) {
builder.ownerUsers(response.getOwnerUsers().stream().map(SuggestionResponse.SuggestedItem::getValue).collect(Collectors.toSet()));
} else {
builder.ownerUser(response.getOwnerUsers().get(0).getValue());
}
}
boolean includesTags = false;
if (response.getAtlanTags() != null && !response.getAtlanTags().isEmpty()) {
includesTags = true;
if (allowMultiple) {
builder.atlanTags(response.getAtlanTags().stream().map(t -> AtlanTag.builder().typeName(t.getValue()).propagate(false).build()).collect(Collectors.toSet()));
} else {
builder.atlanTag(AtlanTag.builder().typeName(response.getAtlanTags().get(0).getValue()).propagate(false).build());
}
}
if (response.getAssignedTerms() != null && !response.getAssignedTerms().isEmpty()) {
if (allowMultiple) {
builder.assignedTerms(response.getAssignedTerms().stream().map(SuggestionResponse.SuggestedTerm::getValue).collect(Collectors.toSet()));
} else {
builder.assignedTerm(response.getAssignedTerms().get(0).getValue());
}
}
return new Apply(builder.build(), includesTags);
}
private static List getDescriptions(AggregationResult res, AtlanField field) {
List results = new ArrayList<>();
if (res instanceof AggregationBucketResult) {
AggregationBucketResult result = (AggregationBucketResult) res;
for (AggregationBucketDetails bucket : result.getBuckets()) {
long count = bucket.getDocCount();
String value = bucket.getSourceValue(field).toString();
if (!value.isBlank()) {
results.add(new SuggestionResponse.SuggestedItem(count, value));
}
}
}
return results;
}
private static List getTerms(AggregationResult res) {
List results = new ArrayList<>();
if (res instanceof AggregationBucketResult) {
AggregationBucketResult result = (AggregationBucketResult) res;
for (AggregationBucketDetails bucket : result.getBuckets()) {
long count = bucket.getDocCount();
String value = bucket.getKey().toString();
if (!value.isBlank()) {
results.add(new SuggestionResponse.SuggestedTerm(count, value));
}
}
}
return results;
}
private static List getTags(AtlanClient client, AggregationResult res) throws AtlanException {
List results = new ArrayList<>();
if (res instanceof AggregationBucketResult) {
AggregationBucketResult result = (AggregationBucketResult) res;
for (AggregationBucketDetails bucket : result.getBuckets()) {
long count = bucket.getDocCount();
String value = bucket.getKey().toString();
if (!value.isBlank()) {
String name;
try {
name = client.getAtlanTagCache().getNameForId(value);
} catch (NotFoundException e) {
name = Serde.DELETED_AUDIT_OBJECT;
}
results.add(new SuggestionResponse.SuggestedItem(count, name));
}
}
}
return results;
}
private static List getOthers(AggregationResult res) {
List results = new ArrayList<>();
if (res instanceof AggregationBucketResult) {
AggregationBucketResult result = (AggregationBucketResult) res;
for (AggregationBucketDetails bucket : result.getBuckets()) {
long count = bucket.getDocCount();
String value = bucket.getKey().toString();
if (!value.isBlank()) {
results.add(new SuggestionResponse.SuggestedItem(count, value));
}
}
}
return results;
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
SuggestionsBuilder() {
}
/**
* Client through which to find suggestions.
* @return {@code this}.
*/
@java.lang.SuppressWarnings("all")
@lombok.Generated
public Suggestions.SuggestionsBuilder client(final AtlanClient client) {
this.client = client;
return this;
}
/**
* Asset for which to find suggestions.
* @return {@code this}.
*/
@java.lang.SuppressWarnings("all")
@lombok.Generated
public Suggestions.SuggestionsBuilder asset(final Asset asset) {
this.asset = asset;
return this;
}
/**
* Whether to include archived assets as part of suggestions (true) or not (false, default).
* @return {@code this}.
*/
@java.lang.SuppressWarnings("all")
@lombok.Generated
public Suggestions.SuggestionsBuilder includeArchived(final Boolean includeArchived) {
this.includeArchived = includeArchived;
return this;
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
public Suggestions.SuggestionsBuilder include(final TYPE include) {
if (this.includes == null) this.includes = new java.util.ArrayList();
this.includes.add(include);
return this;
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
public Suggestions.SuggestionsBuilder includes(final java.util.Collection extends TYPE> includes) {
if (includes == null) {
throw new java.lang.NullPointerException("includes cannot be null");
}
if (this.includes == null) this.includes = new java.util.ArrayList();
this.includes.addAll(includes);
return this;
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
public Suggestions.SuggestionsBuilder clearIncludes() {
if (this.includes != null) this.includes.clear();
return this;
}
/**
* Maximum number of suggestions to return (default: 5).
* @return {@code this}.
*/
@java.lang.SuppressWarnings("all")
@lombok.Generated
public Suggestions.SuggestionsBuilder maxSuggestions(final Integer maxSuggestions) {
this.maxSuggestions = maxSuggestions;
return this;
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
public Suggestions.SuggestionsBuilder withOtherType(final String withOtherType) {
if (this.withOtherTypes == null) this.withOtherTypes = new java.util.ArrayList();
this.withOtherTypes.add(withOtherType);
return this;
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
public Suggestions.SuggestionsBuilder withOtherTypes(final java.util.Collection extends String> withOtherTypes) {
if (withOtherTypes == null) {
throw new java.lang.NullPointerException("withOtherTypes cannot be null");
}
if (this.withOtherTypes == null) this.withOtherTypes = new java.util.ArrayList();
this.withOtherTypes.addAll(withOtherTypes);
return this;
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
public Suggestions.SuggestionsBuilder clearWithOtherTypes() {
if (this.withOtherTypes != null) this.withOtherTypes.clear();
return this;
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
public Suggestions.SuggestionsBuilder where(final Query where) {
if (this.wheres == null) this.wheres = new java.util.ArrayList();
this.wheres.add(where);
return this;
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
public Suggestions.SuggestionsBuilder wheres(final java.util.Collection extends Query> wheres) {
if (wheres == null) {
throw new java.lang.NullPointerException("wheres cannot be null");
}
if (this.wheres == null) this.wheres = new java.util.ArrayList();
this.wheres.addAll(wheres);
return this;
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
public Suggestions.SuggestionsBuilder clearWheres() {
if (this.wheres != null) this.wheres.clear();
return this;
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
public Suggestions.SuggestionsBuilder whereNot(final Query whereNot) {
if (this.whereNots == null) this.whereNots = new java.util.ArrayList();
this.whereNots.add(whereNot);
return this;
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
public Suggestions.SuggestionsBuilder whereNots(final java.util.Collection extends Query> whereNots) {
if (whereNots == null) {
throw new java.lang.NullPointerException("whereNots cannot be null");
}
if (this.whereNots == null) this.whereNots = new java.util.ArrayList();
this.whereNots.addAll(whereNots);
return this;
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
public Suggestions.SuggestionsBuilder clearWhereNots() {
if (this.whereNots != null) this.whereNots.clear();
return this;
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
public Suggestions build() {
java.util.Collection includes;
switch (this.includes == null ? 0 : this.includes.size()) {
case 0:
includes = java.util.Collections.emptyList();
break;
case 1:
includes = java.util.Collections.singletonList(this.includes.get(0));
break;
default:
includes = java.util.Collections.unmodifiableList(new java.util.ArrayList(this.includes));
}
java.util.Collection withOtherTypes;
switch (this.withOtherTypes == null ? 0 : this.withOtherTypes.size()) {
case 0:
withOtherTypes = java.util.Collections.emptyList();
break;
case 1:
withOtherTypes = java.util.Collections.singletonList(this.withOtherTypes.get(0));
break;
default:
withOtherTypes = java.util.Collections.unmodifiableList(new java.util.ArrayList(this.withOtherTypes));
}
java.util.Collection wheres;
switch (this.wheres == null ? 0 : this.wheres.size()) {
case 0:
wheres = java.util.Collections.emptyList();
break;
case 1:
wheres = java.util.Collections.singletonList(this.wheres.get(0));
break;
default:
wheres = java.util.Collections.unmodifiableList(new java.util.ArrayList(this.wheres));
}
java.util.Collection whereNots;
switch (this.whereNots == null ? 0 : this.whereNots.size()) {
case 0:
whereNots = java.util.Collections.emptyList();
break;
case 1:
whereNots = java.util.Collections.singletonList(this.whereNots.get(0));
break;
default:
whereNots = java.util.Collections.unmodifiableList(new java.util.ArrayList(this.whereNots));
}
return new Suggestions(this.client, this.asset, this.includeArchived, includes, this.maxSuggestions, withOtherTypes, wheres, whereNots);
}
@java.lang.Override
@java.lang.SuppressWarnings("all")
@lombok.Generated
public java.lang.String toString() {
return "Suggestions.SuggestionsBuilder(client=" + this.client + ", asset=" + this.asset + ", includeArchived=" + this.includeArchived + ", includes=" + this.includes + ", maxSuggestions=" + this.maxSuggestions + ", withOtherTypes=" + this.withOtherTypes + ", wheres=" + this.wheres + ", whereNots=" + this.whereNots + ")";
}
}
@Nullable
private static String getDescriptionToApply(SuggestionResponse response) {
long maxDescriptionCount = 0;
String descriptionToApply = null;
if (response.getUserDescriptions() != null && !response.getUserDescriptions().isEmpty()) {
maxDescriptionCount = response.getUserDescriptions().get(0).getCount();
descriptionToApply = response.getUserDescriptions().get(0).getValue();
}
if (response.getSystemDescriptions() != null && !response.getSystemDescriptions().isEmpty()) {
if (response.getSystemDescriptions().get(0).getCount() > maxDescriptionCount) {
descriptionToApply = response.getSystemDescriptions().get(0).getValue();
}
}
return descriptionToApply;
}
private static class Apply {
boolean includesTags;
Asset asset;
private Apply(Asset asset, boolean includesTags) {
this.asset = asset;
this.includesTags = includesTags;
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
public boolean getIncludesTags() {
return this.includesTags;
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
public Asset getAsset() {
return this.asset;
}
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
Suggestions(final AtlanClient client, final Asset asset, final Boolean includeArchived, final Collection includes, final Integer maxSuggestions, final Collection withOtherTypes, final Collection wheres, final Collection whereNots) {
this.client = client;
this.asset = asset;
this.includeArchived = includeArchived;
this.includes = includes;
this.maxSuggestions = maxSuggestions;
this.withOtherTypes = withOtherTypes;
this.wheres = wheres;
this.whereNots = whereNots;
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
public static Suggestions.SuggestionsBuilder _internal() {
return new Suggestions.SuggestionsBuilder();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy