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 a485ea329f4..6d1d2360998 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 @@ -353,7 +353,19 @@ public void deleteAtlasTypeDefs(AtlasTypesDef typesDef) throws AtlasServiceExcep } 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 { + if (forceDelete) { + + MultivaluedMap queryParams = new MultivaluedMapImpl(); + queryParams.add("force", "true"); + callAPI(API_V2.DELETE_TYPE_DEF_BY_NAME, (Class) null, queryParams, typeName); + } else { + + callAPI(API_V2.DELETE_TYPE_DEF_BY_NAME, (Class) null, null, typeName); + } } // Entity APIs diff --git a/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java b/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java index 9256d0f8865..b6997a09f65 100644 --- a/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java +++ b/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java @@ -257,7 +257,9 @@ public enum AtlasErrorCode { IMPORT_REGISTRATION_FAILED(500, "ATLAS-500-00-020", "Failed to register import request"), IMPORT_FAILED(500, "ATLAS-500-00-021", "Import with id {0} failed"), ABORT_IMPORT_FAILED(500, "ATLAS-500-00-022", "Failed to abort import with id {0}"), - IMPORT_QUEUEING_FAILED(500, "ATLAS-500-00-023", "Failed to add import with id {0} to request queue, please try again later"); + IMPORT_QUEUEING_FAILED(500, "ATLAS-500-00-023", "Failed to add import with id {0} to request queue, please try again later"), + + NON_INDEXABLE_BM_DELETE_NOT_ALLOWED(400, "ATLAS-400-00-106", "Deletion not allowed for non-indexable Business Metadata ''{0}'' without force-delete. Please use the force-delete parameter to remove."); private static final Logger LOG = LoggerFactory.getLogger(AtlasErrorCode.class); private final String errorCode; 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 a956eecb41f..59828fd09e3 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 @@ -45,9 +45,25 @@ 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 { + 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 { + 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 4776912cd13..7213a3fec8d 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,14 +539,20 @@ public AtlasTypesDef createUpdateTypesDef(AtlasTypesDef typesToCreate, AtlasType @Override @GraphTransaction public void deleteTypesDef(AtlasTypesDef typesDef) throws AtlasBaseException { + deleteTypesDef(typesDef, false); + } + + @GraphTransaction + public void deleteTypesDef(AtlasTypesDef typesDef, boolean forceDelete) throws AtlasBaseException { if (LOG.isDebugEnabled()) { - LOG.debug("==> AtlasTypeDefGraphStore.deleteTypesDef(enums={}, structs={}, classfications={}, entities={}, relationships={}, businessMetadataDefs={})", + 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())); + CollectionUtils.size(typesDef.getBusinessMetadataDefs()), + forceDelete); } AtlasTransientTypeRegistry ttr = lockTypeRegistryAndReleasePostCommit(); @@ -678,9 +684,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 +783,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 +807,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 1d61aa9785a..644676884a5 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 5511935f918..d58fcfa62c4 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 @@ -31,10 +31,14 @@ 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.AtlasStructType; import org.apache.atlas.type.AtlasType; +import org.apache.atlas.type.AtlasEntityType; +import org.apache.atlas.type.AtlasArrayType; import org.apache.atlas.type.AtlasTypeRegistry; import org.apache.atlas.typesystem.types.DataTypes; import org.apache.atlas.utils.AtlasJson; @@ -50,6 +54,7 @@ import java.util.Iterator; import java.util.List; import java.util.Set; +import java.util.HashSet; import static org.apache.atlas.model.typedef.AtlasBusinessMetadataDef.ATTR_OPTION_APPLICABLE_ENTITY_TYPES; @@ -58,11 +63,14 @@ public class AtlasBusinessMetadataDefStoreV2 extends AtlasAbstractDefStoreV2 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 +285,29 @@ public AtlasVertex preDeleteByName(String name) throws AtlasBaseException { throw new AtlasBaseException(AtlasErrorCode.TYPE_NAME_NOT_FOUND, name); } - checkBusinessMetadataRef(existingDef.getName()); + if (!forceDelete) { + boolean hasIndexableAttribute = hasIndexableAttribute(existingDef); - LOG.debug("<== AtlasBusinessMetadataDefStoreV2.preDeleteByName({}): {}", name, ret); + if (!hasIndexableAttribute) { + LOG.warn("Deletion blocked for non-indexable Business Metadata '{}' without force-delete flag", name); + throw new AtlasBaseException(AtlasErrorCode.NON_INDEXABLE_BM_DELETE_NOT_ALLOWED, name); + } + checkBusinessMetadataRef(existingDef.getName()); + } + + 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 +319,35 @@ public AtlasVertex preDeleteByGuid(String guid) throws AtlasBaseException { throw new AtlasBaseException(AtlasErrorCode.TYPE_GUID_NOT_FOUND, guid); } - if (existingDef != null) { + if (existingDef != null && !forceDelete) { + boolean hasIndexableAttribute = hasIndexableAttribute(existingDef); + + if (!hasIndexableAttribute) { + LOG.warn("Deletion blocked for non-indexable Business Metadata '{}' without force-delete flag", existingDef.getName()); + throw new AtlasBaseException(AtlasErrorCode.NON_INDEXABLE_BM_DELETE_NOT_ALLOWED, existingDef.getName()); + } checkBusinessMetadataRef(existingDef.getName()); } - LOG.debug("<== AtlasBusinessMetadataDefStoreV2.preDeleteByGuid({}): ret={}", guid, ret); + LOG.debug("<== AtlasBusinessMetadataDefStoreV2.preDeleteByGuid({}, {}): ret={}", guid, forceDelete, ret); return ret; } + private boolean hasIndexableAttribute(AtlasBusinessMetadataDef bmDef) { + if (bmDef == null || CollectionUtils.isEmpty(bmDef.getAttributeDefs())) { + return false; + } + + for (AtlasStructDef.AtlasAttributeDef attributeDef : bmDef.getAttributeDefs()) { + if (attributeDef.getIsIndexable()) { + return true; + } + } + + return false; + } + @Override public void validateType(AtlasBaseTypeDef typeDef) throws AtlasBaseException { super.validateType(typeDef); @@ -363,44 +411,91 @@ private AtlasBusinessMetadataDef toBusinessMetadataDef(AtlasVertex vertex) throw private void checkBusinessMetadataRef(String typeName) throws AtlasBaseException { AtlasBusinessMetadataDef businessMetadataDef = typeRegistry.getBusinessMetadataDefByName(typeName); - if (businessMetadataDef != null) { - List attributeDefs = businessMetadataDef.getAttributeDefs(); + if (businessMetadataDef == null || CollectionUtils.isEmpty(businessMetadataDef.getAttributeDefs())) { + return; + } + + for (AtlasStructDef.AtlasAttributeDef attributeDef : businessMetadataDef.getAttributeDefs()) { + validateAttributeReferences(businessMetadataDef, attributeDef); + } + } - 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); + private void validateAttributeReferences(AtlasBusinessMetadataDef bmDef, AtlasStructDef.AtlasAttributeDef attributeDef) throws AtlasBaseException { + String applicableTypesStr = attributeDef.getOption(ATTR_OPTION_APPLICABLE_ENTITY_TYPES); + Set applicableTypes = StringUtils.isBlank(applicableTypesStr) ? null : AtlasJson.fromJson(applicableTypesStr, Set.class); - if (CollectionUtils.isNotEmpty(applicableTypes) && isBusinessAttributePresent(vertexPropertyName, applicableTypes)) { - throw new AtlasBaseException(AtlasErrorCode.TYPE_HAS_REFERENCES, typeName); - } - } + if (CollectionUtils.isEmpty(applicableTypes)) { + return; + } + + Set allApplicableTypes = getApplicableTypesWithSubTypes(applicableTypes); + String qualifiedName = AtlasStructType.AtlasAttribute.getQualifiedAttributeName(bmDef, attributeDef.getName()); + String vertexPropertyName = AtlasStructType.AtlasAttribute.generateVertexPropertyName(bmDef, attributeDef, qualifiedName); + + long startTime = System.currentTimeMillis(); + + boolean isPresent = isBusinessAttributePresentInGraph(vertexPropertyName, allApplicableTypes); + + if (LOG.isDebugEnabled()) { + LOG.info("Reference check for attribute {} took {} ms. Found: {}", + attributeDef.getName(), (System.currentTimeMillis() - startTime), isPresent); + } + + if (isPresent) { + throw new AtlasBaseException(AtlasErrorCode.TYPE_HAS_REFERENCES, bmDef.getName()); } } - private boolean isBusinessAttributePresent(String attrName, Set applicableTypes) throws AtlasBaseException { - SearchParameters.FilterCriteria criteria = new SearchParameters.FilterCriteria(); - criteria.setAttributeName(attrName); - criteria.setOperator(SearchParameters.Operator.NOT_EMPTY); + private boolean isBusinessAttributePresentInGraph(String vertexPropertyName, Set allApplicableTypes) { + if (graph == null || CollectionUtils.isEmpty(allApplicableTypes)) { + return false; + } - SearchParameters.FilterCriteria entityFilters = new SearchParameters.FilterCriteria(); + try { + List typesList = new ArrayList<>(allApplicableTypes); - entityFilters.setCondition(SearchParameters.FilterCriteria.Condition.OR); - entityFilters.setCriterion(Collections.singletonList(criteria)); + // 1. To Check if the BM property exists on a vertex where the direct type matches + Iterable verticesDirect = graph.query() + .has(vertexPropertyName, AtlasGraphQuery.ComparisionOperator.NOT_EQUAL, (Object) null) + .in(Constants.ENTITY_TYPE_PROPERTY_KEY, typesList) + .vertices(); - SearchParameters searchParameters = new SearchParameters(); + if (verticesDirect != null && verticesDirect.iterator().hasNext()) { + return true; + } - 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); + // 2. To Check if the BM property exists on a vertex where it inherits from one of parent Types + // This is crucial for Case 6 (Parent -> Child) + Iterable verticesInherited = graph.query() + .has(vertexPropertyName, AtlasGraphQuery.ComparisionOperator.NOT_EQUAL, (Object) null) + .in(Constants.SUPER_TYPES_PROPERTY_KEY, typesList) + .vertices(); - AtlasSearchResult atlasSearchResult = entityDiscoveryService.searchWithParameters(searchParameters); + if (verticesInherited != null && verticesInherited.iterator().hasNext()) { + return true; + } + } catch (Exception e) { + LOG.error("Error occurred while querying graph for references of property: {}", vertexPropertyName, e); + return true; + } + return false; + } + + private Set getApplicableTypesWithSubTypes(Set applicableTypes) throws AtlasBaseException { + Set allTypes = new HashSet<>(); + + for (String typeName : applicableTypes) { + AtlasType type = typeRegistry.getType(typeName); - return CollectionUtils.isNotEmpty(atlasSearchResult.getEntities()); + if (type instanceof AtlasEntityType) { + AtlasEntityType entityType = (AtlasEntityType) type; + allTypes.add(entityType.getTypeName()); + allTypes.addAll(entityType.getAllSubTypes()); + } else { + allTypes.add(typeName); + } + } + return allTypes; } -} +} \ No newline at end of file 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 57146521ab8..2ae2ec10164 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 @@ -177,7 +177,7 @@ protected AtlasDefStore getRelationshipDefStore(AtlasTypeR @Override protected AtlasDefStore getBusinessMetadataDefStore(AtlasTypeRegistry typeRegistry) { - return new AtlasBusinessMetadataDefStoreV2(this, typeRegistry, this.entityDiscoveryService); + return new AtlasBusinessMetadataDefStoreV2(this, typeRegistry, this.entityDiscoveryService, this.atlasGraph); } @Override 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 792f714ad3f..6c38f27fde3 100644 --- a/repository/src/main/java/org/apache/atlas/store/AtlasTypeDefStore.java +++ b/repository/src/main/java/org/apache/atlas/store/AtlasTypeDefStore.java @@ -109,7 +109,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 f84d1f4610e..90603af0341 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/webapp/src/main/java/org/apache/atlas/web/rest/TypesREST.java b/webapp/src/main/java/org/apache/atlas/web/rest/TypesREST.java index e42b7627b11..9c7b254c6b3 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 @@ -49,6 +49,8 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.DefaultValue; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; @@ -426,22 +428,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 7859f88181f..5078047e520 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 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 257a4b5ec58..2228c3586a2 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 @@ -593,16 +593,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 +610,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 +631,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); } }