Repository: incubator-atlas Updated Branches: refs/heads/master c7900f255 -> 765d556cc
ATLAS-1564: EntityResource v1 updated to route its calls to v2 EntityREST Signed-off-by: Madhan Neethiraj <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/incubator-atlas/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-atlas/commit/765d556c Tree: http://git-wip-us.apache.org/repos/asf/incubator-atlas/tree/765d556c Diff: http://git-wip-us.apache.org/repos/asf/incubator-atlas/diff/765d556c Branch: refs/heads/master Commit: 765d556cce4f276d295d06cdb513fc3b9b00dc51 Parents: c7900f2 Author: Sarath Subramanian <[email protected]> Authored: Fri Feb 17 16:04:00 2017 -0800 Committer: Madhan Neethiraj <[email protected]> Committed: Fri Feb 24 16:21:19 2017 -0800 ---------------------------------------------------------------------- .../main/java/org/apache/atlas/AtlasClient.java | 8 +- .../java/org/apache/atlas/AtlasErrorCode.java | 1 + .../converters/AtlasFormatConverter.java | 6 +- .../converters/AtlasInstanceConverter.java | 113 ++++++++++++++-- .../store/graph/AtlasEntityStore.java | 20 +++ .../store/graph/v1/AtlasEntityStoreV1.java | 79 ++++++++++- .../atlas/services/DefaultMetadataService.java | 3 +- .../apache/atlas/services/MetadataService.java | 8 ++ .../atlas/web/resources/EntityResource.java | 134 ++++++++++++++----- .../org/apache/atlas/web/rest/EntityREST.java | 19 ++- .../atlas/web/resources/EntityResourceTest.java | 53 +++++--- 11 files changed, 377 insertions(+), 67 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/765d556c/client/src/main/java/org/apache/atlas/AtlasClient.java ---------------------------------------------------------------------- diff --git a/client/src/main/java/org/apache/atlas/AtlasClient.java b/client/src/main/java/org/apache/atlas/AtlasClient.java index 13896ce..909bd23 100755 --- a/client/src/main/java/org/apache/atlas/AtlasClient.java +++ b/client/src/main/java/org/apache/atlas/AtlasClient.java @@ -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); } http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/765d556c/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java ---------------------------------------------------------------------- diff --git a/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java b/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java index ca3023a..542b659 100644 --- a/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java +++ b/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java @@ -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"), http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/765d556c/repository/src/main/java/org/apache/atlas/repository/converters/AtlasFormatConverter.java ---------------------------------------------------------------------- diff --git a/repository/src/main/java/org/apache/atlas/repository/converters/AtlasFormatConverter.java b/repository/src/main/java/org/apache/atlas/repository/converters/AtlasFormatConverter.java index 9d0d7f4..a6d43da 100644 --- a/repository/src/main/java/org/apache/atlas/repository/converters/AtlasFormatConverter.java +++ b/repository/src/main/java/org/apache/atlas/repository/converters/AtlasFormatConverter.java @@ -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; } } http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/765d556c/repository/src/main/java/org/apache/atlas/repository/converters/AtlasInstanceConverter.java ---------------------------------------------------------------------- diff --git a/repository/src/main/java/org/apache/atlas/repository/converters/AtlasInstanceConverter.java b/repository/src/main/java/org/apache/atlas/repository/converters/AtlasInstanceConverter.java index e14fafb..621b32f 100644 --- a/repository/src/main/java/org/apache/atlas/repository/converters/AtlasInstanceConverter.java +++ b/repository/src/main/java/org/apache/atlas/repository/converters/AtlasInstanceConverter.java @@ -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; + } } http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/765d556c/repository/src/main/java/org/apache/atlas/repository/store/graph/AtlasEntityStore.java ---------------------------------------------------------------------- diff --git a/repository/src/main/java/org/apache/atlas/repository/store/graph/AtlasEntityStore.java b/repository/src/main/java/org/apache/atlas/repository/store/graph/AtlasEntityStore.java index 3a037cc..6c372b3 100644 --- a/repository/src/main/java/org/apache/atlas/repository/store/graph/AtlasEntityStore.java +++ b/repository/src/main/java/org/apache/atlas/repository/store/graph/AtlasEntityStore.java @@ -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 http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/765d556c/repository/src/main/java/org/apache/atlas/repository/store/graph/v1/AtlasEntityStoreV1.java ---------------------------------------------------------------------- diff --git a/repository/src/main/java/org/apache/atlas/repository/store/graph/v1/AtlasEntityStoreV1.java b/repository/src/main/java/org/apache/atlas/repository/store/graph/v1/AtlasEntityStoreV1.java index bbfc3e5..c0355d9 100644 --- a/repository/src/main/java/org/apache/atlas/repository/store/graph/v1/AtlasEntityStoreV1.java +++ b/repository/src/main/java/org/apache/atlas/repository/store/graph/v1/AtlasEntityStoreV1.java @@ -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 { http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/765d556c/repository/src/main/java/org/apache/atlas/services/DefaultMetadataService.java ---------------------------------------------------------------------- diff --git a/repository/src/main/java/org/apache/atlas/services/DefaultMetadataService.java b/repository/src/main/java/org/apache/atlas/services/DefaultMetadataService.java index 5127f74..993cf61 100755 --- a/repository/src/main/java/org/apache/atlas/services/DefaultMetadataService.java +++ b/repository/src/main/java/org/apache/atlas/services/DefaultMetadataService.java @@ -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); } http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/765d556c/server-api/src/main/java/org/apache/atlas/services/MetadataService.java ---------------------------------------------------------------------- diff --git a/server-api/src/main/java/org/apache/atlas/services/MetadataService.java b/server-api/src/main/java/org/apache/atlas/services/MetadataService.java index e0fb66c..45b35b3 100644 --- a/server-api/src/main/java/org/apache/atlas/services/MetadataService.java +++ b/server-api/src/main/java/org/apache/atlas/services/MetadataService.java @@ -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; } http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/765d556c/webapp/src/main/java/org/apache/atlas/web/resources/EntityResource.java ---------------------------------------------------------------------- diff --git a/webapp/src/main/java/org/apache/atlas/web/resources/EntityResource.java b/webapp/src/main/java/org/apache/atlas/web/resources/EntityResource.java index f59cd9d..d7adb3a 100755 --- a/webapp/src/main/java/org/apache/atlas/web/resources/EntityResource.java +++ b/webapp/src/main/java/org/apache/atlas/web/resources/EntityResource.java @@ -20,16 +20,28 @@ package org.apache.atlas.web.resources; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; +import com.sun.jersey.api.core.ResourceContext; import org.apache.atlas.AtlasClient; import org.apache.atlas.AtlasConstants; import org.apache.atlas.AtlasException; import org.apache.atlas.CreateUpdateEntitiesResult; import org.apache.atlas.EntityAuditEvent; import org.apache.atlas.AtlasClient.EntityResult; +import org.apache.atlas.exception.AtlasBaseException; +import org.apache.atlas.model.instance.AtlasEntity; +import org.apache.atlas.model.instance.AtlasEntity.AtlasEntityWithExtInfo; +import org.apache.atlas.model.instance.AtlasEntity.AtlasEntitiesWithExtInfo; +import org.apache.atlas.model.instance.EntityMutationResponse; import org.apache.atlas.model.instance.GuidMapping; +import org.apache.atlas.repository.converters.AtlasFormatConverter.ConverterContext; +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.AtlasType; +import org.apache.atlas.type.AtlasEntityType; +import org.apache.atlas.type.AtlasTypeRegistry; import org.apache.atlas.typesystem.IStruct; +import org.apache.atlas.typesystem.ITypedReferenceableInstance; import org.apache.atlas.typesystem.Referenceable; import org.apache.atlas.typesystem.exception.EntityExistsException; import org.apache.atlas.typesystem.exception.EntityNotFoundException; @@ -39,6 +51,7 @@ import org.apache.atlas.typesystem.json.InstanceSerialization; import org.apache.atlas.typesystem.types.ValueConversionException; import org.apache.atlas.utils.AtlasPerfTracer; import org.apache.atlas.utils.ParamChecker; +import org.apache.atlas.web.rest.EntityREST; import org.apache.atlas.web.util.Servlets; import org.apache.commons.lang.StringUtils; import org.codehaus.jettison.json.JSONArray; @@ -59,7 +72,9 @@ import javax.ws.rs.core.UriInfo; import java.net.URI; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** @@ -77,11 +92,17 @@ public class EntityResource { private static final String TRAIT_NAME = "traitName"; - private final MetadataService metadataService; + private final MetadataService metadataService; + private final AtlasInstanceConverter restAdapters; + private final AtlasEntityStore entitiesStore; + private final AtlasTypeRegistry typeRegistry; @Context UriInfo uriInfo; + @Context + private ResourceContext resourceContext; + /** * Created by the Guice ServletModule and injected with the * configured MetadataService. @@ -89,8 +110,11 @@ public class EntityResource { * @param metadataService metadata service handle */ @Inject - public EntityResource(MetadataService metadataService) { + public EntityResource(MetadataService metadataService, AtlasInstanceConverter restAdapters, AtlasEntityStore entitiesStore, AtlasTypeRegistry typeRegistry) { this.metadataService = metadataService; + this.restAdapters = restAdapters; + this.entitiesStore = entitiesStore; + this.typeRegistry = typeRegistry; } /** @@ -131,15 +155,20 @@ public class EntityResource { LOG.debug("submitting entities {} ", entityJson); } - final CreateUpdateEntitiesResult result = metadataService.createEntities(entities); - final List<String> guids = result.getEntityResult().getCreatedEntities(); + EntityREST entityREST = resourceContext.getResource(EntityREST.class); + AtlasEntitiesWithExtInfo entitiesInfo = restAdapters.toAtlasEntities(entities); + EntityMutationResponse mutationResponse = entityREST.createOrUpdate(entitiesInfo); + + final List<String> guids = restAdapters.getGuids(mutationResponse.getCreatedEntities()); + if (LOG.isDebugEnabled()) { LOG.debug("Created entities {}", guids); } - JSONObject response = getResponse(result); + final CreateUpdateEntitiesResult result = restAdapters.toCreateUpdateEntitiesResult(mutationResponse); - URI locationURI = getLocationURI(guids); + JSONObject response = getResponse(result); + URI locationURI = getLocationURI(guids); return Response.created(locationURI).entity(response).build(); @@ -165,7 +194,6 @@ public class EntityResource { } } - @VisibleForTesting public URI getLocationURI(List<String> guids) { URI locationURI = null; @@ -235,7 +263,10 @@ public class EntityResource { LOG.info("updating entities {} ", entityJson); } - CreateUpdateEntitiesResult result = metadataService.updateEntities(entities); + EntityREST entityREST = resourceContext.getResource(EntityREST.class); + AtlasEntitiesWithExtInfo entitiesInfo = restAdapters.toAtlasEntities(entities); + EntityMutationResponse mutationResponse = entityREST.createOrUpdate(entitiesInfo); + CreateUpdateEntitiesResult result = restAdapters.toCreateUpdateEntitiesResult(mutationResponse); if (LOG.isDebugEnabled()) { LOG.debug("Updated entities: {}", result.getEntityResult()); @@ -317,11 +348,19 @@ public class EntityResource { LOG.debug("Partially updating entity by unique attribute {} {} {} {} ", entityType, attribute, value, entityJson); } - Referenceable updatedEntity = - InstanceSerialization.fromJsonReferenceable(entityJson, true); + Referenceable updatedEntity = InstanceSerialization.fromJsonReferenceable(entityJson, true); + + entityType = ParamChecker.notEmpty(entityType, "Entity type cannot be null"); + attribute = ParamChecker.notEmpty(attribute, "attribute name cannot be null"); + value = ParamChecker.notEmpty(value, "attribute value cannot be null"); + + Map<String, Object> attributes = new HashMap<>(); + attributes.put(attribute, value); - CreateUpdateEntitiesResult result = - metadataService.updateEntityByUniqueAttribute(entityType, attribute, value, updatedEntity); + AtlasEntitiesWithExtInfo entitiesInfo = restAdapters.toAtlasEntity(updatedEntity); + AtlasEntity entity = entitiesInfo.getEntity(updatedEntity.getId()._getId()); + EntityMutationResponse mutationResponse = entitiesStore.updateByUniqueAttributes(getEntityType(entityType), attributes, entity); + CreateUpdateEntitiesResult result = restAdapters.toCreateUpdateEntitiesResult(mutationResponse); if (LOG.isDebugEnabled()) { LOG.debug("Updated entities: {}", result.getEntityResult()); @@ -379,9 +418,9 @@ public class EntityResource { } if (StringUtils.isEmpty(attribute)) { - return updateEntityPartialByGuid(guid, request); + return partialUpdateEntityByGuid(guid, request); } else { - return updateEntityAttributeByGuid(guid, attribute, request); + return partialUpdateEntityAttrByGuid(guid, attribute, request); } } finally { AtlasPerfTracer.log(perf); @@ -392,7 +431,7 @@ public class EntityResource { } } - private Response updateEntityPartialByGuid(String guid, HttpServletRequest request) { + private Response partialUpdateEntityByGuid(String guid, HttpServletRequest request) { String entityJson = null; try { guid = ParamChecker.notEmpty(guid, "Guid property cannot be null"); @@ -402,10 +441,11 @@ public class EntityResource { LOG.debug("partially updating entity for guid {} : {} ", guid, entityJson); } - Referenceable updatedEntity = - InstanceSerialization.fromJsonReferenceable(entityJson, true); - - CreateUpdateEntitiesResult result = metadataService.updateEntityPartialByGuid(guid, updatedEntity); + Referenceable updatedEntity = InstanceSerialization.fromJsonReferenceable(entityJson, true); + AtlasEntitiesWithExtInfo entitiesInfo = restAdapters.toAtlasEntity(updatedEntity); + AtlasEntity entity = entitiesInfo.getEntity(updatedEntity.getId()._getId()); + EntityMutationResponse mutationResponse = entitiesStore.updateByGuid(getEntityType(updatedEntity.getTypeName()), guid, entity); + CreateUpdateEntitiesResult result = restAdapters.toCreateUpdateEntitiesResult(mutationResponse); if (LOG.isDebugEnabled()) { LOG.debug("Updated entities: {}", result.getEntityResult()); @@ -435,7 +475,7 @@ public class EntityResource { * @postbody property's value * @return response payload as json */ - private Response updateEntityAttributeByGuid(String guid, String property, HttpServletRequest request) { + private Response partialUpdateEntityAttrByGuid(String guid, String property, HttpServletRequest request) { String value = null; try { Preconditions.checkNotNull(property, "Entity property cannot be null"); @@ -446,7 +486,8 @@ public class EntityResource { LOG.debug("Updating entity {} for property {} = {}", guid, property, value); } - CreateUpdateEntitiesResult result = metadataService.updateEntityAttributeByGuid(guid, property, value); + EntityMutationResponse mutationResponse = entitiesStore.updateEntityAttributeByGuid(guid, property, value); + CreateUpdateEntitiesResult result = restAdapters.toCreateUpdateEntitiesResult(mutationResponse); if (LOG.isDebugEnabled()) { LOG.debug("Updated entities: {}", result.getEntityResult()); @@ -481,9 +522,9 @@ public class EntityResource { @DELETE @Produces(Servlets.JSON_MEDIA_TYPE) public Response deleteEntities(@QueryParam("guid") List<String> guids, - @QueryParam("type") String entityType, - @QueryParam("property") String attribute, - @QueryParam("value") String value) { + @QueryParam("type") String entityType, + @QueryParam("property") final String attribute, + @QueryParam("value") final String value) { if (LOG.isDebugEnabled()) { LOG.debug("==> EntityResource.deleteEntities({}, {}, {}, {})", guids, entityType, attribute, value); } @@ -494,19 +535,26 @@ public class EntityResource { perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "EntityResource.deleteEntities(" + guids + ", " + entityType + ", " + attribute + ", " + value + ")"); } - AtlasClient.EntityResult entityResult; + EntityResult entityResult; + EntityREST entityREST = resourceContext.getResource(EntityREST.class); + if (guids != null && !guids.isEmpty()) { if (LOG.isDebugEnabled()) { LOG.debug("Deleting entities {}", guids); } - entityResult = metadataService.deleteEntities(guids); + EntityMutationResponse mutationResponse = entityREST.deleteByGuids(guids); + entityResult = restAdapters.toCreateUpdateEntitiesResult(mutationResponse).getEntityResult(); } else { if (LOG.isDebugEnabled()) { LOG.debug("Deleting entity type={} with property {}={}", entityType, attribute, value); } - entityResult = metadataService.deleteEntityByUniqueAttribute(entityType, attribute, value); + Map<String, Object> attributes = new HashMap<>(); + attributes.put(attribute, value); + + EntityMutationResponse mutationResponse = entitiesStore.deleteByUniqueAttributes(getEntityType(entityType), attributes); + entityResult = restAdapters.toCreateUpdateEntitiesResult(mutationResponse).getEntityResult(); } if (LOG.isDebugEnabled()) { @@ -634,7 +682,7 @@ public class EntityResource { @Produces(Servlets.JSON_MEDIA_TYPE) public Response getEntity(@QueryParam("type") String entityType, @QueryParam("property") String attribute, - @QueryParam("value") String value) { + @QueryParam("value") final String value) { if (LOG.isDebugEnabled()) { LOG.debug("==> EntityResource.getEntity({}, {}, {})", entityType, attribute, value); } @@ -678,7 +726,26 @@ public class EntityResource { attribute = ParamChecker.notEmpty(attribute, "attribute name cannot be null"); value = ParamChecker.notEmpty(value, "attribute value cannot be null"); - final String entityDefinition = metadataService.getEntityDefinition(entityType, attribute, value); + Map<String, Object> attributes = new HashMap<>(); + attributes.put(attribute, value); + + AtlasEntityWithExtInfo entityInfo; + + try { + entityInfo = entitiesStore.getByUniqueAttributes(getEntityType(entityType), attributes); + } catch (AtlasBaseException e) { + LOG.error("Cannot find entity with type: {0}, attribute: {1} and value: {2}", entityType, attribute, value); + throw new WebApplicationException(Servlets.getErrorResponse(e, Response.Status.BAD_REQUEST)); + } + + String entityDefinition = null; + + if (entityInfo != null) { + AtlasEntity entity = entityInfo.getEntity(); + final ITypedReferenceableInstance instance = restAdapters.getITypedReferenceable(entity); + + entityDefinition = InstanceSerialization.toJson(instance, true); + } JSONObject response = new JSONObject(); response.put(AtlasClient.REQUEST_ID, Servlets.getRequestId()); @@ -694,10 +761,7 @@ public class EntityResource { return Response.status(status).entity(response).build(); - } catch (EntityNotFoundException e) { - LOG.error("An entity with type={} and qualifiedName={} does not exist", entityType, value, e); - throw new WebApplicationException(Servlets.getErrorResponse(e, Response.Status.NOT_FOUND)); - } catch (AtlasException | IllegalArgumentException e) { + } catch (IllegalArgumentException e) { LOG.error("Bad type={}, qualifiedName={}", entityType, value, e); throw new WebApplicationException(Servlets.getErrorResponse(e, Response.Status.BAD_REQUEST)); } catch (Throwable e) { @@ -1032,4 +1096,8 @@ public class EntityResource { } return jsonArray; } + + private AtlasEntityType getEntityType(String typeName) { + return typeRegistry.getEntityTypeByName(typeName); + } } http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/765d556c/webapp/src/main/java/org/apache/atlas/web/rest/EntityREST.java ---------------------------------------------------------------------- diff --git a/webapp/src/main/java/org/apache/atlas/web/rest/EntityREST.java b/webapp/src/main/java/org/apache/atlas/web/rest/EntityREST.java index 92ea93e..d1bef78 100644 --- a/webapp/src/main/java/org/apache/atlas/web/rest/EntityREST.java +++ b/webapp/src/main/java/org/apache/atlas/web/rest/EntityREST.java @@ -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 { http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/765d556c/webapp/src/test/java/org/apache/atlas/web/resources/EntityResourceTest.java ---------------------------------------------------------------------- diff --git a/webapp/src/test/java/org/apache/atlas/web/resources/EntityResourceTest.java b/webapp/src/test/java/org/apache/atlas/web/resources/EntityResourceTest.java index 3fe8e11..2f71378 100644 --- a/webapp/src/test/java/org/apache/atlas/web/resources/EntityResourceTest.java +++ b/webapp/src/test/java/org/apache/atlas/web/resources/EntityResourceTest.java @@ -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; } }
