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 {
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}"),
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
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
ATLAS-1127 Modify creation and modification timestamps to Date instead of Long(sumasai)
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-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)
......
......@@ -146,6 +146,14 @@ public interface MetadataRepository {
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.
*
* @param guid globally unique identifier for the entity
......
......@@ -225,6 +225,26 @@ public class GraphBackedMetadataRepository implements MetadataRepository {
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.
......@@ -236,7 +256,13 @@ public class GraphBackedMetadataRepository implements MetadataRepository {
@Override
@GraphTransaction
public void addTrait(String guid, ITypedStruct traitInstance) throws RepositoryException {
Preconditions.checkNotNull(guid, "guid 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();
if (LOG.isDebugEnabled()) {
......
......@@ -71,7 +71,6 @@ import org.codehaus.jettison.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
......@@ -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.
*
* @param guid globally unique identifier for the entity
......
......@@ -28,6 +28,7 @@ import org.apache.atlas.typesystem.Referenceable;
import org.apache.atlas.typesystem.Struct;
import org.apache.atlas.typesystem.IStruct;
import org.apache.atlas.typesystem.types.cache.TypeCache;
import org.apache.atlas.utils.ParamChecker;
import org.codehaus.jettison.json.JSONObject;
import java.util.List;
......@@ -221,6 +222,15 @@ public interface MetadataService {
*/
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.
*
......
......@@ -22,15 +22,19 @@ import org.apache.atlas.AtlasErrorCode;
import org.apache.atlas.AtlasException;
import org.apache.atlas.exception.AtlasBaseException;
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.AtlasEntityHeader;
import org.apache.atlas.model.instance.ClassificationAssociateRequest;
import org.apache.atlas.model.instance.EntityMutationResponse;
import org.apache.atlas.repository.store.graph.AtlasEntityStore;
import org.apache.atlas.services.MetadataService;
import org.apache.atlas.typesystem.ITypedReferenceableInstance;
import org.apache.atlas.typesystem.ITypedStruct;
import org.apache.atlas.web.adapters.AtlasInstanceRestAdapters;
import org.apache.atlas.web.util.Servlets;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -46,6 +50,7 @@ import javax.ws.rs.Path;
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.Arrays;
import java.util.LinkedList;
......@@ -184,6 +189,37 @@ public class EntitiesREST {
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() {
SearchFilter searchFilter = new SearchFilter();
if (null != httpServletRequest && null != httpServletRequest.getParameterMap()) {
......
......@@ -23,9 +23,11 @@ import org.apache.atlas.AtlasClient;
import org.apache.atlas.RepositoryMetadataModule;
import org.apache.atlas.RequestContext;
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.AtlasEntityHeader;
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.EntityMutations;
import org.apache.atlas.model.typedef.AtlasTypesDef;
......@@ -33,6 +35,7 @@ import org.apache.atlas.repository.graph.AtlasGraphProvider;
import org.apache.atlas.store.AtlasTypeDefStore;
import org.apache.atlas.web.rest.EntitiesREST;
import org.apache.atlas.web.rest.EntityREST;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
......@@ -45,6 +48,7 @@ import org.testng.annotations.Test;
import javax.inject.Inject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
......@@ -59,6 +63,9 @@ public class TestEntitiesREST {
@Inject
private EntitiesREST entitiesREST;
@Inject
private EntityREST entityREST;
private List<String> createdGuids = new ArrayList<>();
private AtlasEntity dbEntity;
......@@ -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
public void testUpdateWithSerializedEntities() throws Exception {
//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