This is an automated email from the ASF dual-hosted git repository.

liuxun pushed a commit to branch branch-0.6
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/branch-0.6 by this push:
     new 9f2b4f720 [#4295] feat(server): Add REST API for the owner (#4385)
9f2b4f720 is described below

commit 9f2b4f720bbb1373822d4a233da01e83024da888
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Aug 6 17:16:01 2024 +0800

    [#4295] feat(server): Add REST API for the owner (#4385)
    
    ### What changes were proposed in this pull request?
    Add REST API for the owner.
    
    ### Why are the changes needed?
    
    Fix: #4295
    
    ### Does this PR introduce _any_ user-facing change?
    I will add the document in the later pull request.
    
    ### How was this patch tested?
    Add the new ut.
    
    Co-authored-by: roryqi <ror...@apache.org>
---
 .../gravitino/dto/authorization/OwnerDTO.java      |  93 +++++++++
 .../gravitino/dto/requests/OwnerSetRequest.java    |  67 +++++++
 .../gravitino/dto/responses/OwnerResponse.java     |  66 +++++++
 .../gravitino/dto/responses/SetResponse.java       |  57 ++++++
 .../apache/gravitino/dto/util/DTOConverters.java   |  12 ++
 .../java/org/apache/gravitino/GravitinoEnv.java    |   1 +
 .../server/web/rest/ExceptionHandlers.java         |  32 +++
 .../gravitino/server/web/rest/OperationType.java   |   1 +
 .../gravitino/server/web/rest/OwnerOperations.java | 116 +++++++++++
 .../server/web/rest/TestOwnerOperations.java       | 220 +++++++++++++++++++++
 10 files changed, 665 insertions(+)

diff --git 
a/common/src/main/java/org/apache/gravitino/dto/authorization/OwnerDTO.java 
b/common/src/main/java/org/apache/gravitino/dto/authorization/OwnerDTO.java
new file mode 100644
index 000000000..e2c74433f
--- /dev/null
+++ b/common/src/main/java/org/apache/gravitino/dto/authorization/OwnerDTO.java
@@ -0,0 +1,93 @@
+/*
+ * 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.dto.authorization;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.gravitino.authorization.Owner;
+
+/** Represents an Owner Data Transfer Object (DTO). */
+public class OwnerDTO implements Owner {
+  @JsonProperty("name")
+  private String name;
+
+  @JsonProperty("type")
+  private Type type;
+
+  private OwnerDTO() {}
+
+  @Override
+  public String name() {
+    return name;
+  }
+
+  @Override
+  public Type type() {
+    return type;
+  }
+
+  /**
+   * Creates a new Builder for constructing an Owner DTO.
+   *
+   * @return A new Builder instance.
+   */
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  /** Builder class for constructing OwnerDTO instances. */
+  public static class Builder {
+
+    private final OwnerDTO ownerDTO;
+
+    private Builder() {
+      ownerDTO = new OwnerDTO();
+    }
+
+    /**
+     * Sets the name for the owner.
+     *
+     * @param name The name of the owner.
+     * @return The builder instance.
+     */
+    public Builder withName(String name) {
+      ownerDTO.name = name;
+      return this;
+    }
+
+    /**
+     * Sets the type for the owner.
+     *
+     * @param type The type of the owner.
+     * @return The builder instance.
+     */
+    public Builder withType(Type type) {
+      ownerDTO.type = type;
+      return this;
+    }
+
+    /**
+     * Builds an instance of OwnerDTO using the builder's properties.
+     *
+     * @return An instance of OwnerDTO.
+     */
+    public OwnerDTO build() {
+      return ownerDTO;
+    }
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/requests/OwnerSetRequest.java 
b/common/src/main/java/org/apache/gravitino/dto/requests/OwnerSetRequest.java
new file mode 100644
index 000000000..2a10904c6
--- /dev/null
+++ 
b/common/src/main/java/org/apache/gravitino/dto/requests/OwnerSetRequest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.dto.requests;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import lombok.extern.jackson.Jacksonized;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.authorization.Owner;
+import org.apache.gravitino.rest.RESTRequest;
+
+/** Request to set the owner for a metadata object. */
+@Getter
+@EqualsAndHashCode
+@ToString
+@Builder
+@Jacksonized
+public class OwnerSetRequest implements RESTRequest {
+  @JsonProperty("name")
+  private final String name;
+
+  @JsonProperty("type")
+  private final Owner.Type type;
+
+  /** Default constructor for OwnerSetRequest. (Used for Jackson 
deserialization.) */
+  public OwnerSetRequest() {
+    this(null, null);
+  }
+
+  /**
+   * Creates a new OwnerSetRequest.
+   *
+   * @param name The name of the owner.
+   * @param type The type of the owner.
+   */
+  public OwnerSetRequest(String name, Owner.Type type) {
+    this.name = name;
+    this.type = type;
+  }
+
+  @Override
+  public void validate() throws IllegalArgumentException {
+    Preconditions.checkArgument(
+        StringUtils.isNotBlank(name), "\"name\" field is required and cannot 
be empty");
+    Preconditions.checkArgument(type != null, "\"type\" field is required and 
cannot be empty");
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/responses/OwnerResponse.java 
b/common/src/main/java/org/apache/gravitino/dto/responses/OwnerResponse.java
new file mode 100644
index 000000000..cf4569f5a
--- /dev/null
+++ b/common/src/main/java/org/apache/gravitino/dto/responses/OwnerResponse.java
@@ -0,0 +1,66 @@
+/*
+ * 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.dto.responses;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import javax.annotation.Nullable;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.dto.authorization.OwnerDTO;
+
+/** Represents a response containing owner information. */
+@Getter
+@ToString
+@EqualsAndHashCode(callSuper = true)
+public class OwnerResponse extends BaseResponse {
+
+  @Nullable
+  @JsonProperty("owner")
+  private final OwnerDTO owner;
+
+  /**
+   * Constructor for OwnerResponse.
+   *
+   * @param owner The owner data transfer object.
+   */
+  public OwnerResponse(OwnerDTO owner) {
+    super(0);
+    this.owner = owner;
+  }
+
+  /** Default constructor for OwnerResponse. (Used for Jackson 
deserialization.) */
+  public OwnerResponse() {
+    super(0);
+    this.owner = null;
+  }
+
+  @Override
+  public void validate() throws IllegalArgumentException {
+    super.validate();
+
+    if (owner != null) {
+      Preconditions.checkArgument(
+          StringUtils.isNotBlank(owner.name()), "owner 'name' must not be null 
or empty");
+      Preconditions.checkArgument(owner.type() != null, "owner 'type' must not 
be null");
+    }
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/responses/SetResponse.java 
b/common/src/main/java/org/apache/gravitino/dto/responses/SetResponse.java
new file mode 100644
index 000000000..52429ceb6
--- /dev/null
+++ b/common/src/main/java/org/apache/gravitino/dto/responses/SetResponse.java
@@ -0,0 +1,57 @@
+/*
+ * 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.dto.responses;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+/** Represents a response for a set operation. */
+@ToString
+@EqualsAndHashCode(callSuper = true)
+public class SetResponse extends BaseResponse {
+
+  @JsonProperty("set")
+  private final boolean set;
+
+  /**
+   * Constructor for SetResponse.
+   *
+   * @param set Whether the set operation was successful.
+   */
+  public SetResponse(boolean set) {
+    super(0);
+    this.set = set;
+  }
+
+  /** Default constructor for SetResponse (used by Jackson deserializer). */
+  public SetResponse() {
+    super(0);
+    this.set = false;
+  }
+
+  /**
+   * Returns whether the set operation was successful.
+   *
+   * @return True if the set operation was successful, otherwise false.
+   */
+  public boolean set() {
+    return set;
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java 
b/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
index 9588fae8b..d83460af1 100644
--- a/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
+++ b/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
@@ -30,6 +30,7 @@ import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.Metalake;
 import org.apache.gravitino.Schema;
 import org.apache.gravitino.authorization.Group;
+import org.apache.gravitino.authorization.Owner;
 import org.apache.gravitino.authorization.Privilege;
 import org.apache.gravitino.authorization.Role;
 import org.apache.gravitino.authorization.SecurableObject;
@@ -39,6 +40,7 @@ import org.apache.gravitino.dto.CatalogDTO;
 import org.apache.gravitino.dto.MetalakeDTO;
 import org.apache.gravitino.dto.SchemaDTO;
 import org.apache.gravitino.dto.authorization.GroupDTO;
+import org.apache.gravitino.dto.authorization.OwnerDTO;
 import org.apache.gravitino.dto.authorization.PrivilegeDTO;
 import org.apache.gravitino.dto.authorization.RoleDTO;
 import org.apache.gravitino.dto.authorization.SecurableObjectDTO;
@@ -118,6 +120,16 @@ public class DTOConverters {
         .build();
   }
 
+  /**
+   * Converts a {@link Owner} to a {@link OwnerDTO}.
+   *
+   * @param owner The owner.
+   * @return The owner DTO.
+   */
+  public static OwnerDTO toDTO(Owner owner) {
+    return 
OwnerDTO.builder().withName(owner.name()).withType(owner.type()).build();
+  }
+
   /**
    * Converts a {@link Metalake} to a {@link MetalakeDTO}.
    *
diff --git a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java 
b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
index d387c1672..8a0f19f99 100644
--- a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
+++ b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
@@ -403,6 +403,7 @@ public class GravitinoEnv {
       this.ownerManager = new OwnerManager(entityStore);
     } else {
       this.accessControlDispatcher = null;
+      this.ownerManager = null;
     }
 
     this.auxServiceManager = new AuxiliaryServiceManager();
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
 
b/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
index 459938ee9..346c39a2c 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
@@ -138,6 +138,11 @@ public class ExceptionHandlers {
         .build();
   }
 
+  public static Response handleOwnerException(
+      OperationType type, String name, String metalake, Exception e) {
+    return OwnerExceptionHandler.INSTANCE.handle(type, name, metalake, e);
+  }
+
   private static class PartitionExceptionHandler extends BaseExceptionHandler {
 
     private static final ExceptionHandler INSTANCE = new 
PartitionExceptionHandler();
@@ -561,6 +566,33 @@ public class ExceptionHandlers {
     }
   }
 
+  private static class OwnerExceptionHandler extends BaseExceptionHandler {
+    private static final ExceptionHandler INSTANCE = new 
OwnerExceptionHandler();
+
+    private static String getOwnerErrorMsg(
+        String name, String operation, String metalake, String reason) {
+      return String.format(
+          "Failed to execute %s owner operation [%s] under metalake [%s], 
reason [%s]",
+          name, operation, metalake, reason);
+    }
+
+    @Override
+    public Response handle(OperationType op, String name, String parent, 
Exception e) {
+      String formatted = StringUtil.isBlank(name) ? "" : " [" + name + "]";
+      String errorMsg = getOwnerErrorMsg(formatted, op.name(), parent, 
getErrorMsg(e));
+      LOG.warn(errorMsg, e);
+
+      if (e instanceof IllegalArgumentException) {
+        return Utils.illegalArguments(errorMsg, e);
+
+      } else if (e instanceof NotFoundException) {
+        return Utils.notFound(errorMsg, e);
+      } else {
+        return super.handle(op, name, parent, e);
+      }
+    }
+  }
+
   @VisibleForTesting
   static class BaseExceptionHandler extends ExceptionHandler {
 
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/OperationType.java 
b/server/src/main/java/org/apache/gravitino/server/web/rest/OperationType.java
index 9e611f6e2..37032124f 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/rest/OperationType.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/OperationType.java
@@ -32,4 +32,5 @@ public enum OperationType {
   GRANT,
   REVOKE,
   ASSOCIATE,
+  SET,
 }
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/OwnerOperations.java
 
b/server/src/main/java/org/apache/gravitino/server/web/rest/OwnerOperations.java
new file mode 100644
index 000000000..517b08cd7
--- /dev/null
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/OwnerOperations.java
@@ -0,0 +1,116 @@
+/*
+ * 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 java.util.Locale;
+import java.util.Optional;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import org.apache.gravitino.GravitinoEnv;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.MetadataObjects;
+import org.apache.gravitino.authorization.Owner;
+import org.apache.gravitino.authorization.OwnerManager;
+import org.apache.gravitino.dto.requests.OwnerSetRequest;
+import org.apache.gravitino.dto.responses.OwnerResponse;
+import org.apache.gravitino.dto.responses.SetResponse;
+import org.apache.gravitino.dto.util.DTOConverters;
+import org.apache.gravitino.metrics.MetricNames;
+import org.apache.gravitino.server.authorization.NameBindings;
+import org.apache.gravitino.server.web.Utils;
+
+@NameBindings.AccessControlInterfaces
+@Path("/metalakes/{metalake}/owners")
+public class OwnerOperations {
+
+  private final OwnerManager ownerManager;
+
+  @Context private HttpServletRequest httpRequest;
+
+  public OwnerOperations() {
+    // Because ownerManager may be null when Gravitino doesn't enable 
authorization,
+    // and Jersey injection doesn't support null value. So OwnerOperations 
chooses to retrieve
+    // ownerManager from GravitinoEnv instead of injection here.
+    this.ownerManager = GravitinoEnv.getInstance().ownerManager();
+  }
+
+  @GET
+  @Path("{metadataObjectType}/{fullName}")
+  @Produces("application/vnd.gravitino.v1+json")
+  @Timed(name = "get-object-owner." + MetricNames.HTTP_PROCESS_DURATION, 
absolute = true)
+  @ResponseMetered(name = "get-object-owner", absolute = true)
+  public Response getOwnerForObject(
+      @PathParam("metalake") String metalake,
+      @PathParam("metadataObjectType") String metadataObjectType,
+      @PathParam("fullName") String fullName) {
+    try {
+      MetadataObject object =
+          MetadataObjects.parse(
+              fullName, 
MetadataObject.Type.valueOf(metadataObjectType.toUpperCase(Locale.ROOT)));
+      return Utils.doAs(
+          httpRequest,
+          () -> {
+            Optional<Owner> owner = ownerManager.getOwner(metalake, object);
+            if (owner.isPresent()) {
+              return Utils.ok(new 
OwnerResponse(DTOConverters.toDTO(owner.get())));
+            } else {
+              return Utils.ok(new OwnerResponse(null));
+            }
+          });
+    } catch (Exception e) {
+      return ExceptionHandlers.handleOwnerException(
+          OperationType.GET, String.format("metadata object %s", fullName), 
metalake, e);
+    }
+  }
+
+  @PUT
+  @Path("{metadataObjectType}/{fullName}")
+  @Produces("application/vnd.gravitino.v1+json")
+  @Timed(name = "set-object-owner." + MetricNames.HTTP_PROCESS_DURATION, 
absolute = true)
+  @ResponseMetered(name = "set-object-owner", absolute = true)
+  public Response setOwnerForObject(
+      @PathParam("metalake") String metalake,
+      @PathParam("metadataObjectType") String metadataObjectType,
+      @PathParam("fullName") String fullName,
+      OwnerSetRequest request) {
+    try {
+      MetadataObject object =
+          MetadataObjects.parse(
+              fullName, 
MetadataObject.Type.valueOf(metadataObjectType.toUpperCase(Locale.ROOT)));
+
+      return Utils.doAs(
+          httpRequest,
+          () -> {
+            ownerManager.setOwner(metalake, object, request.getName(), 
request.getType());
+            return Utils.ok(new SetResponse(true));
+          });
+    } catch (Exception e) {
+      return ExceptionHandlers.handleOwnerException(
+          OperationType.SET, String.format("metadata object %s", fullName), 
metalake, e);
+    }
+  }
+}
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestOwnerOperations.java
 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestOwnerOperations.java
new file mode 100644
index 000000000..2b0b3ff4a
--- /dev/null
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestOwnerOperations.java
@@ -0,0 +1,220 @@
+/*
+ * 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.apache.gravitino.Configs.TREE_LOCK_CLEAN_INTERVAL;
+import static org.apache.gravitino.Configs.TREE_LOCK_MAX_NODE_IN_MEMORY;
+import static org.apache.gravitino.Configs.TREE_LOCK_MIN_NODE_IN_MEMORY;
+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 java.io.IOException;
+import java.util.Optional;
+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.commons.lang3.reflect.FieldUtils;
+import org.apache.gravitino.Config;
+import org.apache.gravitino.GravitinoEnv;
+import org.apache.gravitino.authorization.Owner;
+import org.apache.gravitino.authorization.OwnerManager;
+import org.apache.gravitino.dto.authorization.OwnerDTO;
+import org.apache.gravitino.dto.requests.OwnerSetRequest;
+import org.apache.gravitino.dto.responses.ErrorConstants;
+import org.apache.gravitino.dto.responses.ErrorResponse;
+import org.apache.gravitino.dto.responses.OwnerResponse;
+import org.apache.gravitino.dto.responses.SetResponse;
+import org.apache.gravitino.exceptions.NotFoundException;
+import org.apache.gravitino.lock.LockManager;
+import org.apache.gravitino.rest.RESTUtils;
+import org.glassfish.hk2.utilities.binding.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.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+class TestOwnerOperations extends JerseyTest {
+  private static final OwnerManager manager = mock(OwnerManager.class);
+
+  private static class MockServletRequestFactory extends 
ServletRequestFactoryBase {
+    @Override
+    public HttpServletRequest get() {
+      HttpServletRequest request = mock(HttpServletRequest.class);
+      when(request.getRemoteUser()).thenReturn(null);
+      return request;
+    }
+  }
+
+  @BeforeAll
+  public static void setup() throws IllegalAccessException {
+    Config config = mock(Config.class);
+    Mockito.doReturn(100000L).when(config).get(TREE_LOCK_MAX_NODE_IN_MEMORY);
+    Mockito.doReturn(1000L).when(config).get(TREE_LOCK_MIN_NODE_IN_MEMORY);
+    Mockito.doReturn(36000L).when(config).get(TREE_LOCK_CLEAN_INTERVAL);
+    FieldUtils.writeField(GravitinoEnv.getInstance(), "lockManager", new 
LockManager(config), true);
+    FieldUtils.writeField(GravitinoEnv.getInstance(), "ownerManager", manager, 
true);
+  }
+
+  @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(OwnerOperations.class);
+    resourceConfig.register(
+        new AbstractBinder() {
+          @Override
+          protected void configure() {
+            
bindFactory(MockServletRequestFactory.class).to(HttpServletRequest.class);
+          }
+        });
+
+    return resourceConfig;
+  }
+
+  @Test
+  void testGetOwnerForObject() {
+    Owner owner =
+        new Owner() {
+          @Override
+          public String name() {
+            return "test";
+          }
+
+          @Override
+          public Type type() {
+            return Type.USER;
+          }
+        };
+
+    when(manager.getOwner(any(), any())).thenReturn(Optional.of(owner));
+
+    Response resp =
+        target("/metalakes/metalake1/owners/metalake/metalake1")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .get();
+
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
resp.getStatus());
+
+    OwnerResponse ownerResponse = resp.readEntity(OwnerResponse.class);
+    Assertions.assertEquals(0, ownerResponse.getCode());
+    OwnerDTO ownerDTO = ownerResponse.getOwner();
+    Assertions.assertEquals("test", ownerDTO.name());
+    Assertions.assertEquals(Owner.Type.USER, ownerDTO.type());
+
+    // Owner is not set
+    when(manager.getOwner(any(), any())).thenReturn(Optional.empty());
+    Response resp1 =
+        target("/metalakes/metalake1/owners/metalake/metalake1")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .get();
+
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
resp1.getStatus());
+
+    OwnerResponse ownerResponse1 = resp1.readEntity(OwnerResponse.class);
+    Assertions.assertEquals(0, ownerResponse1.getCode());
+    Assertions.assertNull(ownerResponse1.getOwner());
+
+    // Test to throw NotFoundException
+    doThrow(new NotFoundException("mock error")).when(manager).getOwner(any(), 
any());
+    Response resp2 =
+        target("/metalakes/metalake1/owners/metalake/metalake1")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .get();
+
+    Assertions.assertEquals(Response.Status.NOT_FOUND.getStatusCode(), 
resp2.getStatus());
+
+    ErrorResponse errorResponse1 = resp2.readEntity(ErrorResponse.class);
+    Assertions.assertEquals(ErrorConstants.NOT_FOUND_CODE, 
errorResponse1.getCode());
+    Assertions.assertEquals(NotFoundException.class.getSimpleName(), 
errorResponse1.getType());
+
+    // Test to throw internal RuntimeException
+    doThrow(new RuntimeException("mock error")).when(manager).getOwner(any(), 
any());
+    Response resp3 =
+        target("/metalakes/metalake1/owners/metalake/metalake1")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .get();
+
+    Assertions.assertEquals(
+        Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), 
resp3.getStatus());
+
+    ErrorResponse errorResponse2 = resp3.readEntity(ErrorResponse.class);
+    Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE, 
errorResponse2.getCode());
+  }
+
+  @Test
+  void testSetOwnerForObject() {
+    OwnerSetRequest request = new OwnerSetRequest("test", Owner.Type.USER);
+    Response resp =
+        target("/metalakes/metalake1/owners/metalake/metalake1")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .put(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
resp.getStatus());
+
+    SetResponse setResponse = resp.readEntity(SetResponse.class);
+    Assertions.assertEquals(0, setResponse.getCode());
+    Assertions.assertTrue(setResponse.set());
+
+    // Test to throw NotFoundException
+    doThrow(new NotFoundException("mock error")).when(manager).setOwner(any(), 
any(), any(), any());
+    Response resp2 =
+        target("/metalakes/metalake1/owners/metalake/metalake1")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .put(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+    Assertions.assertEquals(Response.Status.NOT_FOUND.getStatusCode(), 
resp2.getStatus());
+
+    ErrorResponse errorResponse1 = resp2.readEntity(ErrorResponse.class);
+    Assertions.assertEquals(ErrorConstants.NOT_FOUND_CODE, 
errorResponse1.getCode());
+    Assertions.assertEquals(NotFoundException.class.getSimpleName(), 
errorResponse1.getType());
+
+    // Test to throw internal RuntimeException
+    doThrow(new RuntimeException("mock error")).when(manager).setOwner(any(), 
any(), any(), any());
+    Response resp3 =
+        target("/metalakes/metalake1/owners/metalake/metalake1")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .put(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
+
+    Assertions.assertEquals(
+        Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), 
resp3.getStatus());
+
+    ErrorResponse errorResponse2 = resp3.readEntity(ErrorResponse.class);
+    Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE, 
errorResponse2.getCode());
+  }
+}

Reply via email to