| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.atlas.repository.graph; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import org.apache.atlas.AtlasClient; |
| import org.apache.atlas.AtlasClient.EntityResult; |
| import org.apache.atlas.AtlasException; |
| import org.apache.atlas.CreateUpdateEntitiesResult; |
| import org.apache.atlas.RequestContext; |
| import org.apache.atlas.TestOnlyModule; |
| import org.apache.atlas.TestUtils; |
| import org.apache.atlas.repository.Constants; |
| import org.apache.atlas.repository.MetadataRepository; |
| import org.apache.atlas.repository.RepositoryException; |
| import org.apache.atlas.repository.graphdb.AtlasGraph; |
| import org.apache.atlas.repository.graphdb.AtlasVertex; |
| import org.apache.atlas.type.AtlasTypeRegistry; |
| import org.apache.atlas.typesystem.IReferenceableInstance; |
| import org.apache.atlas.typesystem.IStruct; |
| import org.apache.atlas.typesystem.ITypedReferenceableInstance; |
| import org.apache.atlas.typesystem.ITypedStruct; |
| import org.apache.atlas.typesystem.Referenceable; |
| import org.apache.atlas.typesystem.Struct; |
| import org.apache.atlas.typesystem.TypesDef; |
| import org.apache.atlas.typesystem.exception.EntityExistsException; |
| import org.apache.atlas.typesystem.exception.EntityNotFoundException; |
| import org.apache.atlas.typesystem.exception.NullRequiredAttributeException; |
| import org.apache.atlas.typesystem.persistence.Id; |
| import org.apache.atlas.typesystem.types.*; |
| import org.apache.atlas.typesystem.types.utils.TypesUtil; |
| import org.testng.Assert; |
| import org.testng.annotations.AfterClass; |
| import org.testng.annotations.BeforeClass; |
| import org.testng.annotations.BeforeMethod; |
| import org.testng.annotations.Guice; |
| import org.testng.annotations.Test; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import static org.apache.atlas.TestUtils.*; |
| import static org.testng.Assert.assertEquals; |
| import static org.testng.Assert.assertNotEquals; |
| import static org.testng.Assert.assertTrue; |
| import static org.testng.Assert.fail; |
| |
| /** |
| * Test for GraphBackedMetadataRepository.deleteEntities |
| * |
| * Guice loads the dependencies and injects the necessary objects |
| * |
| */ |
| @Guice(modules = TestOnlyModule.class) |
| public abstract class GraphBackedMetadataRepositoryDeleteTestBase { |
| |
| protected MetadataRepository repositoryService; |
| |
| private TypeSystem typeSystem; |
| |
| private ClassType compositeMapOwnerType; |
| |
| private ClassType compositeMapValueType; |
| |
| @BeforeClass |
| public void setUp() throws Exception { |
| |
| typeSystem = TypeSystem.getInstance(); |
| typeSystem.reset(); |
| |
| new GraphBackedSearchIndexer(new AtlasTypeRegistry()); |
| final GraphBackedMetadataRepository delegate = new GraphBackedMetadataRepository(getDeleteHandler(typeSystem)); |
| |
| repositoryService = TestUtils.addTransactionWrapper(delegate); |
| |
| TestUtils.defineDeptEmployeeTypes(typeSystem); |
| TestUtils.createHiveTypes(typeSystem); |
| |
| // Define type for map value. |
| HierarchicalTypeDefinition<ClassType> mapValueDef = TypesUtil.createClassTypeDef("CompositeMapValue", |
| ImmutableSet.<String>of(), |
| TypesUtil.createUniqueRequiredAttrDef(NAME, DataTypes.STRING_TYPE)); |
| |
| // Define type with map where the value is a composite class reference to MapValue. |
| HierarchicalTypeDefinition<ClassType> mapOwnerDef = TypesUtil.createClassTypeDef("CompositeMapOwner", |
| ImmutableSet.<String>of(), |
| TypesUtil.createUniqueRequiredAttrDef(NAME, DataTypes.STRING_TYPE), |
| new AttributeDefinition("map", DataTypes.mapTypeName(DataTypes.STRING_TYPE.getName(), |
| "CompositeMapValue"), Multiplicity.OPTIONAL, true, null)); |
| TypesDef typesDef = TypesUtil.getTypesDef(ImmutableList.<EnumTypeDefinition>of(), |
| ImmutableList.<StructTypeDefinition>of(), ImmutableList.<HierarchicalTypeDefinition<TraitType>>of(), |
| ImmutableList.of(mapOwnerDef, mapValueDef)); |
| typeSystem.defineTypes(typesDef); |
| compositeMapOwnerType = typeSystem.getDataType(ClassType.class, "CompositeMapOwner"); |
| compositeMapValueType = typeSystem.getDataType(ClassType.class, "CompositeMapValue"); |
| } |
| |
| abstract DeleteHandler getDeleteHandler(TypeSystem typeSystem); |
| |
| @BeforeMethod |
| public void setupContext() { |
| TestUtils.resetRequestContext(); |
| } |
| |
| @AfterClass |
| public void tearDown() throws Exception { |
| TypeSystem.getInstance().reset(); |
| AtlasGraphProvider.cleanup(); |
| } |
| |
| @Test |
| public void testDeleteAndCreate() throws Exception { |
| Referenceable entity = createDBEntity(); |
| String id = createInstance(entity); |
| |
| //get entity by unique attribute should return the created entity |
| ITypedReferenceableInstance instance = |
| repositoryService.getEntityDefinition(TestUtils.DATABASE_TYPE, "name", entity.get("name")); |
| assertEquals(instance.getId()._getId(), id); |
| |
| //delete entity should mark it as deleted |
| List<String> results = deleteEntities(id).getDeletedEntities(); |
| assertEquals(results.get(0), id); |
| assertEntityDeleted(id); |
| |
| //get entity by unique attribute should throw EntityNotFoundException |
| try { |
| repositoryService.getEntityDefinition(TestUtils.DATABASE_TYPE, "name", entity.get("name")); |
| fail("Expected EntityNotFoundException"); |
| } catch(EntityNotFoundException e) { |
| //expected |
| } |
| |
| //Create the same entity again, should create new entity |
| String newId = createInstance(entity); |
| assertNotEquals(id, newId); |
| |
| //get by unique attribute should return the new entity |
| instance = repositoryService.getEntityDefinition(TestUtils.DATABASE_TYPE, "name", entity.get("name")); |
| assertEquals(instance.getId()._getId(), newId); |
| } |
| |
| @Test |
| public void testDeleteEntityWithTraits() throws Exception { |
| Referenceable entity = createDBEntity(); |
| String id = createInstance(entity); |
| |
| TraitType dataType = typeSystem.getDataType(TraitType.class, PII); |
| ITypedStruct trait = dataType.convert(new Struct(TestUtils.PII), Multiplicity.REQUIRED); |
| repositoryService.addTrait(id, trait); |
| |
| ITypedReferenceableInstance instance = repositoryService.getEntityDefinition(id); |
| assertTrue(instance.getTraits().contains(PII)); |
| |
| deleteEntities(id); |
| assertEntityDeleted(id); |
| assertTestDeleteEntityWithTraits(id); |
| } |
| |
| protected abstract void assertTestDeleteEntityWithTraits(String guid) |
| throws EntityNotFoundException, RepositoryException, Exception; |
| |
| @Test |
| public void testDeleteReference() throws Exception { |
| //Deleting column should update table |
| Referenceable db = createDBEntity(); |
| String dbId = createInstance(db); |
| |
| Referenceable column = createColumnEntity(); |
| String colId = createInstance(column); |
| |
| Referenceable table = createTableEntity(dbId); |
| table.set(COLUMNS_ATTR_NAME, Arrays.asList(new Id(colId, 0, COLUMN_TYPE))); |
| String tableId = createInstance(table); |
| |
| AtlasClient.EntityResult entityResult = deleteEntities(colId); |
| assertEquals(entityResult.getDeletedEntities().size(), 1); |
| assertEquals(entityResult.getDeletedEntities().get(0), colId); |
| assertEquals(entityResult.getUpdateEntities().size(), 1); |
| assertEquals(entityResult.getUpdateEntities().get(0), tableId); |
| |
| assertEntityDeleted(colId); |
| |
| ITypedReferenceableInstance tableInstance = repositoryService.getEntityDefinition(tableId); |
| assertColumnForTestDeleteReference(tableInstance); |
| |
| //Deleting table should update process |
| Referenceable process = new Referenceable(PROCESS_TYPE); |
| process.set(AtlasClient.PROCESS_ATTRIBUTE_OUTPUTS, Arrays.asList(new Id(tableId, 0, TABLE_TYPE))); |
| String processId = createInstance(process); |
| ITypedReferenceableInstance processInstance = repositoryService.getEntityDefinition(processId); |
| |
| deleteEntities(tableId); |
| assertEntityDeleted(tableId); |
| |
| assertTableForTestDeleteReference(tableId); |
| assertProcessForTestDeleteReference(processInstance); |
| } |
| |
| protected abstract void assertTableForTestDeleteReference(String tableId) throws Exception; |
| |
| protected abstract void assertColumnForTestDeleteReference(ITypedReferenceableInstance tableInstance) |
| throws AtlasException; |
| |
| protected abstract void assertProcessForTestDeleteReference(ITypedReferenceableInstance processInstance) throws Exception; |
| |
| protected abstract void assertEntityDeleted(String id) throws Exception; |
| |
| private AtlasClient.EntityResult deleteEntities(String... id) throws Exception { |
| RequestContext.createContext(); |
| return repositoryService.deleteEntities(Arrays.asList(id)); |
| } |
| |
| private String createInstance(Referenceable entity) throws Exception { |
| ClassType dataType = typeSystem.getDataType(ClassType.class, entity.getTypeName()); |
| ITypedReferenceableInstance instance = dataType.convert(entity, Multiplicity.REQUIRED); |
| CreateUpdateEntitiesResult result = repositoryService.createEntities(instance); |
| List<String> results = result.getCreatedEntities(); |
| return results.get(results.size() - 1); |
| } |
| |
| @Test |
| public void testDeleteEntities() throws Exception { |
| // Create a table entity, with 3 composite column entities |
| Referenceable dbEntity = createDBEntity(); |
| String dbGuid = createInstance(dbEntity); |
| Referenceable table1Entity = createTableEntity(dbGuid); |
| Referenceable col1 = createColumnEntity(); |
| Referenceable col2 = createColumnEntity(); |
| Referenceable col3 = createColumnEntity(); |
| table1Entity.set(COLUMNS_ATTR_NAME, ImmutableList.of(col1, col2, col3)); |
| createInstance(table1Entity); |
| |
| // Retrieve the table entities from the Repository, to get their guids and the composite column guids. |
| ITypedReferenceableInstance tableInstance = repositoryService.getEntityDefinition(TestUtils.TABLE_TYPE, |
| NAME, table1Entity.get(NAME)); |
| List<IReferenceableInstance> columns = (List<IReferenceableInstance>) tableInstance.get(COLUMNS_ATTR_NAME); |
| |
| //Delete column |
| String colId = columns.get(0).getId()._getId(); |
| String tableId = tableInstance.getId()._getId(); |
| |
| AtlasClient.EntityResult entityResult = deleteEntities(colId); |
| assertEquals(entityResult.getDeletedEntities().size(), 1); |
| assertEquals(entityResult.getDeletedEntities().get(0), colId); |
| assertEquals(entityResult.getUpdateEntities().size(), 1); |
| assertEquals(entityResult.getUpdateEntities().get(0), tableId); |
| assertEntityDeleted(colId); |
| |
| tableInstance = repositoryService.getEntityDefinition(TestUtils.TABLE_TYPE, NAME, table1Entity.get(NAME)); |
| assertDeletedColumn(tableInstance); |
| |
| //update by removing a column |
| tableInstance.set(COLUMNS_ATTR_NAME, ImmutableList.of(col3)); |
| entityResult = updatePartial(tableInstance); |
| colId = columns.get(1).getId()._getId(); |
| assertEquals(entityResult.getDeletedEntities().size(), 1); |
| assertEquals(entityResult.getDeletedEntities().get(0), colId); |
| assertEntityDeleted(colId); |
| |
| // Delete the table entities. The deletion should cascade to their composite columns. |
| tableInstance = repositoryService.getEntityDefinition(TestUtils.TABLE_TYPE, NAME, table1Entity.get(NAME)); |
| List<String> deletedGuids = deleteEntities(tableInstance.getId()._getId()).getDeletedEntities(); |
| assertEquals(deletedGuids.size(), 2); |
| |
| // Verify that deleteEntities() response has guids for tables and their composite columns. |
| Assert.assertTrue(deletedGuids.contains(tableInstance.getId()._getId())); |
| Assert.assertTrue(deletedGuids.contains(columns.get(2).getId()._getId())); |
| |
| // Verify that tables and their composite columns have been deleted from the graph Repository. |
| for (String guid : deletedGuids) { |
| assertEntityDeleted(guid); |
| } |
| assertTestDeleteEntities(tableInstance); |
| } |
| |
| protected abstract void assertDeletedColumn(ITypedReferenceableInstance tableInstance) throws AtlasException; |
| |
| protected abstract void assertTestDeleteEntities(ITypedReferenceableInstance tableInstance) throws Exception; |
| |
| /** |
| * Verify deleting entities with composite references to other entities. |
| * The composite entities should also be deleted. |
| */ |
| @Test |
| public void testDeleteEntitiesWithCompositeArrayReference() throws Exception { |
| String hrDeptGuid = createHrDeptGraph(); |
| |
| ITypedReferenceableInstance hrDept = repositoryService.getEntityDefinition(hrDeptGuid); |
| List<ITypedReferenceableInstance> employees = (List<ITypedReferenceableInstance>) hrDept.get("employees"); |
| Assert.assertEquals(employees.size(), 4); |
| |
| List<String> employeeGuids = new ArrayList(4); |
| for (ITypedReferenceableInstance employee : employees) { |
| employeeGuids.add(employee.getId()._getId()); |
| } |
| |
| // There should be 4 vertices for Address structs (one for each Person.address attribute value). |
| int vertexCount = getVertices(Constants.ENTITY_TYPE_PROPERTY_KEY, "Address").size(); |
| Assert.assertEquals(vertexCount, 4); |
| vertexCount = getVertices(Constants.ENTITY_TYPE_PROPERTY_KEY, "SecurityClearance").size(); |
| Assert.assertEquals(vertexCount, 1); |
| |
| List<String> deletedEntities = deleteEntities(hrDeptGuid).getDeletedEntities(); |
| assertTrue(deletedEntities.contains(hrDeptGuid)); |
| assertEntityDeleted(hrDeptGuid); |
| |
| // Verify Department entity and its contained Person entities were deleted. |
| for (String employeeGuid : employeeGuids) { |
| assertTrue(deletedEntities.contains(employeeGuid)); |
| assertEntityDeleted(employeeGuid); |
| } |
| |
| // Verify all Person.address struct vertices were removed. |
| assertVerticesDeleted(getVertices(Constants.ENTITY_TYPE_PROPERTY_KEY, "Address")); |
| |
| // Verify all SecurityClearance trait vertices were removed. |
| assertVerticesDeleted(getVertices(Constants.ENTITY_TYPE_PROPERTY_KEY, "SecurityClearance")); |
| } |
| |
| protected abstract void assertVerticesDeleted(List<AtlasVertex> vertices); |
| |
| @Test |
| public void testDeleteEntitiesWithCompositeMapReference() throws Exception { |
| // Create instances of MapOwner and MapValue. |
| // Set MapOwner.map with one entry that references MapValue instance. |
| ITypedReferenceableInstance entityDefinition = createMapOwnerAndValueEntities(); |
| String mapOwnerGuid = entityDefinition.getId()._getId(); |
| |
| // Verify MapOwner.map attribute has expected value. |
| ITypedReferenceableInstance mapOwnerInstance = repositoryService.getEntityDefinition(mapOwnerGuid); |
| Object object = mapOwnerInstance.get("map"); |
| Assert.assertNotNull(object); |
| Assert.assertTrue(object instanceof Map); |
| Map<String, ITypedReferenceableInstance> map = (Map<String, ITypedReferenceableInstance>)object; |
| Assert.assertEquals(map.size(), 1); |
| ITypedReferenceableInstance mapValueInstance = map.get("value1"); |
| Assert.assertNotNull(mapValueInstance); |
| String mapValueGuid = mapValueInstance.getId()._getId(); |
| String edgeLabel = GraphHelper.getEdgeLabel(compositeMapOwnerType, compositeMapOwnerType.fieldMapping.fields.get("map")); |
| String mapEntryLabel = edgeLabel + "." + "value1"; |
| AtlasEdgeLabel atlasEdgeLabel = new AtlasEdgeLabel(mapEntryLabel); |
| AtlasVertex mapOwnerVertex = GraphHelper.getInstance().getVertexForGUID(mapOwnerGuid); |
| object = mapOwnerVertex.getProperty(atlasEdgeLabel.getQualifiedMapKey(), Object.class); |
| Assert.assertNotNull(object); |
| |
| List<String> deletedEntities = deleteEntities(mapOwnerGuid).getDeletedEntities(); |
| Assert.assertEquals(deletedEntities.size(), 2); |
| Assert.assertTrue(deletedEntities.contains(mapOwnerGuid)); |
| Assert.assertTrue(deletedEntities.contains(mapValueGuid)); |
| |
| assertEntityDeleted(mapOwnerGuid); |
| assertEntityDeleted(mapValueGuid); |
| } |
| |
| private ITypedReferenceableInstance createMapOwnerAndValueEntities() |
| throws AtlasException, RepositoryException, EntityExistsException { |
| |
| ITypedReferenceableInstance mapOwnerInstance = compositeMapOwnerType.createInstance(); |
| mapOwnerInstance.set(NAME, TestUtils.randomString()); |
| ITypedReferenceableInstance mapValueInstance = compositeMapValueType.createInstance(); |
| mapValueInstance.set(NAME, TestUtils.randomString()); |
| mapOwnerInstance.set("map", Collections.singletonMap("value1", mapValueInstance)); |
| List<String> createEntitiesResult = repositoryService.createEntities(mapOwnerInstance, mapValueInstance).getCreatedEntities(); |
| Assert.assertEquals(createEntitiesResult.size(), 2); |
| ITypedReferenceableInstance entityDefinition = repositoryService.getEntityDefinition("CompositeMapOwner", |
| NAME, mapOwnerInstance.get(NAME)); |
| return entityDefinition; |
| } |
| |
| private AtlasClient.EntityResult updatePartial(ITypedReferenceableInstance entity) throws RepositoryException { |
| RequestContext.createContext(); |
| return repositoryService.updatePartial(entity).getEntityResult(); |
| } |
| |
| @Test |
| public void testUpdateEntity_MultiplicityOneNonCompositeReference() throws Exception { |
| String hrDeptGuid = createHrDeptGraph(); |
| ITypedReferenceableInstance hrDept = repositoryService.getEntityDefinition(hrDeptGuid); |
| Map<String, String> nameGuidMap = getEmployeeNameGuidMap(hrDept); |
| |
| ITypedReferenceableInstance john = repositoryService.getEntityDefinition(nameGuidMap.get("John")); |
| Id johnGuid = john.getId(); |
| |
| ITypedReferenceableInstance max = repositoryService.getEntityDefinition(nameGuidMap.get("Max")); |
| String maxGuid = max.getId()._getId(); |
| AtlasVertex vertex = GraphHelper.getInstance().getVertexForGUID(maxGuid); |
| Long creationTimestamp = GraphHelper.getSingleValuedProperty(vertex, Constants.TIMESTAMP_PROPERTY_KEY, Long.class); |
| Assert.assertNotNull(creationTimestamp); |
| |
| Long modificationTimestampPreUpdate = GraphHelper.getSingleValuedProperty(vertex, Constants.MODIFICATION_TIMESTAMP_PROPERTY_KEY, Long.class); |
| Assert.assertNotNull(modificationTimestampPreUpdate); |
| |
| ITypedReferenceableInstance jane = repositoryService.getEntityDefinition(nameGuidMap.get("Jane")); |
| Id janeId = jane.getId(); |
| |
| // Update max's mentor reference to john. |
| ClassType personType = typeSystem.getDataType(ClassType.class, "Person"); |
| ITypedReferenceableInstance maxEntity = personType.createInstance(max.getId()); |
| maxEntity.set("mentor", johnGuid); |
| AtlasClient.EntityResult entityResult = updatePartial(maxEntity); |
| assertEquals(entityResult.getUpdateEntities().size(), 1); |
| assertTrue(entityResult.getUpdateEntities().contains(maxGuid)); |
| |
| // Verify the update was applied correctly - john should now be max's mentor. |
| max = repositoryService.getEntityDefinition(maxGuid); |
| ITypedReferenceableInstance refTarget = (ITypedReferenceableInstance) max.get("mentor"); |
| Assert.assertEquals(refTarget.getId()._getId(), johnGuid._getId()); |
| |
| // Verify modification timestamp was updated. |
| vertex = GraphHelper.getInstance().getVertexForGUID(maxGuid); |
| Long modificationTimestampPostUpdate = GraphHelper.getSingleValuedProperty(vertex, Constants.MODIFICATION_TIMESTAMP_PROPERTY_KEY, Long.class); |
| Assert.assertNotNull(modificationTimestampPostUpdate); |
| Assert.assertTrue(creationTimestamp < modificationTimestampPostUpdate); |
| |
| // Update max's mentor reference to jane. |
| maxEntity.set("mentor", janeId); |
| entityResult = updatePartial(maxEntity); |
| assertEquals(entityResult.getUpdateEntities().size(), 1); |
| assertTrue(entityResult.getUpdateEntities().contains(maxGuid)); |
| |
| // Verify the update was applied correctly - jane should now be max's mentor. |
| max = repositoryService.getEntityDefinition(maxGuid); |
| refTarget = (ITypedReferenceableInstance) max.get("mentor"); |
| Assert.assertEquals(refTarget.getId()._getId(), janeId._getId()); |
| |
| // Verify modification timestamp was updated. |
| vertex = GraphHelper.getInstance().getVertexForGUID(maxGuid); |
| Long modificationTimestampPost2ndUpdate = GraphHelper.getSingleValuedProperty(vertex, Constants.MODIFICATION_TIMESTAMP_PROPERTY_KEY, Long.class); |
| Assert.assertNotNull(modificationTimestampPost2ndUpdate); |
| Assert.assertTrue(modificationTimestampPostUpdate < modificationTimestampPost2ndUpdate); |
| |
| ITypedReferenceableInstance julius = repositoryService.getEntityDefinition(nameGuidMap.get("Julius")); |
| Id juliusId = julius.getId(); |
| maxEntity = personType.createInstance(max.getId()); |
| maxEntity.set("manager", juliusId); |
| entityResult = updatePartial(maxEntity); |
| // Verify julius' subordinates were updated. |
| assertEquals(entityResult.getUpdateEntities().size(), 3); |
| assertTrue(entityResult.getUpdateEntities().contains(maxGuid)); |
| assertTrue(entityResult.getUpdateEntities().containsAll(Arrays.asList(maxGuid, janeId._getId(), juliusId._getId()))); |
| |
| // Verify the update was applied correctly - julius should now be max's manager. |
| max = repositoryService.getEntityDefinition(maxGuid); |
| refTarget = (ITypedReferenceableInstance) max.get("manager"); |
| Assert.assertEquals(refTarget.getId()._getId(), juliusId._getId()); |
| Assert.assertEquals(refTarget.getId()._getId(), juliusId._getId()); |
| julius = repositoryService.getEntityDefinition(nameGuidMap.get("Julius")); |
| Object object = julius.get("subordinates"); |
| Assert.assertTrue(object instanceof List); |
| List<ITypedReferenceableInstance> refValues = (List<ITypedReferenceableInstance>) object; |
| Assert.assertEquals(refValues.size(), 1); |
| Assert.assertTrue(refValues.contains(max.getId())); |
| |
| assertTestUpdateEntity_MultiplicityOneNonCompositeReference(janeId._getId()); |
| } |
| |
| protected abstract void assertTestUpdateEntity_MultiplicityOneNonCompositeReference(String janeGuid) throws Exception; |
| |
| /** |
| * Verify deleting an entity which is contained by another |
| * entity through a bi-directional composite reference. |
| * |
| * @throws Exception |
| */ |
| @Test |
| public void testDisconnectBidirectionalReferences() throws Exception { |
| String hrDeptGuid = createHrDeptGraph(); |
| ITypedReferenceableInstance hrDept = repositoryService.getEntityDefinition(hrDeptGuid); |
| Map<String, String> nameGuidMap = getEmployeeNameGuidMap(hrDept); |
| String maxGuid = nameGuidMap.get("Max"); |
| String janeGuid = nameGuidMap.get("Jane"); |
| String johnGuid = nameGuidMap.get("John"); |
| |
| Assert.assertNotNull(maxGuid); |
| Assert.assertNotNull(janeGuid); |
| Assert.assertNotNull(johnGuid); |
| |
| // Verify that Max is one of Jane's subordinates. |
| ITypedReferenceableInstance jane = repositoryService.getEntityDefinition(janeGuid); |
| Object refValue = jane.get("subordinates"); |
| Assert.assertTrue(refValue instanceof List); |
| List<Object> subordinates = (List<Object>)refValue; |
| Assert.assertEquals(subordinates.size(), 2); |
| List<String> subordinateIds = new ArrayList<>(2); |
| for (Object listValue : subordinates) { |
| Assert.assertTrue(listValue instanceof ITypedReferenceableInstance); |
| ITypedReferenceableInstance employee = (ITypedReferenceableInstance) listValue; |
| subordinateIds.add(employee.getId()._getId()); |
| } |
| Assert.assertTrue(subordinateIds.contains(maxGuid)); |
| |
| |
| AtlasClient.EntityResult entityResult = deleteEntities(maxGuid); |
| ITypedReferenceableInstance john = repositoryService.getEntityDefinition("Person", "name", "John"); |
| |
| assertEquals(entityResult.getDeletedEntities().size(), 1); |
| assertTrue(entityResult.getDeletedEntities().contains(maxGuid)); |
| assertEquals(entityResult.getUpdateEntities().size(), 3); |
| assertTrue(entityResult.getUpdateEntities().containsAll(Arrays.asList(jane.getId()._getId(), hrDeptGuid, |
| john.getId()._getId()))); |
| assertEntityDeleted(maxGuid); |
| |
| assertMaxForTestDisconnectBidirectionalReferences(nameGuidMap); |
| |
| // Now delete jane - this should disconnect the manager reference from her |
| // subordinate. |
| entityResult = deleteEntities(janeGuid); |
| assertEquals(entityResult.getDeletedEntities().size(), 1); |
| assertTrue(entityResult.getDeletedEntities().contains(janeGuid)); |
| assertEquals(entityResult.getUpdateEntities().size(), 2); |
| assertTrue(entityResult.getUpdateEntities().containsAll(Arrays.asList(hrDeptGuid, john.getId()._getId()))); |
| |
| assertEntityDeleted(janeGuid); |
| |
| john = repositoryService.getEntityDefinition("Person", "name", "John"); |
| assertJohnForTestDisconnectBidirectionalReferences(john, janeGuid); |
| } |
| |
| protected abstract void assertJohnForTestDisconnectBidirectionalReferences(ITypedReferenceableInstance john, |
| String janeGuid) throws Exception; |
| |
| protected abstract void assertMaxForTestDisconnectBidirectionalReferences(Map<String, String> nameGuidMap) |
| throws Exception; |
| |
| /** |
| * Verify deleting entity that is the target of a unidirectional class array reference |
| * from a class instance. |
| */ |
| @Test |
| public void testDisconnectUnidirectionalArrayReferenceFromClassType() throws Exception { |
| createDbTableGraph(TestUtils.DATABASE_NAME, TestUtils.TABLE_NAME); |
| |
| // Get the guid for one of the table's columns. |
| ITypedReferenceableInstance table = repositoryService.getEntityDefinition(TestUtils.TABLE_TYPE, "name", TestUtils.TABLE_NAME); |
| String tableGuid = table.getId()._getId(); |
| List<ITypedReferenceableInstance> columns = (List<ITypedReferenceableInstance>) table.get("columns"); |
| Assert.assertEquals(columns.size(), 5); |
| String columnGuid = columns.get(0).getId()._getId(); |
| |
| // Delete the column. |
| AtlasClient.EntityResult entityResult = deleteEntities(columnGuid); |
| assertEquals(entityResult.getDeletedEntities().size(), 1); |
| Assert.assertTrue(entityResult.getDeletedEntities().contains(columnGuid)); |
| assertEquals(entityResult.getUpdateEntities().size(), 1); |
| Assert.assertTrue(entityResult.getUpdateEntities().contains(tableGuid)); |
| assertEntityDeleted(columnGuid); |
| |
| // Verify table.columns reference to the deleted column has been disconnected. |
| table = repositoryService.getEntityDefinition(tableGuid); |
| assertTestDisconnectUnidirectionalArrayReferenceFromClassType( |
| (List<ITypedReferenceableInstance>) table.get("columns"), columnGuid); |
| } |
| |
| protected abstract void assertTestDisconnectUnidirectionalArrayReferenceFromClassType( |
| List<ITypedReferenceableInstance> columns, String columnGuid); |
| |
| /** |
| * Verify deleting entities that are the target of a unidirectional class array reference |
| * from a struct or trait instance. |
| */ |
| @Test |
| public void testDisconnectUnidirectionalArrayReferenceFromStructAndTraitTypes() throws Exception { |
| // Define class types. |
| HierarchicalTypeDefinition<ClassType> structTargetDef = TypesUtil.createClassTypeDef("StructTarget", |
| ImmutableSet.<String>of(), TypesUtil.createOptionalAttrDef("attr1", DataTypes.STRING_TYPE)); |
| HierarchicalTypeDefinition<ClassType> traitTargetDef = TypesUtil.createClassTypeDef("TraitTarget", |
| ImmutableSet.<String>of(), TypesUtil.createOptionalAttrDef("attr1", DataTypes.STRING_TYPE)); |
| HierarchicalTypeDefinition<ClassType> structContainerDef = TypesUtil.createClassTypeDef("StructContainer", |
| ImmutableSet.<String>of(), TypesUtil.createOptionalAttrDef("struct", "TestStruct")); |
| |
| // Define struct and trait types which have a unidirectional array reference |
| // to a class type. |
| StructTypeDefinition structDef = TypesUtil.createStructTypeDef("TestStruct", |
| new AttributeDefinition("target", DataTypes.arrayTypeName("StructTarget"), Multiplicity.OPTIONAL, false, null), |
| new AttributeDefinition("nestedStructs", DataTypes.arrayTypeName("NestedStruct"), Multiplicity.OPTIONAL, false, null)); |
| StructTypeDefinition nestedStructDef = TypesUtil.createStructTypeDef("NestedStruct", |
| TypesUtil.createOptionalAttrDef("attr1", DataTypes.STRING_TYPE)); |
| HierarchicalTypeDefinition<TraitType> traitDef = TypesUtil.createTraitTypeDef("TestTrait", ImmutableSet.<String>of(), |
| new AttributeDefinition("target", DataTypes.arrayTypeName("TraitTarget"), Multiplicity.OPTIONAL, false, null)); |
| |
| TypesDef typesDef = TypesUtil.getTypesDef(ImmutableList.<EnumTypeDefinition>of(), ImmutableList.of(structDef, nestedStructDef), |
| ImmutableList.of(traitDef), ImmutableList.of(structTargetDef, traitTargetDef, structContainerDef)); |
| typeSystem.defineTypes(typesDef); |
| |
| // Create instances of class, struct, and trait types. |
| Referenceable structTargetEntity = new Referenceable("StructTarget"); |
| Referenceable traitTargetEntity = new Referenceable("TraitTarget"); |
| Referenceable structContainerEntity = new Referenceable("StructContainer"); |
| Struct structInstance = new Struct("TestStruct"); |
| Struct nestedStructInstance = new Struct("NestedStruct"); |
| Referenceable traitInstance = new Referenceable("TestTrait"); |
| structContainerEntity.set("struct", structInstance); |
| structInstance.set("target", ImmutableList.of(structTargetEntity)); |
| structInstance.set("nestedStructs", ImmutableList.of(nestedStructInstance)); |
| |
| ClassType structTargetType = typeSystem.getDataType(ClassType.class, "StructTarget"); |
| ClassType traitTargetType = typeSystem.getDataType(ClassType.class, "TraitTarget"); |
| ClassType structContainerType = typeSystem.getDataType(ClassType.class, "StructContainer"); |
| |
| ITypedReferenceableInstance structTargetConvertedEntity = |
| structTargetType.convert(structTargetEntity, Multiplicity.REQUIRED); |
| ITypedReferenceableInstance traitTargetConvertedEntity = |
| traitTargetType.convert(traitTargetEntity, Multiplicity.REQUIRED); |
| ITypedReferenceableInstance structContainerConvertedEntity = |
| structContainerType.convert(structContainerEntity, Multiplicity.REQUIRED); |
| |
| List<String> guids = repositoryService.createEntities( |
| structTargetConvertedEntity, traitTargetConvertedEntity, structContainerConvertedEntity).getCreatedEntities(); |
| Assert.assertEquals(guids.size(), 3); |
| |
| guids = repositoryService.getEntityList("StructTarget"); |
| Assert.assertEquals(guids.size(), 1); |
| String structTargetGuid = guids.get(0); |
| |
| guids = repositoryService.getEntityList("TraitTarget"); |
| Assert.assertEquals(guids.size(), 1); |
| String traitTargetGuid = guids.get(0); |
| |
| guids = repositoryService.getEntityList("StructContainer"); |
| Assert.assertEquals(guids.size(), 1); |
| String structContainerGuid = guids.get(0); |
| |
| // Add TestTrait to StructContainer instance |
| traitInstance.set("target", ImmutableList.of(new Id(traitTargetGuid, 0, "TraitTarget"))); |
| TraitType traitType = typeSystem.getDataType(TraitType.class, "TestTrait"); |
| ITypedStruct convertedTrait = traitType.convert(traitInstance, Multiplicity.REQUIRED); |
| repositoryService.addTrait(structContainerGuid, convertedTrait); |
| |
| // Verify that the unidirectional references from the struct and trait instances |
| // are pointing at the target entities. |
| structContainerConvertedEntity = repositoryService.getEntityDefinition(structContainerGuid); |
| Object object = structContainerConvertedEntity.get("struct"); |
| Assert.assertNotNull(object); |
| Assert.assertTrue(object instanceof ITypedStruct); |
| ITypedStruct struct = (ITypedStruct) object; |
| object = struct.get("target"); |
| Assert.assertNotNull(object); |
| Assert.assertTrue(object instanceof List); |
| List<ITypedReferenceableInstance> refList = (List<ITypedReferenceableInstance>)object; |
| Assert.assertEquals(refList.size(), 1); |
| Assert.assertEquals(refList.get(0).getId()._getId(), structTargetGuid); |
| |
| IStruct trait = structContainerConvertedEntity.getTrait("TestTrait"); |
| Assert.assertNotNull(trait); |
| object = trait.get("target"); |
| Assert.assertNotNull(object); |
| Assert.assertTrue(object instanceof List); |
| refList = (List<ITypedReferenceableInstance>)object; |
| Assert.assertEquals(refList.size(), 1); |
| Assert.assertEquals(refList.get(0).getId()._getId(), traitTargetGuid); |
| |
| // Delete the entities that are targets of the struct and trait instances. |
| AtlasClient.EntityResult entityResult = deleteEntities(structTargetGuid, traitTargetGuid); |
| Assert.assertEquals(entityResult.getDeletedEntities().size(), 2); |
| Assert.assertTrue(entityResult.getDeletedEntities().containsAll(Arrays.asList(structTargetGuid, traitTargetGuid))); |
| assertEntityDeleted(structTargetGuid); |
| assertEntityDeleted(traitTargetGuid); |
| |
| assertTestDisconnectUnidirectionalArrayReferenceFromStructAndTraitTypes(structContainerGuid); |
| |
| // Delete the entity which contains nested structs and has the TestTrait trait. |
| entityResult = deleteEntities(structContainerGuid); |
| Assert.assertEquals(entityResult.getDeletedEntities().size(), 1); |
| Assert.assertTrue(entityResult.getDeletedEntities().contains(structContainerGuid)); |
| assertEntityDeleted(structContainerGuid); |
| |
| // Verify all TestStruct struct vertices were removed. |
| assertVerticesDeleted(getVertices(Constants.ENTITY_TYPE_PROPERTY_KEY, "TestStruct")); |
| |
| // Verify all NestedStruct struct vertices were removed. |
| assertVerticesDeleted(getVertices(Constants.ENTITY_TYPE_PROPERTY_KEY, "NestedStruct")); |
| |
| // Verify all TestTrait trait vertices were removed. |
| assertVerticesDeleted(getVertices(Constants.ENTITY_TYPE_PROPERTY_KEY, "TestTrait")); |
| } |
| |
| protected abstract void assertTestDisconnectUnidirectionalArrayReferenceFromStructAndTraitTypes( |
| String structContainerGuid) throws Exception; |
| |
| /** |
| * Verify deleting entities that are the target of class map references. |
| */ |
| @Test |
| public void testDisconnectMapReferenceFromClassType() throws Exception { |
| // Define type for map value. |
| HierarchicalTypeDefinition<ClassType> mapValueDef = TypesUtil.createClassTypeDef("MapValue", |
| ImmutableSet.<String>of(), |
| new AttributeDefinition("biMapOwner", "MapOwner", Multiplicity.OPTIONAL, false, "biMap")); |
| |
| // Define type with unidirectional and bidirectional map references, |
| // where the map value is a class reference to MapValue. |
| HierarchicalTypeDefinition<ClassType> mapOwnerDef = TypesUtil.createClassTypeDef("MapOwner", |
| ImmutableSet.<String>of(), |
| new AttributeDefinition("map", DataTypes.mapTypeName(DataTypes.STRING_TYPE.getName(), |
| "MapValue"), Multiplicity.OPTIONAL, false, null), |
| new AttributeDefinition("biMap", DataTypes.mapTypeName(DataTypes.STRING_TYPE.getName(), |
| "MapValue"), Multiplicity.OPTIONAL, false, "biMapOwner")); |
| TypesDef typesDef = TypesUtil.getTypesDef(ImmutableList.<EnumTypeDefinition>of(), |
| ImmutableList.<StructTypeDefinition>of(), ImmutableList.<HierarchicalTypeDefinition<TraitType>>of(), |
| ImmutableList.of(mapOwnerDef, mapValueDef)); |
| typeSystem.defineTypes(typesDef); |
| ClassType mapOwnerType = typeSystem.getDataType(ClassType.class, "MapOwner"); |
| ClassType mapValueType = typeSystem.getDataType(ClassType.class, "MapValue"); |
| |
| // Create instances of MapOwner and MapValue. |
| // Set MapOwner.map and MapOwner.biMap with one entry that references MapValue instance. |
| ITypedReferenceableInstance mapOwnerInstance = mapOwnerType.createInstance(); |
| ITypedReferenceableInstance mapValueInstance = mapValueType.createInstance(); |
| mapOwnerInstance.set("map", Collections.singletonMap("value1", mapValueInstance)); |
| mapOwnerInstance.set("biMap", Collections.singletonMap("value1", mapValueInstance)); |
| // Set biMapOwner reverse reference on MapValue. |
| mapValueInstance.set("biMapOwner", mapOwnerInstance); |
| List<String> createEntitiesResult = repositoryService.createEntities(mapOwnerInstance, mapValueInstance).getCreatedEntities(); |
| Assert.assertEquals(createEntitiesResult.size(), 2); |
| List<String> guids = repositoryService.getEntityList("MapOwner"); |
| Assert.assertEquals(guids.size(), 1); |
| String mapOwnerGuid = guids.get(0); |
| |
| String edgeLabel = GraphHelper.getEdgeLabel(mapOwnerType, mapOwnerType.fieldMapping.fields.get("map")); |
| String mapEntryLabel = edgeLabel + "." + "value1"; |
| AtlasEdgeLabel atlasEdgeLabel = new AtlasEdgeLabel(mapEntryLabel); |
| |
| // Verify MapOwner.map attribute has expected value. |
| String mapValueGuid = null; |
| AtlasVertex mapOwnerVertex = null; |
| mapOwnerInstance = repositoryService.getEntityDefinition(mapOwnerGuid); |
| for (String mapAttrName : Arrays.asList("map", "biMap")) { |
| Object object = mapOwnerInstance.get(mapAttrName); |
| Assert.assertNotNull(object); |
| Assert.assertTrue(object instanceof Map); |
| Map<String, ITypedReferenceableInstance> map = (Map<String, ITypedReferenceableInstance>)object; |
| Assert.assertEquals(map.size(), 1); |
| mapValueInstance = map.get("value1"); |
| Assert.assertNotNull(mapValueInstance); |
| mapValueGuid = mapValueInstance.getId()._getId(); |
| mapOwnerVertex = GraphHelper.getInstance().getVertexForGUID(mapOwnerGuid); |
| object = mapOwnerVertex.getProperty(atlasEdgeLabel.getQualifiedMapKey(), Object.class); |
| Assert.assertNotNull(object); |
| } |
| |
| // Delete the map value instance. |
| // This should disconnect the references from the map owner instance. |
| deleteEntities(mapValueGuid); |
| assertEntityDeleted(mapValueGuid); |
| assertTestDisconnectMapReferenceFromClassType(mapOwnerGuid); |
| } |
| |
| protected abstract void assertTestDisconnectMapReferenceFromClassType(String mapOwnerGuid) throws Exception; |
| |
| @Test |
| public void testDeleteTargetOfMultiplicityOneRequiredReference() throws Exception { |
| createDbTableGraph("db1", "table1"); |
| ITypedReferenceableInstance db = repositoryService.getEntityDefinition(TestUtils.DATABASE_TYPE, "name", "db1"); |
| try { |
| // table1 references db1 through the required reference hive_table.database. |
| // Attempt to delete db1 should cause a NullRequiredAttributeException, |
| // as that would violate the lower bound on table1's database attribute. |
| deleteEntities(db.getId()._getId()); |
| Assert.fail("Lower bound on attribute hive_table.database was not enforced - " + |
| NullRequiredAttributeException.class.getSimpleName() + " was expected but none thrown"); |
| } |
| catch (Exception e) { |
| verifyExceptionThrown(e, NullRequiredAttributeException.class); |
| } |
| |
| // Delete table1. |
| ITypedReferenceableInstance table1 = repositoryService.getEntityDefinition(TestUtils.TABLE_TYPE, "name", "table1"); |
| Assert.assertNotNull(table1); |
| deleteEntities(table1.getId()._getId()); |
| |
| // Now delete of db1 should succeed, since it is no longer the target |
| // of the required reference from the deleted table1. |
| deleteEntities(db.getId()._getId()); |
| } |
| |
| @Test |
| public void testDeleteTargetOfMultiplicityManyRequiredReference() throws Exception { |
| String deptGuid = createHrDeptGraph(); |
| ITypedReferenceableInstance hrDept = repositoryService.getEntityDefinition(deptGuid); |
| Map<String, String> nameGuidMap = getEmployeeNameGuidMap(hrDept); |
| |
| // Delete John - this should work, as it would reduce the cardinality of Jane's subordinates reference |
| // from 2 to 1. |
| deleteEntities(nameGuidMap.get("John")); |
| |
| // Attempt to delete Max - this should cause a NullRequiredAttributeException, |
| // as that would reduce the cardinality on Jane's subordinates reference from 1 to 0 |
| // and violate the lower bound. |
| try { |
| deleteEntities(nameGuidMap.get("Max")); |
| assertTestDeleteTargetOfMultiplicityRequiredReference(); |
| } |
| catch (Exception e) { |
| verifyExceptionThrown(e, NullRequiredAttributeException.class); |
| } |
| } |
| |
| protected abstract void assertTestDeleteTargetOfMultiplicityRequiredReference() throws Exception; |
| |
| @Test |
| public void testDeleteTargetOfRequiredMapReference() throws Exception { |
| // Define type for map value. |
| HierarchicalTypeDefinition<ClassType> mapValueDef = TypesUtil.createClassTypeDef("RequiredMapValue", |
| ImmutableSet.<String>of()); |
| |
| // Define type with required map references where the map value is a class reference to RequiredMapValue. |
| HierarchicalTypeDefinition<ClassType> mapOwnerDef = TypesUtil.createClassTypeDef("RequiredMapOwner", |
| ImmutableSet.<String>of(), |
| new AttributeDefinition("map", DataTypes.mapTypeName(DataTypes.STRING_TYPE.getName(), |
| "RequiredMapValue"), Multiplicity.REQUIRED, false, null)); |
| TypesDef typesDef = TypesUtil.getTypesDef(ImmutableList.<EnumTypeDefinition>of(), |
| ImmutableList.<StructTypeDefinition>of(), ImmutableList.<HierarchicalTypeDefinition<TraitType>>of(), |
| ImmutableList.of(mapOwnerDef, mapValueDef)); |
| typeSystem.defineTypes(typesDef); |
| ClassType mapOwnerType = typeSystem.getDataType(ClassType.class, "RequiredMapOwner"); |
| ClassType mapValueType = typeSystem.getDataType(ClassType.class, "RequiredMapValue"); |
| |
| // Create instances of RequiredMapOwner and RequiredMapValue. |
| // Set RequiredMapOwner.map with one entry that references RequiredMapValue instance. |
| ITypedReferenceableInstance mapOwnerInstance = mapOwnerType.createInstance(); |
| ITypedReferenceableInstance mapValueInstance = mapValueType.createInstance(); |
| mapOwnerInstance.set("map", Collections.singletonMap("value1", mapValueInstance)); |
| List<String> createEntitiesResult = repositoryService.createEntities(mapOwnerInstance, mapValueInstance).getCreatedEntities(); |
| Assert.assertEquals(createEntitiesResult.size(), 2); |
| List<String> guids = repositoryService.getEntityList("RequiredMapOwner"); |
| Assert.assertEquals(guids.size(), 1); |
| String mapOwnerGuid = guids.get(0); |
| guids = repositoryService.getEntityList("RequiredMapValue"); |
| Assert.assertEquals(guids.size(), 1); |
| String mapValueGuid = guids.get(0); |
| |
| // Verify MapOwner.map attribute has expected value. |
| mapOwnerInstance = repositoryService.getEntityDefinition(mapOwnerGuid); |
| Object object = mapOwnerInstance.get("map"); |
| Assert.assertNotNull(object); |
| Assert.assertTrue(object instanceof Map); |
| Map<String, ITypedReferenceableInstance> map = (Map<String, ITypedReferenceableInstance>)object; |
| Assert.assertEquals(map.size(), 1); |
| mapValueInstance = map.get("value1"); |
| Assert.assertNotNull(mapValueInstance); |
| Assert.assertEquals(mapValueInstance.getId()._getId(), mapValueGuid); |
| String edgeLabel = GraphHelper.getEdgeLabel(mapOwnerType, mapOwnerType.fieldMapping.fields.get("map")); |
| String mapEntryLabel = edgeLabel + "." + "value1"; |
| AtlasEdgeLabel atlasEdgeLabel = new AtlasEdgeLabel(mapEntryLabel); |
| AtlasVertex mapOwnerVertex = GraphHelper.getInstance().getVertexForGUID(mapOwnerGuid); |
| object = mapOwnerVertex.getProperty(atlasEdgeLabel.getQualifiedMapKey(), Object.class); |
| Assert.assertNotNull(object); |
| |
| // Verify deleting the target of required map attribute throws a NullRequiredAttributeException. |
| try { |
| deleteEntities(mapValueGuid); |
| Assert.fail(NullRequiredAttributeException.class.getSimpleName() + " was expected but none thrown."); |
| } |
| catch (Exception e) { |
| verifyExceptionThrown(e, NullRequiredAttributeException.class); |
| } |
| } |
| |
| @Test |
| public void testLowerBoundsIgnoredOnDeletedEntities() throws Exception { |
| |
| String hrDeptGuid = createHrDeptGraph(); |
| ITypedReferenceableInstance hrDept = repositoryService.getEntityDefinition(hrDeptGuid); |
| Map<String, String> nameGuidMap = getEmployeeNameGuidMap(hrDept); |
| |
| ITypedReferenceableInstance john = repositoryService.getEntityDefinition(nameGuidMap.get("John")); |
| String johnGuid = john.getId()._getId(); |
| |
| ITypedReferenceableInstance max = repositoryService.getEntityDefinition(nameGuidMap.get("Max")); |
| String maxGuid = max.getId()._getId(); |
| |
| ITypedReferenceableInstance jane = repositoryService.getEntityDefinition(nameGuidMap.get("Jane")); |
| String janeGuid = jane.getId()._getId(); |
| |
| // The lower bound constraint on Manager.subordinates should not be enforced on Jane since that entity is being deleted. |
| // Prior to the fix for ATLAS-991, this call would fail with a NullRequiredAttributeException. |
| EntityResult deleteResult = deleteEntities(johnGuid, maxGuid, janeGuid); |
| Assert.assertEquals(deleteResult.getDeletedEntities().size(), 3); |
| Assert.assertTrue(deleteResult.getDeletedEntities().containsAll(Arrays.asList(johnGuid, maxGuid, janeGuid))); |
| Assert.assertEquals(deleteResult.getUpdateEntities().size(), 1); |
| |
| // Verify that Department entity was updated to disconnect its references to the deleted employees. |
| Assert.assertEquals(deleteResult.getUpdateEntities().get(0), hrDeptGuid); |
| hrDept = repositoryService.getEntityDefinition(hrDeptGuid); |
| Object object = hrDept.get("employees"); |
| Assert.assertTrue(object instanceof List); |
| List<ITypedReferenceableInstance> employees = (List<ITypedReferenceableInstance>) object; |
| assertTestLowerBoundsIgnoredOnDeletedEntities(employees); |
| } |
| |
| protected abstract void assertTestLowerBoundsIgnoredOnDeletedEntities(List<ITypedReferenceableInstance> employees); |
| |
| @Test |
| public void testLowerBoundsIgnoredOnCompositeDeletedEntities() throws Exception { |
| String hrDeptGuid = createHrDeptGraph(); |
| ITypedReferenceableInstance hrDept = repositoryService.getEntityDefinition(hrDeptGuid); |
| Map<String, String> nameGuidMap = getEmployeeNameGuidMap(hrDept); |
| ITypedReferenceableInstance john = repositoryService.getEntityDefinition(nameGuidMap.get("John")); |
| String johnGuid = john.getId()._getId(); |
| ITypedReferenceableInstance max = repositoryService.getEntityDefinition(nameGuidMap.get("Max")); |
| String maxGuid = max.getId()._getId(); |
| |
| // The lower bound constraint on Manager.subordinates should not be enforced on the composite entity |
| // for Jane owned by the Department entity, since that entity is being deleted. |
| // Prior to the fix for ATLAS-991, this call would fail with a NullRequiredAttributeException. |
| EntityResult deleteResult = deleteEntities(johnGuid, maxGuid, hrDeptGuid); |
| Assert.assertEquals(deleteResult.getDeletedEntities().size(), 5); |
| Assert.assertTrue(deleteResult.getDeletedEntities().containsAll(nameGuidMap.values())); |
| Assert.assertTrue(deleteResult.getDeletedEntities().contains(hrDeptGuid)); |
| assertTestLowerBoundsIgnoredOnCompositeDeletedEntities(hrDeptGuid); |
| } |
| |
| |
| protected abstract void assertTestLowerBoundsIgnoredOnCompositeDeletedEntities(String hrDeptGuid) throws Exception; |
| |
| @Test |
| public void testLowerBoundsIgnoredWhenDeletingCompositeEntitesOwnedByMap() throws Exception { |
| // Define MapValueReferencer type with required reference to CompositeMapValue. |
| HierarchicalTypeDefinition<ClassType> mapValueReferencerTypeDef = TypesUtil.createClassTypeDef("MapValueReferencer", |
| ImmutableSet.<String>of(), |
| new AttributeDefinition("refToMapValue", "CompositeMapValue", Multiplicity.REQUIRED, false, null)); |
| |
| // Define MapValueReferencerContainer type with required composite map reference to MapValueReferencer. |
| HierarchicalTypeDefinition<ClassType> mapValueReferencerContainerTypeDef = |
| TypesUtil.createClassTypeDef("MapValueReferencerContainer", |
| ImmutableSet.<String>of(), |
| new AttributeDefinition("requiredMap", DataTypes.mapTypeName(DataTypes.STRING_TYPE.getName(), "MapValueReferencer"), Multiplicity.REQUIRED, true, null)); |
| |
| Map<String, IDataType> definedClassTypes = typeSystem.defineClassTypes(mapValueReferencerTypeDef, mapValueReferencerContainerTypeDef); |
| ClassType mapValueReferencerClassType = (ClassType) definedClassTypes.get("MapValueReferencer"); |
| ClassType mapValueReferencerContainerType = (ClassType) definedClassTypes.get("MapValueReferencerContainer"); |
| |
| // Create instances of CompositeMapOwner and CompositeMapValue. |
| // Set MapOwner.map with one entry that references MapValue instance. |
| ITypedReferenceableInstance entityDefinition = createMapOwnerAndValueEntities(); |
| String mapOwnerGuid = entityDefinition.getId()._getId(); |
| |
| // Verify MapOwner.map attribute has expected value. |
| ITypedReferenceableInstance mapOwnerInstance = repositoryService.getEntityDefinition(mapOwnerGuid); |
| Object object = mapOwnerInstance.get("map"); |
| Assert.assertNotNull(object); |
| Assert.assertTrue(object instanceof Map); |
| Map<String, ITypedReferenceableInstance> map = (Map<String, ITypedReferenceableInstance>)object; |
| Assert.assertEquals(map.size(), 1); |
| ITypedReferenceableInstance mapValueInstance = map.get("value1"); |
| Assert.assertNotNull(mapValueInstance); |
| String mapValueGuid = mapValueInstance.getId()._getId(); |
| |
| // Create instance of MapValueReferencerContainer |
| RequestContext.createContext(); |
| ITypedReferenceableInstance mapValueReferencerContainer = mapValueReferencerContainerType.createInstance(); |
| List<String> createdEntities = repositoryService.createEntities(mapValueReferencerContainer).getCreatedEntities(); |
| Assert.assertEquals(createdEntities.size(), 1); |
| String mapValueReferencerContainerGuid = createdEntities.get(0); |
| mapValueReferencerContainer = repositoryService.getEntityDefinition(createdEntities.get(0)); |
| |
| // Create instance of MapValueReferencer, and update mapValueReferencerContainer |
| // to reference it. |
| ITypedReferenceableInstance mapValueReferencer = mapValueReferencerClassType.createInstance(); |
| mapValueReferencerContainer.set("requiredMap", Collections.singletonMap("value1", mapValueReferencer)); |
| mapValueReferencer.set("refToMapValue", mapValueInstance.getId()); |
| |
| RequestContext.createContext(); |
| EntityResult updateEntitiesResult = repositoryService.updateEntities(mapValueReferencerContainer).getEntityResult(); |
| Assert.assertEquals(updateEntitiesResult.getCreatedEntities().size(), 1); |
| Assert.assertEquals(updateEntitiesResult.getUpdateEntities().size(), 1); |
| Assert.assertEquals(updateEntitiesResult.getUpdateEntities().get(0), mapValueReferencerContainerGuid); |
| String mapValueReferencerGuid = updateEntitiesResult.getCreatedEntities().get(0); |
| |
| // Delete map owner and map referencer container. A total of 4 entities should be deleted, |
| // including the composite entities. The lower bound constraint on MapValueReferencer.refToMapValue |
| // should not be enforced on the composite MapValueReferencer since it is being deleted. |
| EntityResult deleteEntitiesResult = repositoryService.deleteEntities(Arrays.asList(mapOwnerGuid, mapValueReferencerContainerGuid)); |
| Assert.assertEquals(deleteEntitiesResult.getDeletedEntities().size(), 4); |
| Assert.assertTrue(deleteEntitiesResult.getDeletedEntities().containsAll( |
| Arrays.asList(mapOwnerGuid, mapValueGuid, mapValueReferencerContainerGuid, mapValueReferencerGuid))); |
| } |
| |
| @Test |
| public void testDeleteMixOfExistentAndNonExistentEntities() throws Exception { |
| ITypedReferenceableInstance entity1 = compositeMapValueType.createInstance(); |
| ITypedReferenceableInstance entity2 = compositeMapValueType.createInstance(); |
| List<String> createEntitiesResult = repositoryService.createEntities(entity1, entity2).getCreatedEntities(); |
| Assert.assertEquals(createEntitiesResult.size(), 2); |
| List<String> guids = Arrays.asList(createEntitiesResult.get(0), "non-existent-guid1", "non-existent-guid2", createEntitiesResult.get(1)); |
| EntityResult deleteEntitiesResult = repositoryService.deleteEntities(guids); |
| Assert.assertEquals(deleteEntitiesResult.getDeletedEntities().size(), 2); |
| Assert.assertTrue(deleteEntitiesResult.getDeletedEntities().containsAll(createEntitiesResult)); |
| } |
| |
| @Test |
| public void testDeleteMixOfNullAndNonNullGuids() throws Exception { |
| ITypedReferenceableInstance entity1 = compositeMapValueType.createInstance(); |
| ITypedReferenceableInstance entity2 = compositeMapValueType.createInstance(); |
| List<String> createEntitiesResult = repositoryService.createEntities(entity1, entity2).getCreatedEntities(); |
| Assert.assertEquals(createEntitiesResult.size(), 2); |
| List<String> guids = Arrays.asList(createEntitiesResult.get(0), null, null, createEntitiesResult.get(1)); |
| EntityResult deleteEntitiesResult = repositoryService.deleteEntities(guids); |
| Assert.assertEquals(deleteEntitiesResult.getDeletedEntities().size(), 2); |
| Assert.assertTrue(deleteEntitiesResult.getDeletedEntities().containsAll(createEntitiesResult)); |
| } |
| |
| @Test |
| public void testDeleteCompositeEntityAndContainer() throws Exception { |
| Referenceable db = createDBEntity(); |
| String dbId = createInstance(db); |
| |
| Referenceable column = createColumnEntity(); |
| String colId = createInstance(column); |
| |
| Referenceable table1 = createTableEntity(dbId); |
| table1.set(COLUMNS_ATTR_NAME, Arrays.asList(new Id(colId, 0, COLUMN_TYPE))); |
| String table1Id = createInstance(table1); |
| Referenceable table2 = createTableEntity(dbId); |
| String table2Id = createInstance(table2); |
| |
| // Delete the tables and column |
| AtlasClient.EntityResult entityResult = deleteEntities(table1Id, colId, table2Id); |
| Assert.assertEquals(entityResult.getDeletedEntities().size(), 3); |
| Assert.assertTrue(entityResult.getDeletedEntities().containsAll(Arrays.asList(colId, table1Id, table2Id))); |
| assertEntityDeleted(table1Id); |
| assertEntityDeleted(colId); |
| assertEntityDeleted(table2Id); |
| } |
| |
| @Test |
| public void testDeleteEntityWithDuplicateReferenceListElements() throws Exception { |
| // Create a table entity, with 2 composite column entities |
| Referenceable dbEntity = createDBEntity(); |
| String dbGuid = createInstance(dbEntity); |
| Referenceable table1Entity = createTableEntity(dbGuid); |
| String tableName = TestUtils.randomString(); |
| table1Entity.set(NAME, tableName); |
| Referenceable col1 = createColumnEntity(); |
| col1.set(NAME, TestUtils.randomString()); |
| Referenceable col2 = createColumnEntity(); |
| col2.set(NAME, TestUtils.randomString()); |
| // Populate columns reference list with duplicates. |
| table1Entity.set(COLUMNS_ATTR_NAME, ImmutableList.of(col1, col2, col1, col2)); |
| ClassType dataType = typeSystem.getDataType(ClassType.class, table1Entity.getTypeName()); |
| ITypedReferenceableInstance instance = dataType.convert(table1Entity, Multiplicity.REQUIRED); |
| TestUtils.resetRequestContext(); |
| List<String> result = repositoryService.createEntities(instance).getCreatedEntities(); |
| Assert.assertEquals(result.size(), 3); |
| ITypedReferenceableInstance entityDefinition = repositoryService.getEntityDefinition(TABLE_TYPE, NAME, tableName); |
| String tableGuid = entityDefinition.getId()._getId(); |
| Object attrValue = entityDefinition.get(COLUMNS_ATTR_NAME); |
| assertTrue(attrValue instanceof List); |
| List<ITypedReferenceableInstance> columns = (List<ITypedReferenceableInstance>) attrValue; |
| Assert.assertEquals(columns.size(), 4); |
| TestUtils.resetRequestContext(); |
| String columnGuid = columns.get(0).getId()._getId(); |
| |
| // Delete one of the columns. |
| EntityResult deleteResult = repositoryService.deleteEntities(Collections.singletonList(columnGuid)); |
| Assert.assertEquals(deleteResult.getDeletedEntities().size(), 1); |
| Assert.assertTrue(deleteResult.getDeletedEntities().contains(columnGuid)); |
| Assert.assertEquals(deleteResult.getUpdateEntities().size(), 1); |
| Assert.assertTrue(deleteResult.getUpdateEntities().contains(tableGuid)); |
| |
| // Verify the duplicate edge IDs were all removed from reference property list. |
| AtlasVertex tableVertex = GraphHelper.getInstance().getVertexForGUID(tableGuid); |
| String columnsPropertyName = GraphHelper.getQualifiedFieldName(dataType, COLUMNS_ATTR_NAME); |
| List columnsPropertyValue = tableVertex.getProperty(columnsPropertyName, List.class); |
| verifyTestDeleteEntityWithDuplicateReferenceListElements(columnsPropertyValue); |
| } |
| |
| protected abstract void verifyTestDeleteEntityWithDuplicateReferenceListElements(List columnsPropertyValue); |
| |
| private String createHrDeptGraph() throws Exception { |
| ITypedReferenceableInstance hrDept = TestUtils.createDeptEg1(typeSystem); |
| |
| List<String> guids = repositoryService.createEntities(hrDept).getCreatedEntities(); |
| Assert.assertNotNull(guids); |
| Assert.assertEquals(guids.size(), 5); |
| |
| return getDepartmentGuid(guids); |
| } |
| |
| private String getDepartmentGuid(List<String> guids) |
| throws RepositoryException, EntityNotFoundException { |
| |
| String hrDeptGuid = null; |
| for (String guid : guids) { |
| ITypedReferenceableInstance entityDefinition = repositoryService.getEntityDefinition(guid); |
| Id id = entityDefinition.getId(); |
| if (id.getTypeName().equals("Department")) { |
| hrDeptGuid = id._getId(); |
| break; |
| } |
| } |
| if (hrDeptGuid == null) { |
| Assert.fail("Entity for type Department not found"); |
| } |
| return hrDeptGuid; |
| } |
| |
| private void createDbTableGraph(String dbName, String tableName) throws Exception { |
| Referenceable databaseInstance = new Referenceable(TestUtils.DATABASE_TYPE); |
| databaseInstance.set("name", dbName); |
| databaseInstance.set("description", "foo database"); |
| |
| ClassType dbType = typeSystem.getDataType(ClassType.class, TestUtils.DATABASE_TYPE); |
| ITypedReferenceableInstance db = dbType.convert(databaseInstance, Multiplicity.REQUIRED); |
| Referenceable tableInstance = new Referenceable(TestUtils.TABLE_TYPE, TestUtils.CLASSIFICATION); |
| tableInstance.set("name", tableName); |
| tableInstance.set("description", "bar table"); |
| tableInstance.set("type", "managed"); |
| Struct traitInstance = (Struct) tableInstance.getTrait(TestUtils.CLASSIFICATION); |
| traitInstance.set("tag", "foundation_etl"); |
| tableInstance.set("tableType", 1); // enum |
| |
| tableInstance.set("database", databaseInstance); |
| ArrayList<Referenceable> columns = new ArrayList<>(); |
| for (int index = 0; index < 5; index++) { |
| Referenceable columnInstance = new Referenceable("column_type"); |
| final String name = "column_" + index; |
| columnInstance.set("name", name); |
| columnInstance.set("type", "string"); |
| columns.add(columnInstance); |
| } |
| tableInstance.set("columns", columns); |
| ClassType tableType = typeSystem.getDataType(ClassType.class, TestUtils.TABLE_TYPE); |
| ITypedReferenceableInstance table = tableType.convert(tableInstance, Multiplicity.REQUIRED); |
| repositoryService.createEntities(db, table); |
| } |
| |
| protected List<AtlasVertex> getVertices(String propertyName, Object value) { |
| AtlasGraph graph = TestUtils.getGraph(); |
| Iterable<AtlasVertex> vertices = graph.getVertices(propertyName, value); |
| List<AtlasVertex> list = new ArrayList<>(); |
| for (AtlasVertex vertex : vertices) { |
| list.add(vertex); |
| } |
| return list; |
| } |
| |
| private Map<String, String> getEmployeeNameGuidMap(final ITypedReferenceableInstance hrDept) throws AtlasException { |
| Object refValue = hrDept.get("employees"); |
| Assert.assertTrue(refValue instanceof List); |
| List<Object> employees = (List<Object>)refValue; |
| Assert.assertEquals(employees.size(), 4); |
| Map<String, String> nameGuidMap = new HashMap<String, String>() {{ |
| put("hr", hrDept.getId()._getId()); |
| }}; |
| |
| for (Object listValue : employees) { |
| Assert.assertTrue(listValue instanceof ITypedReferenceableInstance); |
| ITypedReferenceableInstance employee = (ITypedReferenceableInstance) listValue; |
| nameGuidMap.put((String)employee.get("name"), employee.getId()._getId()); |
| } |
| return nameGuidMap; |
| } |
| |
| /** |
| * Search exception cause chain for specified exception. |
| * |
| * @param thrown root of thrown exception chain |
| * @param expected class of expected exception |
| */ |
| private void verifyExceptionThrown(Exception thrown, Class expected) { |
| |
| boolean exceptionFound = false; |
| Throwable cause = thrown; |
| while (cause != null) { |
| if (expected.isInstance(cause)) { |
| // good |
| exceptionFound = true; |
| break; |
| } |
| else { |
| cause = cause.getCause(); |
| } |
| } |
| if (!exceptionFound) { |
| Assert.fail(expected.getSimpleName() + " was expected but not thrown", thrown); |
| } |
| } |
| } |