Commit 7f914ab9 by Vimal Sharma Committed by Madhan Neethiraj

ATLAS-1478: REST API to add classification to multiple entities

parent bda289ef
...@@ -53,6 +53,8 @@ public enum AtlasErrorCode { ...@@ -53,6 +53,8 @@ public enum AtlasErrorCode {
PATCH_FOR_UNKNOWN_TYPE(400, "ATLAS40023E", "{0} - patch references unknown type {1}"), PATCH_FOR_UNKNOWN_TYPE(400, "ATLAS40023E", "{0} - patch references unknown type {1}"),
PATCH_INVALID_DATA(400, "ATLAS40024E", "{0} - patch data is invalid for type {1}"), PATCH_INVALID_DATA(400, "ATLAS40024E", "{0} - patch data is invalid for type {1}"),
TYPE_NAME_INVALID_FORMAT(400, "ATLAS40025E", "{0}: invalid name for {1}. Names must consist of a letter followed by a sequence of letter, number, or '_' characters"), TYPE_NAME_INVALID_FORMAT(400, "ATLAS40025E", "{0}: invalid name for {1}. Names must consist of a letter followed by a sequence of letter, number, or '_' characters"),
INVALID_PARAMETERS(400, "ATLAS40025E", "invalid parameters: {0}"),
CLASSIFICATION_ALREADY_ASSOCIATED(400, "ATLAS40026E", "instance {0} already is associated with classification {1}"),
// All Not found enums go here // All Not found enums go here
TYPE_NAME_NOT_FOUND(404, "ATLAS4041E", "Given typename {0} was invalid"), TYPE_NAME_NOT_FOUND(404, "ATLAS4041E", "Given typename {0} was invalid"),
......
/**
* 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.model.instance;
import org.apache.atlas.model.typedef.AtlasBaseTypeDef;
import org.codehaus.jackson.annotate.JsonAutoDetect;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import static org.codehaus.jackson.annotate.JsonAutoDetect.Visibility.NONE;
import static org.codehaus.jackson.annotate.JsonAutoDetect.Visibility.PUBLIC_ONLY;
import java.util.List;
import java.util.Objects;
@JsonAutoDetect(getterVisibility=PUBLIC_ONLY, setterVisibility=PUBLIC_ONLY, fieldVisibility=NONE)
@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown=true)
@XmlRootElement
@XmlAccessorType(XmlAccessType.PROPERTY)
public class ClassificationAssociateRequest {
private AtlasClassification classification;
private List<String> entityGuids;
public ClassificationAssociateRequest() {
this(null, null);
}
public ClassificationAssociateRequest(List<String> entityGuids, AtlasClassification classification) {
setEntityGuids(entityGuids);
setClassification(classification);
}
public AtlasClassification getClassification() { return classification; }
public void setClassification(AtlasClassification classification) { this.classification = classification; }
public List<String> getEntityGuids() { return entityGuids; }
public void setEntityGuids(List<String> entityGuids) { this.entityGuids = entityGuids; }
@Override
public boolean equals(Object o) {
if (this == o) { return true; }
if (o == null || getClass() != o.getClass()) { return false; }
ClassificationAssociateRequest that = (ClassificationAssociateRequest) o;
return Objects.equals(classification, that.classification) && Objects.equals(entityGuids, that.entityGuids);
}
@Override
public int hashCode() {
return Objects.hash(classification, entityGuids);
}
public StringBuilder toString(StringBuilder sb) {
if (sb == null) {
sb = new StringBuilder();
}
sb.append("ClassificationAssociateRequest{");
sb.append("classification='");
if (classification != null) {
classification.toString(sb);
}
sb.append(", entityGuids=[");
AtlasBaseTypeDef.dumpObjects(entityGuids, sb);
sb.append("]");
sb.append('}');
return sb;
}
@Override
public String toString() {
return toString(new StringBuilder()).toString();
}
}
...@@ -9,6 +9,7 @@ ATLAS-1060 Add composite indexes for exact match performance improvements for al ...@@ -9,6 +9,7 @@ ATLAS-1060 Add composite indexes for exact match performance improvements for al
ATLAS-1127 Modify creation and modification timestamps to Date instead of Long(sumasai) ATLAS-1127 Modify creation and modification timestamps to Date instead of Long(sumasai)
ALL CHANGES: ALL CHANGES:
ATLAS-1478 REST API to add classification to multiple entities (svimal2106 via mneethiraj)
ATLAS-1490 added methods to get sub-types of entity and classification types (mneethiraj) ATLAS-1490 added methods to get sub-types of entity and classification types (mneethiraj)
ATLAS-1437 UI update to disallow tag association changes to deleted entities (Kalyanikashikar via mneethiraj) ATLAS-1437 UI update to disallow tag association changes to deleted entities (Kalyanikashikar via mneethiraj)
ATLAS-1352 fix for error in redirecting to Knox gateway URL (nixonrodrigues via mneethiraj) ATLAS-1352 fix for error in redirecting to Knox gateway URL (nixonrodrigues via mneethiraj)
......
...@@ -146,6 +146,14 @@ public interface MetadataRepository { ...@@ -146,6 +146,14 @@ public interface MetadataRepository {
void addTrait(String guid, ITypedStruct traitInstance) throws RepositoryException; void addTrait(String guid, ITypedStruct traitInstance) throws RepositoryException;
/** /**
* Adds a new trait to a list of entities represented by their respective guids
* @param entityGuids list of globally unique identifier for the entities
* @param traitInstance trait instance that needs to be added to entities
* @throws RepositoryException
*/
void addTrait(List<String> entityGuids, ITypedStruct traitInstance) throws RepositoryException;
/**
* Deletes a given trait from an existing entity represented by a guid. * Deletes a given trait from an existing entity represented by a guid.
* *
* @param guid globally unique identifier for the entity * @param guid globally unique identifier for the entity
......
...@@ -225,6 +225,26 @@ public class GraphBackedMetadataRepository implements MetadataRepository { ...@@ -225,6 +225,26 @@ public class GraphBackedMetadataRepository implements MetadataRepository {
return GraphHelper.getTraitNames(instanceVertex); return GraphHelper.getTraitNames(instanceVertex);
} }
/**
* Adds a new trait to the list of entities represented by their respective guids
* @param entityGuids list of globally unique identifier for the entities
* @param traitInstance trait instance that needs to be added to entities
* @throws RepositoryException
*/
@Override
@GraphTransaction
public void addTrait(List<String> entityGuids, ITypedStruct traitInstance) throws RepositoryException {
Preconditions.checkNotNull(entityGuids, "entityGuids list cannot be null");
Preconditions.checkNotNull(traitInstance, "Trait instance cannot be null");
if (LOG.isDebugEnabled()) {
LOG.debug("Adding a new trait={} for entities={}", traitInstance.getTypeName(), entityGuids);
}
for (String entityGuid : entityGuids) {
addTraitImpl(entityGuid, traitInstance);
}
}
/** /**
* Adds a new trait to an existing entity represented by a guid. * Adds a new trait to an existing entity represented by a guid.
...@@ -236,7 +256,13 @@ public class GraphBackedMetadataRepository implements MetadataRepository { ...@@ -236,7 +256,13 @@ public class GraphBackedMetadataRepository implements MetadataRepository {
@Override @Override
@GraphTransaction @GraphTransaction
public void addTrait(String guid, ITypedStruct traitInstance) throws RepositoryException { public void addTrait(String guid, ITypedStruct traitInstance) throws RepositoryException {
Preconditions.checkNotNull(guid, "guid cannot be null");
Preconditions.checkNotNull(traitInstance, "Trait instance cannot be null"); Preconditions.checkNotNull(traitInstance, "Trait instance cannot be null");
addTraitImpl(guid, traitInstance);
}
private void addTraitImpl(String guid, ITypedStruct traitInstance) throws RepositoryException {
final String traitName = traitInstance.getTypeName(); final String traitName = traitInstance.getTypeName();
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
...@@ -259,7 +285,7 @@ public class GraphBackedMetadataRepository implements MetadataRepository { ...@@ -259,7 +285,7 @@ public class GraphBackedMetadataRepository implements MetadataRepository {
GraphHelper.setProperty(instanceVertex, Constants.MODIFICATION_TIMESTAMP_PROPERTY_KEY, GraphHelper.setProperty(instanceVertex, Constants.MODIFICATION_TIMESTAMP_PROPERTY_KEY,
RequestContext.get().getRequestTime()); RequestContext.get().getRequestTime());
GraphHelper.setProperty(instanceVertex, Constants.MODIFIED_BY_KEY, RequestContext.get().getUser()); GraphHelper.setProperty(instanceVertex, Constants.MODIFIED_BY_KEY, RequestContext.get().getUser());
} catch (RepositoryException e) { } catch (RepositoryException e) {
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
......
...@@ -71,7 +71,6 @@ import org.codehaus.jettison.json.JSONObject; ...@@ -71,7 +71,6 @@ import org.codehaus.jettison.json.JSONObject;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
...@@ -568,6 +567,39 @@ public class DefaultMetadataService implements MetadataService, ActiveStateChang ...@@ -568,6 +567,39 @@ public class DefaultMetadataService implements MetadataService, ActiveStateChang
} }
/** /**
* Adds a new trait to the list of existing entities represented by their respective guids
* @param entityGuids list of guids of entities
* @param traitInstance trait instance json that needs to be added to entities
* @throws AtlasException
*/
@Override
public void addTrait(List<String> entityGuids, ITypedStruct traitInstance) throws AtlasException {
Preconditions.checkNotNull(entityGuids, "entityGuids list cannot be null");
Preconditions.checkNotNull(traitInstance, "Trait instance cannot be null");
final String traitName = traitInstance.getTypeName();
// ensure trait type is already registered with the TypeSystem
if (!typeSystem.isRegistered(traitName)) {
String msg = String.format("trait=%s should be defined in type system before it can be added", traitName);
LOG.error(msg);
throw new TypeNotFoundException(msg);
}
//ensure trait is not already registered with any of the given entities
for (String entityGuid : entityGuids) {
Preconditions.checkArgument(!getTraitNames(entityGuid).contains(traitName),
"trait=%s is already defined for entity=%s", traitName, entityGuid);
}
repository.addTrait(entityGuids, traitInstance);
for (String entityGuid : entityGuids) {
onTraitAddedToEntity(repository.getEntityDefinition(entityGuid), traitInstance);
}
}
/**
* Adds a new trait to an existing entity represented by a guid. * Adds a new trait to an existing entity represented by a guid.
* *
* @param guid globally unique identifier for the entity * @param guid globally unique identifier for the entity
......
...@@ -28,6 +28,7 @@ import org.apache.atlas.typesystem.Referenceable; ...@@ -28,6 +28,7 @@ import org.apache.atlas.typesystem.Referenceable;
import org.apache.atlas.typesystem.Struct; import org.apache.atlas.typesystem.Struct;
import org.apache.atlas.typesystem.IStruct; import org.apache.atlas.typesystem.IStruct;
import org.apache.atlas.typesystem.types.cache.TypeCache; import org.apache.atlas.typesystem.types.cache.TypeCache;
import org.apache.atlas.utils.ParamChecker;
import org.codehaus.jettison.json.JSONObject; import org.codehaus.jettison.json.JSONObject;
import java.util.List; import java.util.List;
...@@ -221,6 +222,15 @@ public interface MetadataService { ...@@ -221,6 +222,15 @@ public interface MetadataService {
*/ */
void addTrait(String guid, ITypedStruct traitInstance) throws AtlasException; void addTrait(String guid, ITypedStruct traitInstance) throws AtlasException;
/**
* Adds a new trait to a list of existing entities represented by their respective guids
* @param entityGuids list of guids of entities
* @param traitInstance trait instance json that needs to be added to entities
* @throws AtlasException
*/
void addTrait(List<String> entityGuids, ITypedStruct traitInstance) throws AtlasException;
/** /**
* Create a typed trait instance. * Create a typed trait instance.
* *
......
...@@ -22,15 +22,19 @@ import org.apache.atlas.AtlasErrorCode; ...@@ -22,15 +22,19 @@ import org.apache.atlas.AtlasErrorCode;
import org.apache.atlas.AtlasException; import org.apache.atlas.AtlasException;
import org.apache.atlas.exception.AtlasBaseException; import org.apache.atlas.exception.AtlasBaseException;
import org.apache.atlas.model.SearchFilter; import org.apache.atlas.model.SearchFilter;
import org.apache.atlas.model.instance.AtlasClassification;
import org.apache.atlas.model.instance.AtlasEntity; import org.apache.atlas.model.instance.AtlasEntity;
import org.apache.atlas.model.instance.AtlasEntityHeader; import org.apache.atlas.model.instance.AtlasEntityHeader;
import org.apache.atlas.model.instance.ClassificationAssociateRequest;
import org.apache.atlas.model.instance.EntityMutationResponse; import org.apache.atlas.model.instance.EntityMutationResponse;
import org.apache.atlas.repository.store.graph.AtlasEntityStore; import org.apache.atlas.repository.store.graph.AtlasEntityStore;
import org.apache.atlas.services.MetadataService; import org.apache.atlas.services.MetadataService;
import org.apache.atlas.typesystem.ITypedReferenceableInstance; import org.apache.atlas.typesystem.ITypedReferenceableInstance;
import org.apache.atlas.typesystem.ITypedStruct;
import org.apache.atlas.web.adapters.AtlasInstanceRestAdapters; import org.apache.atlas.web.adapters.AtlasInstanceRestAdapters;
import org.apache.atlas.web.util.Servlets; import org.apache.atlas.web.util.Servlets;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -46,6 +50,7 @@ import javax.ws.rs.Path; ...@@ -46,6 +50,7 @@ import javax.ws.rs.Path;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam; import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context; import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedList; import java.util.LinkedList;
...@@ -184,6 +189,37 @@ public class EntitiesREST { ...@@ -184,6 +189,37 @@ public class EntitiesREST {
return entityHeaders; return entityHeaders;
} }
/**
* Bulk API to associate a tag to multiple entities
*
*/
@POST
@Path("/classification")
@Consumes({Servlets.JSON_MEDIA_TYPE, MediaType.APPLICATION_JSON})
@Produces(Servlets.JSON_MEDIA_TYPE)
public void addClassification(ClassificationAssociateRequest request) throws AtlasBaseException {
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 entity list");
}
final ITypedStruct trait = restAdapters.getTrait(classification);
try {
metadataService.addTrait(entityGuids, trait);
} catch (IllegalArgumentException e) {
throw new AtlasBaseException(AtlasErrorCode.TYPE_NAME_NOT_FOUND, e);
} catch (AtlasException e) {
throw toAtlasBaseException(e);
}
}
private SearchFilter getSearchFilter() { private SearchFilter getSearchFilter() {
SearchFilter searchFilter = new SearchFilter(); SearchFilter searchFilter = new SearchFilter();
if (null != httpServletRequest && null != httpServletRequest.getParameterMap()) { if (null != httpServletRequest && null != httpServletRequest.getParameterMap()) {
......
...@@ -23,9 +23,11 @@ import org.apache.atlas.AtlasClient; ...@@ -23,9 +23,11 @@ import org.apache.atlas.AtlasClient;
import org.apache.atlas.RepositoryMetadataModule; import org.apache.atlas.RepositoryMetadataModule;
import org.apache.atlas.RequestContext; import org.apache.atlas.RequestContext;
import org.apache.atlas.TestUtilsV2; import org.apache.atlas.TestUtilsV2;
import org.apache.atlas.model.instance.AtlasClassification;
import org.apache.atlas.model.instance.AtlasEntity; import org.apache.atlas.model.instance.AtlasEntity;
import org.apache.atlas.model.instance.AtlasEntityHeader; import org.apache.atlas.model.instance.AtlasEntityHeader;
import org.apache.atlas.model.instance.AtlasStruct; import org.apache.atlas.model.instance.AtlasStruct;
import org.apache.atlas.model.instance.ClassificationAssociateRequest;
import org.apache.atlas.model.instance.EntityMutationResponse; import org.apache.atlas.model.instance.EntityMutationResponse;
import org.apache.atlas.model.instance.EntityMutations; import org.apache.atlas.model.instance.EntityMutations;
import org.apache.atlas.model.typedef.AtlasTypesDef; import org.apache.atlas.model.typedef.AtlasTypesDef;
...@@ -33,6 +35,7 @@ import org.apache.atlas.repository.graph.AtlasGraphProvider; ...@@ -33,6 +35,7 @@ import org.apache.atlas.repository.graph.AtlasGraphProvider;
import org.apache.atlas.store.AtlasTypeDefStore; import org.apache.atlas.store.AtlasTypeDefStore;
import org.apache.atlas.web.rest.EntitiesREST; import org.apache.atlas.web.rest.EntitiesREST;
import org.apache.atlas.web.rest.EntityREST;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.testng.Assert; import org.testng.Assert;
...@@ -45,6 +48,7 @@ import org.testng.annotations.Test; ...@@ -45,6 +48,7 @@ import org.testng.annotations.Test;
import javax.inject.Inject; import javax.inject.Inject;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
...@@ -59,6 +63,9 @@ public class TestEntitiesREST { ...@@ -59,6 +63,9 @@ public class TestEntitiesREST {
@Inject @Inject
private EntitiesREST entitiesREST; private EntitiesREST entitiesREST;
@Inject
private EntityREST entityREST;
private List<String> createdGuids = new ArrayList<>(); private List<String> createdGuids = new ArrayList<>();
private AtlasEntity dbEntity; private AtlasEntity dbEntity;
...@@ -106,6 +113,18 @@ public class TestEntitiesREST { ...@@ -106,6 +113,18 @@ public class TestEntitiesREST {
} }
} }
@Test(dependsOnMethods = "testCreateOrUpdateEntities")
public void testTagToMultipleEntities() throws Exception{
AtlasClassification tag = new AtlasClassification(TestUtilsV2.CLASSIFICATION, new HashMap<String, Object>() {{ put("tag", "tagName"); }});
ClassificationAssociateRequest classificationAssociateRequest = new ClassificationAssociateRequest(createdGuids, tag);
entitiesREST.addClassification(classificationAssociateRequest);
for (String guid : createdGuids) {
final AtlasClassification result_tag = entityREST.getClassification(guid, TestUtilsV2.CLASSIFICATION);
Assert.assertNotNull(result_tag);
Assert.assertEquals(result_tag, tag);
}
}
@Test @Test
public void testUpdateWithSerializedEntities() throws Exception { public void testUpdateWithSerializedEntities() throws Exception {
//Check with serialization and deserialization of entity attributes for the case //Check with serialization and deserialization of entity attributes for the case
......
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