You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@atlas.apache.org by ma...@apache.org on 2017/01/21 05:22:12 UTC

incubator-atlas git commit: ATLAS-1478: REST API to add classification to multiple entities

Repository: incubator-atlas
Updated Branches:
  refs/heads/master bda289ef7 -> 7f914ab9e


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

Signed-off-by: Madhan Neethiraj <ma...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/incubator-atlas/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-atlas/commit/7f914ab9
Tree: http://git-wip-us.apache.org/repos/asf/incubator-atlas/tree/7f914ab9
Diff: http://git-wip-us.apache.org/repos/asf/incubator-atlas/diff/7f914ab9

Branch: refs/heads/master
Commit: 7f914ab9eb3c4cb0ceccea090bed47db220c3780
Parents: bda289e
Author: Vimal Sharma <sv...@apache.org>
Authored: Fri Jan 20 10:37:06 2017 -0800
Committer: Madhan Neethiraj <ma...@apache.org>
Committed: Fri Jan 20 17:13:39 2017 -0800

----------------------------------------------------------------------
 .../java/org/apache/atlas/AtlasErrorCode.java   |   2 +
 .../ClassificationAssociateRequest.java         | 100 +++++++++++++++++++
 release-log.txt                                 |   1 +
 .../atlas/repository/MetadataRepository.java    |   8 ++
 .../graph/GraphBackedMetadataRepository.java    |  28 +++++-
 .../atlas/services/DefaultMetadataService.java  |  34 ++++++-
 .../apache/atlas/services/MetadataService.java  |  10 ++
 .../org/apache/atlas/web/rest/EntitiesREST.java |  36 +++++++
 .../atlas/web/adapters/TestEntitiesREST.java    |  19 ++++
 9 files changed, 236 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/7f914ab9/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 f0aae0c..e4f3dfd 100644
--- a/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java
+++ b/intg/src/main/java/org/apache/atlas/AtlasErrorCode.java
@@ -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"),

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/7f914ab9/intg/src/main/java/org/apache/atlas/model/instance/ClassificationAssociateRequest.java
----------------------------------------------------------------------
diff --git a/intg/src/main/java/org/apache/atlas/model/instance/ClassificationAssociateRequest.java b/intg/src/main/java/org/apache/atlas/model/instance/ClassificationAssociateRequest.java
new file mode 100644
index 0000000..8d0fac6
--- /dev/null
+++ b/intg/src/main/java/org/apache/atlas/model/instance/ClassificationAssociateRequest.java
@@ -0,0 +1,100 @@
+/**
+ * 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();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/7f914ab9/release-log.txt
----------------------------------------------------------------------
diff --git a/release-log.txt b/release-log.txt
index 55ac3da..760f4c3 100644
--- a/release-log.txt
+++ b/release-log.txt
@@ -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)

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/7f914ab9/repository/src/main/java/org/apache/atlas/repository/MetadataRepository.java
----------------------------------------------------------------------
diff --git a/repository/src/main/java/org/apache/atlas/repository/MetadataRepository.java b/repository/src/main/java/org/apache/atlas/repository/MetadataRepository.java
index 886a8d1..1d61ea8 100755
--- a/repository/src/main/java/org/apache/atlas/repository/MetadataRepository.java
+++ b/repository/src/main/java/org/apache/atlas/repository/MetadataRepository.java
@@ -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

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/7f914ab9/repository/src/main/java/org/apache/atlas/repository/graph/GraphBackedMetadataRepository.java
----------------------------------------------------------------------
diff --git a/repository/src/main/java/org/apache/atlas/repository/graph/GraphBackedMetadataRepository.java b/repository/src/main/java/org/apache/atlas/repository/graph/GraphBackedMetadataRepository.java
index b9671b2..0c80aed 100755
--- a/repository/src/main/java/org/apache/atlas/repository/graph/GraphBackedMetadataRepository.java
+++ b/repository/src/main/java/org/apache/atlas/repository/graph/GraphBackedMetadataRepository.java
@@ -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()) {
@@ -259,7 +285,7 @@ public class GraphBackedMetadataRepository implements MetadataRepository {
             GraphHelper.setProperty(instanceVertex, Constants.MODIFICATION_TIMESTAMP_PROPERTY_KEY,
                     RequestContext.get().getRequestTime());
             GraphHelper.setProperty(instanceVertex, Constants.MODIFIED_BY_KEY, RequestContext.get().getUser());
-            
+
         } catch (RepositoryException e) {
             throw e;
         } catch (Exception e) {

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/7f914ab9/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 8307835..35a489f 100755
--- a/repository/src/main/java/org/apache/atlas/services/DefaultMetadataService.java
+++ b/repository/src/main/java/org/apache/atlas/services/DefaultMetadataService.java
@@ -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

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/7f914ab9/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 e653184..d5d8d9b 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
@@ -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.
      *

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/7f914ab9/webapp/src/main/java/org/apache/atlas/web/rest/EntitiesREST.java
----------------------------------------------------------------------
diff --git a/webapp/src/main/java/org/apache/atlas/web/rest/EntitiesREST.java b/webapp/src/main/java/org/apache/atlas/web/rest/EntitiesREST.java
index 21f8977..5107767 100644
--- a/webapp/src/main/java/org/apache/atlas/web/rest/EntitiesREST.java
+++ b/webapp/src/main/java/org/apache/atlas/web/rest/EntitiesREST.java
@@ -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()) {

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/7f914ab9/webapp/src/test/java/org/apache/atlas/web/adapters/TestEntitiesREST.java
----------------------------------------------------------------------
diff --git a/webapp/src/test/java/org/apache/atlas/web/adapters/TestEntitiesREST.java b/webapp/src/test/java/org/apache/atlas/web/adapters/TestEntitiesREST.java
index 90a46f8..c55234c 100644
--- a/webapp/src/test/java/org/apache/atlas/web/adapters/TestEntitiesREST.java
+++ b/webapp/src/test/java/org/apache/atlas/web/adapters/TestEntitiesREST.java
@@ -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