| /** |
| * 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.web.rest; |
| |
| import org.apache.atlas.AtlasErrorCode; |
| 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.AtlasEntitiesWithExtInfo; |
| import org.apache.atlas.model.instance.AtlasEntity.AtlasEntityWithExtInfo; |
| import org.apache.atlas.model.instance.ClassificationAssociateRequest; |
| import org.apache.atlas.model.instance.EntityMutationResponse; |
| import org.apache.atlas.model.typedef.AtlasStructDef.AtlasAttributeDef; |
| import org.apache.atlas.repository.store.graph.AtlasEntityStore; |
| import org.apache.atlas.repository.store.graph.v1.AtlasEntityStream; |
| import org.apache.atlas.repository.store.graph.v1.EntityStream; |
| import org.apache.atlas.type.AtlasClassificationType; |
| import org.apache.atlas.type.AtlasEntityType; |
| import org.apache.atlas.type.AtlasTypeRegistry; |
| import org.apache.atlas.utils.AtlasPerfTracer; |
| import org.apache.atlas.web.util.Servlets; |
| import org.apache.commons.collections.CollectionUtils; |
| import org.apache.commons.collections.MapUtils; |
| import org.apache.commons.lang3.StringUtils; |
| import org.slf4j.Logger; |
| import org.springframework.stereotype.Service; |
| |
| import javax.inject.Inject; |
| import javax.inject.Singleton; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.ws.rs.Consumes; |
| import javax.ws.rs.DELETE; |
| import javax.ws.rs.GET; |
| import javax.ws.rs.POST; |
| import javax.ws.rs.PUT; |
| import javax.ws.rs.Path; |
| import javax.ws.rs.PathParam; |
| import javax.ws.rs.Produces; |
| import javax.ws.rs.QueryParam; |
| import javax.ws.rs.core.Context; |
| import javax.ws.rs.core.MediaType; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| |
| /** |
| * REST for a single entity |
| */ |
| @Path("v2/entity") |
| @Singleton |
| @Service |
| public class EntityREST { |
| private static final Logger PERF_LOG = AtlasPerfTracer.getPerfLogger("rest.EntityREST"); |
| |
| public static final String PREFIX_ATTR = "attr:"; |
| |
| private final AtlasTypeRegistry typeRegistry; |
| private final AtlasEntityStore entitiesStore; |
| |
| @Inject |
| public EntityREST(AtlasTypeRegistry typeRegistry, AtlasEntityStore entitiesStore) { |
| this.typeRegistry = typeRegistry; |
| this.entitiesStore = entitiesStore; |
| } |
| |
| /** |
| * Fetch complete definition of an entity given its GUID. |
| * @param guid GUID for the entity |
| * @return AtlasEntity |
| * @throws AtlasBaseException |
| */ |
| @GET |
| @Path("/guid/{guid}") |
| @Consumes(Servlets.JSON_MEDIA_TYPE) |
| @Produces(Servlets.JSON_MEDIA_TYPE) |
| public AtlasEntityWithExtInfo getById(@PathParam("guid") String guid) throws AtlasBaseException { |
| AtlasPerfTracer perf = null; |
| |
| try { |
| if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) { |
| perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "EntityREST.getById(" + guid + ")"); |
| } |
| |
| return entitiesStore.getById(guid); |
| } finally { |
| AtlasPerfTracer.log(perf); |
| } |
| } |
| |
| /** |
| * Fetch complete definition of an entity given its type and unique attribute. |
| * @param typeName |
| * @return AtlasEntityWithExtInfo |
| * @throws AtlasBaseException |
| */ |
| @GET |
| @Path("/uniqueAttribute/type/{typeName}") |
| @Consumes(Servlets.JSON_MEDIA_TYPE) |
| @Produces(Servlets.JSON_MEDIA_TYPE) |
| public AtlasEntityWithExtInfo getByUniqueAttributes(@PathParam("typeName") String typeName, |
| @Context HttpServletRequest servletRequest) throws AtlasBaseException { |
| AtlasPerfTracer perf = null; |
| |
| try { |
| Map<String, Object> attributes = getAttributes(servletRequest); |
| |
| if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) { |
| perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "EntityREST.getByUniqueAttributes(" + typeName + "," + attributes + ")"); |
| } |
| |
| AtlasEntityType entityType = ensureEntityType(typeName); |
| |
| validateUniqueAttribute(entityType, attributes); |
| |
| return entitiesStore.getByUniqueAttributes(entityType, attributes); |
| } finally { |
| AtlasPerfTracer.log(perf); |
| } |
| } |
| |
| /** |
| * Create new entity or update existing entity in Atlas. |
| * Existing entity is matched using its unique guid if supplied or by its unique attributes eg: qualifiedName |
| * @param entity |
| * @return EntityMutationResponse |
| * @throws AtlasBaseException |
| */ |
| @POST |
| @Consumes(Servlets.JSON_MEDIA_TYPE) |
| @Produces(Servlets.JSON_MEDIA_TYPE) |
| public EntityMutationResponse createOrUpdate(AtlasEntityWithExtInfo entity) throws AtlasBaseException { |
| AtlasPerfTracer perf = null; |
| |
| try { |
| if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) { |
| perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "EntityREST.createOrUpdate()"); |
| } |
| |
| return entitiesStore.createOrUpdate(new AtlasEntityStream(entity), false); |
| } finally { |
| AtlasPerfTracer.log(perf); |
| } |
| } |
| |
| /******* |
| * Entity Partial Update - Allows a subset of attributes to be updated on |
| * an entity which is identified by its type and unique attribute eg: Referenceable.qualifiedName. |
| * Null updates are not possible |
| *******/ |
| @PUT |
| @Consumes(Servlets.JSON_MEDIA_TYPE) |
| @Produces(Servlets.JSON_MEDIA_TYPE) |
| @Path("/uniqueAttribute/type/{typeName}") |
| public EntityMutationResponse partialUpdateEntityByUniqueAttrs(@PathParam("typeName") String typeName, |
| @Context HttpServletRequest servletRequest, |
| AtlasEntityWithExtInfo entityInfo) throws Exception { |
| AtlasPerfTracer perf = null; |
| |
| try { |
| Map<String, Object> uniqueAttributes = getAttributes(servletRequest); |
| |
| if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) { |
| perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "EntityREST.partialUpdateEntityByUniqueAttrs(" + typeName + "," + uniqueAttributes + ")"); |
| } |
| |
| AtlasEntityType entityType = ensureEntityType(typeName); |
| |
| validateUniqueAttribute(entityType, uniqueAttributes); |
| |
| return entitiesStore.updateByUniqueAttributes(entityType, uniqueAttributes, entityInfo); |
| } finally { |
| AtlasPerfTracer.log(perf); |
| } |
| } |
| |
| /******* |
| * 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 partialUpdateEntityAttrByGuid(@PathParam("guid") String guid, |
| @QueryParam("name") String attrName, |
| Object attrValue) throws Exception { |
| AtlasPerfTracer perf = null; |
| |
| try { |
| if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) { |
| perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "EntityREST.partialUpdateEntityAttrByGuid(" + guid + "," + attrName + ")"); |
| } |
| |
| return entitiesStore.updateEntityAttributeByGuid(guid, attrName, attrValue); |
| } finally { |
| AtlasPerfTracer.log(perf); |
| } |
| } |
| |
| /** |
| * Delete an entity identified by its GUID. |
| * @param guid GUID for the entity |
| * @return EntityMutationResponse |
| */ |
| @DELETE |
| @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 { |
| AtlasPerfTracer perf = null; |
| |
| try { |
| if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) { |
| perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "EntityREST.deleteByGuid(" + guid + ")"); |
| } |
| |
| return entitiesStore.deleteById(guid); |
| } finally { |
| AtlasPerfTracer.log(perf); |
| } |
| } |
| |
| /** |
| * Delete an entity identified by its type and unique attributes. |
| * @param typeName - entity type to be deleted |
| * @param servletRequest - request containing unique attributes/values |
| * @return EntityMutationResponse |
| */ |
| @DELETE |
| @Consumes(Servlets.JSON_MEDIA_TYPE) |
| @Produces(Servlets.JSON_MEDIA_TYPE) |
| @Path("/uniqueAttribute/type/{typeName}") |
| public EntityMutationResponse deleteByUniqueAttribute(@PathParam("typeName") String typeName, |
| @Context HttpServletRequest servletRequest) throws AtlasBaseException { |
| AtlasPerfTracer perf = null; |
| |
| try { |
| Map<String, Object> attributes = getAttributes(servletRequest); |
| |
| if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) { |
| perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "EntityREST.deleteByUniqueAttribute(" + typeName + "," + attributes + ")"); |
| } |
| |
| AtlasEntityType entityType = ensureEntityType(typeName); |
| |
| return entitiesStore.deleteByUniqueAttributes(entityType, attributes); |
| } finally { |
| AtlasPerfTracer.log(perf); |
| } |
| } |
| |
| /** |
| * Gets the list of classifications for a given entity represented by a guid. |
| * @param guid globally unique identifier for the entity |
| * @return classification for the given entity guid |
| */ |
| @GET |
| @Path("/guid/{guid}/classification/{classificationName}") |
| @Produces(Servlets.JSON_MEDIA_TYPE) |
| public AtlasClassification getClassification(@PathParam("guid") String guid, @PathParam("classificationName") final String classificationName) throws AtlasBaseException { |
| AtlasPerfTracer perf = null; |
| |
| try { |
| if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) { |
| perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "EntityREST.getClassification(" + guid + "," + classificationName + ")"); |
| } |
| |
| if (StringUtils.isEmpty(guid)) { |
| throw new AtlasBaseException(AtlasErrorCode.INSTANCE_GUID_NOT_FOUND, guid); |
| } |
| |
| ensureClassificationType(classificationName); |
| return entitiesStore.getClassification(guid, classificationName); |
| } finally { |
| AtlasPerfTracer.log(perf); |
| } |
| } |
| |
| /** |
| * Gets the list of classifications for a given entity represented by a guid. |
| * @param guid globally unique identifier for the entity |
| * @return a list of classifications for the given entity guid |
| */ |
| @GET |
| @Path("/guid/{guid}/classifications") |
| @Produces(Servlets.JSON_MEDIA_TYPE) |
| public AtlasClassification.AtlasClassifications getClassifications(@PathParam("guid") String guid) throws AtlasBaseException { |
| AtlasPerfTracer perf = null; |
| |
| try { |
| if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) { |
| perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "EntityREST.getClassifications(" + guid + ")"); |
| } |
| |
| if (StringUtils.isEmpty(guid)) { |
| throw new AtlasBaseException(AtlasErrorCode.INSTANCE_GUID_NOT_FOUND, guid); |
| } |
| |
| return new AtlasClassification.AtlasClassifications(entitiesStore.getClassifications(guid)); |
| } finally { |
| AtlasPerfTracer.log(perf); |
| } |
| } |
| |
| /** |
| * Adds classifications to an existing entity represented by a guid. |
| * @param guid globally unique identifier for the entity |
| */ |
| @POST |
| @Path("/guid/{guid}/classifications") |
| @Consumes({Servlets.JSON_MEDIA_TYPE, MediaType.APPLICATION_JSON}) |
| @Produces(Servlets.JSON_MEDIA_TYPE) |
| public void addClassifications(@PathParam("guid") final String guid, List<AtlasClassification> classifications) throws AtlasBaseException { |
| AtlasPerfTracer perf = null; |
| |
| try { |
| if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) { |
| perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "EntityREST.addClassifications(" + guid + ")"); |
| } |
| |
| if (StringUtils.isEmpty(guid)) { |
| throw new AtlasBaseException(AtlasErrorCode.INSTANCE_GUID_NOT_FOUND, guid); |
| } |
| |
| entitiesStore.addClassifications(guid, classifications); |
| } finally { |
| AtlasPerfTracer.log(perf); |
| } |
| } |
| |
| /** |
| * Updates classifications to an existing entity represented by a guid. |
| * @param guid globally unique identifier for the entity |
| * @return classification for the given entity guid |
| */ |
| @PUT |
| @Path("/guid/{guid}/classifications") |
| @Produces(Servlets.JSON_MEDIA_TYPE) |
| public void updateClassification(@PathParam("guid") final String guid, List<AtlasClassification> classifications) throws AtlasBaseException { |
| AtlasPerfTracer perf = null; |
| |
| try { |
| if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) { |
| perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "EntityREST.updateClassification(" + guid + ")"); |
| } |
| |
| if (StringUtils.isEmpty(guid)) { |
| throw new AtlasBaseException(AtlasErrorCode.INSTANCE_GUID_NOT_FOUND, guid); |
| } |
| |
| entitiesStore.updateClassifications(guid, classifications); |
| |
| } finally { |
| AtlasPerfTracer.log(perf); |
| } |
| } |
| |
| /** |
| * Deletes a given classification from an existing entity represented by a guid. |
| * @param guid globally unique identifier for the entity |
| * @param classificationName name of the classifcation |
| */ |
| @DELETE |
| @Path("/guid/{guid}/classification/{classificationName}") |
| @Produces(Servlets.JSON_MEDIA_TYPE) |
| public void deleteClassification(@PathParam("guid") String guid, |
| @PathParam("classificationName") final String classificationName) throws AtlasBaseException { |
| AtlasPerfTracer perf = null; |
| |
| try { |
| if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) { |
| perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "EntityREST.deleteClassification(" + guid + "," + classificationName + ")"); |
| } |
| |
| if (StringUtils.isEmpty(guid)) { |
| throw new AtlasBaseException(AtlasErrorCode.INSTANCE_GUID_NOT_FOUND, guid); |
| } |
| |
| ensureClassificationType(classificationName); |
| |
| entitiesStore.deleteClassifications(guid, new ArrayList<String>() {{ add(classificationName);}} ); |
| } finally { |
| AtlasPerfTracer.log(perf); |
| } |
| } |
| |
| /******************************************************************/ |
| /** Bulk API operations **/ |
| /******************************************************************/ |
| |
| /** |
| * Bulk API to retrieve list of entities identified by its GUIDs. |
| */ |
| @GET |
| @Path("/bulk") |
| @Consumes(Servlets.JSON_MEDIA_TYPE) |
| @Produces(Servlets.JSON_MEDIA_TYPE) |
| public AtlasEntitiesWithExtInfo getByGuids(@QueryParam("guid") List<String> guids) throws AtlasBaseException { |
| AtlasPerfTracer perf = null; |
| |
| try { |
| if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) { |
| perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "EntityREST.getByGuids(" + guids + ")"); |
| } |
| |
| if (CollectionUtils.isEmpty(guids)) { |
| throw new AtlasBaseException(AtlasErrorCode.INSTANCE_GUID_NOT_FOUND, guids); |
| } |
| |
| return entitiesStore.getByIds(guids); |
| } finally { |
| AtlasPerfTracer.log(perf); |
| } |
| } |
| |
| /** |
| * Bulk API to create new entities or update existing entities in Atlas. |
| * Existing entity is matched using its unique guid if supplied or by its unique attributes eg: qualifiedName |
| */ |
| @POST |
| @Path("/bulk") |
| @Consumes(Servlets.JSON_MEDIA_TYPE) |
| @Produces(Servlets.JSON_MEDIA_TYPE) |
| public EntityMutationResponse createOrUpdate(AtlasEntitiesWithExtInfo entities) throws AtlasBaseException { |
| AtlasPerfTracer perf = null; |
| |
| try { |
| if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) { |
| perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "EntityREST.createOrUpdate(entityCount=" + |
| (CollectionUtils.isEmpty(entities.getEntities()) ? 0 : entities.getEntities().size()) + ")"); |
| } |
| |
| EntityStream entityStream = new AtlasEntityStream(entities); |
| |
| return entitiesStore.createOrUpdate(entityStream, false); |
| } finally { |
| AtlasPerfTracer.log(perf); |
| } |
| } |
| |
| /** |
| * Bulk API to delete list of entities identified by its GUIDs |
| */ |
| @DELETE |
| @Path("/bulk") |
| @Consumes(Servlets.JSON_MEDIA_TYPE) |
| @Produces(Servlets.JSON_MEDIA_TYPE) |
| public EntityMutationResponse deleteByGuids(@QueryParam("guid") final List<String> guids) throws AtlasBaseException { |
| AtlasPerfTracer perf = null; |
| |
| try { |
| if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) { |
| perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "EntityREST.deleteByGuids(" + guids + ")"); |
| } |
| |
| return entitiesStore.deleteByIds(guids); |
| } finally { |
| AtlasPerfTracer.log(perf); |
| } |
| } |
| |
| /** |
| * Bulk API to associate a tag to multiple entities |
| */ |
| @POST |
| @Path("/bulk/classification") |
| @Consumes({Servlets.JSON_MEDIA_TYPE, MediaType.APPLICATION_JSON}) |
| @Produces(Servlets.JSON_MEDIA_TYPE) |
| public void addClassification(ClassificationAssociateRequest request) throws AtlasBaseException { |
| AtlasPerfTracer perf = null; |
| |
| try { |
| if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) { |
| perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "EntityREST.addClassification(" + request + ")"); |
| } |
| |
| AtlasClassification classification = request == null ? null : request.getClassification(); |
| List<String> entityGuids = request == null ? null : request.getEntityGuids(); |
| |
| if (classification == null || StringUtils.isEmpty(classification.getTypeName())) { |
| throw new AtlasBaseException(AtlasErrorCode.INVALID_PARAMETERS, "no classification"); |
| } |
| |
| if (CollectionUtils.isEmpty(entityGuids)) { |
| throw new AtlasBaseException(AtlasErrorCode.INVALID_PARAMETERS, "empty guid list"); |
| } |
| |
| entitiesStore.addClassification(entityGuids, classification); |
| } finally { |
| AtlasPerfTracer.log(perf); |
| } |
| } |
| |
| private AtlasEntityType ensureEntityType(String typeName) throws AtlasBaseException { |
| AtlasEntityType ret = typeRegistry.getEntityTypeByName(typeName); |
| |
| if (ret == null) { |
| throw new AtlasBaseException(AtlasErrorCode.TYPE_NAME_INVALID, TypeCategory.ENTITY.name(), typeName); |
| } |
| |
| return ret; |
| } |
| |
| private AtlasClassificationType ensureClassificationType(String typeName) throws AtlasBaseException { |
| AtlasClassificationType ret = typeRegistry.getClassificationTypeByName(typeName); |
| |
| if (ret == null) { |
| throw new AtlasBaseException(AtlasErrorCode.TYPE_NAME_INVALID, TypeCategory.CLASSIFICATION.name(), typeName); |
| } |
| |
| return ret; |
| } |
| |
| private Map<String, Object> getAttributes(HttpServletRequest request) { |
| Map<String, Object> attributes = new HashMap<>(); |
| |
| if (MapUtils.isNotEmpty(request.getParameterMap())) { |
| for (Map.Entry<String, String[]> e : request.getParameterMap().entrySet()) { |
| String key = e.getKey(); |
| |
| if (key != null && key.startsWith(PREFIX_ATTR)) { |
| String[] values = e.getValue(); |
| String value = values != null && values.length > 0 ? values[0] : null; |
| |
| attributes.put(key.substring(PREFIX_ATTR.length()), value); |
| } |
| } |
| } |
| |
| return attributes; |
| } |
| |
| /** |
| * Validate that each attribute given is an unique attribute |
| * @param entityType the entity type |
| * @param attributes attributes |
| */ |
| private void validateUniqueAttribute(AtlasEntityType entityType, Map<String, Object> attributes) throws AtlasBaseException { |
| if (MapUtils.isEmpty(attributes)) { |
| throw new AtlasBaseException(AtlasErrorCode.ATTRIBUTE_UNIQUE_INVALID, entityType.getTypeName(), ""); |
| } |
| |
| for (String attributeName : attributes.keySet()) { |
| AtlasAttributeDef attribute = entityType.getAttributeDef(attributeName); |
| |
| if (attribute == null || !attribute.getIsUnique()) { |
| throw new AtlasBaseException(AtlasErrorCode.ATTRIBUTE_UNIQUE_INVALID, entityType.getTypeName(), attributeName); |
| } |
| } |
| } |
| } |