Commit 765d556c by Sarath Subramanian Committed by Madhan Neethiraj

ATLAS-1564: EntityResource v1 updated to route its calls to v2 EntityREST

parent c7900f25
......@@ -270,12 +270,12 @@ public class AtlasClient extends AtlasBaseClient {
}
public EntityResult(List<String> created, List<String> updated, List<String> deleted) {
add(OP_CREATED, created);
add(OP_UPDATED, updated);
add(OP_DELETED, deleted);
set(OP_CREATED, created);
set(OP_UPDATED, updated);
set(OP_DELETED, deleted);
}
private void add(String type, List<String> list) {
public void set(String type, List<String> list) {
if (list != null && list.size() > 0) {
entities.put(type, list);
}
......
......@@ -66,6 +66,7 @@ public enum AtlasErrorCode {
SYSTEM_TYPE(400, "ATLAS40035E", "{0} is a System-type"),
INVALID_STRUCT_VALUE(400, "ATLAS40036E", "not a valid struct value {0}"),
INSTANCE_LINEAGE_INVALID_PARAMS(400, "ATLAS40037E", "Invalid lineage query parameters passed {0}: {1}"),
ATTRIBUTE_UPDATE_NOT_SUPPORTED(400, "ATLAS40038E", "{0}.{1} : attribute update not supported"),
// All Not found enums go here
TYPE_NAME_NOT_FOUND(404, "ATLAS4041E", "Given typename {0} was invalid"),
......
......@@ -34,7 +34,7 @@ public interface AtlasFormatConverter {
class ConverterContext {
private AtlasEntity.AtlasEntitiesWithExtInfo entities = null;
private AtlasEntitiesWithExtInfo entities = null;
public void addEntity(AtlasEntity entity) {
if (entities == null) {
......@@ -61,6 +61,10 @@ public interface AtlasFormatConverter {
public boolean entityExists(String guid) { return entities != null && entities.hasEntity(guid); }
public AtlasEntitiesWithExtInfo getEntities() {
if (entities != null) {
entities.compact();
}
return entities;
}
}
......
......@@ -20,6 +20,7 @@ package org.apache.atlas.repository.converters;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.apache.atlas.AtlasClient;
import org.apache.atlas.AtlasClient.EntityResult;
import org.apache.atlas.AtlasErrorCode;
import org.apache.atlas.AtlasException;
import org.apache.atlas.CreateUpdateEntitiesResult;
......@@ -27,9 +28,11 @@ import org.apache.atlas.exception.AtlasBaseException;
import org.apache.atlas.model.TypeCategory;
import org.apache.atlas.model.instance.AtlasClassification;
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.EntityMutationResponse;
import org.apache.atlas.model.instance.EntityMutations;
import org.apache.atlas.model.instance.EntityMutations.EntityOperation;
import org.apache.atlas.model.instance.GuidMapping;
import org.apache.atlas.services.MetadataService;
import org.apache.atlas.type.AtlasClassificationType;
......@@ -45,12 +48,17 @@ import org.apache.atlas.typesystem.Struct;
import org.apache.atlas.typesystem.exception.EntityNotFoundException;
import org.apache.atlas.typesystem.exception.TraitNotFoundException;
import org.apache.atlas.typesystem.exception.TypeNotFoundException;
import org.apache.atlas.repository.converters.AtlasFormatConverter.ConverterContext;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@Singleton
public class AtlasInstanceConverter {
......@@ -100,10 +108,10 @@ public class AtlasInstanceConverter {
}
}
public Referenceable getReferenceable(AtlasEntity entity, final AtlasFormatConverter.ConverterContext ctx) throws AtlasBaseException {
public Referenceable getReferenceable(AtlasEntity entity, final ConverterContext ctx) throws AtlasBaseException {
AtlasFormatConverter converter = instanceFormatters.getConverter(TypeCategory.ENTITY);
AtlasType entityType = typeRegistry.getType(entity.getTypeName());
Referenceable ref = (Referenceable)converter.fromV2ToV1(entity, entityType, ctx);
Referenceable ref = (Referenceable) converter.fromV2ToV1(entity, entityType, ctx);
return ref;
}
......@@ -111,7 +119,7 @@ public class AtlasInstanceConverter {
public ITypedStruct getTrait(AtlasClassification classification) throws AtlasBaseException {
AtlasFormatConverter converter = instanceFormatters.getConverter(TypeCategory.CLASSIFICATION);
AtlasType classificationType = typeRegistry.getType(classification.getTypeName());
Struct trait = (Struct)converter.fromV2ToV1(classification, classificationType, new AtlasFormatConverter.ConverterContext());
Struct trait = (Struct)converter.fromV2ToV1(classification, classificationType, new ConverterContext());
try {
return metadataService.createTraitInstance(trait);
......@@ -132,18 +140,17 @@ public class AtlasInstanceConverter {
return ret;
}
public AtlasEntity.AtlasEntitiesWithExtInfo toAtlasEntity(IReferenceableInstance referenceable) throws AtlasBaseException {
public AtlasEntitiesWithExtInfo toAtlasEntity(IReferenceableInstance referenceable) throws AtlasBaseException {
AtlasEntityFormatConverter converter = (AtlasEntityFormatConverter) instanceFormatters.getConverter(TypeCategory.ENTITY);
AtlasEntityType entityType = typeRegistry.getEntityTypeByName(referenceable.getTypeName());
AtlasEntityType entityType = typeRegistry.getEntityTypeByName(referenceable.getTypeName());
if (entityType == null) {
throw new AtlasBaseException(AtlasErrorCode.TYPE_NAME_INVALID, TypeCategory.ENTITY.name(), referenceable.getTypeName());
}
AtlasFormatConverter.ConverterContext ctx = new AtlasFormatConverter.ConverterContext();
ConverterContext ctx = new ConverterContext();
AtlasEntity entity = converter.fromV1ToV2(referenceable, entityType, ctx);
AtlasEntity entity = converter.fromV1ToV2(referenceable, entityType, ctx);
ctx.addEntity(entity);
return ctx.getEntities();
......@@ -212,6 +219,31 @@ public class AtlasInstanceConverter {
return context.getEntities();
}
public AtlasEntitiesWithExtInfo toAtlasEntities(String entitiesJson) throws AtlasBaseException, AtlasException {
ITypedReferenceableInstance[] referenceables = metadataService.deserializeClassInstances(entitiesJson);
AtlasEntityFormatConverter converter = (AtlasEntityFormatConverter) instanceFormatters.getConverter(TypeCategory.ENTITY);
ConverterContext context = new ConverterContext();
AtlasEntitiesWithExtInfo ret = null;
if (referenceables != null) {
for (IReferenceableInstance referenceable : referenceables) {
AtlasEntityType entityType = typeRegistry.getEntityTypeByName(referenceable.getTypeName());
if (entityType == null) {
throw new AtlasBaseException(AtlasErrorCode.TYPE_NAME_INVALID, TypeCategory.ENTITY.name(), referenceable.getTypeName());
}
AtlasEntity entity = converter.fromV1ToV2(referenceable, entityType, context);
context.addEntity(entity);
}
ret = context.getEntities();
}
return ret;
}
private AtlasEntity fromV1toV2Entity(Referenceable referenceable, AtlasFormatConverter.ConverterContext context) throws AtlasBaseException {
if (LOG.isDebugEnabled()) {
LOG.debug("==> fromV1toV2Entity");
......@@ -227,4 +259,69 @@ public class AtlasInstanceConverter {
return entity;
}
public CreateUpdateEntitiesResult toCreateUpdateEntitiesResult(EntityMutationResponse reponse) {
CreateUpdateEntitiesResult ret = null;
if (reponse != null) {
Map<EntityOperation, List<AtlasEntityHeader>> mutatedEntities = reponse.getMutatedEntities();
Map<String, String> guidAssignments = reponse.getGuidAssignments();
ret = new CreateUpdateEntitiesResult();
if (MapUtils.isNotEmpty(guidAssignments)) {
ret.setGuidMapping(new GuidMapping(guidAssignments));
}
if (MapUtils.isNotEmpty(mutatedEntities)) {
EntityResult entityResult = new EntityResult();
for (Map.Entry<EntityOperation, List<AtlasEntityHeader>> e : mutatedEntities.entrySet()) {
switch (e.getKey()) {
case CREATE:
List<AtlasEntityHeader> createdEntities = mutatedEntities.get(EntityOperation.CREATE);
if (CollectionUtils.isNotEmpty(createdEntities)) {
entityResult.set(EntityResult.OP_CREATED, getGuids(createdEntities));
}
break;
case UPDATE:
List<AtlasEntityHeader> updatedEntities = mutatedEntities.get(EntityOperation.UPDATE);
if (CollectionUtils.isNotEmpty(updatedEntities)) {
entityResult.set(EntityResult.OP_UPDATED, getGuids(updatedEntities));
}
break;
case PARTIAL_UPDATE:
List<AtlasEntityHeader> partialUpdatedEntities = mutatedEntities.get(EntityOperation.PARTIAL_UPDATE);
if (CollectionUtils.isNotEmpty(partialUpdatedEntities)) {
entityResult.set(EntityResult.OP_UPDATED, getGuids(partialUpdatedEntities));
}
break;
case DELETE:
List<AtlasEntityHeader> deletedEntities = mutatedEntities.get(EntityOperation.DELETE);
if (CollectionUtils.isNotEmpty(deletedEntities)) {
entityResult.set(EntityResult.OP_DELETED, getGuids(deletedEntities));
}
break;
}
}
ret.setEntityResult(entityResult);
}
}
return ret;
}
public List<String> getGuids(List<AtlasEntityHeader> entities) {
List<String> ret = null;
if (CollectionUtils.isNotEmpty(entities)) {
ret = new ArrayList<>();
for (AtlasEntityHeader entity : entities) {
ret.add(entity.getGuid());
}
}
return ret;
}
}
......@@ -89,6 +89,26 @@ public interface AtlasEntityStore {
AtlasEntity entity) throws AtlasBaseException;
/**
* Partial update a single entity using its guid.
* @param entityType type of the entity
* @param guid Entity guid
* @return EntityMutationResponse details of the updates performed by this call
* @throws AtlasBaseException
*
*/
EntityMutationResponse updateByGuid(AtlasEntityType entityType, String guid, AtlasEntity entity) throws AtlasBaseException;
/**
* Partial update entities attribute using its guid.
* @param guid Entity guid
* @param attrName attribute name to be updated
* @param attrValue updated attribute value
* @return EntityMutationResponse details of the updates performed by this call
* @throws AtlasBaseException
*/
EntityMutationResponse updateEntityAttributeByGuid(String guid, String attrName, Object attrValue) throws AtlasBaseException;
/**
* Delete an entity by its guid
* @param guid
* @return
......
......@@ -21,15 +21,16 @@ package org.apache.atlas.repository.store.graph.v1;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.apache.atlas.AtlasErrorCode;
import org.apache.atlas.AtlasException;
import org.apache.atlas.GraphTransaction;
import org.apache.atlas.RequestContextV1;
import org.apache.atlas.exception.AtlasBaseException;
import org.apache.atlas.model.impexp.AtlasImportResult;
import org.apache.atlas.model.instance.AtlasClassification;
import org.apache.atlas.model.instance.AtlasEntity;
import org.apache.atlas.model.instance.AtlasEntityHeader;
import org.apache.atlas.model.instance.AtlasEntity.AtlasEntityWithExtInfo;
import org.apache.atlas.model.instance.AtlasEntity.AtlasEntitiesWithExtInfo;
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.EntityMutationResponse;
import org.apache.atlas.repository.graphdb.AtlasVertex;
......@@ -37,17 +38,28 @@ import org.apache.atlas.repository.store.graph.AtlasEntityStore;
import org.apache.atlas.repository.store.graph.EntityGraphDiscovery;
import org.apache.atlas.repository.store.graph.EntityGraphDiscoveryContext;
import org.apache.atlas.type.AtlasEntityType;
import org.apache.atlas.type.AtlasStructType;
import org.apache.atlas.type.AtlasStructType.AtlasAttribute;
import org.apache.atlas.type.AtlasType;
import org.apache.atlas.type.AtlasTypeRegistry;
import org.apache.atlas.type.AtlasTypeUtil;
import org.apache.atlas.typesystem.persistence.Id;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.apache.atlas.model.instance.EntityMutations.EntityOperation.*;
import static org.apache.atlas.model.instance.EntityMutations.EntityOperation.DELETE;
import static org.apache.atlas.model.instance.EntityMutations.EntityOperation.UPDATE;
@Singleton
......@@ -245,6 +257,65 @@ public class AtlasEntityStoreV1 implements AtlasEntityStore {
return createOrUpdate(new AtlasEntityStream(updatedEntity), true);
}
@Override
@GraphTransaction
public EntityMutationResponse updateByGuid(AtlasEntityType entityType, String guid, AtlasEntity updatedEntity)
throws AtlasBaseException {
if (LOG.isDebugEnabled()) {
LOG.debug("==> updateByUniqueAttributes({}, {})", entityType.getTypeName(), guid);
}
if (updatedEntity == null) {
throw new AtlasBaseException(AtlasErrorCode.INVALID_PARAMETERS, "no entity to update.");
}
updatedEntity.setGuid(guid);
return createOrUpdate(new AtlasEntityStream(updatedEntity), true);
}
@Override
@GraphTransaction
public EntityMutationResponse updateEntityAttributeByGuid(String guid, String attrName, Object attrValue)
throws AtlasBaseException {
if (LOG.isDebugEnabled()) {
LOG.debug("==> updateEntityAttributeByGuid({}, {}, {})", guid, attrName, attrValue);
}
AtlasEntityWithExtInfo entityInfo = getById(guid);
if (entityInfo == null || entityInfo.getEntity() == null) {
throw new AtlasBaseException(AtlasErrorCode.INSTANCE_GUID_NOT_FOUND, guid);
}
AtlasEntity entity = entityInfo.getEntity();
AtlasEntityType entityType = (AtlasEntityType) typeRegistry.getType(entity.getTypeName());
AtlasAttribute attr = entityType.getAttribute(attrName);
if (attr == null) {
throw new AtlasBaseException(AtlasErrorCode.UNKNOWN_ATTRIBUTE, attrName, entity.getTypeName());
}
AtlasType attrType = attr.getAttributeType();
AtlasEntity updateEntity = new AtlasEntity();
updateEntity.setGuid(guid);
updateEntity.setTypeName(entity.getTypeName());
switch (attrType.getTypeCategory()) {
case PRIMITIVE:
case OBJECT_ID_TYPE:
updateEntity.setAttribute(attrName, attrValue);
break;
default:
throw new AtlasBaseException(AtlasErrorCode.ATTRIBUTE_UPDATE_NOT_SUPPORTED, attrName, attrType.getTypeName());
}
return createOrUpdate(new AtlasEntityStream(updateEntity), true);
}
@GraphTransaction
public EntityMutationResponse deleteById(final String guid) throws AtlasBaseException {
......
......@@ -308,7 +308,8 @@ public class DefaultMetadataService implements MetadataService, ActiveStateChang
return result;
}
private ITypedReferenceableInstance[] deserializeClassInstances(String entityInstanceDefinition) throws AtlasException {
@Override
public ITypedReferenceableInstance[] deserializeClassInstances(String entityInstanceDefinition) throws AtlasException {
return GraphHelper.deserializeClassInstances(typeSystem, entityInstanceDefinition);
}
......
......@@ -301,4 +301,12 @@ public interface MetadataService {
* @return
*/
List<EntityAuditEvent> getAuditEvents(String guid, String startKey, short count) throws AtlasException;
/**
* Deserializes entity instances into ITypedReferenceableInstance array.
* @param entityInstanceDefinition
* @return ITypedReferenceableInstance[]
* @throws AtlasException
*/
ITypedReferenceableInstance[] deserializeClassInstances(String entityInstanceDefinition) throws AtlasException;
}
......@@ -155,13 +155,30 @@ public class EntityREST {
return entitiesStore.updateByUniqueAttributes(entityType, uniqueAttributes, entity);
}
/*******
* Entity Partial Update - Add/Update entity attribute identified by its GUID.
* Supports only uprimitive attribute type and entity references.
* does not support updation of complex types like arrays, maps
* Null updates are not possible
*******/
@PUT
@Consumes(Servlets.JSON_MEDIA_TYPE)
@Produces(Servlets.JSON_MEDIA_TYPE)
@Path("/guid/{guid}")
public EntityMutationResponse partialUpdateByGuid(@PathParam("guid") String guid,
@QueryParam("name") String attrName,
Object attrValue) throws Exception {
return entitiesStore.updateEntityAttributeByGuid(guid, attrName, attrValue);
}
/**
* Delete an entity identified by its GUID.
* @param guid GUID for the entity
* @return EntityMutationResponse
*/
@DELETE
@Path("guid/{guid}")
@Path("/guid/{guid}")
@Consumes({Servlets.JSON_MEDIA_TYPE, MediaType.APPLICATION_JSON})
@Produces(Servlets.JSON_MEDIA_TYPE)
public EntityMutationResponse deleteByGuid(@PathParam("guid") final String guid) throws AtlasBaseException {
......
......@@ -6,9 +6,9 @@
* 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
*
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.
......@@ -21,13 +21,22 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.ws.rs.core.Response;
import com.vividsolutions.jts.util.CollectionUtil;
import org.apache.atlas.AtlasClient.EntityResult;
import org.apache.atlas.model.instance.AtlasEntityHeader;
import org.apache.atlas.model.instance.EntityMutationResponse;
import org.apache.atlas.model.instance.EntityMutations;
import org.apache.atlas.repository.converters.AtlasInstanceConverter;
import org.apache.atlas.repository.store.graph.AtlasEntityStore;
import org.apache.atlas.services.MetadataService;
import org.apache.atlas.type.AtlasTypeRegistry;
import org.apache.commons.collections.CollectionUtils;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
......@@ -42,8 +51,9 @@ public class EntityResourceTest {
private static final String DELETED_GUID = "deleted_guid";
@Mock
MetadataService mockService;
AtlasEntityStore entitiesStore;
@BeforeMethod
public void setUp() {
......@@ -53,22 +63,35 @@ public class EntityResourceTest {
@Test
public void testDeleteEntitiesDoesNotLookupDeletedEntity() throws Exception {
List<String> guids = Collections.singletonList(DELETED_GUID);
List<AtlasEntityHeader> deletedEntities = Collections.singletonList(new AtlasEntityHeader(null, DELETED_GUID, null));
// Create EntityResult with a deleted guid and no other guids.
EntityResult entityResult = new EntityResult(Collections.<String>emptyList(),
Collections.<String>emptyList(), guids);
when(mockService.deleteEntities(guids)).thenReturn(entityResult);
EntityMutationResponse resp = new EntityMutationResponse();
List<AtlasEntityHeader> headers = toAtlasEntityHeaders(guids);
for (AtlasEntityHeader entity : headers) {
resp.addEntity(EntityMutations.EntityOperation.DELETE, entity);
}
when(entitiesStore.deleteByIds(guids)).thenReturn(resp);
// Create EntityResource with mock MetadataService.
EntityResource entityResource = new EntityResource(mockService);
EntityMutationResponse response = entitiesStore.deleteByIds(guids);
List<AtlasEntityHeader> responseDeletedEntities = response.getDeletedEntities();
Assert.assertEquals(responseDeletedEntities, deletedEntities);
}
Response response = entityResource.deleteEntities(guids, null, null, null);
private List<AtlasEntityHeader> toAtlasEntityHeaders(List<String> guids) {
List<AtlasEntityHeader> ret = null;
// Verify that if the EntityResult returned by MetadataService includes only deleted guids,
// deleteEntities() does not perform any entity lookup.
verify(mockService, never()).getEntityDefinition(Matchers.anyString());
if (CollectionUtils.isNotEmpty(guids)) {
ret = new ArrayList<>(guids.size());
for (String guid : guids) {
ret.add(new AtlasEntityHeader(null, guid, null));
}
}
EntityResult resultFromEntityResource = EntityResult.fromString(response.getEntity().toString());
Assert.assertTrue(resultFromEntityResource.getDeletedEntities().contains(DELETED_GUID));
return ret;
}
}
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