blob: bae2ea1532536dd28900181c4ed2b2ce7f295f3e [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.atlas.glossary;
import org.apache.atlas.AtlasErrorCode;
import org.apache.atlas.SortOrder;
import org.apache.atlas.annotation.GraphTransaction;
import org.apache.atlas.exception.AtlasBaseException;
import org.apache.atlas.model.glossary.AtlasGlossary;
import org.apache.atlas.model.glossary.AtlasGlossaryCategory;
import org.apache.atlas.model.glossary.AtlasGlossaryTerm;
import org.apache.atlas.model.glossary.relations.AtlasRelatedCategoryHeader;
import org.apache.atlas.model.glossary.relations.AtlasRelatedTermHeader;
import org.apache.atlas.model.glossary.relations.AtlasTermCategorizationHeader;
import org.apache.atlas.model.instance.AtlasRelatedObjectId;
import org.apache.atlas.repository.graphdb.AtlasVertex;
import org.apache.atlas.repository.ogm.DataAccess;
import org.apache.atlas.repository.store.graph.AtlasRelationshipStore;
import org.apache.atlas.repository.store.graph.v1.AtlasGraphUtilsV1;
import org.apache.atlas.type.AtlasTypeRegistry;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static org.apache.atlas.glossary.GlossaryUtils.getAtlasGlossaryCategorySkeleton;
import static org.apache.atlas.glossary.GlossaryUtils.getAtlasGlossaryTermSkeleton;
import static org.apache.atlas.glossary.GlossaryUtils.getGlossarySkeleton;
@Service
public class GlossaryService {
private static final Logger LOG = LoggerFactory.getLogger(GlossaryService.class);
private static final boolean DEBUG_ENABLED = LOG.isDebugEnabled();
private static final String QUALIFIED_NAME_ATTR = "qualifiedName";
private final DataAccess dataAccess;
private final GlossaryTermUtils glossaryTermUtils;
private final GlossaryCategoryUtils glossaryCategoryUtils;
private final AtlasTypeRegistry atlasTypeRegistry;
private final char[] invalidNameChars = {'@', '.'};
@Inject
public GlossaryService(DataAccess dataAccess, final AtlasRelationshipStore relationshipStore, final AtlasTypeRegistry typeRegistry) {
this.dataAccess = dataAccess;
this.atlasTypeRegistry = typeRegistry;
glossaryTermUtils = new GlossaryTermUtils(relationshipStore, typeRegistry, dataAccess);
glossaryCategoryUtils = new GlossaryCategoryUtils(relationshipStore, typeRegistry, dataAccess);
}
/**
* List all glossaries
*
* @param limit page size - no paging by default
* @param offset offset for pagination
* @param sortOrder ASC (default) or DESC
* @return List of all glossaries
* @throws AtlasBaseException
*/
@GraphTransaction
public List<AtlasGlossary> getGlossaries(int limit, int offset, SortOrder sortOrder) throws AtlasBaseException {
if (DEBUG_ENABLED) {
LOG.debug("==> GlossaryService.getGlossaries({}, {}, {})", limit, offset, sortOrder);
}
List<String> glossaryGuids = AtlasGraphUtilsV1.findEntityGUIDsByType(GlossaryUtils.ATLAS_GLOSSARY_TYPENAME, sortOrder);
PaginationHelper paginationHelper = new PaginationHelper<>(glossaryGuids, offset, limit);
List<AtlasGlossary> ret;
List<String> guidsToLoad = paginationHelper.getPaginatedList();
if (CollectionUtils.isNotEmpty(guidsToLoad)) {
ret = guidsToLoad.stream().map(GlossaryUtils::getGlossarySkeleton).collect(Collectors.toList());
Iterable<AtlasGlossary> glossaries = dataAccess.load(ret);
ret.clear();
// Set the displayText for all relations
for (AtlasGlossary glossary : glossaries) {
setInfoForRelations(glossary);
ret.add(glossary);
}
} else {
ret = Collections.emptyList();
}
if (DEBUG_ENABLED) {
LOG.debug("<== GlossaryService.getGlossaries() : {}", ret);
}
return ret;
}
/**
* Create a glossary
*
* @param atlasGlossary Glossary specification to be created
* @return Glossary definition
* @throws AtlasBaseException
*/
@GraphTransaction
public AtlasGlossary createGlossary(AtlasGlossary atlasGlossary) throws AtlasBaseException {
if (DEBUG_ENABLED) {
LOG.debug("==> GlossaryService.createGlossary({})", atlasGlossary);
}
if (Objects.isNull(atlasGlossary)) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "Glossary definition missing");
}
if (StringUtils.isEmpty(atlasGlossary.getQualifiedName())) {
if (StringUtils.isEmpty(atlasGlossary.getDisplayName())) {
throw new AtlasBaseException(AtlasErrorCode.GLOSSARY_QUALIFIED_NAME_CANT_BE_DERIVED);
}
if (isNameInvalid(atlasGlossary.getDisplayName())){
throw new AtlasBaseException(AtlasErrorCode.INVALID_DISPLAY_NAME);
} else {
atlasGlossary.setQualifiedName(atlasGlossary.getDisplayName());
}
}
if (glossaryExists(atlasGlossary)) {
throw new AtlasBaseException(AtlasErrorCode.GLOSSARY_ALREADY_EXISTS, atlasGlossary.getQualifiedName());
}
AtlasGlossary storeObject = dataAccess.save(atlasGlossary);
if (DEBUG_ENABLED) {
LOG.debug("<== GlossaryService.createGlossary() : {}", storeObject);
}
return storeObject;
}
/**
* Get specific glossary
*
* @param glossaryGuid unique identifier
* @return Glossary corresponding to specified glossaryGuid
* @throws AtlasBaseException
*/
@GraphTransaction
public AtlasGlossary getGlossary(String glossaryGuid) throws AtlasBaseException {
if (DEBUG_ENABLED) {
LOG.debug("==> GlossaryService.getGlossary({})", glossaryGuid);
}
if (Objects.isNull(glossaryGuid)) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "glossaryGuid is null/empty");
}
AtlasGlossary atlasGlossary = getGlossarySkeleton(glossaryGuid);
AtlasGlossary ret = dataAccess.load(atlasGlossary);
setInfoForRelations(ret);
if (DEBUG_ENABLED) {
LOG.debug("<== GlossaryService.getGlossary() : {}", ret);
}
return ret;
}
/**
* Get specific glossary
*
* @param glossaryGuid unique identifier
* @return Glossary corresponding to specified glossaryGuid
* @throws AtlasBaseException
*/
@GraphTransaction
public AtlasGlossary.AtlasGlossaryExtInfo getDetailedGlossary(String glossaryGuid) throws AtlasBaseException {
if (DEBUG_ENABLED) {
LOG.debug("==> GlossaryService.getGlossary({})", glossaryGuid);
}
if (Objects.isNull(glossaryGuid)) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "glossaryGuid is null/empty");
}
AtlasGlossary atlasGlossary = getGlossarySkeleton(glossaryGuid);
AtlasGlossary glossary = dataAccess.load(atlasGlossary);
AtlasGlossary.AtlasGlossaryExtInfo ret = new AtlasGlossary.AtlasGlossaryExtInfo(glossary);
// Load all linked terms
if (CollectionUtils.isNotEmpty(ret.getTerms())) {
List<AtlasGlossaryTerm> termsToLoad = ret.getTerms()
.stream()
.map(id -> getAtlasGlossaryTermSkeleton(id.getTermGuid()))
.collect(Collectors.toList());
Iterable<AtlasGlossaryTerm> glossaryTerms = dataAccess.load(termsToLoad);
glossaryTerms.forEach(ret::addTermInfo);
}
// Load all linked categories
if (CollectionUtils.isNotEmpty(ret.getCategories())) {
List<AtlasGlossaryCategory> categoriesToLoad = ret.getCategories()
.stream()
.map(id -> getAtlasGlossaryCategorySkeleton(id.getCategoryGuid()))
.collect(Collectors.toList());
Iterable<AtlasGlossaryCategory> glossaryCategories = dataAccess.load(categoriesToLoad);
glossaryCategories.forEach(ret::addCategoryInfo);
}
if (DEBUG_ENABLED) {
LOG.debug("<== GlossaryService.getGlossary() : {}", ret);
}
return ret;
}
@GraphTransaction
public AtlasGlossary updateGlossary(AtlasGlossary atlasGlossary) throws AtlasBaseException {
if (DEBUG_ENABLED) {
LOG.debug("==> GlossaryService.updateGlossary({})", atlasGlossary);
}
if (Objects.isNull(atlasGlossary)) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "Glossary is null/empty");
}
if (StringUtils.isEmpty(atlasGlossary.getDisplayName())) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "DisplayName can't be null/empty");
}
if (isNameInvalid(atlasGlossary.getDisplayName())) {
throw new AtlasBaseException(AtlasErrorCode.INVALID_DISPLAY_NAME);
}
AtlasGlossary storeObject = dataAccess.load(atlasGlossary);
if (!storeObject.equals(atlasGlossary)) {
atlasGlossary.setGuid(storeObject.getGuid());
atlasGlossary.setQualifiedName(storeObject.getQualifiedName());
storeObject = dataAccess.save(atlasGlossary);
setInfoForRelations(storeObject);
}
if (DEBUG_ENABLED) {
LOG.debug("<== GlossaryService.updateGlossary() : {}", storeObject);
}
return storeObject;
}
@GraphTransaction
public void deleteGlossary(String glossaryGuid) throws AtlasBaseException {
if (DEBUG_ENABLED) {
LOG.debug("==> GlossaryService.deleteGlossary({})", glossaryGuid);
}
if (Objects.isNull(glossaryGuid)) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "glossaryGuid is null/empty");
}
AtlasGlossary storeObject = dataAccess.load(getGlossarySkeleton(glossaryGuid));
Set<AtlasRelatedTermHeader> terms = storeObject.getTerms();
deleteTerms(storeObject, terms);
Set<AtlasRelatedCategoryHeader> categories = storeObject.getCategories();
deleteCategories(storeObject, categories);
// Once all relations are deleted, then delete the Glossary
dataAccess.delete(glossaryGuid);
if (DEBUG_ENABLED) {
LOG.debug("<== GlossaryService.deleteGlossary()");
}
}
/*
* GlossaryTerms related operations
* */
@GraphTransaction
public AtlasGlossaryTerm getTerm(String termGuid) throws AtlasBaseException {
if (DEBUG_ENABLED) {
LOG.debug("==> GlossaryService.getTerm({})", termGuid);
}
if (Objects.isNull(termGuid)) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "termGuid is null/empty");
}
AtlasGlossaryTerm atlasGlossary = getAtlasGlossaryTermSkeleton(termGuid);
AtlasGlossaryTerm ret = dataAccess.load(atlasGlossary);
setInfoForRelations(ret);
if (DEBUG_ENABLED) {
LOG.debug("<== GlossaryService.getTerm() : {}", ret);
}
return ret;
}
@GraphTransaction
public AtlasGlossaryTerm createTerm(AtlasGlossaryTerm glossaryTerm) throws AtlasBaseException {
if (DEBUG_ENABLED) {
LOG.debug("==> GlossaryService.create({})", glossaryTerm);
}
if (Objects.isNull(glossaryTerm)) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "GlossaryTerm definition missing");
}
if (Objects.isNull(glossaryTerm.getAnchor())) {
throw new AtlasBaseException(AtlasErrorCode.MISSING_MANDATORY_ANCHOR);
}
if (StringUtils.isEmpty(glossaryTerm.getQualifiedName())) {
if (StringUtils.isEmpty(glossaryTerm.getDisplayName())) {
throw new AtlasBaseException(AtlasErrorCode.GLOSSARY_TERM_QUALIFIED_NAME_CANT_BE_DERIVED);
}
if (isNameInvalid(glossaryTerm.getDisplayName())){
throw new AtlasBaseException(AtlasErrorCode.INVALID_DISPLAY_NAME);
} else {
glossaryTerm.setQualifiedName(glossaryTerm.getDisplayName());
}
}
// This might fail for the case when the term's qualifiedName has been updated and the duplicate request comes in with old name
if (termExists(glossaryTerm)) {
throw new AtlasBaseException(AtlasErrorCode.GLOSSARY_TERM_ALREADY_EXISTS, glossaryTerm.getQualifiedName());
}
AtlasGlossaryTerm storeObject = dataAccess.save(glossaryTerm);
glossaryTermUtils.processTermRelations(storeObject, glossaryTerm, GlossaryUtils.RelationshipOperation.CREATE);
// Re-load term after handling relations
storeObject = dataAccess.load(glossaryTerm);
setInfoForRelations(storeObject);
if (DEBUG_ENABLED) {
LOG.debug("<== GlossaryService.create() : {}", storeObject);
}
return storeObject;
}
@GraphTransaction
public List<AtlasGlossaryTerm> createTerms(List<AtlasGlossaryTerm> glossaryTerm) throws AtlasBaseException {
if (DEBUG_ENABLED) {
LOG.debug("==> GlossaryService.create({})", glossaryTerm);
}
if (Objects.isNull(glossaryTerm)) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "glossaryTerm(s) is null/empty");
}
List<AtlasGlossaryTerm> ret = new ArrayList<>();
for (AtlasGlossaryTerm atlasGlossaryTerm : glossaryTerm) {
ret.add(createTerm(atlasGlossaryTerm));
}
if (LOG.isDebugEnabled()) {
LOG.debug("<== GlossaryService.createTerms() : {}", ret);
}
return ret;
}
@GraphTransaction
public AtlasGlossaryTerm updateTerm(AtlasGlossaryTerm atlasGlossaryTerm) throws AtlasBaseException {
if (DEBUG_ENABLED) {
LOG.debug("==> GlossaryService.updateTerm({})", atlasGlossaryTerm);
}
if (Objects.isNull(atlasGlossaryTerm)) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "atlasGlossaryTerm is null/empty");
}
if (StringUtils.isEmpty(atlasGlossaryTerm.getDisplayName())) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "DisplayName can't be null/empty");
}
if (isNameInvalid(atlasGlossaryTerm.getDisplayName())) {
throw new AtlasBaseException(AtlasErrorCode.INVALID_DISPLAY_NAME);
}
AtlasGlossaryTerm storeObject = dataAccess.load(atlasGlossaryTerm);
if (!storeObject.equals(atlasGlossaryTerm)) {
try {
atlasGlossaryTerm.setGuid(storeObject.getGuid());
atlasGlossaryTerm.setQualifiedName(storeObject.getQualifiedName());
storeObject = dataAccess.save(atlasGlossaryTerm);
} catch (AtlasBaseException e) {
LOG.debug("Glossary term had no immediate attr updates. Exception: {}", e.getMessage());
} finally {
glossaryTermUtils.processTermRelations(storeObject, atlasGlossaryTerm, GlossaryUtils.RelationshipOperation.UPDATE);
}
}
storeObject = dataAccess.load(atlasGlossaryTerm);
setInfoForRelations(storeObject);
if (DEBUG_ENABLED) {
LOG.debug("<== GlossaryService.updateTerm() : {}", storeObject);
}
return storeObject;
}
@GraphTransaction
public void deleteTerm(String termGuid) throws AtlasBaseException {
if (DEBUG_ENABLED) {
LOG.debug("==> GlossaryService.deleteTerm({})", termGuid);
}
if (Objects.isNull(termGuid)) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "termGuid is null/empty");
}
AtlasGlossaryTerm storeObject = dataAccess.load(getAtlasGlossaryTermSkeleton(termGuid));
// Term can't be deleted if it is assigned to any entity
if (CollectionUtils.isNotEmpty(storeObject.getAssignedEntities())) {
throw new AtlasBaseException(AtlasErrorCode.TERM_HAS_ENTITY_ASSOCIATION, storeObject.getGuid(), String.valueOf(storeObject.getAssignedEntities().size()));
}
// Remove term from Glossary
glossaryTermUtils.processTermRelations(storeObject, storeObject, GlossaryUtils.RelationshipOperation.DELETE);
// Now delete the term
dataAccess.delete(termGuid);
if (DEBUG_ENABLED) {
LOG.debug("<== GlossaryService.deleteTerm()");
}
}
@GraphTransaction
public void assignTermToEntities(String termGuid, List<AtlasRelatedObjectId> relatedObjectIds) throws AtlasBaseException {
if (DEBUG_ENABLED) {
LOG.debug("==> GlossaryService.assignTermToEntities({}, {})", termGuid, relatedObjectIds);
}
AtlasGlossaryTerm glossaryTerm = dataAccess.load(getAtlasGlossaryTermSkeleton(termGuid));
glossaryTermUtils.processTermAssignments(glossaryTerm, relatedObjectIds);
if (DEBUG_ENABLED) {
LOG.debug("<== GlossaryService.assignTermToEntities()");
}
}
@GraphTransaction
public void removeTermFromEntities(String termGuid, List<AtlasRelatedObjectId> relatedObjectIds) throws AtlasBaseException {
if (LOG.isDebugEnabled()) {
LOG.debug("==> GlossaryService.removeTermFromEntities({}, {})", termGuid, relatedObjectIds);
}
AtlasGlossaryTerm glossaryTerm = dataAccess.load(getAtlasGlossaryTermSkeleton(termGuid));
glossaryTermUtils.processTermDissociation(glossaryTerm, relatedObjectIds);
if (LOG.isDebugEnabled()) {
LOG.debug("<== GlossaryService.removeTermFromEntities()");
}
}
/*
* GlossaryCategory related operations
* */
@GraphTransaction
public AtlasGlossaryCategory getCategory(String categoryGuid) throws AtlasBaseException {
if (DEBUG_ENABLED) {
LOG.debug("==> GlossaryService.getCategory({})", categoryGuid);
}
if (Objects.isNull(categoryGuid)) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "categoryGuid is null/empty");
}
AtlasGlossaryCategory atlasGlossary = getAtlasGlossaryCategorySkeleton(categoryGuid);
AtlasGlossaryCategory ret = dataAccess.load(atlasGlossary);
setInfoForRelations(ret);
if (DEBUG_ENABLED) {
LOG.debug("<== GlossaryService.getCategory() : {}", ret);
}
return ret;
}
@GraphTransaction
public AtlasGlossaryCategory createCategory(AtlasGlossaryCategory glossaryCategory) throws AtlasBaseException {
if (DEBUG_ENABLED) {
LOG.debug("==> GlossaryService.createCategory({})", glossaryCategory);
}
if (Objects.isNull(glossaryCategory)) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "GlossaryCategory definition missing");
}
if (Objects.isNull(glossaryCategory.getAnchor())) {
throw new AtlasBaseException(AtlasErrorCode.MISSING_MANDATORY_ANCHOR);
}
if (StringUtils.isEmpty(glossaryCategory.getQualifiedName())) {
if (StringUtils.isEmpty(glossaryCategory.getDisplayName())) {
throw new AtlasBaseException(AtlasErrorCode.GLOSSARY_CATEGORY_QUALIFIED_NAME_CANT_BE_DERIVED);
}
if (isNameInvalid(glossaryCategory.getDisplayName())){
throw new AtlasBaseException(AtlasErrorCode.INVALID_DISPLAY_NAME);
} else {
glossaryCategory.setQualifiedName(glossaryCategory.getDisplayName());
}
}
// This might fail for the case when the category's qualifiedName has been updated during a hierarchy change
// and the duplicate request comes in with old name
if (categoryExists(glossaryCategory)) {
throw new AtlasBaseException(AtlasErrorCode.GLOSSARY_CATEGORY_ALREADY_EXISTS, glossaryCategory.getQualifiedName());
}
AtlasGlossaryCategory storeObject = dataAccess.save(glossaryCategory);
// Attempt relation creation and gather all impacted categories
Map<String, AtlasGlossaryCategory> impactedCategories = glossaryCategoryUtils.processCategoryRelations(storeObject, glossaryCategory, GlossaryUtils.RelationshipOperation.CREATE);
// Re save the categories in case any qualifiedName change has occurred
dataAccess.save(impactedCategories.values());
// Since the current category is also affected, we need to update qualifiedName and save again
if (!StringUtils.equals(glossaryCategory.getQualifiedName(), storeObject.getQualifiedName())) {
glossaryCategory.setQualifiedName(storeObject.getQualifiedName());
storeObject = dataAccess.save(glossaryCategory);
} else {
storeObject = dataAccess.load(glossaryCategory);
}
setInfoForRelations(storeObject);
if (DEBUG_ENABLED) {
LOG.debug("<== GlossaryService.createCategory() : {}", storeObject);
}
return storeObject;
}
@GraphTransaction
public List<AtlasGlossaryCategory> createCategories(List<AtlasGlossaryCategory> glossaryCategory) throws AtlasBaseException {
if (DEBUG_ENABLED) {
LOG.debug("==> GlossaryService.createCategories({})", glossaryCategory);
}
if (Objects.isNull(glossaryCategory)) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "glossaryCategory is null/empty");
}
List<AtlasGlossaryCategory> ret = new ArrayList<>();
for (AtlasGlossaryCategory category : glossaryCategory) {
ret.add(createCategory(category));
}
if (DEBUG_ENABLED) {
LOG.debug("<== GlossaryService.createCategories() : {}", ret);
}
return ret;
}
@GraphTransaction
public AtlasGlossaryCategory updateCategory(AtlasGlossaryCategory glossaryCategory) throws AtlasBaseException {
if (DEBUG_ENABLED) {
LOG.debug("==> GlossaryService.updateCategory({})", glossaryCategory);
}
if (Objects.isNull(glossaryCategory)) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "GlossaryCategory is null/empty");
}
if (StringUtils.isEmpty(glossaryCategory.getDisplayName())) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "DisplayName can't be null/empty");
}
if (isNameInvalid(glossaryCategory.getDisplayName())) {
throw new AtlasBaseException(AtlasErrorCode.INVALID_DISPLAY_NAME);
}
AtlasGlossaryCategory storeObject = dataAccess.load(glossaryCategory);
if (!storeObject.equals(glossaryCategory)) {
try {
glossaryCategory.setGuid(storeObject.getGuid());
glossaryCategory.setQualifiedName(storeObject.getQualifiedName());
storeObject = dataAccess.save(glossaryCategory);
} catch (AtlasBaseException e) {
LOG.debug("No immediate attribute update. Exception: {}", e.getMessage());
} finally {
Map<String, AtlasGlossaryCategory> impactedCategories = glossaryCategoryUtils.processCategoryRelations(storeObject, glossaryCategory, GlossaryUtils.RelationshipOperation.UPDATE);
dataAccess.save(impactedCategories.values());
// Since the current category is also affected, we need to update qualifiedName and save again
if (!StringUtils.equals(glossaryCategory.getQualifiedName(), storeObject.getQualifiedName())){
glossaryCategory.setQualifiedName(storeObject.getQualifiedName());
storeObject = dataAccess.save(glossaryCategory);
} else {
storeObject = dataAccess.load(glossaryCategory);
}
}
}
if (DEBUG_ENABLED) {
LOG.debug("<== GlossaryService.updateCategory() : {}", storeObject);
}
setInfoForRelations(storeObject);
return storeObject;
}
@GraphTransaction
public void deleteCategory(String categoryGuid) throws AtlasBaseException {
if (DEBUG_ENABLED) {
LOG.debug("==> GlossaryService.deleteCategory({})", categoryGuid);
}
if (Objects.isNull(categoryGuid)) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "Category guid is null/empty");
}
AtlasGlossaryCategory storeObject = dataAccess.load(getAtlasGlossaryCategorySkeleton(categoryGuid));
// Delete all relations
glossaryCategoryUtils.processCategoryRelations(storeObject, storeObject, GlossaryUtils.RelationshipOperation.DELETE);
// Now delete the category
dataAccess.delete(categoryGuid);
if (DEBUG_ENABLED) {
LOG.debug("<== GlossaryService.deleteCategory()");
}
}
@GraphTransaction
public List<AtlasRelatedTermHeader> getGlossaryTermsHeaders(String glossaryGuid, int offset, int limit, SortOrder sortOrder) throws AtlasBaseException {
if (Objects.isNull(glossaryGuid)) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "glossaryGuid is null/empty");
}
if (DEBUG_ENABLED) {
LOG.debug("==> GlossaryService.getGlossaryTermsHeaders({}, {}, {}, {})", glossaryGuid, offset, limit, sortOrder);
}
AtlasGlossary glossary = getGlossary(glossaryGuid);
List<AtlasRelatedTermHeader> ret;
if (CollectionUtils.isNotEmpty(glossary.getTerms())) {
List<AtlasRelatedTermHeader> terms = new ArrayList<>(glossary.getTerms());
if (sortOrder != null) {
terms.sort((o1, o2) -> sortOrder == SortOrder.ASCENDING ?
o1.getDisplayText().compareTo(o2.getDisplayText()) :
o2.getDisplayText().compareTo(o1.getDisplayText()));
}
ret = new PaginationHelper<>(terms, offset, limit).getPaginatedList();
} else {
ret = Collections.emptyList();
}
if (DEBUG_ENABLED) {
LOG.debug("<== GlossaryService.getGlossaryTermsHeaders() : {}", ret);
}
return ret;
}
@GraphTransaction
public List<AtlasGlossaryTerm> getGlossaryTerms(String glossaryGuid, int offset, int limit, SortOrder sortOrder) throws AtlasBaseException {
if (Objects.isNull(glossaryGuid)) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "glossaryGuid is null/empty");
}
if (DEBUG_ENABLED) {
LOG.debug("==> GlossaryService.getGlossaryTerms({}, {}, {}, {})", glossaryGuid, offset, limit, sortOrder);
}
List<AtlasGlossaryTerm> ret = new ArrayList<>();
List<AtlasRelatedTermHeader> termHeaders = getGlossaryTermsHeaders(glossaryGuid, offset, limit, sortOrder);
for (AtlasRelatedTermHeader header : termHeaders) {
ret.add(dataAccess.load(getAtlasGlossaryTermSkeleton(header.getTermGuid())));
}
if (DEBUG_ENABLED) {
LOG.debug("<== GlossaryService.getGlossaryTerms() : {}", ret);
}
return ret;
}
@GraphTransaction
public List<AtlasRelatedCategoryHeader> getGlossaryCategoriesHeaders(String glossaryGuid, int offset, int limit, SortOrder sortOrder) throws AtlasBaseException {
if (Objects.isNull(glossaryGuid)) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "glossaryGuid is null/empty");
}
if (DEBUG_ENABLED) {
LOG.debug("==> GlossaryService.getGlossaryCategoriesHeaders({}, {}, {}, {})", glossaryGuid, offset, limit, sortOrder);
}
List<AtlasRelatedCategoryHeader> ret;
AtlasGlossary glossary = getGlossary(glossaryGuid);
if (CollectionUtils.isNotEmpty(glossary.getCategories())) {
List<AtlasRelatedCategoryHeader> categories = new ArrayList<>(glossary.getCategories());
if (sortOrder != null) {
categories.sort((o1, o2) -> sortOrder == SortOrder.ASCENDING ?
o1.getDisplayText().compareTo(o2.getDisplayText()) :
o2.getDisplayText().compareTo(o1.getDisplayText()));
}
ret = new PaginationHelper<>(categories, offset, limit).getPaginatedList();
} else {
ret = Collections.emptyList();
}
if (DEBUG_ENABLED) {
LOG.debug("<== GlossaryService.getGlossaryCategoriesHeaders() : {}", ret);
}
return ret;
}
@GraphTransaction
public List<AtlasGlossaryCategory> getGlossaryCategories(String glossaryGuid, int offset, int limit, SortOrder sortOrder) throws AtlasBaseException {
if (Objects.isNull(glossaryGuid)) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "glossaryGuid is null/empty");
}
if (DEBUG_ENABLED) {
LOG.debug("==> GlossaryService.getGlossaryCategories({}, {}, {}, {})", glossaryGuid, offset, limit, sortOrder);
}
List<AtlasGlossaryCategory> ret = new ArrayList<>();
List<AtlasRelatedCategoryHeader> categoryHeaders = getGlossaryCategoriesHeaders(glossaryGuid, offset, limit, sortOrder);
for (AtlasRelatedCategoryHeader header : categoryHeaders) {
ret.add(dataAccess.load(getAtlasGlossaryCategorySkeleton(header.getCategoryGuid())));
}
if (DEBUG_ENABLED) {
LOG.debug("<== GlossaryService.getGlossaryCategories() : {}", ret);
}
return ret;
}
@GraphTransaction
public List<AtlasRelatedTermHeader> getCategoryTerms(String categoryGuid, int offset, int limit, SortOrder sortOrder) throws AtlasBaseException {
if (Objects.isNull(categoryGuid)) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "categoryGuid is null/empty");
}
if (DEBUG_ENABLED) {
LOG.debug("==> GlossaryService.getCategoryTerms({}, {}, {}, {})", categoryGuid, offset, limit, sortOrder);
}
List<AtlasRelatedTermHeader> ret;
AtlasGlossaryCategory glossaryCategory = getCategory(categoryGuid);
if (CollectionUtils.isNotEmpty(glossaryCategory.getTerms())) {
List<AtlasRelatedTermHeader> terms = new ArrayList<>(glossaryCategory.getTerms());
if (sortOrder != null) {
terms.sort((o1, o2) -> sortOrder == SortOrder.ASCENDING ?
o1.getDisplayText().compareTo(o2.getDisplayText()) :
o2.getDisplayText().compareTo(o1.getDisplayText()));
}
ret = new PaginationHelper<>(terms, offset, limit).getPaginatedList();
} else {
ret = Collections.emptyList();
}
if (DEBUG_ENABLED) {
LOG.debug("<== GlossaryService.getCategoryTerms() : {}", ret);
}
return ret;
}
@GraphTransaction
public Map<AtlasGlossaryTerm.Relation, Set<AtlasRelatedTermHeader>> getRelatedTerms(String termGuid, int offset, int limit, SortOrder sortOrder) throws AtlasBaseException {
if (Objects.isNull(termGuid)) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "termGuid is null/empty");
}
if (DEBUG_ENABLED) {
LOG.debug("==> GlossaryService.getRelatedTerms({}, {}, {}, {})", termGuid, offset, limit, sortOrder);
}
AtlasGlossaryTerm glossaryTerm = getTerm(termGuid);
Map<AtlasGlossaryTerm.Relation, Set<AtlasRelatedTermHeader>> ret;
if (glossaryTerm.hasTerms()) {
ret = glossaryTerm.getRelatedTerms();
} else {
ret = Collections.emptyMap();
}
if (DEBUG_ENABLED) {
LOG.debug("<== GlossaryService.getRelatedTerms() : {}", ret);
}
return ret;
}
@GraphTransaction
public Map<String, List<AtlasRelatedCategoryHeader>> getRelatedCategories(String categoryGuid, int offset, int limit, SortOrder sortOrder) throws AtlasBaseException {
if (Objects.isNull(categoryGuid)) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "categoryGuid is null/empty");
}
if (DEBUG_ENABLED) {
LOG.debug("==> GlossaryService.getRelatedCategories({}, {}, {}, {})", categoryGuid, offset, limit, sortOrder);
}
AtlasGlossaryCategory glossaryCategory = getCategory(categoryGuid);
Map<String, List<AtlasRelatedCategoryHeader>> ret = new HashMap<>();
if (glossaryCategory.getParentCategory() != null) {
ret.put("parent", new ArrayList<AtlasRelatedCategoryHeader>() {{
add(glossaryCategory.getParentCategory());
}});
}
if (CollectionUtils.isNotEmpty(glossaryCategory.getChildrenCategories())) {
ret.put("children", new ArrayList<>(glossaryCategory.getChildrenCategories()));
}
if (DEBUG_ENABLED) {
LOG.debug("<== GlossaryService.getRelatedCategories() : {}", ret);
}
return ret;
}
@GraphTransaction
public List<AtlasRelatedObjectId> getAssignedEntities(final String termGuid, int offset, int limit, SortOrder sortOrder) throws AtlasBaseException {
if (Objects.isNull(termGuid)) {
throw new AtlasBaseException(AtlasErrorCode.BAD_REQUEST, "termGuid is null/empty");
}
AtlasGlossaryTerm glossaryTerm = dataAccess.load(getAtlasGlossaryTermSkeleton(termGuid));
Set<AtlasRelatedObjectId> assignedEntities = glossaryTerm.getAssignedEntities();
List<AtlasRelatedObjectId> ret;
if (CollectionUtils.isNotEmpty(assignedEntities)) {
ret = new ArrayList<>(assignedEntities);
if (sortOrder != null) {
ret.sort((o1, o2) -> sortOrder == SortOrder.ASCENDING ?
o1.getDisplayText().compareTo(o2.getDisplayText()) :
o2.getDisplayText().compareTo(o1.getDisplayText()));
}
ret = new PaginationHelper<>(assignedEntities, offset, limit).getPaginatedList();
} else {
ret = Collections.emptyList();
}
return ret;
}
private boolean glossaryExists(AtlasGlossary atlasGlossary) {
AtlasVertex vertex = AtlasGraphUtilsV1.findByUniqueAttributes(atlasTypeRegistry.getEntityTypeByName(GlossaryUtils.ATLAS_GLOSSARY_TYPENAME), new HashMap<String, Object>() {{
put(QUALIFIED_NAME_ATTR, atlasGlossary.getQualifiedName());
}});
return Objects.nonNull(vertex);
}
private boolean termExists(AtlasGlossaryTerm term) {
AtlasVertex vertex = AtlasGraphUtilsV1.findByUniqueAttributes(atlasTypeRegistry.getEntityTypeByName(GlossaryUtils.ATLAS_GLOSSARY_TERM_TYPENAME), new HashMap<String, Object>() {{
put(QUALIFIED_NAME_ATTR, term.getQualifiedName());
}});
return Objects.nonNull(vertex);
}
private boolean categoryExists(AtlasGlossaryCategory category) {
AtlasVertex vertex = AtlasGraphUtilsV1.findByUniqueAttributes(atlasTypeRegistry.getEntityTypeByName(GlossaryUtils.ATLAS_GLOSSARY_CATEGORY_TYPENAME), new HashMap<String, Object>() {{
put(QUALIFIED_NAME_ATTR, category.getQualifiedName());
}});
return Objects.nonNull(vertex);
}
private void deleteCategories(final AtlasGlossary storeObject, final Collection<AtlasRelatedCategoryHeader> categories) throws AtlasBaseException {
if (CollectionUtils.isNotEmpty(categories)) {
if (DEBUG_ENABLED) {
LOG.debug("Deleting categories within glossary guid = {}", storeObject.getGuid());
}
for (AtlasRelatedCategoryHeader category : categories) {
// Delete category
deleteCategory(category.getCategoryGuid());
}
}
}
private void deleteTerms(final AtlasGlossary storeObject, final Collection<AtlasRelatedTermHeader> terms) throws AtlasBaseException {
if (CollectionUtils.isNotEmpty(terms)) {
if (DEBUG_ENABLED) {
LOG.debug("Deleting terms within glossary guid = {}", storeObject.getGuid());
}
for (AtlasRelatedTermHeader term : terms) {
// Delete the term
deleteTerm(term.getTermGuid());
}
}
}
private void setInfoForRelations(final AtlasGlossary ret) throws AtlasBaseException {
if (Objects.nonNull(ret.getTerms())) {
setInfoForTerms(ret.getTerms());
}
if (Objects.nonNull(ret.getCategories())) {
setInfoForRelatedCategories(ret.getCategories());
}
}
private void setInfoForRelations(final AtlasGlossaryTerm ret) throws AtlasBaseException {
if (Objects.nonNull(ret.getCategories())) {
setDisplayNameForTermCategories(ret.getCategories());
}
if (Objects.nonNull(ret.getRelatedTerms())) {
for (Map.Entry<AtlasGlossaryTerm.Relation, Set<AtlasRelatedTermHeader>> entry : ret.getRelatedTerms().entrySet()) {
setInfoForTerms(entry.getValue());
}
}
}
private void setInfoForRelations(final AtlasGlossaryCategory glossaryCategory) throws AtlasBaseException {
if (Objects.nonNull(glossaryCategory.getChildrenCategories())) {
setInfoForRelatedCategories(glossaryCategory.getChildrenCategories());
}
if (Objects.nonNull(glossaryCategory.getTerms())) {
setInfoForTerms(glossaryCategory.getTerms());
}
}
private void setDisplayNameForTermCategories(final Set<AtlasTermCategorizationHeader> categorizationHeaders) throws AtlasBaseException {
List<AtlasGlossaryCategory> categories = categorizationHeaders
.stream()
.map(id -> getAtlasGlossaryCategorySkeleton(id.getCategoryGuid()))
.collect(Collectors.toList());
Map<String, AtlasGlossaryCategory> categoryMap = new HashMap<>();
dataAccess.load(categories).forEach(c -> categoryMap.put(c.getGuid(), c));
categorizationHeaders.forEach(c -> c.setDisplayText(categoryMap.get(c.getCategoryGuid()).getDisplayName()));
}
private void setInfoForRelatedCategories(final Collection<AtlasRelatedCategoryHeader> categoryHeaders) throws AtlasBaseException {
List<AtlasGlossaryCategory> categories = categoryHeaders
.stream()
.map(id -> getAtlasGlossaryCategorySkeleton(id.getCategoryGuid()))
.collect(Collectors.toList());
Map<String, AtlasGlossaryCategory> categoryMap = new HashMap<>();
dataAccess.load(categories).forEach(c -> categoryMap.put(c.getGuid(), c));
for (AtlasRelatedCategoryHeader c : categoryHeaders) {
AtlasGlossaryCategory category = categoryMap.get(c.getCategoryGuid());
c.setDisplayText(category.getDisplayName());
if (Objects.nonNull(category.getParentCategory())) {
c.setParentCategoryGuid(category.getParentCategory().getCategoryGuid());
}
}
}
private void setInfoForTerms(final Collection<AtlasRelatedTermHeader> termHeaders) throws AtlasBaseException {
List<AtlasGlossaryTerm> terms = termHeaders
.stream()
.map(id -> getAtlasGlossaryTermSkeleton(id.getTermGuid()))
.collect(Collectors.toList());
Map<String, AtlasGlossaryTerm> termMap = new HashMap<>();
dataAccess.load(terms).iterator().forEachRemaining(t -> termMap.put(t.getGuid(), t));
termHeaders.forEach(t -> t.setDisplayText(termMap.get(t.getTermGuid()).getDisplayName()));
}
private boolean isNameInvalid(String name) {
return StringUtils.containsAny(name, invalidNameChars);
}
static class PaginationHelper<T> {
private int pageStart;
private int pageEnd;
private int maxSize;
private List<T> items;
PaginationHelper(Collection<T> items, int offset, int limit) {
Objects.requireNonNull(items, "items can't be empty/null");
this.items = new ArrayList<>(items);
this.maxSize = items.size();
// If limit is negative then limit is effectively the maxSize, else the smaller one out of limit and maxSize
int adjustedLimit = limit < 0 ? maxSize : Integer.min(maxSize, limit);
// Page starting can only be between zero and adjustedLimit
pageStart = Integer.max(0, offset);
// Page end can't exceed the maxSize
pageEnd = Integer.min(adjustedLimit + pageStart, maxSize);
}
List<T> getPaginatedList() {
List<T> ret;
if (isValidOffset()) {
if (isPagingNeeded()) {
ret = items.subList(pageStart, pageEnd);
} else {
ret = items;
}
} else {
ret = Collections.emptyList();
}
return ret;
}
private boolean isPagingNeeded() {
return !(pageStart == 0 && pageEnd == maxSize) && pageStart <= pageEnd;
}
private boolean isValidOffset() {
return pageStart <= maxSize;
}
}
}