Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.openmetadata.service.jdbi3.SuggestionRepository Maven / Gradle / Ivy
package org.openmetadata.service.jdbi3;
import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty;
import static org.openmetadata.schema.type.EventType.SUGGESTION_ACCEPTED;
import static org.openmetadata.schema.type.EventType.SUGGESTION_DELETED;
import static org.openmetadata.schema.type.EventType.SUGGESTION_REJECTED;
import static org.openmetadata.schema.type.Include.ALL;
import static org.openmetadata.schema.type.Include.NON_DELETED;
import static org.openmetadata.schema.type.Relationship.CREATED;
import static org.openmetadata.schema.type.Relationship.IS_ABOUT;
import static org.openmetadata.service.Entity.TEAM;
import static org.openmetadata.service.Entity.USER;
import static org.openmetadata.service.jdbi3.UserRepository.TEAMS_FIELD;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import javax.json.JsonPatch;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.UriInfo;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.jdbi.v3.sqlobject.transaction.Transaction;
import org.openmetadata.schema.EntityInterface;
import org.openmetadata.schema.entity.feed.Suggestion;
import org.openmetadata.schema.entity.teams.Team;
import org.openmetadata.schema.entity.teams.User;
import org.openmetadata.schema.type.EntityReference;
import org.openmetadata.schema.type.MetadataOperation;
import org.openmetadata.schema.type.SuggestionStatus;
import org.openmetadata.schema.type.SuggestionType;
import org.openmetadata.schema.type.TagLabel;
import org.openmetadata.sdk.exception.SuggestionException;
import org.openmetadata.service.Entity;
import org.openmetadata.service.ResourceRegistry;
import org.openmetadata.service.exception.CatalogExceptionMessage;
import org.openmetadata.service.exception.EntityNotFoundException;
import org.openmetadata.service.resources.feeds.MessageParser;
import org.openmetadata.service.resources.feeds.SuggestionsResource;
import org.openmetadata.service.security.AuthorizationException;
import org.openmetadata.service.security.Authorizer;
import org.openmetadata.service.security.policyevaluator.OperationContext;
import org.openmetadata.service.security.policyevaluator.ResourceContext;
import org.openmetadata.service.util.EntityUtil;
import org.openmetadata.service.util.JsonUtils;
import org.openmetadata.service.util.RestUtil;
import org.openmetadata.service.util.ResultList;
@Slf4j
@Repository
public class SuggestionRepository {
private final CollectionDAO dao;
public enum PaginationType {
BEFORE,
AFTER
}
public SuggestionRepository() {
this.dao = Entity.getCollectionDAO();
Entity.setSuggestionRepository(this);
ResourceRegistry.addResource("suggestion", null, Entity.getEntityFields(Suggestion.class));
}
@Transaction
public Suggestion create(Suggestion suggestion) {
store(suggestion);
storeRelationships(suggestion);
return suggestion;
}
@Transaction
public Suggestion update(Suggestion suggestion, String userName) {
suggestion.setUpdatedBy(userName);
dao.suggestionDAO().update(suggestion.getId(), JsonUtils.pojoToJson(suggestion));
storeRelationships(suggestion);
return suggestion;
}
@Transaction
public void store(Suggestion suggestion) {
// Insert a new Suggestion
MessageParser.EntityLink entityLink =
MessageParser.EntityLink.parse(suggestion.getEntityLink());
dao.suggestionDAO().insert(entityLink.getEntityFQN(), JsonUtils.pojoToJson(suggestion));
}
@Transaction
public void storeRelationships(Suggestion suggestion) {
MessageParser.EntityLink entityLink =
MessageParser.EntityLink.parse(suggestion.getEntityLink());
// Add relationship User -- created --> Suggestion relationship
dao.relationshipDAO()
.insert(
suggestion.getCreatedBy().getId(),
suggestion.getId(),
suggestion.getCreatedBy().getType(),
Entity.SUGGESTION,
CREATED.ordinal());
// Add field relationship for data asset - Suggestion -- entityLink ---> entity/entityField
dao.fieldRelationshipDAO()
.insert(
suggestion.getId().toString(), // from FQN
entityLink.getFullyQualifiedFieldValue(), // to FQN,
suggestion.getId().toString(),
entityLink.getFullyQualifiedFieldValue(),
Entity.SUGGESTION, // From type
entityLink.getFullyQualifiedFieldType(), // to Type
IS_ABOUT.ordinal(),
null);
}
public Suggestion get(UUID id) {
return EntityUtil.validate(id, dao.suggestionDAO().findById(id), Suggestion.class);
}
@Transaction
public RestUtil.DeleteResponse deleteSuggestion(
Suggestion suggestion, String deletedByUser) {
deleteSuggestionInternal(suggestion.getId());
LOG.debug("{} deleted suggestion with id {}", deletedByUser, suggestion.getId());
return new RestUtil.DeleteResponse<>(suggestion, SUGGESTION_DELETED);
}
@Transaction
public RestUtil.DeleteResponse deleteSuggestionsForAnEntity(
EntityInterface entity, String deletedByUser) {
deleteSuggestionInternalForAnEntity(entity);
LOG.debug("{} deleted suggestions for the entity id {}", deletedByUser, entity.getId());
return new RestUtil.DeleteResponse<>(entity, SUGGESTION_DELETED);
}
@Transaction
public void deleteSuggestionInternal(UUID id) {
// Delete all the relationships to other entities
dao.relationshipDAO().deleteAll(id, Entity.SUGGESTION);
// Delete all the field relationships to other entities
dao.fieldRelationshipDAO().deleteAllByPrefix(id.toString());
// Finally, delete the suggestion
dao.suggestionDAO().delete(id);
}
@Transaction
public void deleteSuggestionInternalForAnEntity(EntityInterface entity) {
// Delete all the field relationships to other entities
dao.fieldRelationshipDAO().deleteAllByPrefix(entity.getId().toString());
// Finally, delete the suggestion
dao.suggestionDAO().deleteByFQN(entity.getFullyQualifiedName());
}
@Getter
public static class SuggestionWorkflow {
// The workflow is applied to a specific entity at a time
protected final EntityInterface entity;
SuggestionWorkflow(EntityInterface entity) {
this.entity = entity;
}
public EntityInterface acceptSuggestion(Suggestion suggestion, EntityInterface entity) {
MessageParser.EntityLink entityLink =
MessageParser.EntityLink.parse(suggestion.getEntityLink());
if (entityLink.getFieldName() != null) {
EntityRepository> repository = Entity.getEntityRepository(entityLink.getEntityType());
return repository.applySuggestion(
entity, entityLink.getFullyQualifiedFieldValue(), suggestion);
} else {
if (suggestion.getType().equals(SuggestionType.SuggestTagLabel)) {
List tags = new ArrayList<>(entity.getTags());
tags.addAll(suggestion.getTagLabels());
entity.setTags(tags);
return entity;
} else if (suggestion.getType().equals(SuggestionType.SuggestDescription)) {
entity.setDescription(suggestion.getDescription());
return entity;
} else {
throw new SuggestionException("Invalid suggestion Type");
}
}
}
}
public RestUtil.PutResponse acceptSuggestion(
UriInfo uriInfo,
Suggestion suggestion,
SecurityContext securityContext,
Authorizer authorizer) {
acceptSuggestion(suggestion, securityContext, authorizer);
Suggestion updatedHref = SuggestionsResource.addHref(uriInfo, suggestion);
return new RestUtil.PutResponse<>(Response.Status.OK, updatedHref, SUGGESTION_ACCEPTED);
}
public RestUtil.PutResponse> acceptSuggestionList(
UriInfo uriInfo,
List suggestions,
SuggestionType suggestionType,
SecurityContext securityContext,
Authorizer authorizer) {
acceptSuggestionList(suggestions, suggestionType, securityContext, authorizer);
List updatedHref =
suggestions.stream()
.map(suggestion -> SuggestionsResource.addHref(uriInfo, suggestion))
.toList();
return new RestUtil.PutResponse<>(Response.Status.OK, updatedHref, SUGGESTION_ACCEPTED);
}
protected void acceptSuggestion(
Suggestion suggestion, SecurityContext securityContext, Authorizer authorizer) {
String user = securityContext.getUserPrincipal().getName();
MessageParser.EntityLink entityLink =
MessageParser.EntityLink.parse(suggestion.getEntityLink());
EntityRepository> repository = Entity.getEntityRepository(entityLink.getEntityType());
EntityInterface entity =
Entity.getEntity(entityLink, repository.getSuggestionFields(suggestion), ALL);
// Prepare the original JSON before updating the Entity, otherwise we get an empty patch
String origJson = JsonUtils.pojoToJson(entity);
SuggestionWorkflow suggestionWorkflow = repository.getSuggestionWorkflow(entity);
EntityInterface updatedEntity = suggestionWorkflow.acceptSuggestion(suggestion, entity);
String updatedEntityJson = JsonUtils.pojoToJson(updatedEntity);
// Patch the entity with the updated suggestions
JsonPatch patch = JsonUtils.getJsonPatch(origJson, updatedEntityJson);
OperationContext operationContext = new OperationContext(entityLink.getEntityType(), patch);
authorizer.authorize(
securityContext,
operationContext,
new ResourceContext<>(entityLink.getEntityType(), entity.getId(), null));
repository.patch(null, entity.getId(), user, patch);
suggestion.setStatus(SuggestionStatus.Accepted);
update(suggestion, user);
}
@Transaction
protected void acceptSuggestionList(
List suggestions,
SuggestionType suggestionType,
SecurityContext securityContext,
Authorizer authorizer) {
String user = securityContext.getUserPrincipal().getName();
// Entity being updated
EntityInterface entity = null;
EntityRepository> repository = null;
String origJson = null;
SuggestionWorkflow suggestionWorkflow = null;
for (Suggestion suggestion : suggestions) {
MessageParser.EntityLink entityLink =
MessageParser.EntityLink.parse(suggestion.getEntityLink());
// Validate all suggestions indeed talk about the same entity
if (entity == null) {
// Initialize the Entity and the Repository
entity =
Entity.getEntity(
entityLink,
suggestionType == SuggestionType.SuggestTagLabel ? "tags" : "",
NON_DELETED);
repository = Entity.getEntityRepository(entityLink.getEntityType());
origJson = JsonUtils.pojoToJson(entity);
suggestionWorkflow = repository.getSuggestionWorkflow(entity);
} else if (!entity.getFullyQualifiedName().equals(entityLink.getEntityFQN())) {
throw new SuggestionException("All suggestions must be for the same entity");
}
// update entity with the suggestion
entity = suggestionWorkflow.acceptSuggestion(suggestion, entity);
}
// Patch the entity with the updated suggestions
String updatedEntityJson = JsonUtils.pojoToJson(entity);
JsonPatch patch = JsonUtils.getJsonPatch(origJson, updatedEntityJson);
OperationContext operationContext = new OperationContext(repository.getEntityType(), patch);
authorizer.authorize(
securityContext,
operationContext,
new ResourceContext<>(repository.getEntityType(), entity.getId(), null));
repository.patch(null, entity.getId(), user, patch);
// Only mark the suggestions as accepted after the entity has been successfully updated
for (Suggestion suggestion : suggestions) {
suggestion.setStatus(SuggestionStatus.Accepted);
update(suggestion, user);
}
}
public RestUtil.PutResponse rejectSuggestion(
UriInfo uriInfo, Suggestion suggestion, String user) {
suggestion.setStatus(SuggestionStatus.Rejected);
update(suggestion, user);
Suggestion updatedHref = SuggestionsResource.addHref(uriInfo, suggestion);
return new RestUtil.PutResponse<>(Response.Status.OK, updatedHref, SUGGESTION_REJECTED);
}
@Transaction
public RestUtil.PutResponse> rejectSuggestionList(
UriInfo uriInfo, List suggestions, String user) {
for (Suggestion suggestion : suggestions) {
suggestion.setStatus(SuggestionStatus.Rejected);
update(suggestion, user);
SuggestionsResource.addHref(uriInfo, suggestion);
}
return new RestUtil.PutResponse<>(Response.Status.OK, suggestions, SUGGESTION_REJECTED);
}
public void checkPermissionsForUpdateSuggestion(
Suggestion suggestion, SecurityContext securityContext) {
String userName = securityContext.getUserPrincipal().getName();
User user = Entity.getEntityByName(USER, userName, TEAMS_FIELD, NON_DELETED);
if (Boolean.FALSE.equals(user.getIsAdmin())
&& !userName.equalsIgnoreCase(suggestion.getCreatedBy().getName())) {
throw new AuthorizationException(
CatalogExceptionMessage.suggestionOperationNotAllowed(userName, "Update"));
}
}
public void checkPermissionsForAcceptOrRejectSuggestion(
Suggestion suggestion, SuggestionStatus status, SecurityContext securityContext) {
String userName = securityContext.getUserPrincipal().getName();
User user = Entity.getEntityByName(USER, userName, TEAMS_FIELD, NON_DELETED);
MessageParser.EntityLink about = MessageParser.EntityLink.parse(suggestion.getEntityLink());
EntityReference aboutRef = EntityUtil.validateEntityLink(about);
List ownerRefs = Entity.getOwners(aboutRef);
List ownerTeamNames = new ArrayList<>();
if (!nullOrEmpty(ownerRefs)) {
for (EntityReference ownerRef : ownerRefs) {
try {
User owner =
Entity.getEntityByName(
USER, ownerRef.getFullyQualifiedName(), TEAMS_FIELD, NON_DELETED);
ownerTeamNames =
owner.getTeams().stream().map(EntityReference::getFullyQualifiedName).toList();
} catch (EntityNotFoundException e) {
Team owner =
Entity.getEntityByName(TEAM, ownerRef.getFullyQualifiedName(), "", NON_DELETED);
ownerTeamNames.add(owner.getFullyQualifiedName());
}
}
}
List userTeamNames =
user.getTeams().stream().map(EntityReference::getFullyQualifiedName).toList();
if (Boolean.FALSE.equals(user.getIsAdmin())
&& (!nullOrEmpty(ownerRefs)
&& ownerRefs.stream().noneMatch(ownerRef -> ownerRef.getName().equals(userName)))
&& Collections.disjoint(userTeamNames, ownerTeamNames)) {
throw new AuthorizationException(
CatalogExceptionMessage.suggestionOperationNotAllowed(userName, status.value()));
}
}
public void checkPermissionsForEditEntity(
Suggestion suggestion,
SuggestionType suggestionType,
SecurityContext securityContext,
Authorizer authorizer) {
MessageParser.EntityLink entityLink =
MessageParser.EntityLink.parse(suggestion.getEntityLink());
EntityInterface entity = Entity.getEntity(entityLink, "", NON_DELETED);
// Check that the user has the right permissions to update the entity
authorizer.authorize(
securityContext,
new OperationContext(
entityLink.getEntityType(),
suggestionType == SuggestionType.SuggestTagLabel
? MetadataOperation.EDIT_TAGS
: MetadataOperation.EDIT_DESCRIPTION),
new ResourceContext<>(entityLink.getEntityType(), entity.getId(), null));
}
public int listCount(SuggestionFilter filter) {
String mySqlCondition = filter.getCondition(false);
String postgresCondition = filter.getCondition(false);
return dao.suggestionDAO().listCount(mySqlCondition, postgresCondition);
}
public ResultList listBefore(SuggestionFilter filter, int limit, String before) {
int total = listCount(filter);
String mySqlCondition = filter.getCondition(true);
String postgresCondition = filter.getCondition(true);
List jsons =
dao.suggestionDAO()
.listBefore(
mySqlCondition, postgresCondition, limit + 1, RestUtil.decodeCursor(before));
List suggestions = getSuggestionList(jsons);
String beforeCursor = null;
String afterCursor;
if (suggestions.size() > limit) {
suggestions.remove(0);
beforeCursor = suggestions.get(0).getUpdatedAt().toString();
}
afterCursor =
!suggestions.isEmpty()
? suggestions.get(suggestions.size() - 1).getUpdatedAt().toString()
: null;
return new ResultList<>(suggestions, beforeCursor, afterCursor, total);
}
public ResultList listAfter(SuggestionFilter filter, int limit, String after) {
int total = listCount(filter);
String mySqlCondition = filter.getCondition(true);
String postgresCondition = filter.getCondition(true);
List jsons =
dao.suggestionDAO()
.listAfter(mySqlCondition, postgresCondition, limit + 1, RestUtil.decodeCursor(after));
List suggestions = getSuggestionList(jsons);
String beforeCursor;
String afterCursor = null;
beforeCursor = after == null ? null : suggestions.get(0).getUpdatedAt().toString();
if (suggestions.size() > limit) {
suggestions.remove(limit);
afterCursor = suggestions.get(limit - 1).getUpdatedAt().toString();
}
return new ResultList<>(suggestions, beforeCursor, afterCursor, total);
}
private List getSuggestionList(List jsons) {
List suggestions = new ArrayList<>();
for (String json : jsons) {
Suggestion suggestion = JsonUtils.readValue(json, Suggestion.class);
suggestions.add(suggestion);
}
return suggestions;
}
public final List listAll(SuggestionFilter filter) {
ResultList suggestionList = listAfter(filter, Integer.MAX_VALUE - 1, "");
return suggestionList.getData();
}
}