This is an automated email from the ASF dual-hosted git repository. liuxun pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push: new a314b410e [#4295] feat(server): Add REST API for the owner (#4354) a314b410e is described below commit a314b410eaa834edacf2b2a808ae12e385fbaf25 Author: roryqi <ror...@apache.org> AuthorDate: Tue Aug 6 14:41:57 2024 +0800 [#4295] feat(server): Add REST API for the owner (#4354) ### 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. --- .../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()); + } +}