diff --git a/client/client-v2/src/main/java/org/apache/atlas/AtlasClientV2.java b/client/client-v2/src/main/java/org/apache/atlas/AtlasClientV2.java index 31687acda8..04e4376d62 100644 --- a/client/client-v2/src/main/java/org/apache/atlas/AtlasClientV2.java +++ b/client/client-v2/src/main/java/org/apache/atlas/AtlasClientV2.java @@ -349,11 +349,33 @@ public AtlasTypesDef updateAtlasTypeDefs(AtlasTypesDef typesDef) throws AtlasSer * @param typesDef A composite object that captures all types to be deleted */ public void deleteAtlasTypeDefs(AtlasTypesDef typesDef) throws AtlasServiceException { - callAPI(API_V2.DELETE_TYPE_DEFS, (Class) null, AtlasType.toJson(typesDef)); + deleteAtlasTypeDefs(typesDef, false); + } + + public void deleteAtlasTypeDefs(AtlasTypesDef typesDef, boolean forceDelete) throws AtlasServiceException { + MultivaluedMap queryParams = null; + + if (forceDelete) { + queryParams = new MultivaluedMapImpl(); + queryParams.add("force", "true"); + } + + callAPI(API_V2.DELETE_TYPE_DEFS, (Class) null, AtlasType.toJson(typesDef), queryParams); } public void deleteTypeByName(String typeName) throws AtlasServiceException { - callAPI(API_V2.DELETE_TYPE_DEF_BY_NAME, (Class) null, null, typeName); + callAPI(API_V2.DELETE_TYPE_DEF_BY_NAME, (Class) null, null, typeName); + } + + public void deleteTypeByName(String typeName, boolean forceDelete) throws AtlasServiceException { + MultivaluedMap queryParams = null; + + if (forceDelete) { + queryParams = new MultivaluedMapImpl(); + queryParams.add("force", "true"); + } + + callAPI(API_V2.DELETE_TYPE_DEF_BY_NAME, (Class) null, queryParams, typeName); } // Entity APIs diff --git a/client/client-v2/src/test/java/org/apache/atlas/AtlasClientV2Test.java b/client/client-v2/src/test/java/org/apache/atlas/AtlasClientV2Test.java index be180b56ab..f4942d7f59 100644 --- a/client/client-v2/src/test/java/org/apache/atlas/AtlasClientV2Test.java +++ b/client/client-v2/src/test/java/org/apache/atlas/AtlasClientV2Test.java @@ -82,6 +82,7 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; @@ -808,6 +809,7 @@ private static class TestableAtlasClientV2 extends AtlasClientV2 { private Object mockResponse; private Class expectedReturnType; private boolean shouldThrowException; + private javax.ws.rs.core.MultivaluedMap lastQueryParams; public TestableAtlasClientV2() { super(mock(WebResource.class), mock(Configuration.class)); @@ -823,8 +825,13 @@ public void setShouldThrowException(boolean shouldThrow) { this.shouldThrowException = shouldThrow; } + public javax.ws.rs.core.MultivaluedMap getLastQueryParams() { + return lastQueryParams; + } + @Override public T callAPI(API api, Class responseType, Object requestObject, String... params) throws AtlasServiceException { + lastQueryParams = null; return handleCallAPI(responseType); } @@ -838,11 +845,13 @@ public T callAPI(API api, GenericType responseType, Object requestObject, @Override public T callAPI(API api, Class responseType, javax.ws.rs.core.MultivaluedMap queryParams, String... params) throws AtlasServiceException { + lastQueryParams = queryParams; return handleCallAPI(responseType); } @Override public T callAPI(API api, Class responseType, Object requestObject, javax.ws.rs.core.MultivaluedMap queryParams, String... params) throws AtlasServiceException { + lastQueryParams = queryParams; return handleCallAPI(responseType); } @@ -1053,6 +1062,19 @@ public void testDeleteAtlasTypeDefsRealExecution() throws Exception { // Should not throw exception client.deleteAtlasTypeDefs(input); assertTrue(true); + assertNull(client.getLastQueryParams()); + } + + @Test + public void testDeleteAtlasTypeDefsRealExecutionWithForceDelete() throws Exception { + TestableAtlasClientV2 client = new TestableAtlasClientV2(); + client.setMockResponse(null, Object.class); + + AtlasTypesDef input = new AtlasTypesDef(); + client.deleteAtlasTypeDefs(input, true); + + assertNotNull(client.getLastQueryParams()); + assertEquals(client.getLastQueryParams().getFirst("force"), "true"); } @Test diff --git a/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java b/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java index aa79ad5d1b..e52eff14ff 100644 --- a/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java +++ b/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java @@ -183,6 +183,7 @@ public enum AtlasErrorCode { BLANK_NAME_ATTRIBUTE(400, "ATLAS-400-00-104", "Name Attribute can't be empty!"), BLANK_VALUE_ATTRIBUTE(400, "ATLAS-400-00-105", "Value Attribute can't be empty!"), INVALID_RELATIONSHIP_LABEL(400, "ATLAS-400-00-106", "Invalid relationship label {0}. The referenced entity type {1} could not be resolved from the type registry."), + NON_INDEXABLE_BM_DELETE_NOT_ALLOWED(400, "ATLAS-400-00-107", "Deletion not allowed for non-indexable Business Metadata ''{0}'' without force=true. Non-indexable attributes cannot be validated efficiently for references; use force=true to skip validation and delete (warning: orphaned references may remain)."), UNAUTHORIZED_ACCESS(403, "ATLAS-403-00-001", "{0} is not authorized to perform {1}"), diff --git a/intg/src/main/java/org/apache/atlas/type/AtlasBusinessMetadataType.java b/intg/src/main/java/org/apache/atlas/type/AtlasBusinessMetadataType.java index ccea47c728..0824530b8a 100644 --- a/intg/src/main/java/org/apache/atlas/type/AtlasBusinessMetadataType.java +++ b/intg/src/main/java/org/apache/atlas/type/AtlasBusinessMetadataType.java @@ -44,12 +44,18 @@ public class AtlasBusinessMetadataType extends AtlasStructType { private final AtlasBusinessMetadataDef businessMetadataDef; + private boolean hasNonIndexableAttributes; + public AtlasBusinessMetadataType(AtlasBusinessMetadataDef businessMetadataDef) { super(businessMetadataDef); this.businessMetadataDef = businessMetadataDef; } + public boolean hasNonIndexableAttributes() { + return hasNonIndexableAttributes; + } + @Override public AtlasStruct createDefaultValue() { return null; // there is no runtime instance for businessMetadataDef, so return null @@ -71,6 +77,8 @@ void resolveReferences(AtlasTypeRegistry typeRegistry) throws AtlasBaseException Map a = new HashMap<>(); + hasNonIndexableAttributes = false; + for (AtlasAttribute attribute : super.allAttributes.values()) { AtlasAttributeDef attributeDef = attribute.getAttributeDef(); String attrName = attribute.getName(); @@ -115,10 +123,16 @@ void resolveReferences(AtlasTypeRegistry typeRegistry) throws AtlasBaseException bmAttribute = new AtlasBusinessAttribute(attribute, entityTypes); } + if (!hasNonIndexableAttributes && !Boolean.TRUE.equals(attributeDef.getIsIndexable())) { + hasNonIndexableAttributes = true; + } + a.put(attrName, bmAttribute); } super.allAttributes = Collections.unmodifiableMap(a); + LOG.debug("CACHE-POPULATE [Phase1] BM='{}' hasNonIndexableAttributes={} totalAttributes={}", + getTypeName(), hasNonIndexableAttributes, a.size()); } @Override @@ -137,6 +151,29 @@ void resolveReferencesPhase2(AtlasTypeRegistry typeRegistry) throws AtlasBaseExc } } + @Override + void resolveReferencesPhase3(AtlasTypeRegistry typeRegistry) throws AtlasBaseException { + super.resolveReferencesPhase3(typeRegistry); + + for (AtlasAttribute attribute : super.allAttributes.values()) { + AtlasBusinessAttribute bmAttribute = (AtlasBusinessAttribute) attribute; + Set entityTypes = bmAttribute.getApplicableEntityTypes(); + + if (CollectionUtils.isNotEmpty(entityTypes)) { + Set expandedTypeNames = new HashSet<>(); + + for (AtlasEntityType entityType : entityTypes) { + expandedTypeNames.add(entityType.getTypeName()); + expandedTypeNames.addAll(entityType.getAllSubTypes()); + } + + bmAttribute.applicableEntityTypeNamesWithSubTypes = Collections.unmodifiableSet(expandedTypeNames); + LOG.debug("CACHE-POPULATE [Phase3] BM='{}' attr='{}' expandedTypes={}", + getTypeName(), bmAttribute.getName(), expandedTypeNames); + } + } + } + public AtlasBusinessMetadataDef getBusinessMetadataDef() { return businessMetadataDef; } @@ -146,6 +183,8 @@ public static class AtlasBusinessAttribute extends AtlasAttribute { private final int maxStringLength; private final String validPattern; + private Set applicableEntityTypeNamesWithSubTypes = Collections.emptySet(); + public AtlasBusinessAttribute(AtlasAttribute attribute, Set applicableEntityTypes) { super(attribute); @@ -171,6 +210,10 @@ public Set getApplicableEntityTypes() { return applicableEntityTypes; } + public Set getApplicableEntityTypeNamesWithSubTypes() { + return applicableEntityTypeNamesWithSubTypes; + } + public String getValidPattern() { return validPattern; } diff --git a/repository/src/main/java/org/apache/atlas/repository/store/graph/AtlasDefStore.java b/repository/src/main/java/org/apache/atlas/repository/store/graph/AtlasDefStore.java index a956eecb41..3185ac4985 100644 --- a/repository/src/main/java/org/apache/atlas/repository/store/graph/AtlasDefStore.java +++ b/repository/src/main/java/org/apache/atlas/repository/store/graph/AtlasDefStore.java @@ -20,6 +20,8 @@ import org.apache.atlas.exception.AtlasBaseException; import org.apache.atlas.model.typedef.AtlasBaseTypeDef; import org.apache.atlas.repository.graphdb.AtlasVertex; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.List; @@ -27,6 +29,8 @@ * Interface for graph persistence store for AtlasTypeDef */ public interface AtlasDefStore { + Logger LOG = LoggerFactory.getLogger(AtlasDefStore.class); + AtlasVertex preCreate(T typeDef) throws AtlasBaseException; T create(T typeDef, AtlasVertex preCreateResult) throws AtlasBaseException; @@ -45,9 +49,33 @@ public interface AtlasDefStore { AtlasVertex preDeleteByName(String name) throws AtlasBaseException; - void deleteByName(String name, AtlasVertex preDeleteResult) throws AtlasBaseException; - AtlasVertex preDeleteByGuid(String guid) throws AtlasBaseException; + void deleteByName(String name, AtlasVertex preDeleteResult) throws AtlasBaseException; + void deleteByGuid(String guid, AtlasVertex preDeleteResult) throws AtlasBaseException; + + default AtlasVertex preDeleteByName(String name, boolean forceDelete) throws AtlasBaseException { + if (forceDelete) { + LOG.debug("Force-delete flag ignored in {}.preDeleteByName() for type '{}'; feature is implemented only for BusinessMetadata.", getClass().getSimpleName(), name); + } + + return preDeleteByName(name); + } + + default void deleteByName(String name, AtlasVertex preDeleteResult, boolean forceDelete) throws AtlasBaseException { + deleteByName(name, preDeleteResult); + } + + default AtlasVertex preDeleteByGuid(String guid, boolean forceDelete) throws AtlasBaseException { + if (forceDelete) { + LOG.debug("Force-delete flag ignored in {}.preDeleteByGuid() for guid '{}'; feature is implemented only for BusinessMetadata.", getClass().getSimpleName(), guid); + } + + return preDeleteByGuid(guid); + } + + default void deleteByGuid(String guid, AtlasVertex preDeleteResult, boolean forceDelete) throws AtlasBaseException { + deleteByGuid(guid, preDeleteResult); + } } diff --git a/repository/src/main/java/org/apache/atlas/repository/store/graph/AtlasTypeDefGraphStore.java b/repository/src/main/java/org/apache/atlas/repository/store/graph/AtlasTypeDefGraphStore.java index 4776912cd1..06a5bcee98 100644 --- a/repository/src/main/java/org/apache/atlas/repository/store/graph/AtlasTypeDefGraphStore.java +++ b/repository/src/main/java/org/apache/atlas/repository/store/graph/AtlasTypeDefGraphStore.java @@ -539,15 +539,19 @@ public AtlasTypesDef createUpdateTypesDef(AtlasTypesDef typesToCreate, AtlasType @Override @GraphTransaction public void deleteTypesDef(AtlasTypesDef typesDef) throws AtlasBaseException { - if (LOG.isDebugEnabled()) { - LOG.debug("==> AtlasTypeDefGraphStore.deleteTypesDef(enums={}, structs={}, classfications={}, entities={}, relationships={}, businessMetadataDefs={})", - CollectionUtils.size(typesDef.getEnumDefs()), - CollectionUtils.size(typesDef.getStructDefs()), - CollectionUtils.size(typesDef.getClassificationDefs()), - CollectionUtils.size(typesDef.getEntityDefs()), - CollectionUtils.size(typesDef.getRelationshipDefs()), - CollectionUtils.size(typesDef.getBusinessMetadataDefs())); - } + deleteTypesDef(typesDef, false); + } + + @GraphTransaction + public void deleteTypesDef(AtlasTypesDef typesDef, boolean forceDelete) throws AtlasBaseException { + LOG.debug("==> AtlasTypeDefGraphStore.deleteTypesDef(enums={}, structs={}, classfications={}, entities={}, relationships={}, businessMetadataDefs={}, forceDelete={})", + CollectionUtils.size(typesDef.getEnumDefs()), + CollectionUtils.size(typesDef.getStructDefs()), + CollectionUtils.size(typesDef.getClassificationDefs()), + CollectionUtils.size(typesDef.getEntityDefs()), + CollectionUtils.size(typesDef.getRelationshipDefs()), + CollectionUtils.size(typesDef.getBusinessMetadataDefs()), + forceDelete); AtlasTransientTypeRegistry ttr = lockTypeRegistryAndReleasePostCommit(); @@ -678,9 +682,9 @@ public void deleteTypesDef(AtlasTypesDef typesDef) throws AtlasBaseException { if (CollectionUtils.isNotEmpty(typesDef.getBusinessMetadataDefs())) { for (AtlasBusinessMetadataDef businessMetadataDef : typesDef.getBusinessMetadataDefs()) { if (StringUtils.isNotBlank(businessMetadataDef.getGuid())) { - businessMetadataDefStore.deleteByGuid(businessMetadataDef.getGuid(), null); + businessMetadataDefStore.deleteByGuid(businessMetadataDef.getGuid(), null, forceDelete); } else { - businessMetadataDefStore.deleteByName(businessMetadataDef.getName(), null); + businessMetadataDefStore.deleteByName(businessMetadataDef.getName(), null, forceDelete); } } } @@ -777,7 +781,7 @@ public AtlasBaseTypeDef getByGuid(String guid) throws AtlasBaseException { @Override @GraphTransaction - public void deleteTypeByName(String typeName) throws AtlasBaseException { + public void deleteTypeByName(String typeName, boolean forceDelete) throws AtlasBaseException { AtlasType atlasType = typeRegistry.getType(typeName); if (atlasType == null) { @@ -801,7 +805,7 @@ public void deleteTypeByName(String typeName) throws AtlasBaseException { typesDef.setStructDefs(Collections.singletonList((AtlasStructDef) baseTypeDef)); } - deleteTypesDef(typesDef); + deleteTypesDef(typesDef, forceDelete); } @Override diff --git a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasAbstractDefStoreV2.java b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasAbstractDefStoreV2.java index 609275c4b6..c3dd86327e 100644 --- a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasAbstractDefStoreV2.java +++ b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasAbstractDefStoreV2.java @@ -148,24 +148,46 @@ public boolean isValidName(String typeName) { @Override public void deleteByName(String name, AtlasVertex preDeleteResult) throws AtlasBaseException { - LOG.debug("==> AtlasAbstractDefStoreV1.deleteByName({}, {})", name, preDeleteResult); + LOG.debug("==> AtlasAbstractDefStoreV2.deleteByName({}, {})", name, preDeleteResult); AtlasVertex vertex = (preDeleteResult == null) ? preDeleteByName(name) : preDeleteResult; typeDefStore.deleteTypeVertex(vertex); - LOG.debug("<== AtlasAbstractDefStoreV1.deleteByName({}, {})", name, preDeleteResult); + LOG.debug("<== AtlasAbstractDefStoreV2.deleteByName({}, {})", name, preDeleteResult); } @Override public void deleteByGuid(String guid, AtlasVertex preDeleteResult) throws AtlasBaseException { - LOG.debug("==> AtlasAbstractDefStoreV1.deleteByGuid({}, {})", guid, preDeleteResult); + LOG.debug("==> AtlasAbstractDefStoreV2.deleteByGuid({}, {})", guid, preDeleteResult); AtlasVertex vertex = (preDeleteResult == null) ? preDeleteByGuid(guid) : preDeleteResult; typeDefStore.deleteTypeVertex(vertex); - LOG.debug("<== AtlasAbstractDefStoreV1.deleteByGuid({}, {})", guid, preDeleteResult); + LOG.debug("<== AtlasAbstractDefStoreV2.deleteByGuid({}, {})", guid, preDeleteResult); + } + + @Override + public void deleteByName(String name, AtlasVertex preDeleteResult, boolean forceDelete) throws AtlasBaseException { + LOG.debug("==> AtlasAbstractDefStoreV2.deleteByName({}, {}, {})", name, preDeleteResult, forceDelete); + + AtlasVertex vertex = (preDeleteResult == null) ? preDeleteByName(name, forceDelete) : preDeleteResult; + + typeDefStore.deleteTypeVertex(vertex); + + LOG.debug("<== AtlasAbstractDefStoreV2.deleteByName({}, {}, {})", name, preDeleteResult, forceDelete); + } + + @Override + public void deleteByGuid(String guid, AtlasVertex preDeleteResult, boolean forceDelete) throws AtlasBaseException { + LOG.debug("==> AtlasAbstractDefStoreV2.deleteByGuid({}, {}, {})", guid, preDeleteResult, forceDelete); + + AtlasVertex vertex = (preDeleteResult == null) ? preDeleteByGuid(guid, forceDelete) : preDeleteResult; + + typeDefStore.deleteTypeVertex(vertex); + + LOG.debug("<== AtlasAbstractDefStoreV2.deleteByGuid({}, {}, {})", guid, preDeleteResult, forceDelete); } public boolean isInvalidTypeDefName(String typeName) { diff --git a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasBusinessMetadataDefStoreV2.java b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasBusinessMetadataDefStoreV2.java index 3543c2322e..f379fbfd2a 100644 --- a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasBusinessMetadataDefStoreV2.java +++ b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasBusinessMetadataDefStoreV2.java @@ -21,23 +21,21 @@ import org.apache.atlas.authorize.AtlasAuthorizationUtils; import org.apache.atlas.authorize.AtlasPrivilege; import org.apache.atlas.authorize.AtlasTypeAccessRequest; -import org.apache.atlas.discovery.EntityDiscoveryService; -import org.apache.atlas.discovery.SearchContext; import org.apache.atlas.exception.AtlasBaseException; import org.apache.atlas.model.TypeCategory; -import org.apache.atlas.model.discovery.AtlasSearchResult; -import org.apache.atlas.model.discovery.SearchParameters; import org.apache.atlas.model.typedef.AtlasBaseTypeDef; import org.apache.atlas.model.typedef.AtlasBusinessMetadataDef; import org.apache.atlas.model.typedef.AtlasStructDef; import org.apache.atlas.repository.Constants; +import org.apache.atlas.repository.graphdb.AtlasGraph; +import org.apache.atlas.repository.graphdb.AtlasGraphQuery; import org.apache.atlas.repository.graphdb.AtlasVertex; import org.apache.atlas.type.AtlasBusinessMetadataType; +import org.apache.atlas.type.AtlasEntityType; import org.apache.atlas.type.AtlasStructType; import org.apache.atlas.type.AtlasType; import org.apache.atlas.type.AtlasTypeRegistry; import org.apache.atlas.typesystem.types.DataTypes; -import org.apache.atlas.utils.AtlasJson; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -47,6 +45,7 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; @@ -56,13 +55,13 @@ public class AtlasBusinessMetadataDefStoreV2 extends AtlasAbstractDefStoreV2 { private static final Logger LOG = LoggerFactory.getLogger(AtlasBusinessMetadataDefStoreV2.class); - private final EntityDiscoveryService entityDiscoveryService; + private final AtlasGraph graph; @Inject - public AtlasBusinessMetadataDefStoreV2(AtlasTypeDefGraphStoreV2 typeDefStore, AtlasTypeRegistry typeRegistry, EntityDiscoveryService entityDiscoveryService) { + public AtlasBusinessMetadataDefStoreV2(AtlasTypeDefGraphStoreV2 typeDefStore, AtlasTypeRegistry typeRegistry, AtlasGraph graph) { super(typeDefStore, typeRegistry); - this.entityDiscoveryService = entityDiscoveryService; + this.graph = graph; } @Override @@ -258,8 +257,14 @@ public AtlasBusinessMetadataDef updateByGuid(String guid, AtlasBusinessMetadataD return ret; } + @Override public AtlasVertex preDeleteByName(String name) throws AtlasBaseException { - LOG.debug("==> AtlasBusinessMetadataDefStoreV2.preDeleteByName({})", name); + return preDeleteByName(name, false); + } + + @Override + public AtlasVertex preDeleteByName(String name, boolean forceDelete) throws AtlasBaseException { + LOG.debug("==> AtlasBusinessMetadataDefStoreV2.preDeleteByName({}, {})", name, forceDelete); AtlasBusinessMetadataDef existingDef = typeRegistry.getBusinessMetadataDefByName(name); @@ -271,15 +276,21 @@ public AtlasVertex preDeleteByName(String name) throws AtlasBaseException { throw new AtlasBaseException(AtlasErrorCode.TYPE_NAME_NOT_FOUND, name); } - checkBusinessMetadataRef(existingDef.getName()); + validateDeletion(existingDef, forceDelete, name); - LOG.debug("<== AtlasBusinessMetadataDefStoreV2.preDeleteByName({}): {}", name, ret); + LOG.debug("<== AtlasBusinessMetadataDefStoreV2.preDeleteByName({}, {}): {}", name, forceDelete, ret); return ret; } + @Override public AtlasVertex preDeleteByGuid(String guid) throws AtlasBaseException { - LOG.debug("==> AtlasBusinessMetadataDefStoreV2.preDeleteByGuid({})", guid); + return preDeleteByGuid(guid, false); + } + + @Override + public AtlasVertex preDeleteByGuid(String guid, boolean forceDelete) throws AtlasBaseException { + LOG.debug("==> AtlasBusinessMetadataDefStoreV2.preDeleteByGuid({}, {})", guid, forceDelete); AtlasBusinessMetadataDef existingDef = typeRegistry.getBusinessMetadataDefByGuid(guid); @@ -291,15 +302,49 @@ public AtlasVertex preDeleteByGuid(String guid) throws AtlasBaseException { throw new AtlasBaseException(AtlasErrorCode.TYPE_GUID_NOT_FOUND, guid); } - if (existingDef != null) { - checkBusinessMetadataRef(existingDef.getName()); - } + validateDeletion(existingDef, forceDelete, guid); - LOG.debug("<== AtlasBusinessMetadataDefStoreV2.preDeleteByGuid({}): ret={}", guid, ret); + LOG.debug("<== AtlasBusinessMetadataDefStoreV2.preDeleteByGuid({}, {}): ret={}", guid, forceDelete, ret); return ret; } + private void validateDeletion(AtlasBusinessMetadataDef businessMetadataDef, boolean forceDelete, String identifier) throws AtlasBaseException { + if (businessMetadataDef == null) { + return; + } + + if (forceDelete) { + LOG.warn("Force-deleting BusinessMetadata '{}'. Skipping validation - orphaned references may remain.", businessMetadataDef.getName()); + return; + } + + if (hasNonIndexableAttribute(businessMetadataDef)) { + LOG.warn("Deletion blocked for non-indexable Business Metadata '{}' without force-delete flag", businessMetadataDef.getName()); + throw new AtlasBaseException(AtlasErrorCode.NON_INDEXABLE_BM_DELETE_NOT_ALLOWED, businessMetadataDef.getName()); + } + + checkBusinessMetadataRef(businessMetadataDef, identifier); + } + + private boolean hasNonIndexableAttribute(AtlasBusinessMetadataDef businessMetadataDef) { + AtlasBusinessMetadataType bmType = typeRegistry.getBusinessMetadataTypeByName(businessMetadataDef.getName()); + + if (bmType != null && bmType.hasNonIndexableAttributes()) { + return true; + } + + if (CollectionUtils.isNotEmpty(businessMetadataDef.getAttributeDefs())) { + for (AtlasStructDef.AtlasAttributeDef attributeDef : businessMetadataDef.getAttributeDefs()) { + if (!Boolean.TRUE.equals(attributeDef.getIsIndexable())) { + return true; + } + } + } + + return false; + } + @Override public void validateType(AtlasBaseTypeDef typeDef) throws AtlasBaseException { super.validateType(typeDef); @@ -360,47 +405,100 @@ private AtlasBusinessMetadataDef toBusinessMetadataDef(AtlasVertex vertex) throw return ret; } - private void checkBusinessMetadataRef(String typeName) throws AtlasBaseException { - AtlasBusinessMetadataDef businessMetadataDef = typeRegistry.getBusinessMetadataDefByName(typeName); + private void checkBusinessMetadataRef(AtlasBusinessMetadataDef businessMetadataDef, String identifier) throws AtlasBaseException { + if (businessMetadataDef == null || CollectionUtils.isEmpty(businessMetadataDef.getAttributeDefs())) { + return; + } - if (businessMetadataDef != null) { - List attributeDefs = businessMetadataDef.getAttributeDefs(); + AtlasBusinessMetadataType bmType = typeRegistry.getBusinessMetadataTypeByName(businessMetadataDef.getName()); - for (AtlasStructDef.AtlasAttributeDef attributeDef : attributeDefs) { - String qualifiedName = AtlasStructType.AtlasAttribute.getQualifiedAttributeName(businessMetadataDef, attributeDef.getName()); - String vertexPropertyName = AtlasStructType.AtlasAttribute.generateVertexPropertyName(businessMetadataDef, attributeDef, qualifiedName); - Set applicableTypes = AtlasJson.fromJson(attributeDef.getOption(AtlasBusinessMetadataDef.ATTR_OPTION_APPLICABLE_ENTITY_TYPES), Set.class); + for (AtlasStructDef.AtlasAttributeDef attributeDef : businessMetadataDef.getAttributeDefs()) { + Set allApplicableTypes; - if (CollectionUtils.isNotEmpty(applicableTypes) && isBusinessAttributePresent(vertexPropertyName, applicableTypes)) { - throw new AtlasBaseException(AtlasErrorCode.TYPE_HAS_REFERENCES, typeName); - } + if (bmType != null) { + AtlasBusinessMetadataType.AtlasBusinessAttribute bmAttr = + (AtlasBusinessMetadataType.AtlasBusinessAttribute) bmType.getAttribute(attributeDef.getName()); + Set cachedTypes = bmAttr != null ? bmAttr.getApplicableEntityTypeNamesWithSubTypes() : Collections.emptySet(); + + allApplicableTypes = CollectionUtils.isNotEmpty(cachedTypes) ? cachedTypes : getApplicableTypesWithSubTypes(attributeDef); + } else { + allApplicableTypes = getApplicableTypesWithSubTypes(attributeDef); } + + LOG.debug("REF-CHECK [ApplicableTypes] BM='{}' attr='{}' types={}", + businessMetadataDef.getName(), attributeDef.getName(), allApplicableTypes); + + if (CollectionUtils.isEmpty(allApplicableTypes)) { + continue; + } + + validateAttributeReferences(businessMetadataDef, attributeDef, allApplicableTypes, identifier); } } - private boolean isBusinessAttributePresent(String attrName, Set applicableTypes) throws AtlasBaseException { - SearchParameters.FilterCriteria criteria = new SearchParameters.FilterCriteria(); + private void validateAttributeReferences(AtlasBusinessMetadataDef bmDef, AtlasStructDef.AtlasAttributeDef attributeDef, + Set allApplicableTypes, String identifier) throws AtlasBaseException { + String qualifiedName = AtlasStructType.AtlasAttribute.getQualifiedAttributeName(bmDef, attributeDef.getName()); + String vertexPropertyName = AtlasStructType.AtlasAttribute.generateVertexPropertyName(bmDef, attributeDef, qualifiedName); + boolean isPresent = isBusinessAttributePresentInGraph(vertexPropertyName, allApplicableTypes); - criteria.setAttributeName(attrName); - criteria.setOperator(SearchParameters.Operator.NOT_EMPTY); + if (isPresent) { + LOG.warn("Cannot delete BusinessMetadata '{}' (request='{}') - attribute '{}' (vertex property: '{}') has references in entity types: {}", + bmDef.getName(), identifier, attributeDef.getName(), vertexPropertyName, allApplicableTypes); + throw new AtlasBaseException(AtlasErrorCode.TYPE_HAS_REFERENCES, bmDef.getName()); + } + } - SearchParameters.FilterCriteria entityFilters = new SearchParameters.FilterCriteria(); + private boolean isBusinessAttributePresentInGraph(String vertexPropertyName, Set allApplicableTypes) throws AtlasBaseException { + if (CollectionUtils.isEmpty(allApplicableTypes)) { + return false; + } - entityFilters.setCondition(SearchParameters.FilterCriteria.Condition.OR); - entityFilters.setCriterion(Collections.singletonList(criteria)); + try { + List typesList = new ArrayList<>(allApplicableTypes); - SearchParameters searchParameters = new SearchParameters(); + AtlasGraphQuery query = graph.query() + .has(vertexPropertyName, AtlasGraphQuery.ComparisionOperator.NOT_EQUAL, (Object) null); - searchParameters.setTypeName(String.join(SearchContext.TYPENAME_DELIMITER, applicableTypes)); - searchParameters.setExcludeDeletedEntities(true); - searchParameters.setIncludeSubClassifications(false); - searchParameters.setEntityFilters(entityFilters); - searchParameters.setAttributes(Collections.singleton(attrName)); - searchParameters.setOffset(0); - searchParameters.setLimit(1); + List orConditions = new ArrayList<>(); + orConditions.add(query.createChildQuery().in(Constants.ENTITY_TYPE_PROPERTY_KEY, typesList)); + orConditions.add(query.createChildQuery().in(Constants.SUPER_TYPES_PROPERTY_KEY, typesList)); - AtlasSearchResult atlasSearchResult = entityDiscoveryService.searchWithParameters(searchParameters); + query.or(orConditions); - return CollectionUtils.isNotEmpty(atlasSearchResult.getEntities()); + Iterable vertices = query.vertices(1); + + if (vertices != null && vertices.iterator().hasNext()) { + return true; + } + } catch (Exception e) { + throw new AtlasBaseException(AtlasErrorCode.INTERNAL_ERROR, + e, String.format("failed to validate BusinessMetadata references for property %s", vertexPropertyName)); + } + return false; + } + + /** + * Expands configured applicable entity types to include all concrete sub-types. + * Without this expansion, deletion checks can miss references present only on inherited child entity types. + */ + private Set getApplicableTypesWithSubTypes(AtlasStructDef.AtlasAttributeDef attributeDef) { + String applicableTypesJson = attributeDef.getOption(ATTR_OPTION_APPLICABLE_ENTITY_TYPES); + Set applicableTypeNames = StringUtils.isBlank(applicableTypesJson) ? null : AtlasType.fromJson(applicableTypesJson, Set.class); + + if (CollectionUtils.isEmpty(applicableTypeNames)) { + return Collections.emptySet(); + } + + Set result = new HashSet<>(applicableTypeNames); + + for (String typeName : applicableTypeNames) { + AtlasEntityType entityType = typeRegistry.getEntityTypeByName(typeName); + + if (entityType != null) { + result.addAll(entityType.getAllSubTypes()); + } + } + return result; } } diff --git a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasTypeDefGraphStoreV2.java b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasTypeDefGraphStoreV2.java index 57146521ab..09f4dcc9bb 100644 --- a/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasTypeDefGraphStoreV2.java +++ b/repository/src/main/java/org/apache/atlas/repository/store/graph/v2/AtlasTypeDefGraphStoreV2.java @@ -21,7 +21,6 @@ import org.apache.atlas.AtlasErrorCode; import org.apache.atlas.RequestContext; import org.apache.atlas.annotation.GraphTransaction; -import org.apache.atlas.discovery.EntityDiscoveryService; import org.apache.atlas.exception.AtlasBaseException; import org.apache.atlas.listener.TypeDefChangeListener; import org.apache.atlas.model.typedef.AtlasBaseTypeDef; @@ -64,7 +63,7 @@ import static org.apache.atlas.repository.store.graph.v2.AtlasGraphUtilsV2.VERTEX_TYPE; /** - * Graph persistence store for TypeDef - v1 + * Graph persistence store for TypeDef - v2 */ @Singleton @Component @@ -73,16 +72,13 @@ public class AtlasTypeDefGraphStoreV2 extends AtlasTypeDefGraphStore { protected final AtlasGraph atlasGraph; - private final EntityDiscoveryService entityDiscoveryService; - @Inject - public AtlasTypeDefGraphStoreV2(AtlasTypeRegistry typeRegistry, List typeDefChangeListeners, AtlasGraph atlasGraph, EntityDiscoveryService entityDiscoveryService) { + public AtlasTypeDefGraphStoreV2(AtlasTypeRegistry typeRegistry, List typeDefChangeListeners, AtlasGraph atlasGraph) { super(typeRegistry, typeDefChangeListeners); - this.atlasGraph = atlasGraph; - this.entityDiscoveryService = entityDiscoveryService; + this.atlasGraph = atlasGraph; - LOG.debug("<== AtlasTypeDefGraphStoreV1()"); + LOG.debug("<== AtlasTypeDefGraphStoreV2()"); } public static String getCurrentUser() { @@ -177,17 +173,17 @@ protected AtlasDefStore getRelationshipDefStore(AtlasTypeR @Override protected AtlasDefStore getBusinessMetadataDefStore(AtlasTypeRegistry typeRegistry) { - return new AtlasBusinessMetadataDefStoreV2(this, typeRegistry, this.entityDiscoveryService); + return new AtlasBusinessMetadataDefStoreV2(this, typeRegistry, this.atlasGraph); } @Override @GraphTransaction public void init() throws AtlasBaseException { - LOG.info("==> AtlasTypeDefGraphStoreV1.init()"); + LOG.info("==> AtlasTypeDefGraphStoreV2.init()"); super.init(); - LOG.info("<== AtlasTypeDefGraphStoreV1.init()"); + LOG.info("<== AtlasTypeDefGraphStoreV2.init()"); } AtlasGraph getAtlasGraph() { diff --git a/repository/src/main/java/org/apache/atlas/store/AtlasTypeDefStore.java b/repository/src/main/java/org/apache/atlas/store/AtlasTypeDefStore.java index 792f714ad3..64d8cf1c07 100644 --- a/repository/src/main/java/org/apache/atlas/store/AtlasTypeDefStore.java +++ b/repository/src/main/java/org/apache/atlas/store/AtlasTypeDefStore.java @@ -102,6 +102,8 @@ AtlasClassificationDef updateClassificationDefByGuid(String guid, AtlasClassific void deleteTypesDef(AtlasTypesDef typesDef) throws AtlasBaseException; + void deleteTypesDef(AtlasTypesDef typesDef, boolean forceDelete) throws AtlasBaseException; + AtlasTypesDef searchTypesDef(SearchFilter searchFilter) throws AtlasBaseException; /* Generic operation */ @@ -109,7 +111,7 @@ AtlasClassificationDef updateClassificationDefByGuid(String guid, AtlasClassific AtlasBaseTypeDef getByGuid(String guid) throws AtlasBaseException; - void deleteTypeByName(String typeName) throws AtlasBaseException; + void deleteTypeByName(String typeName, boolean forceDelete) throws AtlasBaseException; void notifyLoadCompletion(); } diff --git a/repository/src/test/java/org/apache/atlas/repository/store/graph/AtlasTypeDefGraphStoreTest.java b/repository/src/test/java/org/apache/atlas/repository/store/graph/AtlasTypeDefGraphStoreTest.java index f84d1f4610..90603af034 100644 --- a/repository/src/test/java/org/apache/atlas/repository/store/graph/AtlasTypeDefGraphStoreTest.java +++ b/repository/src/test/java/org/apache/atlas/repository/store/graph/AtlasTypeDefGraphStoreTest.java @@ -369,10 +369,10 @@ public void deleteTypeByName() throws IOException { AtlasTypesDef typesDef = TestResourceFileUtils.readObjectFromJson(".", hivedbV2Json, AtlasTypesDef.class); typeDefStore.createTypesDef(typesDef); - typeDefStore.deleteTypeByName(hiveDB2); - typeDefStore.deleteTypeByName(relationshipDefName); - typeDefStore.deleteTypeByName(hostEntityDef); - typeDefStore.deleteTypeByName(clusterEntityDef); + typeDefStore.deleteTypeByName(hiveDB2, false); + typeDefStore.deleteTypeByName(relationshipDefName, false); + typeDefStore.deleteTypeByName(hostEntityDef, false); + typeDefStore.deleteTypeByName(clusterEntityDef, false); } catch (AtlasBaseException e) { fail("Deletion should've succeeded"); } diff --git a/repository/src/test/java/org/apache/atlas/repository/store/graph/v2/AtlasBusinessMetadataDefStoreV2Test.java b/repository/src/test/java/org/apache/atlas/repository/store/graph/v2/AtlasBusinessMetadataDefStoreV2Test.java index a93ff80407..a71f99299b 100644 --- a/repository/src/test/java/org/apache/atlas/repository/store/graph/v2/AtlasBusinessMetadataDefStoreV2Test.java +++ b/repository/src/test/java/org/apache/atlas/repository/store/graph/v2/AtlasBusinessMetadataDefStoreV2Test.java @@ -59,6 +59,7 @@ import static org.testng.Assert.assertNotEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; /* Please note that for these tests, since the typeRegistry can be injected only once, * any new tests should make sure that they flush the type registry at the end of the test. @@ -206,6 +207,39 @@ public void deleteBusinessMetadataDefWithNoAssignedTypes() throws AtlasBaseExcep } } + @Test + public void deleteBusinessMetadataDefWithMixedIndexableAttributesWithoutForceShouldFail() throws AtlasBaseException { + createBusinessMetadataTypesWithMixedIndexability(businessMetadataName); + + AtlasBusinessMetadataDef businessMetadataDef = findBusinessMetadataDef(businessMetadataName); + assertNotNull(businessMetadataDef); + + typesDefs = new AtlasTypesDef(Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.singletonList(businessMetadataDef)); + + try { + typeDefStore.deleteTypesDef(typesDefs, false); + fail("Deletion without force should fail when BM has non-indexable attributes"); + } catch (AtlasBaseException e) { + assertEquals(e.getAtlasErrorCode(), AtlasErrorCode.NON_INDEXABLE_BM_DELETE_NOT_ALLOWED); + } + } + + @Test + public void deleteBusinessMetadataDefWithMixedIndexableAttributesWithForceShouldSucceed() throws AtlasBaseException { + createBusinessMetadataTypesWithMixedIndexability(businessMetadataName); + + AtlasBusinessMetadataDef businessMetadataDef = findBusinessMetadataDef(businessMetadataName); + assertNotNull(businessMetadataDef); + + typesDefs = new AtlasTypesDef(Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.singletonList(businessMetadataDef)); + + typeDefStore.deleteTypesDef(typesDefs, true); + + for (AtlasBusinessMetadataDef def : typeRegistry.getAllBusinessMetadataDefs()) { + assertNotEquals(def.getName(), businessMetadataName); + } + } + @Test public void updateBusinessMetadataDefs() throws AtlasBaseException { createBusinessMetadataTypes(businessMetadataName); @@ -494,6 +528,29 @@ private AtlasBusinessMetadataDef createBusinessMetadataDefWithoutAssignedTypes(S return businessMetadataDef1; } + private void createBusinessMetadataTypesWithMixedIndexability(String businessMetadataName) throws AtlasBaseException { + List businessMetadataDefs = new ArrayList<>(typesDefs.getBusinessMetadataDefs()); + + businessMetadataDefs.add(createBusinessMetadataDefWithMixedIndexability(businessMetadataName)); + + typesDefs.setBusinessMetadataDefs(businessMetadataDefs); + + AtlasTypesDef createdTypesDef = typeDefStore.createTypesDef(typesDefs); + + assertEquals(createdTypesDef.getBusinessMetadataDefs(), businessMetadataDefs, "Data integrity issue while persisting"); + } + + private AtlasBusinessMetadataDef createBusinessMetadataDefWithMixedIndexability(String businessMetadataName) { + AtlasBusinessMetadataDef businessMetadataDef = new AtlasBusinessMetadataDef(businessMetadataName, "test_mixed_indexability", null); + + addBusinessAttribute(businessMetadataDef, "mixed_indexable_attr", Collections.singleton("hive_table"), "string", AtlasStructDef.AtlasAttributeDef.Cardinality.SINGLE, true); + addBusinessAttribute(businessMetadataDef, "mixed_non_indexable_attr", Collections.singleton("hive_table"), "string", AtlasStructDef.AtlasAttributeDef.Cardinality.SINGLE, false); + + TestUtilsV2.populateSystemAttributes(businessMetadataDef); + + return businessMetadataDef; + } + private AtlasBusinessMetadataDef createBusinessMetadataDef2(String businessMetadataName) { AtlasBusinessMetadataDef businessMetadataDef1 = new AtlasBusinessMetadataDef(businessMetadataName, "test_description", null); @@ -534,6 +591,27 @@ private void addBusinessAttribute(AtlasBusinessMetadataDef businessMetadataDef, businessMetadataDef.addAttribute(attributeDef); } + private void addBusinessAttribute(AtlasBusinessMetadataDef businessMetadataDef, String name, Set applicableEntityTypes, String typeName, + AtlasStructDef.AtlasAttributeDef.Cardinality cardinality, boolean isIndexable) { + AtlasStructDef.AtlasAttributeDef attributeDef = new AtlasStructDef.AtlasAttributeDef(name, typeName); + + attributeDef.setCardinality(cardinality); + attributeDef.setOption(ATTR_OPTION_APPLICABLE_ENTITY_TYPES, AtlasType.toJson(applicableEntityTypes)); + + if (typeName.contains(AtlasBaseTypeDef.ATLAS_TYPE_STRING)) { + attributeDef.setOption(ATTR_MAX_STRING_LENGTH, "20"); + } + + attributeDef.setIsIndexable(isIndexable); + attributeDef.setIsOptional(true); + attributeDef.setValuesMinCount(0); + attributeDef.setValuesMaxCount(1); + attributeDef.setIsUnique(false); + attributeDef.setDisplayName(name); + + businessMetadataDef.addAttribute(attributeDef); + } + private AtlasBusinessMetadataDef findBusinessMetadataDef(String businessMetadataName) { for (AtlasBusinessMetadataDef atlasBusinessMetaDataDef : typesDefs.getBusinessMetadataDefs()) { if (atlasBusinessMetaDataDef.getName().equals(businessMetadataName)) { diff --git a/webapp/src/main/java/org/apache/atlas/web/rest/TypesREST.java b/webapp/src/main/java/org/apache/atlas/web/rest/TypesREST.java index e42b7627b1..dfecf1f6fc 100644 --- a/webapp/src/main/java/org/apache/atlas/web/rest/TypesREST.java +++ b/webapp/src/main/java/org/apache/atlas/web/rest/TypesREST.java @@ -43,12 +43,14 @@ import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; +import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; @@ -399,25 +401,34 @@ public AtlasTypesDef updateAtlasTypeDefs(final AtlasTypesDef typesDef) throws At } /** - * Bulk delete API for all types - * @param typesDef A composite object that captures all types to be deleted + * Bulk delete API for all types. + * + * @param typesDef A composite object that captures all types to be deleted. + * @param forceDelete If true, bypasses pre-delete validation checks and forcefully removes type definitions. + * For BusinessMetadata types: + * - If isIndexable=true and force=false: performs normal graph scan validation + * - If isIndexable=false and force=false: blocks deletion (requires force=true) + * - If force=true: skips all validation and deletes the type + * Defaults to false for backward compatibility. * @throws AtlasBaseException * @HTTP 204 On successful deletion of the requested type definitions - * @HTTP 400 On validation failure for any type definitions + * @HTTP 400 On validation failure for any type definitions or when attempting to delete + * non-indexable BusinessMetadata without force-delete parameter */ @DELETE @Path("/typedefs") @Experimental @Timed - public void deleteAtlasTypeDefs(final AtlasTypesDef typesDef) throws AtlasBaseException { + public void deleteAtlasTypeDefs(final AtlasTypesDef typesDef, + @QueryParam("force") @DefaultValue("false") final boolean forceDelete) throws AtlasBaseException { AtlasPerfTracer perf = null; try { if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) { - perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "TypesREST.deleteAtlasTypeDefs(" + AtlasTypeUtil.toDebugString(typesDef) + ")"); + perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "TypesREST.deleteAtlasTypeDefs(" + AtlasTypeUtil.toDebugString(typesDef) + ", force=" + forceDelete + ")"); } - typeDefStore.deleteTypesDef(typesDef); + typeDefStore.deleteTypesDef(typesDef, forceDelete); } finally { AtlasPerfTracer.log(perf); } @@ -426,22 +437,30 @@ public void deleteAtlasTypeDefs(final AtlasTypesDef typesDef) throws AtlasBaseEx /** * Delete API for type identified by its name. * @param typeName Name of the type to be deleted. + * @param forceDelete If true, bypasses pre-delete validation checks and forcefully removes the type definition. + * For BusinessMetadata types: + * - If isIndexable=true and force=false: performs normal graph scan validation + * - If isIndexable=false and force=false: blocks deletion (requires force=true) + * - If force=true: skips all validation and deletes the type + * Defaults to false for backward compatibility. * @throws AtlasBaseException * @HTTP 204 On successful deletion of the requested type definitions - * @HTTP 400 On validation failure for any type definitions + * @HTTP 400 On validation failure for any type definitions or when attempting to delete + * non-indexable BusinessMetadata without force-delete parameter */ @DELETE @Path("/typedef/name/{typeName}") @Timed - public void deleteAtlasTypeByName(@PathParam("typeName") final String typeName) throws AtlasBaseException { + public void deleteAtlasTypeByName(@PathParam("typeName") final String typeName, + @QueryParam("force") @DefaultValue("false") final boolean forceDelete) throws AtlasBaseException { AtlasPerfTracer perf = null; try { if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) { - perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "TypesREST.deleteAtlasTypeByName(" + typeName + ")"); + perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "TypesREST.deleteAtlasTypeByName(" + typeName + ", force=" + forceDelete + ")"); } - typeDefStore.deleteTypeByName(typeName); + typeDefStore.deleteTypeByName(typeName, forceDelete); } finally { AtlasPerfTracer.log(perf); } diff --git a/webapp/src/test/java/org/apache/atlas/web/adapters/TypeDefsRESTTest.java b/webapp/src/test/java/org/apache/atlas/web/adapters/TypeDefsRESTTest.java index 0c74dc1d38..3ef8ed65af 100644 --- a/webapp/src/test/java/org/apache/atlas/web/adapters/TypeDefsRESTTest.java +++ b/webapp/src/test/java/org/apache/atlas/web/adapters/TypeDefsRESTTest.java @@ -108,7 +108,7 @@ public void testDeleteAtlasBusinessTypeDefs() throws AtlasBaseException { AtlasErrorCode errorCode = null; try { - typesREST.deleteAtlasTypeByName(bmWithAllTypes); + typesREST.deleteAtlasTypeByName(bmWithAllTypes, false); } catch (AtlasBaseException e) { errorCode = e.getAtlasErrorCode(); } @@ -124,7 +124,7 @@ public void testDeleteAtlasBusinessTypeDefs() throws AtlasBaseException { errorCode = null; try { - typesREST.deleteAtlasTypeByName(bmWithAllTypes); + typesREST.deleteAtlasTypeByName(bmWithAllTypes, false); } catch (AtlasBaseException e) { errorCode = e.getAtlasErrorCode(); } @@ -135,7 +135,7 @@ public void testDeleteAtlasBusinessTypeDefs() throws AtlasBaseException { entityREST.removeBusinessAttributes(dbEntity.getGuid(), bmWithAllTypes, objectMap); - typesREST.deleteAtlasTypeByName(bmWithAllTypes); + typesREST.deleteAtlasTypeByName(bmWithAllTypes, false); } @Test @@ -155,7 +155,7 @@ public void testDeleteAtlasBusinessTypeDefs_2() throws AtlasBaseException { AtlasErrorCode errorCode = null; try { - typesREST.deleteAtlasTypeByName(bmWithSuperType); + typesREST.deleteAtlasTypeByName(bmWithSuperType, false); } catch (AtlasBaseException e) { errorCode = e.getAtlasErrorCode(); } @@ -171,7 +171,7 @@ public void testDeleteAtlasBusinessTypeDefs_2() throws AtlasBaseException { errorCode = null; try { - typesREST.deleteAtlasTypeByName(bmWithSuperType); + typesREST.deleteAtlasTypeByName(bmWithSuperType, false); } catch (AtlasBaseException e) { errorCode = e.getAtlasErrorCode(); } @@ -182,7 +182,7 @@ public void testDeleteAtlasBusinessTypeDefs_2() throws AtlasBaseException { entityREST.removeBusinessAttributes(dbEntity.getGuid(), bmWithSuperType, objectMap); - typesREST.deleteAtlasTypeByName(bmWithSuperType); + typesREST.deleteAtlasTypeByName(bmWithSuperType, false); } @Test @@ -209,7 +209,7 @@ public void testDeleteAtlasBusinessTypeDefs_3() throws AtlasBaseException { AtlasErrorCode errorCode = null; try { - typesREST.deleteAtlasTypeDefs(atlasTypesDef); + typesREST.deleteAtlasTypeDefs(atlasTypesDef, false); } catch (AtlasBaseException e) { errorCode = e.getAtlasErrorCode(); } @@ -227,7 +227,7 @@ public void testDeleteAtlasBusinessTypeDefs_3() throws AtlasBaseException { errorCode = null; try { - typesREST.deleteAtlasTypeDefs(atlasTypesDef); + typesREST.deleteAtlasTypeDefs(atlasTypesDef, false); } catch (AtlasBaseException e) { errorCode = e.getAtlasErrorCode(); } @@ -243,7 +243,7 @@ public void testDeleteAtlasBusinessTypeDefs_3() throws AtlasBaseException { errorCode = null; try { - typesREST.deleteAtlasTypeDefs(atlasTypesDef); + typesREST.deleteAtlasTypeDefs(atlasTypesDef, false); } catch (AtlasBaseException e) { errorCode = e.getAtlasErrorCode(); } @@ -254,7 +254,7 @@ public void testDeleteAtlasBusinessTypeDefs_3() throws AtlasBaseException { entityREST.removeBusinessAttributes(dbEntity.getGuid(), bmWithAllTypesMV, objectMap); - typesREST.deleteAtlasTypeDefs(atlasTypesDef); + typesREST.deleteAtlasTypeDefs(atlasTypesDef, false); } private void createTestEntity() throws AtlasBaseException { diff --git a/webapp/src/test/java/org/apache/atlas/web/rest/TypesRESTTest.java b/webapp/src/test/java/org/apache/atlas/web/rest/TypesRESTTest.java index 257a4b5ec5..bd44ab077f 100644 --- a/webapp/src/test/java/org/apache/atlas/web/rest/TypesRESTTest.java +++ b/webapp/src/test/java/org/apache/atlas/web/rest/TypesRESTTest.java @@ -532,7 +532,7 @@ public void testUpdateAtlasTypeDefs_WithException() throws AtlasBaseException { @Test public void testDeleteAtlasTypeDefs_Success() throws AtlasBaseException { // Setup - doNothing().when(typeDefStore).deleteTypesDef(mockTypesDef); + doNothing().when(typeDefStore).deleteTypesDef(mockTypesDef, false); try (MockedStatic mockedPerfTracer = mockStatic(AtlasPerfTracer.class); MockedStatic mockedAtlasTypeUtil = mockStatic(AtlasTypeUtil.class)) { @@ -540,17 +540,17 @@ public void testDeleteAtlasTypeDefs_Success() throws AtlasBaseException { mockedAtlasTypeUtil.when(() -> AtlasTypeUtil.toDebugString(mockTypesDef)).thenReturn("debug_string"); // Execute - typesREST.deleteAtlasTypeDefs(mockTypesDef); + typesREST.deleteAtlasTypeDefs(mockTypesDef, false); // Verify - verify(typeDefStore).deleteTypesDef(mockTypesDef); + verify(typeDefStore).deleteTypesDef(mockTypesDef, false); } } @Test public void testDeleteAtlasTypeDefs_WithPerfTracerEnabled() throws AtlasBaseException { // Setup - doNothing().when(typeDefStore).deleteTypesDef(mockTypesDef); + doNothing().when(typeDefStore).deleteTypesDef(mockTypesDef, false); try (MockedStatic mockedPerfTracer = mockStatic(AtlasPerfTracer.class); MockedStatic mockedAtlasTypeUtil = mockStatic(AtlasTypeUtil.class)) { @@ -559,10 +559,10 @@ public void testDeleteAtlasTypeDefs_WithPerfTracerEnabled() throws AtlasBaseExce mockedAtlasTypeUtil.when(() -> AtlasTypeUtil.toDebugString(mockTypesDef)).thenReturn("debug_string"); // Execute - typesREST.deleteAtlasTypeDefs(mockTypesDef); + typesREST.deleteAtlasTypeDefs(mockTypesDef, false); // Verify - verify(typeDefStore).deleteTypesDef(mockTypesDef); + verify(typeDefStore).deleteTypesDef(mockTypesDef, false); mockedPerfTracer.verify(() -> AtlasPerfTracer.isPerfTraceEnabled(any())); mockedPerfTracer.verify(() -> AtlasPerfTracer.getPerfTracer(any(), anyString())); } @@ -572,7 +572,7 @@ public void testDeleteAtlasTypeDefs_WithPerfTracerEnabled() throws AtlasBaseExce public void testDeleteAtlasTypeDefs_WithException() throws AtlasBaseException { // Setup AtlasBaseException exception = new AtlasBaseException("Delete failed"); - doThrow(exception).when(typeDefStore).deleteTypesDef(mockTypesDef); + doThrow(exception).when(typeDefStore).deleteTypesDef(mockTypesDef, false); try (MockedStatic mockedPerfTracer = mockStatic(AtlasPerfTracer.class); MockedStatic mockedAtlasTypeUtil = mockStatic(AtlasTypeUtil.class)) { @@ -581,11 +581,29 @@ public void testDeleteAtlasTypeDefs_WithException() throws AtlasBaseException { // Execute & Verify AtlasBaseException thrownException = expectThrows(AtlasBaseException.class, () -> { - typesREST.deleteAtlasTypeDefs(mockTypesDef); + typesREST.deleteAtlasTypeDefs(mockTypesDef, false); }); assertEquals(thrownException.getMessage(), "Delete failed"); - verify(typeDefStore).deleteTypesDef(mockTypesDef); + verify(typeDefStore).deleteTypesDef(mockTypesDef, false); + } + } + + @Test + public void testDeleteAtlasTypeDefs_WithForceDelete() throws AtlasBaseException { + // Setup + doNothing().when(typeDefStore).deleteTypesDef(mockTypesDef, true); + + try (MockedStatic mockedPerfTracer = mockStatic(AtlasPerfTracer.class); + MockedStatic mockedAtlasTypeUtil = mockStatic(AtlasTypeUtil.class)) { + mockedPerfTracer.when(() -> AtlasPerfTracer.isPerfTraceEnabled(any())).thenReturn(false); + mockedAtlasTypeUtil.when(() -> AtlasTypeUtil.toDebugString(mockTypesDef)).thenReturn("debug_string"); + + // Execute + typesREST.deleteAtlasTypeDefs(mockTypesDef, true); + + // Verify + verify(typeDefStore).deleteTypesDef(mockTypesDef, true); } } @@ -593,16 +611,16 @@ public void testDeleteAtlasTypeDefs_WithException() throws AtlasBaseException { public void testDeleteAtlasTypeByName_Success() throws AtlasBaseException { // Setup String typeName = "test_type"; - doNothing().when(typeDefStore).deleteTypeByName(typeName); + doNothing().when(typeDefStore).deleteTypeByName(typeName, false); try (MockedStatic mockedPerfTracer = mockStatic(AtlasPerfTracer.class)) { mockedPerfTracer.when(() -> AtlasPerfTracer.isPerfTraceEnabled(any())).thenReturn(false); // Execute - typesREST.deleteAtlasTypeByName(typeName); + typesREST.deleteAtlasTypeByName(typeName, false); // Verify - verify(typeDefStore).deleteTypeByName(typeName); + verify(typeDefStore).deleteTypeByName(typeName, false); } } @@ -610,17 +628,17 @@ public void testDeleteAtlasTypeByName_Success() throws AtlasBaseException { public void testDeleteAtlasTypeByName_WithPerfTracerEnabled() throws AtlasBaseException { // Setup String typeName = "test_type_perf"; - doNothing().when(typeDefStore).deleteTypeByName(typeName); + doNothing().when(typeDefStore).deleteTypeByName(typeName, false); try (MockedStatic mockedPerfTracer = mockStatic(AtlasPerfTracer.class)) { mockedPerfTracer.when(() -> AtlasPerfTracer.isPerfTraceEnabled(any())).thenReturn(true); mockedPerfTracer.when(() -> AtlasPerfTracer.getPerfTracer(any(), anyString())).thenReturn(null); // Execute - typesREST.deleteAtlasTypeByName(typeName); + typesREST.deleteAtlasTypeByName(typeName, false); // Verify - verify(typeDefStore).deleteTypeByName(typeName); + verify(typeDefStore).deleteTypeByName(typeName, false); mockedPerfTracer.verify(() -> AtlasPerfTracer.isPerfTraceEnabled(any())); mockedPerfTracer.verify(() -> AtlasPerfTracer.getPerfTracer(any(), anyString())); } @@ -631,18 +649,35 @@ public void testDeleteAtlasTypeByName_WithException() throws AtlasBaseException // Setup String typeName = "test_type_error"; AtlasBaseException exception = new AtlasBaseException("Delete by name failed"); - doThrow(exception).when(typeDefStore).deleteTypeByName(typeName); + doThrow(exception).when(typeDefStore).deleteTypeByName(typeName, false); try (MockedStatic mockedPerfTracer = mockStatic(AtlasPerfTracer.class)) { mockedPerfTracer.when(() -> AtlasPerfTracer.isPerfTraceEnabled(any())).thenReturn(false); // Execute & Verify AtlasBaseException thrownException = expectThrows(AtlasBaseException.class, () -> { - typesREST.deleteAtlasTypeByName(typeName); + typesREST.deleteAtlasTypeByName(typeName, false); }); assertEquals(thrownException.getMessage(), "Delete by name failed"); - verify(typeDefStore).deleteTypeByName(typeName); + verify(typeDefStore).deleteTypeByName(typeName, false); + } + } + + @Test + public void testDeleteAtlasTypeByName_WithForceDelete() throws AtlasBaseException { + // Setup + String typeName = "test_type_force"; + doNothing().when(typeDefStore).deleteTypeByName(typeName, true); + + try (MockedStatic mockedPerfTracer = mockStatic(AtlasPerfTracer.class)) { + mockedPerfTracer.when(() -> AtlasPerfTracer.isPerfTraceEnabled(any())).thenReturn(false); + + // Execute with force=true + typesREST.deleteAtlasTypeByName(typeName, true); + + // Verify + verify(typeDefStore).deleteTypeByName(typeName, true); } }