Commit 3860c95d by Dave Kantor

ATLAS-1552: automatic update of inverse references in V2 code path (dkantor)

parent 214c1572
...@@ -28,6 +28,7 @@ import org.apache.atlas.model.typedef.AtlasStructDef; ...@@ -28,6 +28,7 @@ import org.apache.atlas.model.typedef.AtlasStructDef;
import org.apache.atlas.model.typedef.AtlasStructDef.AtlasConstraintDef; import org.apache.atlas.model.typedef.AtlasStructDef.AtlasConstraintDef;
import org.apache.atlas.model.typedef.AtlasStructDef.AtlasAttributeDef; import org.apache.atlas.model.typedef.AtlasStructDef.AtlasAttributeDef;
import org.apache.atlas.model.typedef.AtlasStructDef.AtlasAttributeDef.Cardinality; import org.apache.atlas.model.typedef.AtlasStructDef.AtlasAttributeDef.Cardinality;
import org.apache.atlas.type.AtlasStructType.AtlasAttribute;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils; import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
...@@ -164,6 +165,16 @@ public class AtlasStructType extends AtlasType { ...@@ -164,6 +165,16 @@ public class AtlasStructType extends AtlasType {
@Override @Override
public void resolveReferencesPhase2(AtlasTypeRegistry typeRegistry) throws AtlasBaseException { public void resolveReferencesPhase2(AtlasTypeRegistry typeRegistry) throws AtlasBaseException {
super.resolveReferencesPhase2(typeRegistry); super.resolveReferencesPhase2(typeRegistry);
for (AtlasAttribute attribute : allAttributes.values()) {
if (attribute.getInverseRefAttributeName() == null) {
continue;
}
// Set the inverse reference attribute.
AtlasType referencedType = typeRegistry.getType(attribute.getAttributeDef().getTypeName());
AtlasEntityType referencedEntityType = getReferencedEntityType(referencedType);
AtlasAttribute inverseReference = referencedEntityType.getAttribute(attribute.getInverseRefAttributeName());
attribute.setInverseRefAttribute(inverseReference);
}
} }
@Override @Override
...@@ -587,7 +598,8 @@ public class AtlasStructType extends AtlasType { ...@@ -587,7 +598,8 @@ public class AtlasStructType extends AtlasType {
private final String qualifiedName; private final String qualifiedName;
private final String vertexPropertyName; private final String vertexPropertyName;
private final boolean isOwnedRef; private final boolean isOwnedRef;
private final String inverseRefAttribute; private final String inverseRefAttributeName;
private AtlasAttribute inverseRefAttribute;
public AtlasAttribute(AtlasStructType definedInType, AtlasAttributeDef attrDef, AtlasType attributeType) { public AtlasAttribute(AtlasStructType definedInType, AtlasAttributeDef attrDef, AtlasType attributeType) {
this.definedInType = definedInType; this.definedInType = definedInType;
...@@ -616,7 +628,7 @@ public class AtlasStructType extends AtlasType { ...@@ -616,7 +628,7 @@ public class AtlasStructType extends AtlasType {
} }
this.isOwnedRef = isOwnedRef; this.isOwnedRef = isOwnedRef;
this.inverseRefAttribute = inverseRefAttribute; this.inverseRefAttributeName = inverseRefAttribute;
} }
public AtlasStructType getDefinedInType() { return definedInType; } public AtlasStructType getDefinedInType() { return definedInType; }
...@@ -641,7 +653,11 @@ public class AtlasStructType extends AtlasType { ...@@ -641,7 +653,11 @@ public class AtlasStructType extends AtlasType {
public boolean isOwnedRef() { return isOwnedRef; } public boolean isOwnedRef() { return isOwnedRef; }
public String getInverseRefAttribute() { return inverseRefAttribute; } public String getInverseRefAttributeName() { return inverseRefAttributeName; }
public AtlasAttribute getInverseRefAttribute() { return inverseRefAttribute; }
public void setInverseRefAttribute(AtlasAttribute inverseAttr) { inverseRefAttribute = inverseAttr; };
public static String encodePropertyKey(String key) { public static String encodePropertyKey(String key) {
if (StringUtils.isBlank(key)) { if (StringUtils.isBlank(key)) {
......
...@@ -23,15 +23,16 @@ import com.google.common.collect.ImmutableSet; ...@@ -23,15 +23,16 @@ import com.google.common.collect.ImmutableSet;
import org.apache.atlas.model.instance.AtlasEntity; import org.apache.atlas.model.instance.AtlasEntity;
import org.apache.atlas.model.instance.AtlasEntity.AtlasEntitiesWithExtInfo; import org.apache.atlas.model.instance.AtlasEntity.AtlasEntitiesWithExtInfo;
import org.apache.atlas.model.instance.AtlasEntity.AtlasEntityWithExtInfo; import org.apache.atlas.model.instance.AtlasEntity.AtlasEntityWithExtInfo;
import org.apache.atlas.model.instance.AtlasEntityHeader;
import org.apache.atlas.model.instance.AtlasObjectId; import org.apache.atlas.model.instance.AtlasObjectId;
import org.apache.atlas.model.instance.AtlasStruct; import org.apache.atlas.model.instance.AtlasStruct;
import org.apache.atlas.model.typedef.AtlasBaseTypeDef;
import org.apache.atlas.model.typedef.AtlasClassificationDef; import org.apache.atlas.model.typedef.AtlasClassificationDef;
import org.apache.atlas.model.typedef.AtlasEntityDef; import org.apache.atlas.model.typedef.AtlasEntityDef;
import org.apache.atlas.model.typedef.AtlasEnumDef; import org.apache.atlas.model.typedef.AtlasEnumDef;
import org.apache.atlas.model.typedef.AtlasEnumDef.AtlasEnumElementDef; import org.apache.atlas.model.typedef.AtlasEnumDef.AtlasEnumElementDef;
import org.apache.atlas.model.typedef.AtlasStructDef; import org.apache.atlas.model.typedef.AtlasStructDef;
import org.apache.atlas.model.typedef.AtlasStructDef.AtlasAttributeDef; import org.apache.atlas.model.typedef.AtlasStructDef.AtlasAttributeDef;
import org.apache.atlas.model.typedef.AtlasStructDef.AtlasAttributeDef.Cardinality;
import org.apache.atlas.model.typedef.AtlasStructDef.AtlasConstraintDef; import org.apache.atlas.model.typedef.AtlasStructDef.AtlasConstraintDef;
import org.apache.atlas.model.typedef.AtlasTypesDef; import org.apache.atlas.model.typedef.AtlasTypesDef;
import org.apache.atlas.type.AtlasTypeUtil; import org.apache.atlas.type.AtlasTypeUtil;
...@@ -151,6 +152,32 @@ public final class TestUtilsV2 { ...@@ -151,6 +152,32 @@ public final class TestUtilsV2 {
ImmutableList.of(deptTypeDef, personTypeDef, employeeTypeDef, managerTypeDef)); ImmutableList.of(deptTypeDef, personTypeDef, employeeTypeDef, managerTypeDef));
} }
public static AtlasTypesDef defineInverseReferenceTestTypes() {
AtlasEntityDef aDef = AtlasTypeUtil.createClassTypeDef("A", ImmutableSet.<String>of(),
AtlasTypeUtil.createUniqueRequiredAttrDef("name", "string"),
new AtlasAttributeDef("b", "B", true, Cardinality.SINGLE, 0, 1, false, false, Collections.<AtlasConstraintDef>emptyList()), // 1-1
new AtlasAttributeDef("oneB", "B", true, Cardinality.SINGLE, 0, 1, false, false, Collections.<AtlasConstraintDef>emptyList()), // 1-*
new AtlasAttributeDef("manyB", AtlasBaseTypeDef.getArrayTypeName("B"), true, Cardinality.SINGLE, 0, 1, false, false, Collections.<AtlasConstraintDef>emptyList()),
new AtlasAttributeDef("mapToB", AtlasBaseTypeDef.getMapTypeName("string", "B"), true, Cardinality.SINGLE, 0, 1, false, false,
Collections.<AtlasConstraintDef>singletonList(new AtlasConstraintDef(
AtlasConstraintDef.CONSTRAINT_TYPE_INVERSE_REF, Collections.<String, Object>singletonMap(AtlasConstraintDef.CONSTRAINT_PARAM_ATTRIBUTE, "mappedFromA"))))); // *-*
AtlasEntityDef bDef = AtlasTypeUtil.createClassTypeDef("B", ImmutableSet.<String>of(),
AtlasTypeUtil.createUniqueRequiredAttrDef("name", "string"),
new AtlasAttributeDef("a", "A", true, Cardinality.SINGLE, 0, 1, false, false,
Collections.<AtlasConstraintDef>singletonList(new AtlasConstraintDef(
AtlasConstraintDef.CONSTRAINT_TYPE_INVERSE_REF, Collections.<String, Object>singletonMap(AtlasConstraintDef.CONSTRAINT_PARAM_ATTRIBUTE, "b")))),
new AtlasAttributeDef("manyA", AtlasBaseTypeDef.getArrayTypeName("A"), true, Cardinality.SINGLE, 0, 1, false, false,
Collections.<AtlasConstraintDef>singletonList(new AtlasConstraintDef(
AtlasConstraintDef.CONSTRAINT_TYPE_INVERSE_REF, Collections.<String, Object>singletonMap(AtlasConstraintDef.CONSTRAINT_PARAM_ATTRIBUTE, "oneB")))),
new AtlasAttributeDef("manyToManyA", AtlasBaseTypeDef.getArrayTypeName("A"), true, Cardinality.SINGLE, 0, 1, false, false,
Collections.<AtlasConstraintDef>singletonList(new AtlasConstraintDef(
AtlasConstraintDef.CONSTRAINT_TYPE_INVERSE_REF, Collections.<String, Object>singletonMap(AtlasConstraintDef.CONSTRAINT_PARAM_ATTRIBUTE, "manyB")))),
new AtlasAttributeDef("mappedFromA", "A", true, Cardinality.SINGLE, 0, 1, false, false, Collections.<AtlasConstraintDef>emptyList()));
return new AtlasTypesDef(ImmutableList.<AtlasEnumDef>of(), ImmutableList.<AtlasStructDef>of(), ImmutableList.<AtlasClassificationDef>of(), ImmutableList.<AtlasEntityDef>of(aDef, bDef));
}
public static AtlasTypesDef defineValidUpdatedDeptEmployeeTypes() { public static AtlasTypesDef defineValidUpdatedDeptEmployeeTypes() {
String _description = "_description_updated"; String _description = "_description_updated";
AtlasEnumDef orgLevelEnum = AtlasEnumDef orgLevelEnum =
......
...@@ -149,9 +149,10 @@ public class TestAtlasEntityType { ...@@ -149,9 +149,10 @@ public class TestAtlasEntityType {
AtlasEntityType typeColumn = ttr.getEntityTypeByName(TYPE_COLUMN); AtlasEntityType typeColumn = ttr.getEntityTypeByName(TYPE_COLUMN);
assertTrue(typeTable.getAttribute(ATTR_COLUMNS).isOwnedRef()); assertTrue(typeTable.getAttribute(ATTR_COLUMNS).isOwnedRef());
assertNull(typeTable.getAttribute(ATTR_COLUMNS).getInverseRefAttribute()); assertNull(typeTable.getAttribute(ATTR_COLUMNS).getInverseRefAttributeName());
assertFalse(typeColumn.getAttribute(ATTR_TABLE).isOwnedRef()); assertFalse(typeColumn.getAttribute(ATTR_TABLE).isOwnedRef());
assertEquals(typeColumn.getAttribute(ATTR_TABLE).getInverseRefAttribute(), ATTR_COLUMNS); assertEquals(typeColumn.getAttribute(ATTR_TABLE).getInverseRefAttributeName(), ATTR_COLUMNS);
assertEquals(typeColumn.getAttribute(ATTR_TABLE).getInverseRefAttribute(), typeTable.getAttribute(ATTR_COLUMNS));
commit = true; commit = true;
} catch (AtlasBaseException excp) { } catch (AtlasBaseException excp) {
......
...@@ -9,6 +9,7 @@ ATLAS-1060 Add composite indexes for exact match performance improvements for al ...@@ -9,6 +9,7 @@ ATLAS-1060 Add composite indexes for exact match performance improvements for al
ATLAS-1127 Modify creation and modification timestamps to Date instead of Long(sumasai) ATLAS-1127 Modify creation and modification timestamps to Date instead of Long(sumasai)
ALL CHANGES: ALL CHANGES:
ATLAS-1552: automatic update of inverse references in V2 code path (dkantor)
ATLAS-1603: fix to handle null value for object_id type attributes (mneethiraj via kevalbhatt) ATLAS-1603: fix to handle null value for object_id type attributes (mneethiraj via kevalbhatt)
ATLAS 1607: notify listeners on classification addition/deletion (sarathkumarsubramanian via mneethiraj) ATLAS 1607: notify listeners on classification addition/deletion (sarathkumarsubramanian via mneethiraj)
ATLAS-1606: introduced query provider to handle Gremlin version specific queries (apoorvnaik via mneethiraj) ATLAS-1606: introduced query provider to handle Gremlin version specific queries (apoorvnaik via mneethiraj)
......
...@@ -476,7 +476,7 @@ public class AtlasStructDefStoreV1 extends AtlasAbstractDefStoreV1 implements At ...@@ -476,7 +476,7 @@ public class AtlasStructDefStoreV1 extends AtlasAbstractDefStoreV1 implements At
attribInfo.put("isUnique", attributeDef.getIsUnique()); attribInfo.put("isUnique", attributeDef.getIsUnique());
attribInfo.put("isIndexable", attributeDef.getIsIndexable()); attribInfo.put("isIndexable", attributeDef.getIsIndexable());
attribInfo.put("isComposite", attribute.isOwnedRef()); attribInfo.put("isComposite", attribute.isOwnedRef());
attribInfo.put("reverseAttributeName", attribute.getInverseRefAttribute()); attribInfo.put("reverseAttributeName", attribute.getInverseRefAttributeName());
final int lower; final int lower;
final int upper; final int upper;
......
...@@ -378,13 +378,13 @@ public abstract class DeleteHandlerV1 { ...@@ -378,13 +378,13 @@ public abstract class DeleteHandlerV1 {
} }
} }
protected AtlasAttributeDef getAttributeForEdge(String edgeLabel) throws AtlasBaseException { protected AtlasAttribute getAttributeForEdge(String edgeLabel) throws AtlasBaseException {
AtlasEdgeLabel atlasEdgeLabel = new AtlasEdgeLabel(edgeLabel); AtlasEdgeLabel atlasEdgeLabel = new AtlasEdgeLabel(edgeLabel);
AtlasType parentType = typeRegistry.getType(atlasEdgeLabel.getTypeName()); AtlasType parentType = typeRegistry.getType(atlasEdgeLabel.getTypeName());
AtlasStructType parentStructType = (AtlasStructType) parentType; AtlasStructType parentStructType = (AtlasStructType) parentType;
return parentStructType.getAttributeDef(atlasEdgeLabel.getAttributeName()); return parentStructType.getAttribute(atlasEdgeLabel.getAttributeName());
} }
protected abstract void _deleteVertex(AtlasVertex instanceVertex, boolean force); protected abstract void _deleteVertex(AtlasVertex instanceVertex, boolean force);
...@@ -395,12 +395,12 @@ public abstract class DeleteHandlerV1 { ...@@ -395,12 +395,12 @@ public abstract class DeleteHandlerV1 {
* Deletes the edge between outvertex and inVertex. The edge is for attribute attributeName of outVertex * Deletes the edge between outvertex and inVertex. The edge is for attribute attributeName of outVertex
* @param outVertex * @param outVertex
* @param inVertex * @param inVertex
* @param attributeName * @param attribute
* @throws AtlasException * @throws AtlasException
*/ */
protected void deleteEdgeBetweenVertices(AtlasVertex outVertex, AtlasVertex inVertex, String attributeName) throws AtlasBaseException { protected void deleteEdgeBetweenVertices(AtlasVertex outVertex, AtlasVertex inVertex, AtlasAttribute attribute) throws AtlasBaseException {
LOG.debug("Removing edge from {} to {} with attribute name {}", string(outVertex), string(inVertex), LOG.debug("Removing edge from {} to {} with attribute name {}", string(outVertex), string(inVertex),
attributeName); attribute.getName());
String typeName = GraphHelper.getTypeName(outVertex); String typeName = GraphHelper.getTypeName(outVertex);
String outId = GraphHelper.getGuid(outVertex); String outId = GraphHelper.getGuid(outVertex);
...@@ -413,12 +413,11 @@ public abstract class DeleteHandlerV1 { ...@@ -413,12 +413,11 @@ public abstract class DeleteHandlerV1 {
} }
AtlasStructType parentType = (AtlasStructType) typeRegistry.getType(typeName); AtlasStructType parentType = (AtlasStructType) typeRegistry.getType(typeName);
String propertyName = AtlasGraphUtilsV1.getQualifiedAttributePropertyKey(parentType, attributeName); String propertyName = AtlasGraphUtilsV1.getQualifiedAttributePropertyKey(parentType, attribute.getName());
String edgeLabel = EDGE_LABEL_PREFIX + propertyName; String edgeLabel = EDGE_LABEL_PREFIX + propertyName;
AtlasEdge edge = null; AtlasEdge edge = null;
AtlasAttribute attribute = parentType.getAttribute(attributeName); AtlasAttributeDef attrDef = attribute.getAttributeDef();
AtlasAttributeDef attrDef = parentType.getAttributeDef(attributeName);
AtlasType attrType = attribute.getAttributeType(); AtlasType attrType = attribute.getAttributeType();
switch (attrType.getTypeCategory()) { switch (attrType.getTypeCategory()) {
...@@ -466,7 +465,7 @@ public abstract class DeleteHandlerV1 { ...@@ -466,7 +465,7 @@ public abstract class DeleteHandlerV1 {
//for example, when table is deleted, process still references the table //for example, when table is deleted, process still references the table
//but when column is deleted, table will not reference the deleted column //but when column is deleted, table will not reference the deleted column
LOG.debug("Removing edge {} from the array attribute {}", string(elementEdge), LOG.debug("Removing edge {} from the array attribute {}", string(elementEdge),
attributeName); attribute.getName());
// Remove all occurrences of the edge ID from the list. // Remove all occurrences of the edge ID from the list.
// This prevents dangling edge IDs (i.e. edge IDs for deleted edges) // This prevents dangling edge IDs (i.e. edge IDs for deleted edges)
// from the remaining in the list if there are duplicates. // from the remaining in the list if there are duplicates.
...@@ -505,7 +504,7 @@ public abstract class DeleteHandlerV1 { ...@@ -505,7 +504,7 @@ public abstract class DeleteHandlerV1 {
if (shouldUpdateInverseReferences) { if (shouldUpdateInverseReferences) {
//remove this key //remove this key
LOG.debug("Removing edge {}, key {} from the map attribute {}", string(mapEdge), key, LOG.debug("Removing edge {}, key {} from the map attribute {}", string(mapEdge), key,
attributeName); attribute.getName());
keys.remove(key); keys.remove(key);
GraphHelper.setProperty(outVertex, propertyName, keys); GraphHelper.setProperty(outVertex, propertyName, keys);
GraphHelper.setProperty(outVertex, keyPropertyName, null); GraphHelper.setProperty(outVertex, keyPropertyName, null);
...@@ -523,7 +522,7 @@ public abstract class DeleteHandlerV1 { ...@@ -523,7 +522,7 @@ public abstract class DeleteHandlerV1 {
default: default:
throw new IllegalStateException("There can't be an edge from " + GraphHelper.getVertexDetails(outVertex) + " to " throw new IllegalStateException("There can't be an edge from " + GraphHelper.getVertexDetails(outVertex) + " to "
+ GraphHelper.getVertexDetails(inVertex) + " with attribute name " + attributeName + " which is not class/array/map attribute. found " + attrType.getTypeCategory().name()); + GraphHelper.getVertexDetails(inVertex) + " with attribute name " + attribute.getName() + " which is not class/array/map attribute. found " + attrType.getTypeCategory().name());
} }
if (edge != null) { if (edge != null) {
...@@ -544,9 +543,9 @@ public abstract class DeleteHandlerV1 { ...@@ -544,9 +543,9 @@ public abstract class DeleteHandlerV1 {
AtlasEntity.Status edgeState = AtlasGraphUtilsV1.getState(edge); AtlasEntity.Status edgeState = AtlasGraphUtilsV1.getState(edge);
if (edgeState == AtlasEntity.Status.ACTIVE) { if (edgeState == AtlasEntity.Status.ACTIVE) {
//Delete only the active edge references //Delete only the active edge references
AtlasAttributeDef attribute = getAttributeForEdge(edge.getLabel()); AtlasAttribute attribute = getAttributeForEdge(edge.getLabel());
//TODO use delete edge instead?? //TODO use delete edge instead??
deleteEdgeBetweenVertices(edge.getOutVertex(), edge.getInVertex(), attribute.getName()); deleteEdgeBetweenVertices(edge.getOutVertex(), edge.getInVertex(), attribute);
} }
} }
_deleteVertex(instanceVertex, force); _deleteVertex(instanceVertex, force);
......
...@@ -159,8 +159,13 @@ public class EntityGraphMapper { ...@@ -159,8 +159,13 @@ public class EntityGraphMapper {
} }
for (AtlasObjectId id : req.getUpdatedEntityIds()) { for (AtlasObjectId id : req.getUpdatedEntityIds()) {
if (isPartialUpdate) {
resp.addEntity(PARTIAL_UPDATE, constructHeader(id));
}
else {
resp.addEntity(UPDATE, constructHeader(id)); resp.addEntity(UPDATE, constructHeader(id));
} }
}
return resp; return resp;
} }
...@@ -294,8 +299,11 @@ public class EntityGraphMapper { ...@@ -294,8 +299,11 @@ public class EntityGraphMapper {
ctx.setExistingEdge(edge); ctx.setExistingEdge(edge);
newEdge = mapObjectIdValue(ctx, context); newEdge = mapObjectIdValue(ctx, context);
if (ctx.getAttribute().getInverseRefAttribute() != null) {
// Update the inverse reference on the target entity
addInverseReference(ctx, ctx.getAttribute().getInverseRefAttribute(), newEdge);
}
} }
if (currentEdge != null && !currentEdge.equals(newEdge)) { if (currentEdge != null && !currentEdge.equals(newEdge)) {
deleteHandler.deleteEdgeReference(currentEdge, ctx.getAttrType().getTypeCategory(), ctx.getAttribute().isOwnedRef(), true); deleteHandler.deleteEdgeReference(currentEdge, ctx.getAttrType().getTypeCategory(), ctx.getAttribute().isOwnedRef(), true);
} }
...@@ -314,6 +322,68 @@ public class EntityGraphMapper { ...@@ -314,6 +322,68 @@ public class EntityGraphMapper {
} }
} }
private void addInverseReference(AttributeMutationContext ctx, AtlasAttribute inverseAttribute, AtlasEdge edge) throws AtlasBaseException {
AtlasStructType inverseType = inverseAttribute.getDefinedInType();
String propertyName = AtlasGraphUtilsV1.getQualifiedAttributePropertyKey(inverseType, inverseAttribute.getName());
AtlasVertex vertex = edge.getOutVertex();
AtlasVertex inverseVertex = edge.getInVertex();
String inverseEdgeLabel = AtlasGraphUtilsV1.getEdgeLabel(propertyName);
AtlasEdge inverseEdge = graphHelper.getEdgeForLabel(inverseVertex, inverseEdgeLabel);
AtlasEdge newEdge;
try {
newEdge = graphHelper.getOrCreateEdge(inverseVertex, vertex, inverseEdgeLabel);
} catch (RepositoryException e) {
throw new AtlasBaseException(AtlasErrorCode.INTERNAL_ERROR, e);
}
boolean inverseUpdated = true;
switch (inverseAttribute.getAttributeType().getTypeCategory()) {
case OBJECT_ID_TYPE:
if (inverseEdge != null) {
if (!inverseEdge.equals(newEdge)) {
// Disconnect old reference
deleteHandler.deleteEdgeReference(inverseEdge, inverseAttribute.getAttributeType().getTypeCategory(),
inverseAttribute.isOwnedRef(), true);
}
else {
// Edge already exists for this attribute between these vertices.
inverseUpdated = false;
}
}
break;
case ARRAY:
// Add edge ID to property value
List<String> elements = inverseVertex.getProperty(propertyName, List.class);
if (elements == null) {
elements = new ArrayList<>();
elements.add(newEdge.getId().toString());
inverseVertex.setProperty(propertyName, elements);
}
else {
if (!elements.contains(newEdge.getId().toString())) {
elements.add(newEdge.getId().toString());
inverseVertex.setProperty(propertyName, elements);
}
else {
// Property value list already contains the edge ID.
inverseUpdated = false;
}
}
break;
default:
break;
}
if (inverseUpdated) {
updateModificationMetadata(inverseVertex);
AtlasObjectId inverseEntityId = new AtlasObjectId(AtlasGraphUtilsV1.getIdFromVertex(inverseVertex), inverseType.getTypeName());
RequestContextV1.get().recordEntityUpdate(inverseEntityId);
}
}
private Object mapPrimitiveValue(AttributeMutationContext ctx) { private Object mapPrimitiveValue(AttributeMutationContext ctx) {
AtlasGraphUtilsV1.setProperty(ctx.getReferringVertex(), ctx.getVertexProperty(), ctx.getValue()); AtlasGraphUtilsV1.setProperty(ctx.getReferringVertex(), ctx.getVertexProperty(), ctx.getValue());
...@@ -429,6 +499,8 @@ public class EntityGraphMapper { ...@@ -429,6 +499,8 @@ public class EntityGraphMapper {
} }
if (MapUtils.isNotEmpty(newVal)) { if (MapUtils.isNotEmpty(newVal)) {
boolean isReference = AtlasGraphUtilsV1.isReference(mapType.getValueType());
AtlasAttribute inverseRefAttribute = attribute.getInverseRefAttribute();
for (Map.Entry<Object, Object> entry : newVal.entrySet()) { for (Map.Entry<Object, Object> entry : newVal.entrySet()) {
String key = entry.getKey().toString(); String key = entry.getKey().toString();
String propertyName = GraphHelper.getQualifiedNameForMapKey(ctx.getVertexProperty(), GraphHelper.encodePropertyKey(key)); String propertyName = GraphHelper.getQualifiedNameForMapKey(ctx.getVertexProperty(), GraphHelper.encodePropertyKey(key));
...@@ -441,6 +513,13 @@ public class EntityGraphMapper { ...@@ -441,6 +513,13 @@ public class EntityGraphMapper {
setMapValueProperty(mapType.getValueType(), ctx.getReferringVertex(), propertyName, newEntry); setMapValueProperty(mapType.getValueType(), ctx.getReferringVertex(), propertyName, newEntry);
newMap.put(key, newEntry); newMap.put(key, newEntry);
// If value type indicates this attribute is a reference, and the attribute has an inverse reference attribute,
// update the inverse reference value.
if (isReference && newEntry instanceof AtlasEdge && inverseRefAttribute != null) {
AtlasEdge newEdge = (AtlasEdge) newEntry;
addInverseReference(mapCtx, inverseRefAttribute, newEdge);
}
} }
} }
...@@ -476,7 +555,8 @@ public class EntityGraphMapper { ...@@ -476,7 +555,8 @@ public class EntityGraphMapper {
AtlasArrayType arrType = (AtlasArrayType) attribute.getAttributeType(); AtlasArrayType arrType = (AtlasArrayType) attribute.getAttributeType();
AtlasType elementType = arrType.getElementType(); AtlasType elementType = arrType.getElementType();
List<Object> currentElements = getArrayElementsProperty(elementType, ctx.getReferringVertex(), ctx.getVertexProperty()); List<Object> currentElements = getArrayElementsProperty(elementType, ctx.getReferringVertex(), ctx.getVertexProperty());
boolean isReference = AtlasGraphUtilsV1.isReference(elementType);
AtlasAttribute inverseRefAttribute = attribute.getInverseRefAttribute();
List<Object> newElementsCreated = new ArrayList<>(); List<Object> newElementsCreated = new ArrayList<>();
if (CollectionUtils.isNotEmpty(newElements)) { if (CollectionUtils.isNotEmpty(newElements)) {
...@@ -486,12 +566,16 @@ public class EntityGraphMapper { ...@@ -486,12 +566,16 @@ public class EntityGraphMapper {
ctx.getVertexProperty(), elementType, existingEdge); ctx.getVertexProperty(), elementType, existingEdge);
Object newEntry = mapCollectionElementsToVertex(arrCtx, context); Object newEntry = mapCollectionElementsToVertex(arrCtx, context);
if (isReference && newEntry instanceof AtlasEdge && inverseRefAttribute != null) {
// Update the inverse reference value.
AtlasEdge newEdge = (AtlasEdge) newEntry;
addInverseReference(arrCtx, inverseRefAttribute, newEdge);
}
newElementsCreated.add(newEntry); newElementsCreated.add(newEntry);
} }
} }
if (AtlasGraphUtilsV1.isReference(elementType)) { if (isReference) {
List<AtlasEdge> additionalEdges = removeUnusedArrayEntries(attribute, (List) currentElements, (List) newElementsCreated); List<AtlasEdge> additionalEdges = removeUnusedArrayEntries(attribute, (List) currentElements, (List) newElementsCreated);
newElementsCreated.addAll(additionalEdges); newElementsCreated.addAll(additionalEdges);
} }
......
...@@ -406,20 +406,28 @@ public abstract class AtlasDeleteHandlerV1Test { ...@@ -406,20 +406,28 @@ public abstract class AtlasDeleteHandlerV1Test {
Assert.assertTrue(modificationTimestampPostUpdate < modificationTimestampPost2ndUpdate); Assert.assertTrue(modificationTimestampPostUpdate < modificationTimestampPost2ndUpdate);
ITypedReferenceableInstance julius = metadataService.getEntityDefinition(juliusEmployeeCreated.getGuid()); ITypedReferenceableInstance julius = metadataService.getEntityDefinition(juliusEmployeeCreated.getGuid());
Id juliusGuid = julius.getId(); Id juliusId = julius.getId();
init(); init();
maxEmployee.setAttribute("manager", AtlasTypeUtil.getAtlasObjectId(juliusEmployeeCreated)); maxEmployee.setAttribute("manager", AtlasTypeUtil.getAtlasObjectId(juliusEmployeeCreated));
entityResult = entityStore.createOrUpdate(new AtlasEntityStream(maxEmployee), false); entityResult = entityStore.createOrUpdate(new AtlasEntityStream(maxEmployee), false);
//TODO ATLAS-499 should have updated julius' subordinates assertEquals(entityResult.getUpdatedEntities().size(), 3);
assertEquals(entityResult.getUpdatedEntities().size(), 2); List<String> updatedGuids = extractGuids(entityResult.getUpdatedEntities());
assertTrue(extractGuids(entityResult.getUpdatedEntities()).contains(maxGuid)); assertTrue(updatedGuids.contains(maxGuid));
assertTrue(extractGuids(entityResult.getUpdatedEntities()).contains(janeEmployeeCreated.getGuid())); assertTrue(updatedGuids.contains(janeEmployeeCreated.getGuid()));
// Should have updated julius to add max in subordinates list.
assertTrue(updatedGuids.contains(juliusEmployeeCreated.getGuid()));
// Verify the update was applied correctly - julius should now be max's manager. // Verify the update was applied correctly - julius should now be max's manager and max should be julius' subordinate.
max = metadataService.getEntityDefinition(maxGuid); max = metadataService.getEntityDefinition(maxGuid);
refTarget = (ITypedReferenceableInstance) max.get("manager"); refTarget = (ITypedReferenceableInstance) max.get("manager");
Assert.assertEquals(refTarget.getId()._getId(), juliusGuid._getId()); Assert.assertEquals(refTarget.getId()._getId(), juliusId._getId());
julius = metadataService.getEntityDefinition(juliusId._getId());
Object value = julius.get("subordinates");
Assert.assertTrue(value instanceof List);
List<ITypedReferenceableInstance> refList = (List<ITypedReferenceableInstance>) value;
Assert.assertEquals(refList.size(), 1);
Assert.assertEquals(refList.get(0).getId()._getId(), maxGuid);
assertTestUpdateEntity_MultiplicityOneNonCompositeReference(janeEmployeeCreated.getGuid()); assertTestUpdateEntity_MultiplicityOneNonCompositeReference(janeEmployeeCreated.getGuid());
} }
......
/**
* 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.store.graph.v1;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import java.util.Map;
import org.apache.atlas.model.instance.AtlasEntity;
import org.apache.atlas.model.instance.AtlasObjectId;
import org.apache.atlas.type.AtlasTypeRegistry;
import org.apache.atlas.type.AtlasTypeUtil;
import com.google.common.collect.ImmutableList;
/**
* Inverse reference update test with {@link HardDeleteHandlerV1}
*/
public class InverseReferenceUpdateHardDeleteV1Test extends InverseReferenceUpdateV1Test {
@Override
protected DeleteHandlerV1 getDeleteHandler(AtlasTypeRegistry typeRegistry) {
return new HardDeleteHandlerV1(typeRegistry);
}
@Override
protected void verify_testInverseReferenceAutoUpdate_NonComposite_OneToMany(AtlasEntity jane) throws Exception {
// Max should have been removed from the subordinates list, leaving only John.
verifyReferenceList(jane, "subordinates", ImmutableList.of(nameIdMap.get("John")));
}
@Override
protected void verify_testInverseReferenceAutoUpdate_NonCompositeManyToOne(AtlasEntity a1, AtlasEntity a2, AtlasEntity a3, AtlasEntity b) {
verifyReferenceValue(a1, "oneB", null);
verifyReferenceValue(a2, "oneB", null);
verifyReferenceList(b, "manyA", ImmutableList.of(AtlasTypeUtil.getAtlasObjectId(a3)));
}
@Override
protected void verify_testInverseReferenceAutoUpdate_NonComposite_OneToOne(AtlasEntity a1, AtlasEntity b) {
verifyReferenceValue(a1, "b", null);
}
@Override
protected void verify_testInverseReferenceAutoUpdate_Map(AtlasEntity a1, AtlasEntity b1,
AtlasEntity b2, AtlasEntity b3) {
Object value = a1.getAttribute("mapToB");
assertTrue(value instanceof Map);
Map<String, AtlasObjectId> refMap = (Map<String, AtlasObjectId>) value;
assertEquals(refMap.size(), 1);
AtlasObjectId referencedEntityId = refMap.get("b3");
assertEquals(referencedEntityId, AtlasTypeUtil.getAtlasObjectId(b3));
verifyReferenceValue(b1, "mappedFromA", null);
verifyReferenceValue(b2, "mappedFromA", null);
}
}
/**
* 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.store.graph.v1;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import java.util.Map;
import org.apache.atlas.model.instance.AtlasEntity;
import org.apache.atlas.model.instance.AtlasObjectId;
import org.apache.atlas.type.AtlasTypeRegistry;
import org.apache.atlas.type.AtlasTypeUtil;
import com.google.common.collect.ImmutableList;
/**
* Inverse reference update test with {@link SoftDeleteHandlerV1}
*/
public class InverseReferenceUpdateSoftDeleteV1Test extends InverseReferenceUpdateV1Test {
@Override
protected DeleteHandlerV1 getDeleteHandler(AtlasTypeRegistry typeRegistry) {
return new SoftDeleteHandlerV1(typeRegistry);
}
@Override
protected void verify_testInverseReferenceAutoUpdate_NonComposite_OneToMany(AtlasEntity jane)
throws Exception {
// Max is still in the subordinates list, as the edge still exists with state DELETED
verifyReferenceList(jane, "subordinates", ImmutableList.of(nameIdMap.get("John"), nameIdMap.get("Max")));
}
@Override
protected void verify_testInverseReferenceAutoUpdate_NonCompositeManyToOne(AtlasEntity a1,
AtlasEntity a2, AtlasEntity a3, AtlasEntity b) {
verifyReferenceValue(a1, "oneB", b.getGuid());
verifyReferenceValue(a2, "oneB", b.getGuid());
verifyReferenceList(b, "manyA", ImmutableList.of(AtlasTypeUtil.getAtlasObjectId(a1), AtlasTypeUtil.getAtlasObjectId(a2), AtlasTypeUtil.getAtlasObjectId(a3)));
}
@Override
protected void verify_testInverseReferenceAutoUpdate_NonComposite_OneToOne(AtlasEntity a1, AtlasEntity b) {
verifyReferenceValue(a1, "b", b.getGuid());
}
@Override
protected void verify_testInverseReferenceAutoUpdate_Map(AtlasEntity a1, AtlasEntity b1,
AtlasEntity b2, AtlasEntity b3) {
Object value = a1.getAttribute("mapToB");
assertTrue(value instanceof Map);
Map<String, AtlasObjectId> refMap = (Map<String, AtlasObjectId>) value;
assertEquals(refMap.size(), 3);
AtlasObjectId referencedEntityId = refMap.get("b3");
assertEquals(referencedEntityId, AtlasTypeUtil.getAtlasObjectId(b3));
verifyReferenceValue(b1, "mappedFromA", a1.getGuid());
verifyReferenceValue(b2, "mappedFromA", a1.getGuid());
}
}
/**
* 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.store.graph.v1;
import static org.apache.atlas.TestUtils.NAME;
import static org.mockito.Mockito.mock;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.apache.atlas.RepositoryMetadataModule;
import org.apache.atlas.RequestContextV1;
import org.apache.atlas.TestUtils;
import org.apache.atlas.TestUtilsV2;
import org.apache.atlas.model.instance.AtlasEntity;
import org.apache.atlas.model.instance.AtlasEntity.AtlasEntitiesWithExtInfo;
import org.apache.atlas.model.instance.AtlasEntityHeader;
import org.apache.atlas.model.instance.AtlasObjectId;
import org.apache.atlas.model.instance.EntityMutationResponse;
import org.apache.atlas.model.typedef.AtlasTypesDef;
import org.apache.atlas.repository.graph.AtlasGraphProvider;
import org.apache.atlas.repository.graph.GraphBackedSearchIndexer;
import org.apache.atlas.repository.store.bootstrap.AtlasTypeDefStoreInitializer;
import org.apache.atlas.repository.store.graph.AtlasEntityStore;
import org.apache.atlas.services.MetadataService;
import org.apache.atlas.store.AtlasTypeDefStore;
import org.apache.atlas.type.AtlasEntityType;
import org.apache.atlas.type.AtlasTypeRegistry;
import org.apache.atlas.type.AtlasTypeUtil;
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 com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
/**
* Test automatic inverse reference updating in V1 (V2?) code path.
*
*/
@Guice(modules = RepositoryMetadataModule.class)
public abstract class InverseReferenceUpdateV1Test {
@Inject
AtlasTypeRegistry typeRegistry;
@Inject
AtlasTypeDefStore typeDefStore;
AtlasEntityStore entityStore;
@Inject
MetadataService metadataService;
@Inject
DeleteHandlerV1 deleteHandler;
private AtlasEntitiesWithExtInfo deptEntity;
AtlasEntityChangeNotifier mockChangeNotifier = mock(AtlasEntityChangeNotifier.class);
protected Map<String, AtlasObjectId> nameIdMap = new HashMap<>();
protected abstract DeleteHandlerV1 getDeleteHandler(AtlasTypeRegistry typeRegistry);
@BeforeClass
public void setUp() throws Exception {
metadataService = TestUtils.addSessionCleanupWrapper(metadataService);
new GraphBackedSearchIndexer(typeRegistry);
AtlasTypesDef[] testTypesDefs = new AtlasTypesDef[] { TestUtilsV2.defineDeptEmployeeTypes(),
TestUtilsV2.defineInverseReferenceTestTypes()
};
for (AtlasTypesDef typesDef : testTypesDefs) {
AtlasTypesDef typesToCreate = AtlasTypeDefStoreInitializer.getTypesToCreate(typesDef, typeRegistry);
if (!typesToCreate.isEmpty()) {
typeDefStore.createTypesDef(typesToCreate);
}
}
deptEntity = TestUtilsV2.createDeptEg2();
DeleteHandlerV1 deleteHandler = getDeleteHandler(typeRegistry);
entityStore = new AtlasEntityStoreV1(deleteHandler, typeRegistry, mockChangeNotifier);
init();
EntityMutationResponse response = entityStore.createOrUpdate(new AtlasEntityStream(deptEntity), false);
for (AtlasEntityHeader entityHeader : response.getCreatedEntities()) {
nameIdMap.put((String)entityHeader.getAttribute(NAME), AtlasTypeUtil.getAtlasObjectId(entityHeader));
}
}
@AfterClass
public void clear() {
AtlasGraphProvider.cleanup();
}
@BeforeMethod
public void init() throws Exception {
RequestContextV1.clear();
}
@Test
public void testInverseReferenceAutoUpdate_NonComposite_OneToMany() throws Exception {
AtlasObjectId juliusId = nameIdMap.get("Julius");
// Change Max's Employee.manager reference to Julius and apply the change as a partial update.
// This should also update Julius to add Max to the inverse Manager.subordinates reference.
AtlasEntity maxEntityForUpdate = new AtlasEntity(TestUtilsV2.EMPLOYEE_TYPE);
maxEntityForUpdate.setAttribute("manager", juliusId);
AtlasEntityType employeeType = typeRegistry.getEntityTypeByName(TestUtilsV2.EMPLOYEE_TYPE);
Map<String, Object> uniqAttributes = Collections.<String, Object>singletonMap("name", "Max");
EntityMutationResponse updateResponse = entityStore.updateByUniqueAttributes(employeeType, uniqAttributes , maxEntityForUpdate);
List<AtlasEntityHeader> partialUpdatedEntities = updateResponse.getPartialUpdatedEntities();
// 3 entities should have been updated:
// * Max to change the Employee.manager reference
// * Julius to add Max to Manager.subordinates
// * Jane to remove Max from Manager.subordinates
assertEquals(partialUpdatedEntities.size(), 3);
AtlasObjectId maxId = nameIdMap.get("Max");
String janeGuid = nameIdMap.get("Jane").getGuid();
AtlasEntitiesWithExtInfo storedEntities = entityStore.getByIds(ImmutableList.of(maxId.getGuid(), juliusId.getGuid(), janeGuid));
AtlasEntity storedEntity = storedEntities.getEntity(maxId.getGuid());
verifyReferenceValue(storedEntity, "manager", juliusId.getGuid());
storedEntity = storedEntities.getEntity(juliusId.getGuid());
verifyReferenceList(storedEntity, "subordinates", ImmutableList.of(maxId));
storedEntity = storedEntities.getEntity(janeGuid);
verify_testInverseReferenceAutoUpdate_NonComposite_OneToMany(storedEntity);
}
protected abstract void verify_testInverseReferenceAutoUpdate_NonComposite_OneToMany(AtlasEntity jane) throws Exception;
@Test
public void testInverseReferenceAutoUpdate_NonCompositeManyToOne() throws Exception {
AtlasEntityType bType = typeRegistry.getEntityTypeByName("B");
AtlasEntity a1 = new AtlasEntity("A");
a1.setAttribute(NAME, TestUtils.randomString());
AtlasEntity a2 = new AtlasEntity("A");
a2.setAttribute(NAME, TestUtils.randomString());
AtlasEntity a3 = new AtlasEntity("A");
a3.setAttribute(NAME, TestUtils.randomString());
AtlasEntity b = new AtlasEntity("B");
b.setAttribute(NAME, TestUtils.randomString());
AtlasEntitiesWithExtInfo atlasEntitiesWithExtInfo = new AtlasEntitiesWithExtInfo();
atlasEntitiesWithExtInfo.addEntity(a1);
atlasEntitiesWithExtInfo.addEntity(a2);
atlasEntitiesWithExtInfo.addEntity(a3);
atlasEntitiesWithExtInfo.addEntity(b);
AtlasEntityStream entityStream = new AtlasEntityStream(atlasEntitiesWithExtInfo);
EntityMutationResponse response = entityStore.createOrUpdate(entityStream , false);
AtlasEntity bForPartialUpdate = new AtlasEntity("B");
bForPartialUpdate.setAttribute("manyA", ImmutableList.of(AtlasTypeUtil.getAtlasObjectId(a1), AtlasTypeUtil.getAtlasObjectId(a2)));
init();
response = entityStore.updateByUniqueAttributes(bType, Collections.<String, Object>singletonMap(NAME, b.getAttribute(NAME)), bForPartialUpdate);
List<AtlasEntityHeader> partialUpdatedEntities = response.getPartialUpdatedEntities();
// Verify 3 entities were updated:
// * set b.manyA reference to a1 and a2
// * set inverse a1.oneB reference to b
// * set inverse a2.oneB reference to b
assertEquals(partialUpdatedEntities.size(), 3);
AtlasEntitiesWithExtInfo storedEntities = entityStore.getByIds(ImmutableList.of(a1.getGuid(), a2.getGuid(), b.getGuid()));
AtlasEntity storedEntity = storedEntities.getEntity(a1.getGuid());
verifyReferenceValue(storedEntity, "oneB", b.getGuid());
storedEntity = storedEntities.getEntity(a2.getGuid());
verifyReferenceValue(storedEntity, "oneB", b.getGuid());
storedEntity = storedEntities.getEntity(b.getGuid());
verifyReferenceList(storedEntity, "manyA", ImmutableList.of(AtlasTypeUtil.getAtlasObjectId(a1), AtlasTypeUtil.getAtlasObjectId(a2)));
bForPartialUpdate.setAttribute("manyA", ImmutableList.of(AtlasTypeUtil.getAtlasObjectId(a3)));
init();
response = entityStore.updateByUniqueAttributes(bType, Collections.<String, Object>singletonMap(NAME, b.getAttribute(NAME)), bForPartialUpdate);
partialUpdatedEntities = response.getPartialUpdatedEntities();
// Verify 4 entities were updated:
// * set b.manyA reference to a3
// * set inverse a3.oneB reference to b
// * disconnect inverse a1.oneB reference to b
// * disconnect inverse a2.oneB reference to b
assertEquals(partialUpdatedEntities.size(), 4);
init();
storedEntities = entityStore.getByIds(ImmutableList.of(a1.getGuid(), a2.getGuid(), a3.getGuid(), b.getGuid()));
verifyReferenceValue(storedEntities.getEntity(a3.getGuid()), "oneB", b.getGuid());
verify_testInverseReferenceAutoUpdate_NonCompositeManyToOne(storedEntities.getEntity(a1.getGuid()), storedEntities.getEntity(a2.getGuid()),
storedEntities.getEntity(a3.getGuid()), storedEntities.getEntity(b.getGuid()));
}
protected abstract void verify_testInverseReferenceAutoUpdate_NonCompositeManyToOne(AtlasEntity a1, AtlasEntity a2, AtlasEntity a3, AtlasEntity b);
@Test
public void testInverseReferenceAutoUpdate_NonComposite_OneToOne() throws Exception {
AtlasEntityType bType = typeRegistry.getEntityTypeByName("B");
AtlasEntity a1 = new AtlasEntity("A");
a1.setAttribute(NAME, TestUtils.randomString());
AtlasEntity a2 = new AtlasEntity("A");
a2.setAttribute(NAME, TestUtils.randomString());
AtlasEntity b = new AtlasEntity("B");
b.setAttribute(NAME, TestUtils.randomString());
AtlasEntitiesWithExtInfo atlasEntitiesWithExtInfo = new AtlasEntitiesWithExtInfo();
atlasEntitiesWithExtInfo.addEntity(a1);
atlasEntitiesWithExtInfo.addEntity(a2);
atlasEntitiesWithExtInfo.addEntity(b);
AtlasEntityStream entityStream = new AtlasEntityStream(atlasEntitiesWithExtInfo);
EntityMutationResponse response = entityStore.createOrUpdate(entityStream , false);
AtlasEntity bForPartialUpdate = new AtlasEntity("B");
bForPartialUpdate.setAttribute("a", AtlasTypeUtil.getAtlasObjectId(a1));
init();
response = entityStore.updateByUniqueAttributes(bType, Collections.<String, Object>singletonMap(NAME, b.getAttribute(NAME)), bForPartialUpdate);
List<AtlasEntityHeader> partialUpdatedEntities = response.getPartialUpdatedEntities();
// Verify 2 entities were updated:
// * set b.a reference to a1
// * set inverse a1.b reference to b
assertEquals(partialUpdatedEntities.size(), 2);
AtlasEntitiesWithExtInfo storedEntities = entityStore.getByIds(ImmutableList.of(a1.getGuid(), b.getGuid()));
AtlasEntity storedEntity = storedEntities.getEntity(a1.getGuid());
verifyReferenceValue(storedEntity, "b", b.getGuid());
storedEntity = storedEntities.getEntity(b.getGuid());
verifyReferenceValue(storedEntity, "a", a1.getGuid());
// Update b.a to reference a2.
bForPartialUpdate.setAttribute("a", AtlasTypeUtil.getAtlasObjectId(a2));
init();
response = entityStore.updateByUniqueAttributes(bType, Collections.<String, Object>singletonMap(NAME, b.getAttribute(NAME)), bForPartialUpdate);
partialUpdatedEntities = response.getPartialUpdatedEntities();
// Verify 3 entities were updated:
// * set b.a reference to a2
// * set a2.b reference to b
// * disconnect a1.b reference
assertEquals(partialUpdatedEntities.size(), 3);
storedEntities = entityStore.getByIds(ImmutableList.of(a1.getGuid(), a2.getGuid(), b.getGuid()));
storedEntity = storedEntities.getEntity(a2.getGuid());
verifyReferenceValue(storedEntity, "b", b.getGuid());
storedEntity = storedEntities.getEntity(b.getGuid());
verifyReferenceValue(storedEntity, "a", a2.getGuid());
storedEntity = storedEntities.getEntity(a1.getGuid());
Object refValue = storedEntity.getAttribute("b");
verify_testInverseReferenceAutoUpdate_NonComposite_OneToOne(storedEntities.getEntity(a1.getGuid()), storedEntities.getEntity(b.getGuid()));
}
protected abstract void verify_testInverseReferenceAutoUpdate_NonComposite_OneToOne(AtlasEntity a1, AtlasEntity b);
@Test
public void testInverseReferenceAutoUpdate_NonComposite_ManyToMany() throws Exception {
AtlasEntityType bType = typeRegistry.getEntityTypeByName("B");
AtlasEntity a1 = new AtlasEntity("A");
a1.setAttribute(NAME, TestUtils.randomString());
AtlasEntity a2 = new AtlasEntity("A");
a2.setAttribute(NAME, TestUtils.randomString());
AtlasEntity a3 = new AtlasEntity("A");
a3.setAttribute(NAME, TestUtils.randomString());
AtlasEntity b1 = new AtlasEntity("B");
b1.setAttribute(NAME, TestUtils.randomString());
AtlasEntity b2 = new AtlasEntity("B");
b2.setAttribute(NAME, TestUtils.randomString());
AtlasEntitiesWithExtInfo atlasEntitiesWithExtInfo = new AtlasEntitiesWithExtInfo();
atlasEntitiesWithExtInfo.addEntity(a1);
atlasEntitiesWithExtInfo.addEntity(a2);
atlasEntitiesWithExtInfo.addEntity(a3);
atlasEntitiesWithExtInfo.addEntity(b1);
atlasEntitiesWithExtInfo.addEntity(b2);
AtlasEntityStream entityStream = new AtlasEntityStream(atlasEntitiesWithExtInfo);
EntityMutationResponse response = entityStore.createOrUpdate(entityStream , false);
AtlasEntity b1ForPartialUpdate = new AtlasEntity("B");
b1ForPartialUpdate.setAttribute("manyToManyA", ImmutableList.of(AtlasTypeUtil.getAtlasObjectId(a1), AtlasTypeUtil.getAtlasObjectId(a2)));
init();
response = entityStore.updateByUniqueAttributes(bType, Collections.<String, Object>singletonMap(NAME, b1.getAttribute(NAME)), b1ForPartialUpdate);
List<AtlasEntityHeader> partialUpdatedEntities = response.getPartialUpdatedEntities();
assertEquals(partialUpdatedEntities.size(), 3);
AtlasEntitiesWithExtInfo storedEntities = entityStore.getByIds(ImmutableList.of(a1.getGuid(), a2.getGuid(), b1.getGuid()));
AtlasEntity storedEntity = storedEntities.getEntity(b1.getGuid());
verifyReferenceList(storedEntity, "manyToManyA", ImmutableList.of(AtlasTypeUtil.getAtlasObjectId(a1), AtlasTypeUtil.getAtlasObjectId(a2)));
storedEntity = storedEntities.getEntity(a1.getGuid());
verifyReferenceList(storedEntity, "manyB", ImmutableList.of(AtlasTypeUtil.getAtlasObjectId(b1)));
storedEntity = storedEntities.getEntity(a2.getGuid());
verifyReferenceList(storedEntity, "manyB", ImmutableList.of(AtlasTypeUtil.getAtlasObjectId(b1)));
}
@Test
public void testInverseReferenceAutoUpdate_Map() throws Exception {
AtlasEntity a1 = new AtlasEntity("A");
a1.setAttribute(NAME, TestUtils.randomString());
AtlasEntity b1 = new AtlasEntity("B");
b1.setAttribute(NAME, TestUtils.randomString());
AtlasEntity b2 = new AtlasEntity("B");
b2.setAttribute(NAME, TestUtils.randomString());
AtlasEntity b3 = new AtlasEntity("B");
b3.setAttribute(NAME, TestUtils.randomString());
AtlasEntitiesWithExtInfo atlasEntitiesWithExtInfo = new AtlasEntitiesWithExtInfo();
atlasEntitiesWithExtInfo.addEntity(a1);
atlasEntitiesWithExtInfo.addEntity(b1);
atlasEntitiesWithExtInfo.addEntity(b2);
atlasEntitiesWithExtInfo.addEntity(b3);
AtlasEntityStream entityStream = new AtlasEntityStream(atlasEntitiesWithExtInfo);
EntityMutationResponse response = entityStore.createOrUpdate(entityStream , false);
AtlasEntityType aType = typeRegistry.getEntityTypeByName("A");
AtlasEntity aForPartialUpdate = new AtlasEntity("A");
aForPartialUpdate.setAttribute("mapToB", ImmutableMap.<String, AtlasObjectId>of("b1", AtlasTypeUtil.getAtlasObjectId(b1), "b2", AtlasTypeUtil.getAtlasObjectId(b2)));
init();
response = entityStore.updateByUniqueAttributes(aType, Collections.<String, Object>singletonMap(NAME, a1.getAttribute(NAME)), aForPartialUpdate);
List<AtlasEntityHeader> partialUpdatedEntities = response.getPartialUpdatedEntities();
// Verify 3 entities were updated:
// * set a1.mapToB to "b1"->b1, "b2"->b2
// * set b1.mappedFromA to a1
// * set b2.mappedFromA to a1
assertEquals(partialUpdatedEntities.size(), 3);
AtlasEntitiesWithExtInfo storedEntities = entityStore.getByIds(ImmutableList.of(a1.getGuid(), b2.getGuid(), b1.getGuid()));
AtlasEntity storedEntity = storedEntities.getEntity(a1.getGuid());
Object value = storedEntity.getAttribute("mapToB");
assertTrue(value instanceof Map);
Map<String, AtlasObjectId> refMap = (Map<String, AtlasObjectId>) value;
assertEquals(refMap.size(), 2);
AtlasObjectId referencedEntityId = refMap.get("b1");
assertEquals(referencedEntityId, AtlasTypeUtil.getAtlasObjectId(b1));
referencedEntityId = refMap.get("b2");
assertEquals(referencedEntityId, AtlasTypeUtil.getAtlasObjectId(b2));
storedEntity = storedEntities.getEntity(b1.getGuid());
verifyReferenceValue(storedEntity, "mappedFromA", a1.getGuid());
storedEntity = storedEntities.getEntity(b2.getGuid());
verifyReferenceValue(storedEntity, "mappedFromA", a1.getGuid());
aForPartialUpdate.setAttribute("mapToB", ImmutableMap.<String, AtlasObjectId>of("b3", AtlasTypeUtil.getAtlasObjectId(b3)));
init();
response = entityStore.updateByUniqueAttributes(aType, Collections.<String, Object>singletonMap(NAME, a1.getAttribute(NAME)), aForPartialUpdate);
partialUpdatedEntities = response.getPartialUpdatedEntities();
// Verify 4 entities were updated:
// * set a1.mapToB to "b3"->b3
// * set b3.mappedFromA to a1
// * disconnect b1.mappedFromA
// * disconnect b2.mappedFromA
assertEquals(partialUpdatedEntities.size(), 4);
storedEntities = entityStore.getByIds(ImmutableList.of(a1.getGuid(), b2.getGuid(), b1.getGuid(), b3.getGuid()));
AtlasEntity storedB3 = storedEntities.getEntity(b3.getGuid());
verifyReferenceValue(storedB3, "mappedFromA", a1.getGuid());
verify_testInverseReferenceAutoUpdate_Map(storedEntities.getEntity(a1.getGuid()), storedEntities.getEntity(b1.getGuid()), storedEntities.getEntity(b2.getGuid()), storedB3);
}
protected abstract void verify_testInverseReferenceAutoUpdate_Map(AtlasEntity a1, AtlasEntity b1, AtlasEntity b2, AtlasEntity b3);
protected void verifyReferenceValue(AtlasEntity entity, String refName, String expectedGuid) {
Object refValue = entity.getAttribute(refName);
if (expectedGuid == null) {
assertNull(refValue);
}
else {
assertTrue(refValue instanceof AtlasObjectId);
AtlasObjectId referencedObjectId = (AtlasObjectId) refValue;
assertEquals(referencedObjectId.getGuid(), expectedGuid);
}
}
protected void verifyReferenceList(AtlasEntity entity, String refName, List<AtlasObjectId> expectedValues) {
Object refValue = entity.getAttribute(refName);
assertTrue(refValue instanceof List);
List<AtlasObjectId> refList = (List<AtlasObjectId>) refValue;
assertEquals(refList.size(), expectedValues.size());
if (expectedValues.size() > 0) {
assertTrue(refList.containsAll(expectedValues));
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment