This is an automated email from the ASF dual-hosted git repository.
pinal-shah pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/atlas.git
The following commit(s) were added to refs/heads/master by this push:
new ae973e76d ATLAS-4988: BusinessMetadata with attribute of data type
Array takes time to Delete. (#490)
ae973e76d is described below
commit ae973e76db131c86417e91c9b43102c0677fe436
Author: UmeshPatil-1 <[email protected]>
AuthorDate: Fri May 8 16:39:41 2026 +0530
ATLAS-4988: BusinessMetadata with attribute of data type Array takes time
to Delete. (#490)
---
.../main/java/org/apache/atlas/AtlasClientV2.java | 26 ++-
.../java/org/apache/atlas/AtlasClientV2Test.java | 22 +++
.../main/java/org/apache/atlas/AtlasErrorCode.java | 1 +
.../atlas/type/AtlasBusinessMetadataType.java | 43 +++++
.../test/java/org/apache/atlas/TestUtilsV2.java | 15 ++
.../repository/store/graph/AtlasDefStore.java | 20 ++-
.../store/graph/AtlasTypeDefGraphStore.java | 30 ++--
.../store/graph/v2/AtlasAbstractDefStoreV2.java | 24 ++-
.../graph/v2/AtlasBusinessMetadataDefStoreV2.java | 186 ++++++++++++++++-----
.../store/graph/v2/AtlasTypeDefGraphStoreV2.java | 18 +-
.../org/apache/atlas/store/AtlasTypeDefStore.java | 4 +-
.../store/graph/AtlasTypeDefGraphStoreTest.java | 8 +-
.../v2/AtlasBusinessMetadataDefStoreV2Test.java | 79 +++++++++
.../java/org/apache/atlas/web/rest/TypesREST.java | 41 +++--
.../atlas/web/adapters/TypeDefsRESTTest.java | 20 +--
.../org/apache/atlas/web/rest/TypesRESTTest.java | 71 ++++++--
16 files changed, 486 insertions(+), 122 deletions(-)
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 31687acda..04e4376d6 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 class AtlasClientV2 extends AtlasBaseClient {
* @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<String, String> 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<String, String> 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 be180b56a..f4942d7f5 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.mockito.Mockito.when;
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 @@ public class AtlasClientV2Test {
private Object mockResponse;
private Class<?> expectedReturnType;
private boolean shouldThrowException;
+ private javax.ws.rs.core.MultivaluedMap<String, String>
lastQueryParams;
public TestableAtlasClientV2() {
super(mock(WebResource.class), mock(Configuration.class));
@@ -823,8 +825,13 @@ public class AtlasClientV2Test {
this.shouldThrowException = shouldThrow;
}
+ public javax.ws.rs.core.MultivaluedMap<String, String>
getLastQueryParams() {
+ return lastQueryParams;
+ }
+
@Override
public <T> T callAPI(API api, Class<T> responseType, Object
requestObject, String... params) throws AtlasServiceException {
+ lastQueryParams = null;
return handleCallAPI(responseType);
}
@@ -838,11 +845,13 @@ public class AtlasClientV2Test {
@Override
public <T> T callAPI(API api, Class<T> responseType,
javax.ws.rs.core.MultivaluedMap<String, String> queryParams, String... params)
throws AtlasServiceException {
+ lastQueryParams = queryParams;
return handleCallAPI(responseType);
}
@Override
public <T> T callAPI(API api, Class<T> responseType, Object
requestObject, javax.ws.rs.core.MultivaluedMap<String, String> queryParams,
String... params) throws AtlasServiceException {
+ lastQueryParams = queryParams;
return handleCallAPI(responseType);
}
@@ -1053,6 +1062,19 @@ public class AtlasClientV2Test {
// 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 ca7220243..a5aa58a1b 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 ccea47c72..ed29bdd7a 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 @@ public class AtlasBusinessMetadataType extends
AtlasStructType {
Map<String, AtlasBusinessAttribute> a = new HashMap<>();
+ hasNonIndexableAttributes = false;
+
for (AtlasAttribute attribute : super.allAttributes.values()) {
AtlasAttributeDef attributeDef = attribute.getAttributeDef();
String attrName = attribute.getName();
@@ -115,10 +123,16 @@ public class AtlasBusinessMetadataType extends
AtlasStructType {
bmAttribute = new AtlasBusinessAttribute(attribute,
entityTypes);
}
+ if (!hasNonIndexableAttributes && !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 @@ public class AtlasBusinessMetadataType extends
AtlasStructType {
}
}
+ @Override
+ void resolveReferencesPhase3(AtlasTypeRegistry typeRegistry) throws
AtlasBaseException {
+ super.resolveReferencesPhase3(typeRegistry);
+
+ for (AtlasAttribute attribute : super.allAttributes.values()) {
+ AtlasBusinessAttribute bmAttribute = (AtlasBusinessAttribute)
attribute;
+ Set<AtlasEntityType> entityTypes =
bmAttribute.getApplicableEntityTypes();
+
+ if (CollectionUtils.isNotEmpty(entityTypes)) {
+ Set<String> 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 class AtlasBusinessMetadataType extends
AtlasStructType {
private final int maxStringLength;
private final String validPattern;
+ private Set<String> applicableEntityTypeNamesWithSubTypes =
Collections.emptySet();
+
public AtlasBusinessAttribute(AtlasAttribute attribute,
Set<AtlasEntityType> applicableEntityTypes) {
super(attribute);
@@ -171,6 +210,10 @@ public class AtlasBusinessMetadataType extends
AtlasStructType {
return applicableEntityTypes;
}
+ public Set<String> getApplicableEntityTypeNamesWithSubTypes() {
+ return applicableEntityTypeNamesWithSubTypes;
+ }
+
public String getValidPattern() {
return validPattern;
}
diff --git a/intg/src/test/java/org/apache/atlas/TestUtilsV2.java
b/intg/src/test/java/org/apache/atlas/TestUtilsV2.java
index 27ad72bf8..27fb27d1c 100755
--- a/intg/src/test/java/org/apache/atlas/TestUtilsV2.java
+++ b/intg/src/test/java/org/apache/atlas/TestUtilsV2.java
@@ -625,6 +625,11 @@ public final class TestUtilsV2 {
createOptionalAttrDef("attrSuperBoolean",
AtlasBusinessMetadataDef.ATLAS_TYPE_BOOLEAN, options, description),
createOptionalAttrDef("attrSuperString",
AtlasBusinessMetadataDef.ATLAS_TYPE_STRING, options, description));
+ setBusinessMetadataAttributesIndexable(bmNoApplicableTypes);
+ setBusinessMetadataAttributesIndexable(bmWithAllTypes);
+ setBusinessMetadataAttributesIndexable(bmWithAllTypesMV);
+ setBusinessMetadataAttributesIndexable(bmWithSuperType);
+
AtlasTypesDef ret = AtlasTypeUtil.getTypesDef(new ArrayList<>(),
new ArrayList<>(), new ArrayList<>(),
new ArrayList<>(), new ArrayList<>(),
@@ -635,6 +640,16 @@ public final class TestUtilsV2 {
return ret;
}
+ private static void
setBusinessMetadataAttributesIndexable(AtlasBusinessMetadataDef def) {
+ if (def == null || CollectionUtils.isEmpty(def.getAttributeDefs())) {
+ return;
+ }
+
+ for (AtlasAttributeDef ad : def.getAttributeDefs()) {
+ ad.setIsIndexable(true);
+ }
+ }
+
public static AtlasTypesDef defineHiveTypes() {
String description = "_description";
AtlasEntityDef superTypeDefinition =
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 a956eecb4..59828fd09 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<T extends AtlasBaseTypeDef> {
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 4776912cd..06a5bcee9 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 abstract class AtlasTypeDefGraphStore implements
AtlasTypeDefStore {
@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 abstract class AtlasTypeDefGraphStore implements
AtlasTypeDefStore {
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 abstract class AtlasTypeDefGraphStore implements
AtlasTypeDefStore {
@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 abstract class AtlasTypeDefGraphStore implements
AtlasTypeDefStore {
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 609275c4b..49bbb5096 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,34 @@ abstract class AtlasAbstractDefStoreV2<T extends
AtlasBaseTypeDef> implements At
@Override
public void deleteByName(String name, AtlasVertex preDeleteResult) throws
AtlasBaseException {
- LOG.debug("==> AtlasAbstractDefStoreV1.deleteByName({}, {})", name,
preDeleteResult);
+ deleteByName(name, preDeleteResult, false);
+ }
+
+ @Override
+ public void deleteByGuid(String guid, AtlasVertex preDeleteResult) throws
AtlasBaseException {
+ deleteByGuid(guid, preDeleteResult, false);
+ }
+
+ @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)
: preDeleteResult;
+ AtlasVertex vertex = (preDeleteResult == null) ? preDeleteByName(name,
forceDelete) : preDeleteResult;
typeDefStore.deleteTypeVertex(vertex);
- LOG.debug("<== AtlasAbstractDefStoreV1.deleteByName({}, {})", name,
preDeleteResult);
+ LOG.debug("<== AtlasAbstractDefStoreV2.deleteByName({}, {}, {})",
name, preDeleteResult, forceDelete);
}
@Override
- public void deleteByGuid(String guid, AtlasVertex preDeleteResult) throws
AtlasBaseException {
- LOG.debug("==> AtlasAbstractDefStoreV1.deleteByGuid({}, {})", guid,
preDeleteResult);
+ public void deleteByGuid(String guid, AtlasVertex preDeleteResult, boolean
forceDelete) throws AtlasBaseException {
+ LOG.debug("==> AtlasAbstractDefStoreV2.deleteByGuid({}, {}, {})",
guid, preDeleteResult, forceDelete);
- AtlasVertex vertex = (preDeleteResult == null) ? preDeleteByGuid(guid)
: preDeleteResult;
+ AtlasVertex vertex = (preDeleteResult == null) ? preDeleteByGuid(guid,
forceDelete) : preDeleteResult;
typeDefStore.deleteTypeVertex(vertex);
- LOG.debug("<== AtlasAbstractDefStoreV1.deleteByGuid({}, {})", guid,
preDeleteResult);
+ 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 3543c2322..c53c3a78e 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.AtlasErrorCode;
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 javax.inject.Inject;
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 @@ import static
org.apache.atlas.model.typedef.AtlasBusinessMetadataDef.ATTR_OPTIO
public class AtlasBusinessMetadataDefStoreV2 extends
AtlasAbstractDefStoreV2<AtlasBusinessMetadataDef> {
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 class AtlasBusinessMetadataDefStoreV2 extends
AtlasAbstractDefStoreV2<Atl
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 class AtlasBusinessMetadataDefStoreV2 extends
AtlasAbstractDefStoreV2<Atl
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 class AtlasBusinessMetadataDefStoreV2 extends
AtlasAbstractDefStoreV2<Atl
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 (!attributeDef.getIsIndexable()) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
@Override
public void validateType(AtlasBaseTypeDef typeDef) throws
AtlasBaseException {
super.validateType(typeDef);
@@ -360,47 +405,100 @@ public class AtlasBusinessMetadataDefStoreV2 extends
AtlasAbstractDefStoreV2<Atl
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<AtlasStructDef.AtlasAttributeDef> 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<String> applicableTypes =
AtlasJson.fromJson(attributeDef.getOption(AtlasBusinessMetadataDef.ATTR_OPTION_APPLICABLE_ENTITY_TYPES),
Set.class);
+ for (AtlasStructDef.AtlasAttributeDef attributeDef :
businessMetadataDef.getAttributeDefs()) {
+ Set<String> 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<String> 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<String>
applicableTypes) throws AtlasBaseException {
- SearchParameters.FilterCriteria criteria = new
SearchParameters.FilterCriteria();
+ private void validateAttributeReferences(AtlasBusinessMetadataDef bmDef,
AtlasStructDef.AtlasAttributeDef attributeDef,
+ Set<String> 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<String> allApplicableTypes) throws AtlasBaseException {
+ if (CollectionUtils.isEmpty(allApplicableTypes)) {
+ return false;
+ }
-
entityFilters.setCondition(SearchParameters.FilterCriteria.Condition.OR);
- entityFilters.setCriterion(Collections.singletonList(criteria));
+ try {
+ List<String> 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<AtlasGraphQuery> 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<AtlasVertex> 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<String>
getApplicableTypesWithSubTypes(AtlasStructDef.AtlasAttributeDef attributeDef) {
+ String applicableTypesJson =
attributeDef.getOption(ATTR_OPTION_APPLICABLE_ENTITY_TYPES);
+ Set<String> applicableTypeNames =
StringUtils.isBlank(applicableTypesJson) ? null :
AtlasType.fromJson(applicableTypesJson, Set.class);
+
+ if (CollectionUtils.isEmpty(applicableTypeNames)) {
+ return Collections.emptySet();
+ }
+
+ Set<String> 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 57146521a..09f4dcc9b 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 com.google.common.annotations.VisibleForTesting;
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.Constants.VERTEX_TYPE_PROPERTY_KEY;
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<TypeDefChangeListener> typeDefChangeListeners, AtlasGraph atlasGraph,
EntityDiscoveryService entityDiscoveryService) {
+ public AtlasTypeDefGraphStoreV2(AtlasTypeRegistry typeRegistry,
List<TypeDefChangeListener> 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 @@ public class AtlasTypeDefGraphStoreV2 extends
AtlasTypeDefGraphStore {
@Override
protected AtlasDefStore<AtlasBusinessMetadataDef>
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 792f714ad..64d8cf1c0 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 @@ public interface AtlasTypeDefStore {
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 @@ public interface AtlasTypeDefStore {
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 f84d1f461..90603af03 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 class AtlasTypeDefGraphStoreTest extends
AtlasTestBase {
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 a93ff8040..7f0fa866c 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.assertFalse;
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 class AtlasBusinessMetadataDefStoreV2Test {
}
}
+ @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 @@ public class AtlasBusinessMetadataDefStoreV2Test {
return businessMetadataDef1;
}
+ private void createBusinessMetadataTypesWithMixedIndexability(String
businessMetadataName) throws AtlasBaseException {
+ List<AtlasBusinessMetadataDef> 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);
@@ -525,6 +582,28 @@ public class AtlasBusinessMetadataDefStoreV2Test {
attributeDef.setOption(ATTR_MAX_STRING_LENGTH, "20");
}
+ attributeDef.setIsOptional(true);
+ attributeDef.setValuesMinCount(0);
+ attributeDef.setValuesMaxCount(1);
+ attributeDef.setIsUnique(false);
+ attributeDef.setDisplayName(name);
+ attributeDef.setIsIndexable(true);
+
+ businessMetadataDef.addAttribute(attributeDef);
+ }
+
+ private void addBusinessAttribute(AtlasBusinessMetadataDef
businessMetadataDef, String name, Set<String> 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);
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 e42b7627b..9a2b679a0 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.inject.Singleton;
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,35 @@ public class TypesREST {
}
/**
- * 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 Supported only for BusinessMetadata typedefs:
+ * If true, bypasses pre-delete validation checks and
forcefully removes the type definition.
+ * For BusinessMetadata only:
+ * - 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 +438,31 @@ public class TypesREST {
/**
* Delete API for type identified by its name.
* @param typeName Name of the type to be deleted.
+ * @param forceDelete Supported only for BusinessMetadata typedefs:
+ * If true, bypasses pre-delete validation checks and
forcefully removes the type definition.
+ * For BusinessMetadata only:
+ * - 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 0c74dc1d3..3ef8ed65a 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 class TypeDefsRESTTest {
AtlasErrorCode errorCode = null;
try {
- typesREST.deleteAtlasTypeByName(bmWithAllTypes);
+ typesREST.deleteAtlasTypeByName(bmWithAllTypes, false);
} catch (AtlasBaseException e) {
errorCode = e.getAtlasErrorCode();
}
@@ -124,7 +124,7 @@ public class TypeDefsRESTTest {
errorCode = null;
try {
- typesREST.deleteAtlasTypeByName(bmWithAllTypes);
+ typesREST.deleteAtlasTypeByName(bmWithAllTypes, false);
} catch (AtlasBaseException e) {
errorCode = e.getAtlasErrorCode();
}
@@ -135,7 +135,7 @@ public class TypeDefsRESTTest {
entityREST.removeBusinessAttributes(dbEntity.getGuid(),
bmWithAllTypes, objectMap);
- typesREST.deleteAtlasTypeByName(bmWithAllTypes);
+ typesREST.deleteAtlasTypeByName(bmWithAllTypes, false);
}
@Test
@@ -155,7 +155,7 @@ public class TypeDefsRESTTest {
AtlasErrorCode errorCode = null;
try {
- typesREST.deleteAtlasTypeByName(bmWithSuperType);
+ typesREST.deleteAtlasTypeByName(bmWithSuperType, false);
} catch (AtlasBaseException e) {
errorCode = e.getAtlasErrorCode();
}
@@ -171,7 +171,7 @@ public class TypeDefsRESTTest {
errorCode = null;
try {
- typesREST.deleteAtlasTypeByName(bmWithSuperType);
+ typesREST.deleteAtlasTypeByName(bmWithSuperType, false);
} catch (AtlasBaseException e) {
errorCode = e.getAtlasErrorCode();
}
@@ -182,7 +182,7 @@ public class TypeDefsRESTTest {
entityREST.removeBusinessAttributes(dbEntity.getGuid(),
bmWithSuperType, objectMap);
- typesREST.deleteAtlasTypeByName(bmWithSuperType);
+ typesREST.deleteAtlasTypeByName(bmWithSuperType, false);
}
@Test
@@ -209,7 +209,7 @@ public class TypeDefsRESTTest {
AtlasErrorCode errorCode = null;
try {
- typesREST.deleteAtlasTypeDefs(atlasTypesDef);
+ typesREST.deleteAtlasTypeDefs(atlasTypesDef, false);
} catch (AtlasBaseException e) {
errorCode = e.getAtlasErrorCode();
}
@@ -227,7 +227,7 @@ public class TypeDefsRESTTest {
errorCode = null;
try {
- typesREST.deleteAtlasTypeDefs(atlasTypesDef);
+ typesREST.deleteAtlasTypeDefs(atlasTypesDef, false);
} catch (AtlasBaseException e) {
errorCode = e.getAtlasErrorCode();
}
@@ -243,7 +243,7 @@ public class TypeDefsRESTTest {
errorCode = null;
try {
- typesREST.deleteAtlasTypeDefs(atlasTypesDef);
+ typesREST.deleteAtlasTypeDefs(atlasTypesDef, false);
} catch (AtlasBaseException e) {
errorCode = e.getAtlasErrorCode();
}
@@ -254,7 +254,7 @@ public class TypeDefsRESTTest {
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 257a4b5ec..bd44ab077 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 class TypesRESTTest {
@Test
public void testDeleteAtlasTypeDefs_Success() throws AtlasBaseException {
// Setup
- doNothing().when(typeDefStore).deleteTypesDef(mockTypesDef);
+ doNothing().when(typeDefStore).deleteTypesDef(mockTypesDef, false);
try (MockedStatic<AtlasPerfTracer> mockedPerfTracer =
mockStatic(AtlasPerfTracer.class);
MockedStatic<AtlasTypeUtil> mockedAtlasTypeUtil =
mockStatic(AtlasTypeUtil.class)) {
@@ -540,17 +540,17 @@ public class TypesRESTTest {
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<AtlasPerfTracer> mockedPerfTracer =
mockStatic(AtlasPerfTracer.class);
MockedStatic<AtlasTypeUtil> mockedAtlasTypeUtil =
mockStatic(AtlasTypeUtil.class)) {
@@ -559,10 +559,10 @@ public class TypesRESTTest {
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 class TypesRESTTest {
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<AtlasPerfTracer> mockedPerfTracer =
mockStatic(AtlasPerfTracer.class);
MockedStatic<AtlasTypeUtil> mockedAtlasTypeUtil =
mockStatic(AtlasTypeUtil.class)) {
@@ -581,11 +581,29 @@ public class TypesRESTTest {
// 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<AtlasPerfTracer> mockedPerfTracer =
mockStatic(AtlasPerfTracer.class);
+ MockedStatic<AtlasTypeUtil> 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 class TypesRESTTest {
public void testDeleteAtlasTypeByName_Success() throws AtlasBaseException {
// Setup
String typeName = "test_type";
- doNothing().when(typeDefStore).deleteTypeByName(typeName);
+ doNothing().when(typeDefStore).deleteTypeByName(typeName, false);
try (MockedStatic<AtlasPerfTracer> 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 class TypesRESTTest {
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<AtlasPerfTracer> 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 class TypesRESTTest {
// 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<AtlasPerfTracer> 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<AtlasPerfTracer> 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);
}
}