This is an automated email from the ASF dual-hosted git repository.
jshao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new 8c62fc5f6 [#5000] improvement(server): Move tag object API to object
path (#5027)
8c62fc5f6 is described below
commit 8c62fc5f67ad74020e132fffb7c45c8fb204584e
Author: roryqi <[email protected]>
AuthorDate: Fri Sep 27 09:54:14 2024 +0800
[#5000] improvement(server): Move tag object API to object path (#5027)
### What changes were proposed in this pull request?
Move tag related about metadata object API to object path
### Why are the changes needed?
Fix: #5000
### Does this PR introduce _any_ user-facing change?
Modified the document
### How was this patch tested?
Modified the UT.
---
.../client/MetadataObjectTagOperations.java | 2 +-
.../apache/gravitino/client/TestSupportTags.java | 19 +-
docs/manage-tags-in-gravitino.md | 22 +-
docs/open-api/openapi.yaml | 8 +-
docs/open-api/tags.yaml | 4 +-
...tions.java => MetadataObjectTagOperations.java} | 296 ++--------
.../gravitino/server/web/rest/TagOperations.java | 230 ++------
.../web/rest/TestMetadataObjectTagOperations.java | 639 +++++++++++++++++++++
8 files changed, 763 insertions(+), 457 deletions(-)
diff --git
a/clients/client-java/src/main/java/org/apache/gravitino/client/MetadataObjectTagOperations.java
b/clients/client-java/src/main/java/org/apache/gravitino/client/MetadataObjectTagOperations.java
index 1aba1c888..2ac84bdbf 100644
---
a/clients/client-java/src/main/java/org/apache/gravitino/client/MetadataObjectTagOperations.java
+++
b/clients/client-java/src/main/java/org/apache/gravitino/client/MetadataObjectTagOperations.java
@@ -52,7 +52,7 @@ class MetadataObjectTagOperations implements SupportsTags {
this.restClient = restClient;
this.tagRequestPath =
String.format(
- "api/metalakes/%s/tags/%s/%s",
+ "api/metalakes/%s/objects/%s/%s/tags",
metalakeName,
metadataObject.type().name().toLowerCase(Locale.ROOT),
metadataObject.fullName());
diff --git
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportTags.java
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportTags.java
index 095d78549..a80fb3246 100644
---
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportTags.java
+++
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportTags.java
@@ -343,10 +343,11 @@ public class TestSupportTags extends TestBase {
String path =
"/api/metalakes/"
+ METALAKE_NAME
- + "/tags/"
+ + "/objects/"
+ metadataObject.type().name().toLowerCase(Locale.ROOT)
+ "/"
- + metadataObject.fullName();
+ + metadataObject.fullName()
+ + "/tags";
String[] tags = new String[] {"tag1", "tag2"};
NameListResponse resp = new NameListResponse(tags);
@@ -383,10 +384,11 @@ public class TestSupportTags extends TestBase {
String path =
"/api/metalakes/"
+ METALAKE_NAME
- + "/tags/"
+ + "/objects/"
+ metadataObject.type().name().toLowerCase(Locale.ROOT)
+ "/"
- + metadataObject.fullName();
+ + metadataObject.fullName()
+ + "/tags";
TagDTO tag1 =
TagDTO.builder()
@@ -435,11 +437,11 @@ public class TestSupportTags extends TestBase {
String path =
"/api/metalakes/"
+ METALAKE_NAME
- + "/tags/"
+ + "/objects/"
+ metadataObject.type().name().toLowerCase(Locale.ROOT)
+ "/"
+ metadataObject.fullName()
- + "/tag1";
+ + "/tags/tag1";
TagDTO tag1 =
TagDTO.builder()
@@ -476,10 +478,11 @@ public class TestSupportTags extends TestBase {
String path =
"/api/metalakes/"
+ METALAKE_NAME
- + "/tags/"
+ + "/objects/"
+ metadataObject.type().name().toLowerCase(Locale.ROOT)
+ "/"
- + metadataObject.fullName();
+ + metadataObject.fullName()
+ + "/tags";
String[] tagsToAdd = new String[] {"tag1", "tag2"};
String[] tagsToRemove = new String[] {"tag3", "tag4"};
diff --git a/docs/manage-tags-in-gravitino.md b/docs/manage-tags-in-gravitino.md
index a02bf4faf..ac088a7c2 100644
--- a/docs/manage-tags-in-gravitino.md
+++ b/docs/manage-tags-in-gravitino.md
@@ -212,7 +212,7 @@ Gravitino allows you to associate and disassociate tags
with metadata objects. C
You can associate and disassociate tags with a metadata object by providing
the object type, object
name and tag names.
-The request path for REST API is
`/api/metalakes/{metalake}/tags/{metadataObjectType}/{metadataObjectName}`.
+The request path for REST API is
`/api/metalakes/{metalake}/objects/{metadataObjectType}/{metadataObjectName}/tags`.
<Tabs groupId='language' queryString>
<TabItem value="shell" label="Shell">
@@ -222,12 +222,12 @@ curl -X POST -H "Accept:
application/vnd.gravitino.v1+json" \
-H "Content-Type: application/json" -d '{
"tagsToAdd": ["tag1", "tag2"],
"tagsToRemove": ["tag3"]
-}' http://localhost:8090/api/metalakes/test/tags/catalog/catalog1
+}' http://localhost:8090/api/metalakes/test/objects/catalog/catalog1/tags
curl -X POST -H "Accept: application/vnd.gravitino.v1+json" \
-H "Content-Type: application/json" -d '{
"tagsToAdd": ["tag1"]
-}' http://localhost:8090/api/metalakes/test/tags/schema/catalog1.schema1
+}'
http://localhost:8090/api/metalakes/test/objects/schema/catalog1.schema1/tags
```
</TabItem>
@@ -252,23 +252,23 @@ You can list all the tags associated with a metadata
object. The tags in Graviti
inheritable, so listing tags of a metadata object will also list the tags of
its parent metadata
objects.
-The request path for REST API is
`/api/metalakes/{metalake}/tags/{metadataObjectType}/{metadataObjectName}`.
+The request path for REST API is
`/api/metalakes/{metalake}/objects/{metadataObjectType}/{metadataObjectName}/tags`.
<Tabs groupId='language' queryString>
<TabItem value="shell" label="Shell">
```shell
curl -X GET -H "Accept: application/vnd.gravitino.v1+json" \
-http://localhost:8090/api/metalakes/test/tags/catalog/catalog1
+http://localhost:8090/api/metalakes/test/objects/catalog/catalog1/tags
curl -X GET -H "Accept: application/vnd.gravitino.v1+json" \
-http://localhost:8090/api/metalakes/test/tags/schema/catalog1.schema1
+http://localhost:8090/api/metalakes/test/objects/schema/catalog1.schema1/tags
curl -X GET -H "Accept: application/vnd.gravitino.v1+json" \
-http://localhost:8090/api/metalakes/test/tags/catalog/catalog1?details=true
+http://localhost:8090/api/metalakes/test/objects/catalog/catalog1/tags?details=true
curl -X GET -H "Accept: application/vnd.gravitino.v1+json" \
-http://localhost:8090/api/metalakes/test/tags/schema/catalog1.schema1?details=true
+http://localhost:8090/api/metalakes/test/objects/schema/catalog1.schema1/tags?details=true
```
</TabItem>
@@ -291,17 +291,17 @@ Tag[] tagsInfo = schema1.supportsTags().listTagsInfo();
You can get an associated tag by its name for a metadata object.
-The request path for REST API is
`/api/metalakes/{metalake}/tags/{metadataObjectType}/{metadataObjectName}/{tagName}`.
+The request path for REST API is
`/api/metalakes/{metalake}/objects/{metadataObjectType}/{metadataObjectName}/tags/{tagName}`.
<Tabs groupId='language' queryString>
<TabItem value="shell" label="Shell">
```shell
curl -X GET -H "Accept: application/vnd.gravitino.v1+json" \
-http://localhost:8090/api/metalakes/test/tags/catalog/catalog1/tag1
+http://localhost:8090/api/metalakes/test/objects/catalog/catalog1/tags/tag1
curl -X GET -H "Accept: application/vnd.gravitino.v1+json" \
-http://localhost:8090/api/metalakes/test/tags/schema/catalog1.schema1/tag1
+http://localhost:8090/api/metalakes/test/objects/schema/catalog1.schema1/tags/tag1
```
</TabItem>
diff --git a/docs/open-api/openapi.yaml b/docs/open-api/openapi.yaml
index 2305eafce..2c8ab1bfe 100644
--- a/docs/open-api/openapi.yaml
+++ b/docs/open-api/openapi.yaml
@@ -62,11 +62,11 @@ paths:
/metalakes/{metalake}/tags/{tag}:
$ref: "./tags.yaml#/paths/~1metalakes~1%7Bmetalake%7D~1tags~1%7Btag%7D"
- /metalakes/{metalake}/tags/{metadataObjectType}/{metadataObjectFullName}:
- $ref:
"./tags.yaml#/paths/~1metalakes~1%7Bmetalake%7D~1tags~1%7BmetadataObjectType%7D~1%7BmetadataObjectFullName%7D"
+
/metalakes/{metalake}/objects/{metadataObjectType}/{metadataObjectFullName}/tags:
+ $ref:
"./tags.yaml#/paths/~1metalakes~1%7Bmetalake%7D~1objects~1%7BmetadataObjectType%7D~1%7BmetadataObjectFullName%7D~1tags"
-
/metalakes/{metalake}/tags/{metadataObjectType}/{metadataObjectFullName}/{tag}:
- $ref:
"./tags.yaml#/paths/~1metalakes~1%7Bmetalake%7D~1tags~1%7BmetadataObjectType%7D~1%7BmetadataObjectFullName%7D~1%7Btag%7D"
+
/metalakes/{metalake}/objects/{metadataObjectType}/{metadataObjectFullName}/tags/{tag}:
+ $ref:
"./tags.yaml#/paths/~1metalakes~1%7Bmetalake%7D~1objects~1%7BmetadataObjectType%7D~1%7BmetadataObjectFullName%7D~1tags~1%7Btag%7D"
/metalakes/{metalake}/tags/{tag}/objects:
$ref:
"./tags.yaml#/paths/~1metalakes~1%7Bmetalake%7D~1tags~1%7Btag%7D~1objects"
diff --git a/docs/open-api/tags.yaml b/docs/open-api/tags.yaml
index 7264c0673..9419b8f6e 100644
--- a/docs/open-api/tags.yaml
+++ b/docs/open-api/tags.yaml
@@ -178,7 +178,7 @@ paths:
$ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
- /metalakes/{metalake}/tags/{metadataObjectType}/{metadataObjectFullName}:
+
/metalakes/{metalake}/objects/{metadataObjectType}/{metadataObjectFullName}/tags:
parameters:
- $ref: "./openapi.yaml#/components/parameters/metalake"
- $ref: "./openapi.yaml#/components/parameters/metadataObjectType"
@@ -245,7 +245,7 @@ paths:
$ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
-
/metalakes/{metalake}/tags/{metadataObjectType}/{metadataObjectFullName}/{tag}:
+
/metalakes/{metalake}/objects/{metadataObjectType}/{metadataObjectFullName}/tags/{tag}:
parameters:
- $ref: "./openapi.yaml#/components/parameters/metalake"
- $ref: "./openapi.yaml#/components/parameters/metadataObjectType"
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/rest/TagOperations.java
b/server/src/main/java/org/apache/gravitino/server/web/rest/MetadataObjectTagOperations.java
similarity index 56%
copy from
server/src/main/java/org/apache/gravitino/server/web/rest/TagOperations.java
copy to
server/src/main/java/org/apache/gravitino/server/web/rest/MetadataObjectTagOperations.java
index 733623977..c8668d225 100644
---
a/server/src/main/java/org/apache/gravitino/server/web/rest/TagOperations.java
+++
b/server/src/main/java/org/apache/gravitino/server/web/rest/MetadataObjectTagOperations.java
@@ -28,11 +28,9 @@ import java.util.Locale;
import java.util.Optional;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
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;
@@ -42,188 +40,106 @@ import javax.ws.rs.core.Response;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.MetadataObjects;
-import org.apache.gravitino.dto.requests.TagCreateRequest;
-import org.apache.gravitino.dto.requests.TagUpdateRequest;
-import org.apache.gravitino.dto.requests.TagUpdatesRequest;
import org.apache.gravitino.dto.requests.TagsAssociateRequest;
-import org.apache.gravitino.dto.responses.DropResponse;
-import org.apache.gravitino.dto.responses.MetadataObjectListResponse;
import org.apache.gravitino.dto.responses.NameListResponse;
import org.apache.gravitino.dto.responses.TagListResponse;
import org.apache.gravitino.dto.responses.TagResponse;
-import org.apache.gravitino.dto.tag.MetadataObjectDTO;
import org.apache.gravitino.dto.tag.TagDTO;
import org.apache.gravitino.dto.util.DTOConverters;
import org.apache.gravitino.exceptions.NoSuchTagException;
import org.apache.gravitino.metrics.MetricNames;
import org.apache.gravitino.server.web.Utils;
import org.apache.gravitino.tag.Tag;
-import org.apache.gravitino.tag.TagChange;
import org.apache.gravitino.tag.TagManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-@Path("metalakes/{metalake}/tags")
-public class TagOperations {
-
- private static final Logger LOG =
LoggerFactory.getLogger(TagOperations.class);
+@Path("/metalakes/{metalake}/objects/{type}/{fullName}/tags")
+public class MetadataObjectTagOperations {
+ private static final Logger LOG =
LoggerFactory.getLogger(MetadataObjectTagOperations.class);
private final TagManager tagManager;
@Context private HttpServletRequest httpRequest;
@Inject
- public TagOperations(TagManager tagManager) {
+ public MetadataObjectTagOperations(TagManager tagManager) {
this.tagManager = tagManager;
}
- @GET
- @Produces("application/vnd.gravitino.v1+json")
- @Timed(name = "list-tags." + MetricNames.HTTP_PROCESS_DURATION, absolute =
true)
- @ResponseMetered(name = "list-tags", absolute = true)
- public Response listTags(
- @PathParam("metalake") String metalake,
- @QueryParam("details") @DefaultValue("false") boolean verbose) {
- LOG.info(
- "Received list tag {} request for metalake: {}", verbose ? "infos" :
"names", metalake);
-
- try {
- return Utils.doAs(
- httpRequest,
- () -> {
- if (verbose) {
- Tag[] tags = tagManager.listTagsInfo(metalake);
- TagDTO[] tagDTOs;
- if (ArrayUtils.isEmpty(tags)) {
- tagDTOs = new TagDTO[0];
- } else {
- tagDTOs =
- Arrays.stream(tags)
- .map(t -> DTOConverters.toDTO(t, Optional.empty()))
- .toArray(TagDTO[]::new);
- }
-
- LOG.info("List {} tags info under metalake: {}", tagDTOs.length,
metalake);
- return Utils.ok(new TagListResponse(tagDTOs));
-
- } else {
- String[] tagNames = tagManager.listTags(metalake);
- tagNames = tagNames == null ? new String[0] : tagNames;
-
- LOG.info("List {} tags under metalake: {}", tagNames.length,
metalake);
- return Utils.ok(new NameListResponse(tagNames));
- }
- });
- } catch (Exception e) {
- return ExceptionHandlers.handleTagException(OperationType.LIST, "",
metalake, e);
- }
- }
-
- @POST
- @Produces("application/vnd.gravitino.v1+json")
- @Timed(name = "create-tag." + MetricNames.HTTP_PROCESS_DURATION, absolute =
true)
- @ResponseMetered(name = "create-tag", absolute = true)
- public Response createTag(@PathParam("metalake") String metalake,
TagCreateRequest request) {
- LOG.info("Received create tag request under metalake: {}", metalake);
-
- try {
- return Utils.doAs(
- httpRequest,
- () -> {
- request.validate();
- Tag tag =
- tagManager.createTag(
- metalake, request.getName(), request.getComment(),
request.getProperties());
-
- LOG.info("Created tag: {} under metalake: {}", tag.name(),
metalake);
- return Utils.ok(new TagResponse(DTOConverters.toDTO(tag,
Optional.empty())));
- });
- } catch (Exception e) {
- return ExceptionHandlers.handleTagException(
- OperationType.CREATE, request.getName(), metalake, e);
- }
+ // TagOperations will reuse this class to be compatible with legacy
interfaces.
+ void setHttpRequest(HttpServletRequest httpRequest) {
+ this.httpRequest = httpRequest;
}
@GET
@Path("{tag}")
@Produces("application/vnd.gravitino.v1+json")
- @Timed(name = "get-tag." + MetricNames.HTTP_PROCESS_DURATION, absolute =
true)
- @ResponseMetered(name = "get-tag", absolute = true)
- public Response getTag(@PathParam("metalake") String metalake,
@PathParam("tag") String name) {
- LOG.info("Received get tag request for tag: {} under metalake: {}", name,
metalake);
-
- try {
- return Utils.doAs(
- httpRequest,
- () -> {
- Tag tag = tagManager.getTag(metalake, name);
- LOG.info("Get tag: {} under metalake: {}", name, metalake);
- return Utils.ok(new TagResponse(DTOConverters.toDTO(tag,
Optional.empty())));
- });
- } catch (Exception e) {
- return ExceptionHandlers.handleTagException(OperationType.GET, name,
metalake, e);
- }
- }
-
- @PUT
- @Path("{tag}")
- @Produces("application/vnd.gravitino.v1+json")
- @Timed(name = "alter-tag." + MetricNames.HTTP_PROCESS_DURATION, absolute =
true)
- @ResponseMetered(name = "alter-tag", absolute = true)
- public Response alterTag(
+ @Timed(name = "get-object-tag." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
+ @ResponseMetered(name = "get-object-tag", absolute = true)
+ public Response getTagForObject(
@PathParam("metalake") String metalake,
- @PathParam("tag") String name,
- TagUpdatesRequest request) {
- LOG.info("Received alter tag request for tag: {} under metalake: {}",
name, metalake);
+ @PathParam("type") String type,
+ @PathParam("fullName") String fullName,
+ @PathParam("tag") String tagName) {
+ LOG.info(
+ "Received get tag {} request for object type: {}, full name: {} under
metalake: {}",
+ tagName,
+ type,
+ fullName,
+ metalake);
try {
return Utils.doAs(
httpRequest,
() -> {
- request.validate();
-
- TagChange[] changes =
- request.getUpdates().stream()
- .map(TagUpdateRequest::tagChange)
- .toArray(TagChange[]::new);
- Tag tag = tagManager.alterTag(metalake, name, changes);
-
- LOG.info("Altered tag: {} under metalake: {}", name, metalake);
- return Utils.ok(new TagResponse(DTOConverters.toDTO(tag,
Optional.empty())));
- });
- } catch (Exception e) {
- return ExceptionHandlers.handleTagException(OperationType.ALTER, name,
metalake, e);
- }
- }
+ MetadataObject object =
+ MetadataObjects.parse(
+ fullName,
MetadataObject.Type.valueOf(type.toUpperCase(Locale.ROOT)));
+ Optional<Tag> tag = getTagForObject(metalake, object, tagName);
+ Optional<TagDTO> tagDTO = tag.map(t -> DTOConverters.toDTO(t,
Optional.of(false)));
- @DELETE
- @Path("{tag}")
- @Produces("application/vnd.gravitino.v1+json")
- @Timed(name = "delete-tag." + MetricNames.HTTP_PROCESS_DURATION, absolute =
true)
- @ResponseMetered(name = "delete-tag", absolute = true)
- public Response deleteTag(@PathParam("metalake") String metalake,
@PathParam("tag") String name) {
- LOG.info("Received delete tag request for tag: {} under metalake: {}",
name, metalake);
+ MetadataObject parentObject = MetadataObjects.parent(object);
+ while (!tag.isPresent() && parentObject != null) {
+ tag = getTagForObject(metalake, parentObject, tagName);
+ tagDTO = tag.map(t -> DTOConverters.toDTO(t, Optional.of(true)));
+ parentObject = MetadataObjects.parent(parentObject);
+ }
- try {
- return Utils.doAs(
- httpRequest,
- () -> {
- boolean deleted = tagManager.deleteTag(metalake, name);
- if (!deleted) {
- LOG.warn("Failed to delete tag {} under metalake {}", name,
metalake);
+ if (!tagDTO.isPresent()) {
+ LOG.warn(
+ "Tag {} not found for object type: {}, full name: {} under
metalake: {}",
+ tagName,
+ type,
+ fullName,
+ metalake);
+ return Utils.notFound(
+ NoSuchTagException.class.getSimpleName(),
+ "Tag not found: "
+ + tagName
+ + " for object type: "
+ + type
+ + ", full name: "
+ + fullName
+ + " under metalake: "
+ + metalake);
} else {
- LOG.info("Deleted tag: {} under metalake: {}", name, metalake);
+ LOG.info(
+ "Get tag: {} for object type: {}, full name: {} under
metalake: {}",
+ tagName,
+ type,
+ fullName,
+ metalake);
+ return Utils.ok(new TagResponse(tagDTO.get()));
}
-
- return Utils.ok(new DropResponse(deleted));
});
+
} catch (Exception e) {
- return ExceptionHandlers.handleTagException(OperationType.DELETE, name,
metalake, e);
+ return ExceptionHandlers.handleTagException(OperationType.GET, tagName,
fullName, e);
}
}
@GET
- @Path("{type}/{fullName}")
@Produces("application/vnd.gravitino.v1+json")
@Timed(name = "list-object-tags." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
@ResponseMetered(name = "list-object-tags", absolute = true)
@@ -300,107 +216,7 @@ public class TagOperations {
}
}
- @GET
- @Path("{type}/{fullName}/{tag}")
- @Produces("application/vnd.gravitino.v1+json")
- @Timed(name = "get-object-tag." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
- @ResponseMetered(name = "get-object-tag", absolute = true)
- public Response getTagForObject(
- @PathParam("metalake") String metalake,
- @PathParam("type") String type,
- @PathParam("fullName") String fullName,
- @PathParam("tag") String tagName) {
- LOG.info(
- "Received get tag {} request for object type: {}, full name: {} under
metalake: {}",
- tagName,
- type,
- fullName,
- metalake);
-
- try {
- return Utils.doAs(
- httpRequest,
- () -> {
- MetadataObject object =
- MetadataObjects.parse(
- fullName,
MetadataObject.Type.valueOf(type.toUpperCase(Locale.ROOT)));
- Optional<Tag> tag = getTagForObject(metalake, object, tagName);
- Optional<TagDTO> tagDTO = tag.map(t -> DTOConverters.toDTO(t,
Optional.of(false)));
-
- MetadataObject parentObject = MetadataObjects.parent(object);
- while (!tag.isPresent() && parentObject != null) {
- tag = getTagForObject(metalake, parentObject, tagName);
- tagDTO = tag.map(t -> DTOConverters.toDTO(t, Optional.of(true)));
- parentObject = MetadataObjects.parent(parentObject);
- }
-
- if (!tagDTO.isPresent()) {
- LOG.warn(
- "Tag {} not found for object type: {}, full name: {} under
metalake: {}",
- tagName,
- type,
- fullName,
- metalake);
- return Utils.notFound(
- NoSuchTagException.class.getSimpleName(),
- "Tag not found: "
- + tagName
- + " for object type: "
- + type
- + ", full name: "
- + fullName
- + " under metalake: "
- + metalake);
- } else {
- LOG.info(
- "Get tag: {} for object type: {}, full name: {} under
metalake: {}",
- tagName,
- type,
- fullName,
- metalake);
- return Utils.ok(new TagResponse(tagDTO.get()));
- }
- });
-
- } catch (Exception e) {
- return ExceptionHandlers.handleTagException(OperationType.GET, tagName,
fullName, e);
- }
- }
-
- @GET
- @Path("{tag}/objects")
- @Produces("application/vnd.gravitino.v1+json")
- @Timed(name = "list-objects-for-tag." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
- @ResponseMetered(name = "list-objects-for-tag", absolute = true)
- public Response listMetadataObjectsForTag(
- @PathParam("metalake") String metalake, @PathParam("tag") String
tagName) {
- LOG.info("Received list objects for tag: {} under metalake: {}", tagName,
metalake);
-
- try {
- return Utils.doAs(
- httpRequest,
- () -> {
- MetadataObject[] objects =
tagManager.listMetadataObjectsForTag(metalake, tagName);
- objects = objects == null ? new MetadataObject[0] : objects;
-
- LOG.info(
- "List {} objects for tag: {} under metalake: {}",
- objects.length,
- tagName,
- metalake);
-
- MetadataObjectDTO[] objectDTOs =
-
Arrays.stream(objects).map(DTOConverters::toDTO).toArray(MetadataObjectDTO[]::new);
- return Utils.ok(new MetadataObjectListResponse(objectDTOs));
- });
-
- } catch (Exception e) {
- return ExceptionHandlers.handleTagException(OperationType.LIST, "",
tagName, e);
- }
- }
-
@POST
- @Path("{type}/{fullName}")
@Produces("application/vnd.gravitino.v1+json")
@Timed(name = "associate-object-tags." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
@ResponseMetered(name = "associate-object-tags", absolute = true)
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/rest/TagOperations.java
b/server/src/main/java/org/apache/gravitino/server/web/rest/TagOperations.java
index 733623977..7fdd2fc69 100644
---
a/server/src/main/java/org/apache/gravitino/server/web/rest/TagOperations.java
+++
b/server/src/main/java/org/apache/gravitino/server/web/rest/TagOperations.java
@@ -20,11 +20,7 @@ package org.apache.gravitino.server.web.rest;
import com.codahale.metrics.annotation.ResponseMetered;
import com.codahale.metrics.annotation.Timed;
-import com.google.common.collect.Lists;
import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
import java.util.Optional;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
@@ -41,7 +37,6 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.gravitino.MetadataObject;
-import org.apache.gravitino.MetadataObjects;
import org.apache.gravitino.dto.requests.TagCreateRequest;
import org.apache.gravitino.dto.requests.TagUpdateRequest;
import org.apache.gravitino.dto.requests.TagUpdatesRequest;
@@ -54,7 +49,6 @@ import org.apache.gravitino.dto.responses.TagResponse;
import org.apache.gravitino.dto.tag.MetadataObjectDTO;
import org.apache.gravitino.dto.tag.TagDTO;
import org.apache.gravitino.dto.util.DTOConverters;
-import org.apache.gravitino.exceptions.NoSuchTagException;
import org.apache.gravitino.metrics.MetricNames;
import org.apache.gravitino.server.web.Utils;
import org.apache.gravitino.tag.Tag;
@@ -222,151 +216,6 @@ public class TagOperations {
}
}
- @GET
- @Path("{type}/{fullName}")
- @Produces("application/vnd.gravitino.v1+json")
- @Timed(name = "list-object-tags." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
- @ResponseMetered(name = "list-object-tags", absolute = true)
- public Response listTagsForMetadataObject(
- @PathParam("metalake") String metalake,
- @PathParam("type") String type,
- @PathParam("fullName") String fullName,
- @QueryParam("details") @DefaultValue("false") boolean verbose) {
- LOG.info(
- "Received list tag {} request for object type: {}, full name: {} under
metalake: {}",
- verbose ? "infos" : "names",
- type,
- fullName,
- metalake);
-
- try {
- return Utils.doAs(
- httpRequest,
- () -> {
- MetadataObject object =
- MetadataObjects.parse(
- fullName,
MetadataObject.Type.valueOf(type.toUpperCase(Locale.ROOT)));
-
- List<TagDTO> tags = Lists.newArrayList();
- Tag[] nonInheritedTags =
tagManager.listTagsInfoForMetadataObject(metalake, object);
- if (ArrayUtils.isNotEmpty(nonInheritedTags)) {
- Collections.addAll(
- tags,
- Arrays.stream(nonInheritedTags)
- .map(t -> DTOConverters.toDTO(t, Optional.of(false)))
- .toArray(TagDTO[]::new));
- }
-
- MetadataObject parentObject = MetadataObjects.parent(object);
- while (parentObject != null) {
- Tag[] inheritedTags =
- tagManager.listTagsInfoForMetadataObject(metalake,
parentObject);
- if (ArrayUtils.isNotEmpty(inheritedTags)) {
- Collections.addAll(
- tags,
- Arrays.stream(inheritedTags)
- .map(t -> DTOConverters.toDTO(t, Optional.of(true)))
- .toArray(TagDTO[]::new));
- }
- parentObject = MetadataObjects.parent(parentObject);
- }
-
- if (verbose) {
- LOG.info(
- "List {} tags info for object type: {}, full name: {} under
metalake: {}",
- tags.size(),
- type,
- fullName,
- metalake);
- return Utils.ok(new TagListResponse(tags.toArray(new
TagDTO[0])));
-
- } else {
- // Due to same name tag will be associated to both parent and
child objects, so we
- // need to deduplicate the tag names.
- String[] tagNames =
tags.stream().map(TagDTO::name).distinct().toArray(String[]::new);
-
- LOG.info(
- "List {} tags for object type: {}, full name: {} under
metalake: {}",
- tagNames.length,
- type,
- fullName,
- metalake);
- return Utils.ok(new NameListResponse(tagNames));
- }
- });
-
- } catch (Exception e) {
- return ExceptionHandlers.handleTagException(OperationType.LIST, "",
fullName, e);
- }
- }
-
- @GET
- @Path("{type}/{fullName}/{tag}")
- @Produces("application/vnd.gravitino.v1+json")
- @Timed(name = "get-object-tag." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
- @ResponseMetered(name = "get-object-tag", absolute = true)
- public Response getTagForObject(
- @PathParam("metalake") String metalake,
- @PathParam("type") String type,
- @PathParam("fullName") String fullName,
- @PathParam("tag") String tagName) {
- LOG.info(
- "Received get tag {} request for object type: {}, full name: {} under
metalake: {}",
- tagName,
- type,
- fullName,
- metalake);
-
- try {
- return Utils.doAs(
- httpRequest,
- () -> {
- MetadataObject object =
- MetadataObjects.parse(
- fullName,
MetadataObject.Type.valueOf(type.toUpperCase(Locale.ROOT)));
- Optional<Tag> tag = getTagForObject(metalake, object, tagName);
- Optional<TagDTO> tagDTO = tag.map(t -> DTOConverters.toDTO(t,
Optional.of(false)));
-
- MetadataObject parentObject = MetadataObjects.parent(object);
- while (!tag.isPresent() && parentObject != null) {
- tag = getTagForObject(metalake, parentObject, tagName);
- tagDTO = tag.map(t -> DTOConverters.toDTO(t, Optional.of(true)));
- parentObject = MetadataObjects.parent(parentObject);
- }
-
- if (!tagDTO.isPresent()) {
- LOG.warn(
- "Tag {} not found for object type: {}, full name: {} under
metalake: {}",
- tagName,
- type,
- fullName,
- metalake);
- return Utils.notFound(
- NoSuchTagException.class.getSimpleName(),
- "Tag not found: "
- + tagName
- + " for object type: "
- + type
- + ", full name: "
- + fullName
- + " under metalake: "
- + metalake);
- } else {
- LOG.info(
- "Get tag: {} for object type: {}, full name: {} under
metalake: {}",
- tagName,
- type,
- fullName,
- metalake);
- return Utils.ok(new TagResponse(tagDTO.get()));
- }
- });
-
- } catch (Exception e) {
- return ExceptionHandlers.handleTagException(OperationType.GET, tagName,
fullName, e);
- }
- }
-
@GET
@Path("{tag}/objects")
@Produces("application/vnd.gravitino.v1+json")
@@ -399,6 +248,41 @@ public class TagOperations {
}
}
+ @Deprecated
+ @GET
+ @Path("{type}/{fullName}")
+ @Produces("application/vnd.gravitino.v1+json")
+ @Timed(name = "list-object-tags." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
+ @ResponseMetered(name = "list-object-tags", absolute = true)
+ public Response listTagsForMetadataObject(
+ @PathParam("metalake") String metalake,
+ @PathParam("type") String type,
+ @PathParam("fullName") String fullName,
+ @QueryParam("details") @DefaultValue("false") boolean verbose) {
+ MetadataObjectTagOperations metadataObjectTagOperations =
+ new MetadataObjectTagOperations(tagManager);
+ metadataObjectTagOperations.setHttpRequest(httpRequest);
+ return metadataObjectTagOperations.listTagsForMetadataObject(metalake,
type, fullName, verbose);
+ }
+
+ @Deprecated
+ @GET
+ @Path("{type}/{fullName}/{tag}")
+ @Produces("application/vnd.gravitino.v1+json")
+ @Timed(name = "get-object-tag." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
+ @ResponseMetered(name = "get-object-tag", absolute = true)
+ public Response getTagForObject(
+ @PathParam("metalake") String metalake,
+ @PathParam("type") String type,
+ @PathParam("fullName") String fullName,
+ @PathParam("tag") String tagName) {
+ MetadataObjectTagOperations metadataObjectTagOperations =
+ new MetadataObjectTagOperations(tagManager);
+ metadataObjectTagOperations.setHttpRequest(httpRequest);
+ return metadataObjectTagOperations.getTagForObject(metalake, type,
fullName, tagName);
+ }
+
+ @Deprecated
@POST
@Path("{type}/{fullName}")
@Produces("application/vnd.gravitino.v1+json")
@@ -409,45 +293,9 @@ public class TagOperations {
@PathParam("type") String type,
@PathParam("fullName") String fullName,
TagsAssociateRequest request) {
- LOG.info(
- "Received associate tags request for object type: {}, full name: {}
under metalake: {}",
- type,
- fullName,
- metalake);
-
- try {
- return Utils.doAs(
- httpRequest,
- () -> {
- request.validate();
- MetadataObject object =
- MetadataObjects.parse(
- fullName,
MetadataObject.Type.valueOf(type.toUpperCase(Locale.ROOT)));
- String[] tagNames =
- tagManager.associateTagsForMetadataObject(
- metalake, object, request.getTagsToAdd(),
request.getTagsToRemove());
- tagNames = tagNames == null ? new String[0] : tagNames;
-
- LOG.info(
- "Associated tags: {} for object type: {}, full name: {} under
metalake: {}",
- Arrays.toString(tagNames),
- type,
- fullName,
- metalake);
- return Utils.ok(new NameListResponse(tagNames));
- });
-
- } catch (Exception e) {
- return ExceptionHandlers.handleTagException(OperationType.ASSOCIATE, "",
fullName, e);
- }
- }
-
- private Optional<Tag> getTagForObject(String metalake, MetadataObject
object, String tagName) {
- try {
- return Optional.ofNullable(tagManager.getTagForMetadataObject(metalake,
object, tagName));
- } catch (NoSuchTagException e) {
- LOG.info("Tag {} not found for object: {}", tagName, object);
- return Optional.empty();
- }
+ MetadataObjectTagOperations metadataObjectTagOperations =
+ new MetadataObjectTagOperations(tagManager);
+ metadataObjectTagOperations.setHttpRequest(httpRequest);
+ return metadataObjectTagOperations.associateTagsForObject(metalake, type,
fullName, request);
}
}
diff --git
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestMetadataObjectTagOperations.java
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestMetadataObjectTagOperations.java
new file mode 100644
index 000000000..8e0324bea
--- /dev/null
+++
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestMetadataObjectTagOperations.java
@@ -0,0 +1,639 @@
+/*
+ * 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.gravitino.server.web.rest;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.Sets;
+import java.io.IOException;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.MetadataObjects;
+import org.apache.gravitino.dto.requests.TagsAssociateRequest;
+import org.apache.gravitino.dto.responses.ErrorConstants;
+import org.apache.gravitino.dto.responses.ErrorResponse;
+import org.apache.gravitino.dto.responses.NameListResponse;
+import org.apache.gravitino.dto.responses.TagListResponse;
+import org.apache.gravitino.dto.responses.TagResponse;
+import org.apache.gravitino.exceptions.NoSuchTagException;
+import org.apache.gravitino.exceptions.TagAlreadyAssociatedException;
+import org.apache.gravitino.meta.AuditInfo;
+import org.apache.gravitino.meta.TagEntity;
+import org.apache.gravitino.rest.RESTUtils;
+import org.apache.gravitino.tag.Tag;
+import org.apache.gravitino.tag.TagManager;
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestMetadataObjectTagOperations extends JerseyTest {
+
+ private static class MockServletRequestFactory extends
ServletRequestFactoryBase {
+
+ @Override
+ public HttpServletRequest get() {
+ HttpServletRequest request = mock(HttpServletRequest.class);
+ when(request.getRemoteUser()).thenReturn(null);
+ return request;
+ }
+ }
+
+ private TagManager tagManager = mock(TagManager.class);
+
+ private String metalake = "test_metalake";
+
+ private AuditInfo testAuditInfo1 =
+
AuditInfo.builder().withCreator("user1").withCreateTime(Instant.now()).build();
+
+ @Override
+ protected Application configure() {
+ try {
+ forceSet(
+ TestProperties.CONTAINER_PORT,
String.valueOf(RESTUtils.findAvailablePort(2000, 3000)));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ ResourceConfig resourceConfig = new ResourceConfig();
+ resourceConfig.register(MetadataObjectTagOperations.class);
+ resourceConfig.register(
+ new AbstractBinder() {
+ @Override
+ protected void configure() {
+ bind(tagManager).to(TagManager.class).ranked(2);
+
bindFactory(MockServletRequestFactory.class).to(HttpServletRequest.class);
+ }
+ });
+
+ return resourceConfig;
+ }
+
+ @Test
+ public void testListTagsForObject() {
+ MetadataObject catalog = MetadataObjects.parse("object1",
MetadataObject.Type.CATALOG);
+ MetadataObject schema = MetadataObjects.parse("object1.object2",
MetadataObject.Type.SCHEMA);
+ MetadataObject table =
+ MetadataObjects.parse("object1.object2.object3",
MetadataObject.Type.TABLE);
+ MetadataObject column =
+ MetadataObjects.parse("object1.object2.object3.object4",
MetadataObject.Type.COLUMN);
+
+ Tag[] catalogTagInfos =
+ new Tag[] {
+
TagEntity.builder().withName("tag1").withId(1L).withAuditInfo(testAuditInfo1).build()
+ };
+ when(tagManager.listTagsInfoForMetadataObject(metalake,
catalog)).thenReturn(catalogTagInfos);
+
+ Tag[] schemaTagInfos =
+ new Tag[] {
+
TagEntity.builder().withName("tag3").withId(1L).withAuditInfo(testAuditInfo1).build()
+ };
+ when(tagManager.listTagsInfoForMetadataObject(metalake,
schema)).thenReturn(schemaTagInfos);
+
+ Tag[] tableTagInfos =
+ new Tag[] {
+
TagEntity.builder().withName("tag5").withId(1L).withAuditInfo(testAuditInfo1).build()
+ };
+ when(tagManager.listTagsInfoForMetadataObject(metalake,
table)).thenReturn(tableTagInfos);
+
+ Tag[] columnTagInfos =
+ new Tag[] {
+
TagEntity.builder().withName("tag7").withId(1L).withAuditInfo(testAuditInfo1).build()
+ };
+ when(tagManager.listTagsInfoForMetadataObject(metalake,
column)).thenReturn(columnTagInfos);
+
+ // Test catalog tags
+ Response response =
+ target(basePath(metalake))
+ .path(catalog.type().toString())
+ .path(catalog.fullName())
+ .path("/tags")
+ .queryParam("details", true)
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
response.getStatus());
+
+ TagListResponse tagListResponse =
response.readEntity(TagListResponse.class);
+ Assertions.assertEquals(0, tagListResponse.getCode());
+ Assertions.assertEquals(catalogTagInfos.length,
tagListResponse.getTags().length);
+
+ Map<String, Tag> resultTags =
+ Arrays.stream(tagListResponse.getTags())
+ .collect(Collectors.toMap(Tag::name, Function.identity()));
+
+ Assertions.assertTrue(resultTags.containsKey("tag1"));
+ Assertions.assertFalse(resultTags.get("tag1").inherited().get());
+
+ Response response1 =
+ target(basePath(metalake))
+ .path(catalog.type().toString())
+ .path(catalog.fullName())
+ .path("tags")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
response1.getStatus());
+
+ NameListResponse nameListResponse =
response1.readEntity(NameListResponse.class);
+ Assertions.assertEquals(0, nameListResponse.getCode());
+ Assertions.assertEquals(catalogTagInfos.length,
nameListResponse.getNames().length);
+ Assertions.assertArrayEquals(
+ Arrays.stream(catalogTagInfos).map(Tag::name).toArray(String[]::new),
+ nameListResponse.getNames());
+
+ // Test schema tags
+ Response response2 =
+ target(basePath(metalake))
+ .path(schema.type().toString())
+ .path(schema.fullName())
+ .path("tags")
+ .queryParam("details", true)
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
response2.getStatus());
+
+ TagListResponse tagListResponse1 =
response2.readEntity(TagListResponse.class);
+ Assertions.assertEquals(0, tagListResponse1.getCode());
+ Assertions.assertEquals(
+ schemaTagInfos.length + catalogTagInfos.length,
tagListResponse1.getTags().length);
+
+ Map<String, Tag> resultTags1 =
+ Arrays.stream(tagListResponse1.getTags())
+ .collect(Collectors.toMap(Tag::name, Function.identity()));
+
+ Assertions.assertTrue(resultTags1.containsKey("tag1"));
+ Assertions.assertTrue(resultTags1.containsKey("tag3"));
+
+ Assertions.assertTrue(resultTags1.get("tag1").inherited().get());
+ Assertions.assertFalse(resultTags1.get("tag3").inherited().get());
+
+ Response response3 =
+ target(basePath(metalake))
+ .path(schema.type().toString())
+ .path(schema.fullName())
+ .path("tags")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
response3.getStatus());
+
+ NameListResponse nameListResponse1 =
response3.readEntity(NameListResponse.class);
+ Assertions.assertEquals(0, nameListResponse1.getCode());
+ Assertions.assertEquals(
+ schemaTagInfos.length + catalogTagInfos.length,
nameListResponse1.getNames().length);
+ Set<String> resultNames = Sets.newHashSet(nameListResponse1.getNames());
+ Assertions.assertTrue(resultNames.contains("tag1"));
+ Assertions.assertTrue(resultNames.contains("tag3"));
+
+ // Test table tags
+ Response response4 =
+ target(basePath(metalake))
+ .path(table.type().toString())
+ .path(table.fullName())
+ .path("tags")
+ .queryParam("details", true)
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
response4.getStatus());
+
+ TagListResponse tagListResponse2 =
response4.readEntity(TagListResponse.class);
+ Assertions.assertEquals(0, tagListResponse2.getCode());
+ Assertions.assertEquals(
+ schemaTagInfos.length + catalogTagInfos.length + tableTagInfos.length,
+ tagListResponse2.getTags().length);
+
+ Map<String, Tag> resultTags2 =
+ Arrays.stream(tagListResponse2.getTags())
+ .collect(Collectors.toMap(Tag::name, Function.identity()));
+
+ Assertions.assertTrue(resultTags2.containsKey("tag1"));
+ Assertions.assertTrue(resultTags2.containsKey("tag3"));
+ Assertions.assertTrue(resultTags2.containsKey("tag5"));
+
+ Assertions.assertTrue(resultTags2.get("tag1").inherited().get());
+ Assertions.assertTrue(resultTags2.get("tag3").inherited().get());
+ Assertions.assertFalse(resultTags2.get("tag5").inherited().get());
+
+ Response response5 =
+ target(basePath(metalake))
+ .path(table.type().toString())
+ .path(table.fullName())
+ .path("tags")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
response5.getStatus());
+
+ NameListResponse nameListResponse2 =
response5.readEntity(NameListResponse.class);
+ Assertions.assertEquals(0, nameListResponse2.getCode());
+ Assertions.assertEquals(
+ schemaTagInfos.length + catalogTagInfos.length + tableTagInfos.length,
+ nameListResponse2.getNames().length);
+
+ Set<String> resultNames1 = Sets.newHashSet(nameListResponse2.getNames());
+ Assertions.assertTrue(resultNames1.contains("tag1"));
+ Assertions.assertTrue(resultNames1.contains("tag3"));
+ Assertions.assertTrue(resultNames1.contains("tag5"));
+
+ // Test column tags
+ Response response6 =
+ target(basePath(metalake))
+ .path(column.type().toString())
+ .path(column.fullName())
+ .path("tags")
+ .queryParam("details", true)
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
response6.getStatus());
+
+ TagListResponse tagListResponse3 =
response6.readEntity(TagListResponse.class);
+ Assertions.assertEquals(0, tagListResponse3.getCode());
+ Assertions.assertEquals(
+ schemaTagInfos.length
+ + catalogTagInfos.length
+ + tableTagInfos.length
+ + columnTagInfos.length,
+ tagListResponse3.getTags().length);
+
+ Map<String, Tag> resultTags3 =
+ Arrays.stream(tagListResponse3.getTags())
+ .collect(Collectors.toMap(Tag::name, Function.identity()));
+
+ Assertions.assertTrue(resultTags3.containsKey("tag1"));
+ Assertions.assertTrue(resultTags3.containsKey("tag3"));
+ Assertions.assertTrue(resultTags3.containsKey("tag5"));
+ Assertions.assertTrue(resultTags3.containsKey("tag7"));
+
+ Assertions.assertTrue(resultTags3.get("tag1").inherited().get());
+ Assertions.assertTrue(resultTags3.get("tag3").inherited().get());
+ Assertions.assertTrue(resultTags3.get("tag5").inherited().get());
+ Assertions.assertFalse(resultTags3.get("tag7").inherited().get());
+
+ Response response7 =
+ target(basePath(metalake))
+ .path(column.type().toString())
+ .path(column.fullName())
+ .path("tags")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
response7.getStatus());
+
+ NameListResponse nameListResponse3 =
response7.readEntity(NameListResponse.class);
+ Assertions.assertEquals(0, nameListResponse3.getCode());
+
+ Assertions.assertEquals(
+ schemaTagInfos.length
+ + catalogTagInfos.length
+ + tableTagInfos.length
+ + columnTagInfos.length,
+ nameListResponse3.getNames().length);
+
+ Set<String> resultNames2 = Sets.newHashSet(nameListResponse3.getNames());
+ Assertions.assertTrue(resultNames2.contains("tag1"));
+ Assertions.assertTrue(resultNames2.contains("tag3"));
+ Assertions.assertTrue(resultNames2.contains("tag5"));
+ Assertions.assertTrue(resultNames2.contains("tag7"));
+ }
+
+ @Test
+ public void testGetTagForObject() {
+ TagEntity tag1 =
+
TagEntity.builder().withName("tag1").withId(1L).withAuditInfo(testAuditInfo1).build();
+ MetadataObject catalog = MetadataObjects.parse("object1",
MetadataObject.Type.CATALOG);
+ when(tagManager.getTagForMetadataObject(metalake, catalog,
"tag1")).thenReturn(tag1);
+
+ TagEntity tag2 =
+
TagEntity.builder().withName("tag2").withId(1L).withAuditInfo(testAuditInfo1).build();
+ MetadataObject schema = MetadataObjects.parse("object1.object2",
MetadataObject.Type.SCHEMA);
+ when(tagManager.getTagForMetadataObject(metalake, schema,
"tag2")).thenReturn(tag2);
+
+ TagEntity tag3 =
+
TagEntity.builder().withName("tag3").withId(1L).withAuditInfo(testAuditInfo1).build();
+ MetadataObject table =
+ MetadataObjects.parse("object1.object2.object3",
MetadataObject.Type.TABLE);
+ when(tagManager.getTagForMetadataObject(metalake, table,
"tag3")).thenReturn(tag3);
+
+ TagEntity tag4 =
+
TagEntity.builder().withName("tag4").withId(1L).withAuditInfo(testAuditInfo1).build();
+ MetadataObject column =
+ MetadataObjects.parse("object1.object2.object3.object4",
MetadataObject.Type.COLUMN);
+ when(tagManager.getTagForMetadataObject(metalake, column,
"tag4")).thenReturn(tag4);
+
+ // Test catalog tag
+ Response response =
+ target(basePath(metalake))
+ .path(catalog.type().toString())
+ .path(catalog.fullName())
+ .path("tags")
+ .path("tag1")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
response.getStatus());
+
+ TagResponse tagResponse = response.readEntity(TagResponse.class);
+ Assertions.assertEquals(0, tagResponse.getCode());
+
+ Tag respTag = tagResponse.getTag();
+ Assertions.assertEquals(tag1.name(), respTag.name());
+ Assertions.assertEquals(tag1.comment(), respTag.comment());
+ Assertions.assertFalse(respTag.inherited().get());
+
+ // Test schema tag
+ Response response1 =
+ target(basePath(metalake))
+ .path(schema.type().toString())
+ .path(schema.fullName())
+ .path("tags")
+ .path("tag2")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
response1.getStatus());
+
+ TagResponse tagResponse1 = response1.readEntity(TagResponse.class);
+ Assertions.assertEquals(0, tagResponse1.getCode());
+
+ Tag respTag1 = tagResponse1.getTag();
+ Assertions.assertEquals(tag2.name(), respTag1.name());
+ Assertions.assertEquals(tag2.comment(), respTag1.comment());
+ Assertions.assertFalse(respTag1.inherited().get());
+
+ // Test table tag
+ Response response2 =
+ target(basePath(metalake))
+ .path(table.type().toString())
+ .path(table.fullName())
+ .path("tags")
+ .path("tag3")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
response2.getStatus());
+
+ TagResponse tagResponse2 = response2.readEntity(TagResponse.class);
+ Assertions.assertEquals(0, tagResponse2.getCode());
+
+ Tag respTag2 = tagResponse2.getTag();
+ Assertions.assertEquals(tag3.name(), respTag2.name());
+ Assertions.assertEquals(tag3.comment(), respTag2.comment());
+ Assertions.assertFalse(respTag2.inherited().get());
+
+ // Test column tag
+ Response response3 =
+ target(basePath(metalake))
+ .path(column.type().toString())
+ .path(column.fullName())
+ .path("tags")
+ .path("tag4")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
response3.getStatus());
+
+ TagResponse tagResponse3 = response3.readEntity(TagResponse.class);
+ Assertions.assertEquals(0, tagResponse3.getCode());
+
+ Tag respTag3 = tagResponse3.getTag();
+ Assertions.assertEquals(tag4.name(), respTag3.name());
+ Assertions.assertEquals(tag4.comment(), respTag3.comment());
+ Assertions.assertFalse(respTag3.inherited().get());
+
+ // Test get schema inherited tag
+ Response response4 =
+ target(basePath(metalake))
+ .path(schema.type().toString())
+ .path(schema.fullName())
+ .path("tags")
+ .path("tag1")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
response4.getStatus());
+
+ TagResponse tagResponse4 = response4.readEntity(TagResponse.class);
+ Assertions.assertEquals(0, tagResponse4.getCode());
+
+ Tag respTag4 = tagResponse4.getTag();
+ Assertions.assertEquals(tag1.name(), respTag4.name());
+ Assertions.assertEquals(tag1.comment(), respTag4.comment());
+ Assertions.assertTrue(respTag4.inherited().get());
+
+ // Test get table inherited tag
+ Response response5 =
+ target(basePath(metalake))
+ .path(table.type().toString())
+ .path(table.fullName())
+ .path("tags")
+ .path("tag2")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
response5.getStatus());
+
+ TagResponse tagResponse5 = response5.readEntity(TagResponse.class);
+ Assertions.assertEquals(0, tagResponse5.getCode());
+
+ Tag respTag5 = tagResponse5.getTag();
+ Assertions.assertEquals(tag2.name(), respTag5.name());
+ Assertions.assertEquals(tag2.comment(), respTag5.comment());
+ Assertions.assertTrue(respTag5.inherited().get());
+
+ // Test get column inherited tag
+ Response response6 =
+ target(basePath(metalake))
+ .path(column.type().toString())
+ .path(column.fullName())
+ .path("tags")
+ .path("tag3")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
response6.getStatus());
+
+ TagResponse tagResponse6 = response6.readEntity(TagResponse.class);
+ Assertions.assertEquals(0, tagResponse6.getCode());
+
+ Tag respTag6 = tagResponse6.getTag();
+ Assertions.assertEquals(tag3.name(), respTag6.name());
+ Assertions.assertEquals(tag3.comment(), respTag6.comment());
+ Assertions.assertTrue(respTag6.inherited().get());
+
+ // Test catalog tag throw NoSuchTagException
+ Response response7 =
+ target(basePath(metalake))
+ .path(catalog.type().toString())
+ .path(catalog.fullName())
+ .path("tags")
+ .path("tag2")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.NOT_FOUND.getStatusCode(),
response7.getStatus());
+
+ ErrorResponse errorResponse = response7.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.NOT_FOUND_CODE,
errorResponse.getCode());
+ Assertions.assertEquals(NoSuchTagException.class.getSimpleName(),
errorResponse.getType());
+
+ // Test schema tag throw NoSuchTagException
+ Response response8 =
+ target(basePath(metalake))
+ .path(schema.type().toString())
+ .path(schema.fullName())
+ .path("tags")
+ .path("tag3")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.NOT_FOUND.getStatusCode(),
response8.getStatus());
+
+ ErrorResponse errorResponse1 = response8.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.NOT_FOUND_CODE,
errorResponse1.getCode());
+ Assertions.assertEquals(NoSuchTagException.class.getSimpleName(),
errorResponse1.getType());
+ }
+
+ @Test
+ public void testAssociateTagsForObject() {
+ String[] tagsToAdd = new String[] {"tag1", "tag2"};
+ String[] tagsToRemove = new String[] {"tag3", "tag4"};
+
+ MetadataObject catalog = MetadataObjects.parse("object1",
MetadataObject.Type.CATALOG);
+ when(tagManager.associateTagsForMetadataObject(metalake, catalog,
tagsToAdd, tagsToRemove))
+ .thenReturn(tagsToAdd);
+
+ TagsAssociateRequest request = new TagsAssociateRequest(tagsToAdd,
tagsToRemove);
+ Response response =
+ target(basePath(metalake))
+ .path(catalog.type().toString())
+ .path(catalog.fullName())
+ .path("tags")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .post(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
response.getStatus());
+ Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE,
response.getMediaType());
+
+ NameListResponse nameListResponse =
response.readEntity(NameListResponse.class);
+ Assertions.assertEquals(0, nameListResponse.getCode());
+
+ Assertions.assertArrayEquals(tagsToAdd, nameListResponse.getNames());
+
+ // Test throw null tags
+ when(tagManager.associateTagsForMetadataObject(metalake, catalog,
tagsToAdd, tagsToRemove))
+ .thenReturn(null);
+ Response response1 =
+ target(basePath(metalake))
+ .path(catalog.type().toString())
+ .path(catalog.fullName())
+ .path("tags")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .post(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
response1.getStatus());
+
+ NameListResponse nameListResponse1 =
response1.readEntity(NameListResponse.class);
+ Assertions.assertEquals(0, nameListResponse1.getCode());
+
+ Assertions.assertEquals(0, nameListResponse1.getNames().length);
+
+ // Test throw TagAlreadyAssociatedException
+ doThrow(new TagAlreadyAssociatedException("mock error"))
+ .when(tagManager)
+ .associateTagsForMetadataObject(metalake, catalog, tagsToAdd,
tagsToRemove);
+ Response response2 =
+ target(basePath(metalake))
+ .path(catalog.type().toString())
+ .path(catalog.fullName())
+ .path("tags")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .post(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+ Assertions.assertEquals(Response.Status.CONFLICT.getStatusCode(),
response2.getStatus());
+
+ ErrorResponse errorResponse = response2.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.ALREADY_EXISTS_CODE,
errorResponse.getCode());
+ Assertions.assertEquals(
+ TagAlreadyAssociatedException.class.getSimpleName(),
errorResponse.getType());
+
+ // Test throw RuntimeException
+ doThrow(new RuntimeException("mock error"))
+ .when(tagManager)
+ .associateTagsForMetadataObject(any(), any(), any(), any());
+
+ Response response3 =
+ target(basePath(metalake))
+ .path(catalog.type().toString())
+ .path(catalog.fullName())
+ .path("tags")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .post(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+ Assertions.assertEquals(
+ Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(),
response3.getStatus());
+
+ ErrorResponse errorResponse1 = response3.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE,
errorResponse1.getCode());
+ Assertions.assertEquals(RuntimeException.class.getSimpleName(),
errorResponse1.getType());
+ }
+
+ private String basePath(String metalake) {
+ return "/metalakes/" + metalake + "/objects";
+ }
+}