blob: 0f6eeb11d90f6495498e87d31b4ff5bb4cfea1e8 [file] [log] [blame]
/**
* 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);
}
}
}
}