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 1aba1c888e0..2ac84bdbf55 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 095d78549ee..a80fb324659 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 @@ private void testListTags(SupportsTags supportsTags, MetadataObject metadataObje 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 @@ private void testListTagsInfo(SupportsTags supportsTags, MetadataObject metadata 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 @@ private void testGetTag(SupportsTags supportsTags, MetadataObject metadataObject 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 @@ private void testAssociateTags(SupportsTags supportsTags, MetadataObject metadat 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 a02bf4faf2c..ac088a7c2df 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`. @@ -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 ``` @@ -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`. ```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 ``` @@ -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}`. ```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 ``` diff --git a/docs/open-api/openapi.yaml b/docs/open-api/openapi.yaml index 2305eafce1b..2c8ab1bfefd 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 7264c0673c2..9419b8f6eb6 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/MetadataObjectTagOperations.java b/server/src/main/java/org/apache/gravitino/server/web/rest/MetadataObjectTagOperations.java new file mode 100644 index 00000000000..e69f048a9c4 --- /dev/null +++ b/server/src/main/java/org/apache/gravitino/server/web/rest/MetadataObjectTagOperations.java @@ -0,0 +1,264 @@ +/* + * 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 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; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.gravitino.MetadataObject; +import org.apache.gravitino.MetadataObjects; +import org.apache.gravitino.dto.requests.TagsAssociateRequest; +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.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.TagManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@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 MetadataObjectTagOperations(TagManager tagManager) { + this.tagManager = tagManager; + } + + @GET + @Path("{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 = getTagForObject(metalake, object, tagName); + Optional 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 + @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 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); + } + } + + @POST + @Produces("application/vnd.gravitino.v1+json") + @Timed(name = "associate-object-tags." + MetricNames.HTTP_PROCESS_DURATION, absolute = true) + @ResponseMetered(name = "associate-object-tags", absolute = true) + public Response associateTagsForObject( + @PathParam("metalake") String metalake, + @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 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(); + } + } +} 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 733623977e6..5621b7eb8b1 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 @@ 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,11 +37,9 @@ 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; @@ -54,7 +48,6 @@ 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 +215,6 @@ public Response deleteTag(@PathParam("metalake") String metalake, @PathParam("ta } } - @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 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 = getTagForObject(metalake, object, tagName); - Optional 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") @@ -398,56 +246,4 @@ public Response listMetadataObjectsForTag( 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) - public Response associateTagsForObject( - @PathParam("metalake") String metalake, - @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 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(); - } - } } 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 00000000000..8e0324bea2b --- /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 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 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 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 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 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 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 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"; + } +} diff --git a/server/src/test/java/org/apache/gravitino/server/web/rest/TestTagOperations.java b/server/src/test/java/org/apache/gravitino/server/web/rest/TestTagOperations.java index 23b87b60d28..b1f1c0c8d88 100644 --- a/server/src/test/java/org/apache/gravitino/server/web/rest/TestTagOperations.java +++ b/server/src/test/java/org/apache/gravitino/server/web/rest/TestTagOperations.java @@ -24,15 +24,9 @@ import static org.mockito.Mockito.when; import com.google.common.collect.Lists; -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.Optional; -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; @@ -43,7 +37,6 @@ 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.ErrorConstants; import org.apache.gravitino.dto.responses.ErrorResponse; @@ -53,7 +46,6 @@ import org.apache.gravitino.dto.responses.TagResponse; import org.apache.gravitino.exceptions.NoSuchMetalakeException; import org.apache.gravitino.exceptions.NoSuchTagException; -import org.apache.gravitino.exceptions.TagAlreadyAssociatedException; import org.apache.gravitino.exceptions.TagAlreadyExistsException; import org.apache.gravitino.meta.AuditInfo; import org.apache.gravitino.meta.TagEntity; @@ -506,519 +498,6 @@ public void testDeleteTag() { Assertions.assertEquals(RuntimeException.class.getSimpleName(), errorResp1.getType()); } - @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(tagPath(metalake)) - .path(catalog.type().toString()) - .path(catalog.fullName()) - .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 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(tagPath(metalake)) - .path(catalog.type().toString()) - .path(catalog.fullName()) - .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(tagPath(metalake)) - .path(schema.type().toString()) - .path(schema.fullName()) - .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 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(tagPath(metalake)) - .path(schema.type().toString()) - .path(schema.fullName()) - .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 resultNames = Sets.newHashSet(nameListResponse1.getNames()); - Assertions.assertTrue(resultNames.contains("tag1")); - Assertions.assertTrue(resultNames.contains("tag3")); - - // Test table tags - Response response4 = - target(tagPath(metalake)) - .path(table.type().toString()) - .path(table.fullName()) - .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 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(tagPath(metalake)) - .path(table.type().toString()) - .path(table.fullName()) - .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 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(tagPath(metalake)) - .path(column.type().toString()) - .path(column.fullName()) - .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 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(tagPath(metalake)) - .path(column.type().toString()) - .path(column.fullName()) - .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 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(tagPath(metalake)) - .path(catalog.type().toString()) - .path(catalog.fullName()) - .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(tagPath(metalake)) - .path(schema.type().toString()) - .path(schema.fullName()) - .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(tagPath(metalake)) - .path(table.type().toString()) - .path(table.fullName()) - .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(tagPath(metalake)) - .path(column.type().toString()) - .path(column.fullName()) - .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(tagPath(metalake)) - .path(schema.type().toString()) - .path(schema.fullName()) - .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(tagPath(metalake)) - .path(table.type().toString()) - .path(table.fullName()) - .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(tagPath(metalake)) - .path(column.type().toString()) - .path(column.fullName()) - .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(tagPath(metalake)) - .path(catalog.type().toString()) - .path(catalog.fullName()) - .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(tagPath(metalake)) - .path(schema.type().toString()) - .path(schema.fullName()) - .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(tagPath(metalake)) - .path(catalog.type().toString()) - .path(catalog.fullName()) - .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(tagPath(metalake)) - .path(catalog.type().toString()) - .path(catalog.fullName()) - .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(tagPath(metalake)) - .path(catalog.type().toString()) - .path(catalog.fullName()) - .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(tagPath(metalake)) - .path(catalog.type().toString()) - .path(catalog.fullName()) - .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()); - } - - @Test public void testListMetadataObjectForTag() { MetadataObject[] objects = new MetadataObject[] {