This is an automated email from the ASF dual-hosted git repository.
roryqi 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 5d18c76932 [#12330] feat(core): Add group by-id APIs and
alterGroupById via GroupChange (#12331)
5d18c76932 is described below
commit 5d18c769329351a98b4abceb1cfbed9236e472ab
Author: MaSai <[email protected]>
AuthorDate: Tue Aug 4 19:13:40 2026 +0800
[#12330] feat(core): Add group by-id APIs and alterGroupById via
GroupChange (#12331)
### What changes were proposed in this pull request?
Add AccessControlDispatcher APIs that locate groups by
Gravitino-assigned id, and a single alter path for mutable attributes.
Builds on user by-id APIs already merged in #12332.
**API / model**
- Expose `Group.id()` and align `GroupDTO` / `GroupInfo` (required
non-null id)
- `getGroupById` / `removeGroupById`
- `GroupChange` + `alterGroupById` (update `externalId`)
**Persistence / store**
- Relational lookup/update/delete by metalake + group id
(`GroupMetaService`, mapper/SQL)
- Wire JDBC and in-memory entity stores for `GROUP` id operations
**Wiring / events**
- `UserGroupIdManager`, `AccessControlManager`, hook dispatcher
- Listener Pre/Event/Failure for get/remove/alter by id (`GetGroupById*`
/ `RemoveGroupById*` / `AlterGroup*`)
- TreeLock paths via `AuthorizationUtils.ofGroupId` /
`GROUP_ID_SCHEMA_NAME`
Existing by-name get/remove and by-externalId get/remove are unchanged.
Fix: #12330
### Why are the changes needed?
SCIM push (Azure Entra / Okta) stores the SCIM `id` returned on create
and uses it for subsequent `GET` / `PATCH` / `DELETE`. Core previously
only keyed groups by name or `externalId`. A single `alterGroupById`
matches SCIM PATCH that may update `externalId`.
### Does this PR introduce _any_ user-facing change?
- Yes: new `Group.id()`, by-id get/remove, `GroupChange` /
`alterGroupById`, and matching listener events / `OperationType` values
- Existing by-name and by-externalId group APIs unchanged
### How was this patch tested?
```bash
./gradlew :api:test \
--tests org.apache.gravitino.authorization.TestGroupChange \
:common:test \
--tests org.apache.gravitino.dto.responses.TestResponses \
:core:test \
--tests org.apache.gravitino.authorization.TestAccessControlManager \
--tests org.apache.gravitino.listener.api.event.TestGroupEvent \
:clients:client-java:test \
--tests org.apache.gravitino.client.TestUserGroup \
--tests org.apache.gravitino.client.TestPermission \
-PskipITs
```
---------
Co-authored-by: Cursor <[email protected]>
---
.../org/apache/gravitino/authorization/Group.java | 13 +++-
.../gravitino/authorization/GroupChange.java | 82 +++++++++++++++++++
.../gravitino/authorization/TestGroupChange.java | 35 +++++++++
.../apache/gravitino/cli/commands/ListGroups.java | 5 ++
.../apache/gravitino/client/TestPermission.java | 2 +
.../org/apache/gravitino/client/TestUserGroup.java | 2 +
.../gravitino/dto/authorization/GroupDTO.java | 32 +++++++-
.../apache/gravitino/dto/util/DTOConverters.java | 1 +
.../gravitino/dto/responses/TestResponses.java | 2 +-
.../src/main/java/org/apache/gravitino/Entity.java | 10 +++
.../java/org/apache/gravitino/audit/AuditLog.java | 6 ++
.../gravitino/audit/v2/CompatibilityUtils.java | 3 +
.../authorization/AccessControlDispatcher.java | 43 ++++++++++
.../authorization/AccessControlManager.java | 26 +++++++
.../authorization/AuthorizationUtils.java | 39 ++++++++++
.../authorization/UserGroupIdManager.java | 87 +++++++++++++++++++++
.../hook/AccessControlHookDispatcher.java | 18 +++++
.../listener/AccessControlEventDispatcher.java | 65 ++++++++++++++++
.../listener/api/event/AlterGroupEvent.java | 72 +++++++++++++++++
.../listener/api/event/AlterGroupFailureEvent.java | 70 +++++++++++++++++
.../listener/api/event/AlterGroupPreEvent.java | 69 ++++++++++++++++
.../listener/api/event/GetGroupByIdEvent.java | 56 +++++++++++++
.../api/event/GetGroupByIdFailureEvent.java | 57 ++++++++++++++
.../listener/api/event/GetGroupByIdPreEvent.java | 55 +++++++++++++
.../listener/api/event/OperationType.java | 3 +
.../listener/api/event/RemoveGroupByIdEvent.java | 67 ++++++++++++++++
.../api/event/RemoveGroupByIdFailureEvent.java | 57 ++++++++++++++
.../api/event/RemoveGroupByIdPreEvent.java | 55 +++++++++++++
.../gravitino/listener/api/info/GroupInfo.java | 14 +++-
.../gravitino/storage/relational/JDBCBackend.java | 10 +++
.../storage/relational/mapper/GroupMetaMapper.java | 6 ++
.../mapper/GroupMetaSQLProviderFactory.java | 5 ++
.../provider/base/GroupMetaBaseSQLProvider.java | 17 ++++
.../relational/service/GroupMetaService.java | 91 ++++++++++++++++++++++
.../gravitino/audit/v2/TestCompatibilityUtils.java | 3 +
.../authorization/TestAccessControlManager.java | 35 +++++++++
.../listener/api/event/TestGroupEvent.java | 76 ++++++++++++++++++
.../storage/memory/TestMemoryEntityStore.java | 19 ++++-
38 files changed, 1299 insertions(+), 9 deletions(-)
diff --git a/api/src/main/java/org/apache/gravitino/authorization/Group.java
b/api/src/main/java/org/apache/gravitino/authorization/Group.java
index 307380d4b9..f97ae948f0 100644
--- a/api/src/main/java/org/apache/gravitino/authorization/Group.java
+++ b/api/src/main/java/org/apache/gravitino/authorization/Group.java
@@ -34,13 +34,24 @@ public interface Group extends Auditable {
*/
String name();
+ /**
+ * The unique id assigned by Gravitino.
+ *
+ * <p>This id is server-assigned and immutable. Upstream systems may also
supply an optional
+ * {@link #externalId()}.
+ *
+ * @return The unique id of the group.
+ */
+ Long id();
+
/**
* The stable identifier assigned by an upstream identity system (for
example, SCIM, LDAP, or
* IAM), or null if not set.
*
* <p>Gravitino {@link Group#name() group names} may differ from upstream
ids or be unknown at
* sync time. External id lets integrators look up and delete groups without
relying on the
- * Gravitino group name.
+ * Gravitino group name. Mutable attributes such as {@code externalId} are
updated via {@code
+ * alterGroupById}.
*
* @return The upstream external identifier, or null if not set.
*/
diff --git
a/api/src/main/java/org/apache/gravitino/authorization/GroupChange.java
b/api/src/main/java/org/apache/gravitino/authorization/GroupChange.java
new file mode 100644
index 0000000000..af9f62e873
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/authorization/GroupChange.java
@@ -0,0 +1,82 @@
+/*
+ * 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.authorization;
+
+import java.util.Objects;
+import javax.annotation.Nullable;
+import org.apache.gravitino.annotation.Evolving;
+
+/**
+ * A group change is a change to a group. It can be used to update the
optional external id.
+ * Multiple changes may be applied in one {@code alterGroupById} call.
+ */
+@Evolving
+public interface GroupChange {
+
+ /**
+ * Creates a group change to update the external identifier.
+ *
+ * @param newExternalId The new external identifier, or null to clear it.
+ * @return The group change.
+ */
+ static GroupChange updateExternalId(@Nullable String newExternalId) {
+ return new UpdateExternalId(newExternalId);
+ }
+
+ /** A group change to update the external identifier. */
+ final class UpdateExternalId implements GroupChange {
+ @Nullable private final String newExternalId;
+
+ private UpdateExternalId(@Nullable String newExternalId) {
+ this.newExternalId = newExternalId;
+ }
+
+ /**
+ * Returns the new external identifier, or null to clear it.
+ *
+ * @return The new external identifier, or null.
+ */
+ @Nullable
+ public String getNewExternalId() {
+ return newExternalId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof UpdateExternalId)) {
+ return false;
+ }
+ UpdateExternalId that = (UpdateExternalId) o;
+ return Objects.equals(newExternalId, that.newExternalId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(newExternalId);
+ }
+
+ @Override
+ public String toString() {
+ return "UpdateExternalId " + newExternalId;
+ }
+ }
+}
diff --git
a/api/src/test/java/org/apache/gravitino/authorization/TestGroupChange.java
b/api/src/test/java/org/apache/gravitino/authorization/TestGroupChange.java
new file mode 100644
index 0000000000..a029befe86
--- /dev/null
+++ b/api/src/test/java/org/apache/gravitino/authorization/TestGroupChange.java
@@ -0,0 +1,35 @@
+/*
+ * 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.authorization;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestGroupChange {
+
+ @Test
+ void testUpdateExternalId() {
+ GroupChange.UpdateExternalId change =
+ (GroupChange.UpdateExternalId) GroupChange.updateExternalId("ext-1");
+ Assertions.assertEquals("ext-1", change.getNewExternalId());
+ Assertions.assertEquals(GroupChange.updateExternalId("ext-1"), change);
+ Assertions.assertEquals(GroupChange.updateExternalId(null),
GroupChange.updateExternalId(null));
+ Assertions.assertNotEquals(GroupChange.updateExternalId("ext-2"), change);
+ }
+}
diff --git
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListGroups.java
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListGroups.java
index 588f30393e..8d84614702 100644
---
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListGroups.java
+++
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListGroups.java
@@ -68,6 +68,11 @@ public class ListGroups extends Command {
private Group getGroup(String name) {
return new Group() {
+ @Override
+ public Long id() {
+ return null;
+ }
+
@Override
public String name() {
return name;
diff --git
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestPermission.java
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestPermission.java
index 4704886190..50c529ed8a 100644
---
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestPermission.java
+++
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestPermission.java
@@ -148,6 +148,7 @@ public class TestPermission extends TestBase {
RoleGrantRequest request = new RoleGrantRequest(roles);
GroupDTO groupDTO =
GroupDTO.builder()
+ .withId(1L)
.withName("group")
.withRoles(Lists.newArrayList("roles"))
.withAudit(AuditDTO.builder().withCreator("test").withCreateTime(Instant.now()).build())
@@ -174,6 +175,7 @@ public class TestPermission extends TestBase {
String.format(API_PERMISSION_PATH, metalakeName,
String.format("groups/%s/revoke", group));
GroupDTO groupDTO =
GroupDTO.builder()
+ .withId(1L)
.withName("group")
.withRoles(Lists.newArrayList())
.withAudit(AuditDTO.builder().withCreator("test").withCreateTime(Instant.now()).build())
diff --git
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestUserGroup.java
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestUserGroup.java
index 9c0a12d133..dbd7aab14e 100644
---
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestUserGroup.java
+++
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestUserGroup.java
@@ -306,6 +306,7 @@ public class TestUserGroup extends TestBase {
GroupDTO mockGroup =
GroupDTO.builder()
+ .withId(1L)
.withName(groupName)
.withExternalId(externalId)
.withAudit(
@@ -436,6 +437,7 @@ public class TestUserGroup extends TestBase {
private GroupDTO mockGroupDTO(String name) {
return GroupDTO.builder()
+ .withId(1L)
.withName(name)
.withAudit(AuditDTO.builder().withCreator("creator").withCreateTime(Instant.now()).build())
.build();
diff --git
a/common/src/main/java/org/apache/gravitino/dto/authorization/GroupDTO.java
b/common/src/main/java/org/apache/gravitino/dto/authorization/GroupDTO.java
index 61fbc814df..7d34ee2c35 100644
--- a/common/src/main/java/org/apache/gravitino/dto/authorization/GroupDTO.java
+++ b/common/src/main/java/org/apache/gravitino/dto/authorization/GroupDTO.java
@@ -31,6 +31,9 @@ import org.apache.gravitino.dto.AuditDTO;
/** Represents a Group Data Transfer Object (DTO). */
public class GroupDTO implements Group {
+ @JsonProperty("id")
+ private Long id;
+
@JsonProperty("name")
private String name;
@@ -50,18 +53,28 @@ public class GroupDTO implements Group {
/**
* Creates a new instance of GroupDTO.
*
+ * @param id The id of the Group DTO.
* @param name The name of the Group DTO.
* @param externalId The external id of the Group DTO.
* @param roles The roles of the Group DTO.
* @param audit The audit information of the Group DTO.
*/
- protected GroupDTO(String name, String externalId, List<String> roles,
AuditDTO audit) {
+ protected GroupDTO(Long id, String name, String externalId, List<String>
roles, AuditDTO audit) {
+ this.id = id;
this.name = name;
this.externalId = externalId;
this.audit = audit;
this.roles = roles;
}
+ /**
+ * @return The id of the Group DTO.
+ */
+ @Override
+ public Long id() {
+ return id;
+ }
+
/**
* @return The name of the Group DTO.
*/
@@ -109,6 +122,9 @@ public class GroupDTO implements Group {
*/
public static class Builder<S extends Builder> {
+ /** The id of the group. */
+ protected Long id;
+
/** The name of the group. */
protected String name;
@@ -121,6 +137,17 @@ public class GroupDTO implements Group {
/** The audit information of the group. */
protected AuditDTO audit;
+ /**
+ * Sets the id of the group.
+ *
+ * @param id The id of the group.
+ * @return The builder instance.
+ */
+ public S withId(Long id) {
+ this.id = id;
+ return (S) this;
+ }
+
/**
* Sets the name of the group.
*
@@ -175,9 +202,10 @@ public class GroupDTO implements Group {
* @throws IllegalArgumentException If the name or audit are not set.
*/
public GroupDTO build() {
+ Preconditions.checkArgument(id != null, "id cannot be null");
Preconditions.checkArgument(StringUtils.isNotBlank(name), "name cannot
be null or empty");
Preconditions.checkArgument(audit != null, "audit cannot be null");
- return new GroupDTO(name, externalId, roles, audit);
+ return new GroupDTO(id, name, externalId, roles, audit);
}
}
}
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 75394cc69f..864823a77b 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
@@ -514,6 +514,7 @@ public class DTOConverters {
}
return GroupDTO.builder()
+ .withId(group.id())
.withName(group.name())
.withExternalId(group.externalId())
.withRoles(group.roles())
diff --git
a/common/src/test/java/org/apache/gravitino/dto/responses/TestResponses.java
b/common/src/test/java/org/apache/gravitino/dto/responses/TestResponses.java
index 67f2c43659..85a321c103 100644
--- a/common/src/test/java/org/apache/gravitino/dto/responses/TestResponses.java
+++ b/common/src/test/java/org/apache/gravitino/dto/responses/TestResponses.java
@@ -310,7 +310,7 @@ public class TestResponses {
void testGroupResponse() throws IllegalArgumentException {
AuditDTO audit =
AuditDTO.builder().withCreator("creator").withCreateTime(Instant.now()).build();
- GroupDTO group =
GroupDTO.builder().withName("group1").withAudit(audit).build();
+ GroupDTO group =
GroupDTO.builder().withId(1L).withName("group1").withAudit(audit).build();
GroupResponse response = new GroupResponse(group);
response.validate(); // No exception thrown
}
diff --git a/core/src/main/java/org/apache/gravitino/Entity.java
b/core/src/main/java/org/apache/gravitino/Entity.java
index 934f119eb0..5c214cb434 100644
--- a/core/src/main/java/org/apache/gravitino/Entity.java
+++ b/core/src/main/java/org/apache/gravitino/Entity.java
@@ -65,6 +65,16 @@ public interface Entity extends Serializable {
/** The group schema name in the system catalog. */
String GROUP_SCHEMA_NAME = "group";
+ /**
+ * A virtual schema name used only for {@link
org.apache.gravitino.lock.TreeLockUtils} lock paths
+ * when operating on groups by Gravitino-assigned id (for example,
get/alter/delete-by-id).
+ *
+ * <p>This is not a real metadata schema and does not store entities. It
forms part of a synthetic
+ * {@link org.apache.gravitino.NameIdentifier} such as {@code {metalake,
system, group-id, <id>}}
+ * so that concurrent operations on the same group id are serialized.
+ */
+ String GROUP_ID_SCHEMA_NAME = "group-id";
+
/**
* A virtual schema name used only for {@link
org.apache.gravitino.lock.TreeLockUtils} lock paths
* when operating on groups by external id (for example,
get/delete-by-external-id).
diff --git a/core/src/main/java/org/apache/gravitino/audit/AuditLog.java
b/core/src/main/java/org/apache/gravitino/audit/AuditLog.java
index 0b431b52f5..6a91a41b24 100644
--- a/core/src/main/java/org/apache/gravitino/audit/AuditLog.java
+++ b/core/src/main/java/org/apache/gravitino/audit/AuditLog.java
@@ -406,6 +406,12 @@ public interface AuditLog {
GET_GROUP_BY_EXTERNAL_ID,
+ GET_GROUP_BY_ID,
+
+ REMOVE_GROUP_BY_ID,
+
+ ALTER_GROUP,
+
LIST_GROUPS,
LIST_GROUP_NAMES,
diff --git
a/core/src/main/java/org/apache/gravitino/audit/v2/CompatibilityUtils.java
b/core/src/main/java/org/apache/gravitino/audit/v2/CompatibilityUtils.java
index 53337d0a82..3e254400ef 100644
--- a/core/src/main/java/org/apache/gravitino/audit/v2/CompatibilityUtils.java
+++ b/core/src/main/java/org/apache/gravitino/audit/v2/CompatibilityUtils.java
@@ -134,6 +134,9 @@ public class CompatibilityUtils {
.put(OperationType.REMOVE_GROUP_BY_EXTERNAL_ID,
Operation.REMOVE_GROUP_BY_EXTERNAL_ID)
.put(OperationType.GET_GROUP, Operation.GET_GROUP)
.put(OperationType.GET_GROUP_BY_EXTERNAL_ID,
Operation.GET_GROUP_BY_EXTERNAL_ID)
+ .put(OperationType.GET_GROUP_BY_ID, Operation.GET_GROUP_BY_ID)
+ .put(OperationType.REMOVE_GROUP_BY_ID, Operation.REMOVE_GROUP_BY_ID)
+ .put(OperationType.ALTER_GROUP, Operation.ALTER_GROUP)
.put(OperationType.LIST_GROUPS, Operation.LIST_GROUPS)
.put(OperationType.LIST_GROUP_NAMES, Operation.LIST_GROUP_NAMES)
.put(OperationType.GRANT_GROUP_ROLES, Operation.GRANT_GROUP_ROLES)
diff --git
a/core/src/main/java/org/apache/gravitino/authorization/AccessControlDispatcher.java
b/core/src/main/java/org/apache/gravitino/authorization/AccessControlDispatcher.java
index 20c3c0ba0d..5f0a1bd462 100644
---
a/core/src/main/java/org/apache/gravitino/authorization/AccessControlDispatcher.java
+++
b/core/src/main/java/org/apache/gravitino/authorization/AccessControlDispatcher.java
@@ -260,6 +260,49 @@ public interface AccessControlDispatcher {
Group getGroupByExternalId(String metalake, String externalId)
throws NoSuchGroupException, NoSuchMetalakeException;
+ /**
+ * Gets a Group by Gravitino-assigned id.
+ *
+ * @param metalake The Metalake of the Group.
+ * @param groupId The Gravitino-assigned id of the Group.
+ * @return The getting Group instance.
+ * @throws NoSuchGroupException If the Group with the given id does not
exist.
+ * @throws NoSuchMetalakeException If the Metalake with the given name does
not exist.
+ * @throws RuntimeException If getting the Group encounters storage issues.
+ */
+ Group getGroupById(String metalake, long groupId)
+ throws NoSuchGroupException, NoSuchMetalakeException;
+
+ /**
+ * Removes a Group by Gravitino-assigned id.
+ *
+ * @param metalake The Metalake of the Group.
+ * @param groupId The Gravitino-assigned id of the Group.
+ * @return True if the Group was successfully removed, false only when
there's no such group,
+ * otherwise it will throw an exception.
+ * @throws NoSuchMetalakeException If the Metalake with the given name does
not exist.
+ * @throws RuntimeException If removing the Group encounters storage issues.
+ */
+ boolean removeGroupById(String metalake, long groupId) throws
NoSuchMetalakeException;
+
+ /**
+ * Alters a Group by Gravitino-assigned id.
+ *
+ * <p>Supports updating {@code externalId} via {@link GroupChange}. Role
bindings are preserved.
+ *
+ * @param metalake The Metalake of the Group.
+ * @param groupId The Gravitino-assigned id of the Group.
+ * @param changes The changes to apply. Must not be empty.
+ * @return The updated Group instance.
+ * @throws IllegalArgumentException If changes is null or empty, or contains
an unsupported
+ * change.
+ * @throws NoSuchGroupException If the Group with the given id does not
exist.
+ * @throws NoSuchMetalakeException If the Metalake with the given name does
not exist.
+ * @throws RuntimeException If updating the Group encounters storage issues.
+ */
+ Group alterGroupById(String metalake, long groupId, GroupChange... changes)
+ throws NoSuchGroupException, NoSuchMetalakeException;
+
/**
* List groups
*
diff --git
a/core/src/main/java/org/apache/gravitino/authorization/AccessControlManager.java
b/core/src/main/java/org/apache/gravitino/authorization/AccessControlManager.java
index 386e7a3936..36cd4443b9 100644
---
a/core/src/main/java/org/apache/gravitino/authorization/AccessControlManager.java
+++
b/core/src/main/java/org/apache/gravitino/authorization/AccessControlManager.java
@@ -210,6 +210,32 @@ public class AccessControlManager implements
AccessControlDispatcher {
() -> userGroupExternalManager.getGroupByExternalId(metalake,
externalId));
}
+ @Override
+ public Group getGroupById(String metalake, long groupId)
+ throws NoSuchGroupException, NoSuchMetalakeException {
+ return TreeLockUtils.doWithTreeLock(
+ AuthorizationUtils.ofGroupId(metalake, groupId),
+ LockType.READ,
+ () -> userGroupIdManager.getGroupById(metalake, groupId));
+ }
+
+ @Override
+ public boolean removeGroupById(String metalake, long groupId) throws
NoSuchMetalakeException {
+ return TreeLockUtils.doWithTreeLock(
+ AuthorizationUtils.ofGroupId(metalake, groupId),
+ LockType.WRITE,
+ () -> userGroupIdManager.removeGroupById(metalake, groupId));
+ }
+
+ @Override
+ public Group alterGroupById(String metalake, long groupId, GroupChange...
changes)
+ throws NoSuchGroupException, NoSuchMetalakeException {
+ return TreeLockUtils.doWithTreeLock(
+ AuthorizationUtils.ofGroupId(metalake, groupId),
+ LockType.WRITE,
+ () -> userGroupIdManager.alterGroupById(metalake, groupId, changes));
+ }
+
@Override
public Group[] listGroups(String metalake) throws NoSuchMetalakeException {
return TreeLockUtils.doWithTreeLock(
diff --git
a/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java
b/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java
index 8c555cf29c..76f77770e8 100644
---
a/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java
+++
b/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java
@@ -71,6 +71,8 @@ public class AuthorizationUtils {
static final String GROUP_DOES_NOT_EXIST_MSG = "Group %s does not exist in
the metalake %s";
static final String GROUP_WITH_EXTERNAL_ID_DOES_NOT_EXIST_MSG =
"Group with external id %s does not exist in the metalake %s";
+ static final String GROUP_WITH_ID_DOES_NOT_EXIST_MSG =
+ "Group with id %s does not exist in the metalake %s";
static final String ROLE_DOES_NOT_EXIST_MSG = "Role %s does not exist in the
metalake %s";
/**
@@ -183,6 +185,22 @@ public class AuthorizationUtils {
String.valueOf(userId));
}
+ /**
+ * Creates a synthetic {@link NameIdentifier} used only as a tree-lock path
for group operations
+ * keyed by Gravitino-assigned id.
+ *
+ * @param metalake the metalake name
+ * @param groupId the Gravitino-assigned group id
+ * @return a synthetic name identifier for tree locking only
+ */
+ public static NameIdentifier ofGroupId(String metalake, long groupId) {
+ return NameIdentifier.of(
+ metalake,
+ Entity.SYSTEM_CATALOG_RESERVED_NAME,
+ Entity.GROUP_ID_SCHEMA_NAME,
+ String.valueOf(groupId));
+ }
+
/**
* Creates a synthetic {@link NameIdentifier} used only as a {@link
* org.apache.gravitino.lock.TreeLockUtils} lock path for group operations
keyed by external id.
@@ -229,6 +247,10 @@ public class AuthorizationUtils {
metalake, Entity.SYSTEM_CATALOG_RESERVED_NAME,
Entity.GROUP_EXTERNAL_ID_SCHEMA_NAME);
}
+ public static Namespace ofGroupIdNamespace(String metalake) {
+ return Namespace.of(metalake, Entity.SYSTEM_CATALOG_RESERVED_NAME,
Entity.GROUP_ID_SCHEMA_NAME);
+ }
+
public static void checkUser(NameIdentifier ident) {
NameIdentifier.check(ident != null, "User identifier must not be null");
checkUserNamespace(ident.namespace());
@@ -269,6 +291,16 @@ public class AuthorizationUtils {
checkGroupExternalIdNamespace(ident.namespace());
}
+ /**
+ * Validates that the name identifier refers to a group id in a metalake.
+ *
+ * @param ident the group id name identifier to validate
+ */
+ public static void checkGroupId(NameIdentifier ident) {
+ NameIdentifier.check(ident != null, "Group id identifier must not be
null");
+ checkGroupIdNamespace(ident.namespace());
+ }
+
public static void checkRole(NameIdentifier ident) {
NameIdentifier.check(ident != null, "Role identifier must not be null");
checkRoleNamespace(ident.namespace());
@@ -302,6 +334,13 @@ public class AuthorizationUtils {
namespace);
}
+ public static void checkGroupIdNamespace(Namespace namespace) {
+ Namespace.check(
+ namespace != null && namespace.length() == 3,
+ "Group id namespace must have 3 levels, the input namespace is %s",
+ namespace);
+ }
+
public static void checkGroupNamespace(Namespace namespace) {
Namespace.check(
namespace != null && namespace.length() == 3,
diff --git
a/core/src/main/java/org/apache/gravitino/authorization/UserGroupIdManager.java
b/core/src/main/java/org/apache/gravitino/authorization/UserGroupIdManager.java
index 22001934b7..6583a73499 100644
---
a/core/src/main/java/org/apache/gravitino/authorization/UserGroupIdManager.java
+++
b/core/src/main/java/org/apache/gravitino/authorization/UserGroupIdManager.java
@@ -24,8 +24,10 @@ import java.time.Instant;
import org.apache.gravitino.Entity;
import org.apache.gravitino.EntityStore;
import org.apache.gravitino.exceptions.NoSuchEntityException;
+import org.apache.gravitino.exceptions.NoSuchGroupException;
import org.apache.gravitino.exceptions.NoSuchUserException;
import org.apache.gravitino.meta.AuditInfo;
+import org.apache.gravitino.meta.GroupEntity;
import org.apache.gravitino.meta.UserEntity;
import org.apache.gravitino.storage.IdGenerator;
import org.apache.gravitino.utils.PrincipalUtils;
@@ -135,4 +137,89 @@ class UserGroupIdManager extends UserGroupManager {
.build())
.build();
}
+
+ boolean removeGroupById(String metalake, long groupId) {
+ try {
+ return store
+ .idOperations()
+ .deleteById(AuthorizationUtils.ofGroupId(metalake, groupId),
Entity.EntityType.GROUP);
+ } catch (IOException ioe) {
+ LOG.error(
+ "Removing group with id {} in the metalake {} failed due to storage
issues",
+ groupId,
+ metalake,
+ ioe);
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ Group getGroupById(String metalake, long groupId) throws
NoSuchGroupException {
+ try {
+ return store
+ .idOperations()
+ .getById(
+ AuthorizationUtils.ofGroupId(metalake, groupId),
+ Entity.EntityType.GROUP,
+ GroupEntity.class);
+ } catch (NoSuchEntityException e) {
+ LOG.warn("Group with id {} does not exist in the metalake {}", groupId,
metalake, e);
+ throw new NoSuchGroupException(
+ AuthorizationUtils.GROUP_WITH_ID_DOES_NOT_EXIST_MSG, groupId,
metalake);
+ } catch (IOException ioe) {
+ LOG.error("Getting group with id {} failed due to storage issues",
groupId, ioe);
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ Group alterGroupById(String metalake, long groupId, GroupChange... changes)
+ throws NoSuchGroupException {
+ Preconditions.checkArgument(
+ changes != null && changes.length > 0, "Group changes cannot be
empty");
+ try {
+ return store
+ .idOperations()
+ .updateById(
+ AuthorizationUtils.ofGroupId(metalake, groupId),
+ Entity.EntityType.GROUP,
+ GroupEntity.class,
+ group -> applyGroupChanges(group, changes));
+ } catch (NoSuchEntityException e) {
+ LOG.warn("Group with id {} does not exist in the metalake {}", groupId,
metalake, e);
+ throw new NoSuchGroupException(
+ AuthorizationUtils.GROUP_WITH_ID_DOES_NOT_EXIST_MSG, groupId,
metalake);
+ } catch (IOException ioe) {
+ LOG.error(
+ "Altering group with id {} in the metalake {} failed due to storage
issues",
+ groupId,
+ metalake,
+ ioe);
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ private static GroupEntity applyGroupChanges(GroupEntity group,
GroupChange... changes) {
+ String externalId = group.externalId();
+ for (GroupChange change : changes) {
+ if (change instanceof GroupChange.UpdateExternalId) {
+ externalId = ((GroupChange.UpdateExternalId)
change).getNewExternalId();
+ } else {
+ throw new IllegalArgumentException("Unsupported group change: " +
change);
+ }
+ }
+ return GroupEntity.builder()
+ .withId(group.id())
+ .withName(group.name())
+ .withNamespace(group.namespace())
+ .withExternalId(externalId)
+ .withRoleNames(group.roleNames())
+ .withRoleIds(group.roleIds())
+ .withAuditInfo(
+ AuditInfo.builder()
+ .withCreator(group.auditInfo().creator())
+ .withCreateTime(group.auditInfo().createTime())
+
.withLastModifier(PrincipalUtils.getCurrentPrincipal().getName())
+ .withLastModifiedTime(Instant.now())
+ .build())
+ .build();
+ }
}
diff --git
a/core/src/main/java/org/apache/gravitino/hook/AccessControlHookDispatcher.java
b/core/src/main/java/org/apache/gravitino/hook/AccessControlHookDispatcher.java
index f8028b8c3a..92c6f47f1b 100644
---
a/core/src/main/java/org/apache/gravitino/hook/AccessControlHookDispatcher.java
+++
b/core/src/main/java/org/apache/gravitino/hook/AccessControlHookDispatcher.java
@@ -28,6 +28,7 @@ import
org.apache.gravitino.authorization.AccessControlDispatcher;
import org.apache.gravitino.authorization.AuthorizationUtils;
import org.apache.gravitino.authorization.GravitinoAuthorizer;
import org.apache.gravitino.authorization.Group;
+import org.apache.gravitino.authorization.GroupChange;
import org.apache.gravitino.authorization.Owner;
import org.apache.gravitino.authorization.OwnerDispatcher;
import org.apache.gravitino.authorization.Privilege;
@@ -161,6 +162,23 @@ public class AccessControlHookDispatcher implements
AccessControlDispatcher {
return dispatcher.getGroupByExternalId(metalake, externalId);
}
+ @Override
+ public Group getGroupById(String metalake, long groupId)
+ throws NoSuchGroupException, NoSuchMetalakeException {
+ return dispatcher.getGroupById(metalake, groupId);
+ }
+
+ @Override
+ public boolean removeGroupById(String metalake, long groupId) throws
NoSuchMetalakeException {
+ return dispatcher.removeGroupById(metalake, groupId);
+ }
+
+ @Override
+ public Group alterGroupById(String metalake, long groupId, GroupChange...
changes)
+ throws NoSuchGroupException, NoSuchMetalakeException {
+ return dispatcher.alterGroupById(metalake, groupId, changes);
+ }
+
@Override
public Group[] listGroups(String metalake) throws NoSuchMetalakeException {
return dispatcher.listGroups(metalake);
diff --git
a/core/src/main/java/org/apache/gravitino/listener/AccessControlEventDispatcher.java
b/core/src/main/java/org/apache/gravitino/listener/AccessControlEventDispatcher.java
index dd3f734402..c52406a4e6 100644
---
a/core/src/main/java/org/apache/gravitino/listener/AccessControlEventDispatcher.java
+++
b/core/src/main/java/org/apache/gravitino/listener/AccessControlEventDispatcher.java
@@ -25,6 +25,7 @@ import java.util.Set;
import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.authorization.AccessControlDispatcher;
import org.apache.gravitino.authorization.Group;
+import org.apache.gravitino.authorization.GroupChange;
import org.apache.gravitino.authorization.Privilege;
import org.apache.gravitino.authorization.Role;
import org.apache.gravitino.authorization.SecurableObject;
@@ -45,6 +46,9 @@ import
org.apache.gravitino.listener.api.event.AddGroupPreEvent;
import org.apache.gravitino.listener.api.event.AddUserEvent;
import org.apache.gravitino.listener.api.event.AddUserFailureEvent;
import org.apache.gravitino.listener.api.event.AddUserPreEvent;
+import org.apache.gravitino.listener.api.event.AlterGroupEvent;
+import org.apache.gravitino.listener.api.event.AlterGroupFailureEvent;
+import org.apache.gravitino.listener.api.event.AlterGroupPreEvent;
import org.apache.gravitino.listener.api.event.CreateRoleEvent;
import org.apache.gravitino.listener.api.event.CreateRoleFailureEvent;
import org.apache.gravitino.listener.api.event.CreateRolePreEvent;
@@ -54,6 +58,9 @@ import
org.apache.gravitino.listener.api.event.DeleteRolePreEvent;
import org.apache.gravitino.listener.api.event.GetGroupByExternalIdEvent;
import
org.apache.gravitino.listener.api.event.GetGroupByExternalIdFailureEvent;
import org.apache.gravitino.listener.api.event.GetGroupByExternalIdPreEvent;
+import org.apache.gravitino.listener.api.event.GetGroupByIdEvent;
+import org.apache.gravitino.listener.api.event.GetGroupByIdFailureEvent;
+import org.apache.gravitino.listener.api.event.GetGroupByIdPreEvent;
import org.apache.gravitino.listener.api.event.GetGroupEvent;
import org.apache.gravitino.listener.api.event.GetGroupFailureEvent;
import org.apache.gravitino.listener.api.event.GetGroupPreEvent;
@@ -96,6 +103,9 @@ import
org.apache.gravitino.listener.api.event.OverridePrivilegesPreEvent;
import org.apache.gravitino.listener.api.event.RemoveGroupByExternalIdEvent;
import
org.apache.gravitino.listener.api.event.RemoveGroupByExternalIdFailureEvent;
import org.apache.gravitino.listener.api.event.RemoveGroupByExternalIdPreEvent;
+import org.apache.gravitino.listener.api.event.RemoveGroupByIdEvent;
+import org.apache.gravitino.listener.api.event.RemoveGroupByIdFailureEvent;
+import org.apache.gravitino.listener.api.event.RemoveGroupByIdPreEvent;
import org.apache.gravitino.listener.api.event.RemoveGroupEvent;
import org.apache.gravitino.listener.api.event.RemoveGroupFailureEvent;
import org.apache.gravitino.listener.api.event.RemoveGroupPreEvent;
@@ -418,6 +428,61 @@ public class AccessControlEventDispatcher implements
AccessControlDispatcher {
}
}
+ /** {@inheritDoc} */
+ @Override
+ public Group getGroupById(String metalake, long groupId)
+ throws NoSuchGroupException, NoSuchMetalakeException {
+ String initiator = PrincipalUtils.getCurrentUserName();
+
+ eventBus.dispatchEvent(new GetGroupByIdPreEvent(initiator, metalake,
groupId));
+ try {
+ Group groupObject = dispatcher.getGroupById(metalake, groupId);
+ eventBus.dispatchEvent(
+ new GetGroupByIdEvent(initiator, metalake, new
GroupInfo(groupObject)));
+
+ return groupObject;
+ } catch (Exception e) {
+ eventBus.dispatchEvent(new GetGroupByIdFailureEvent(initiator, metalake,
e, groupId));
+ throw e;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean removeGroupById(String metalake, long groupId) throws
NoSuchMetalakeException {
+ String initiator = PrincipalUtils.getCurrentUserName();
+
+ eventBus.dispatchEvent(new RemoveGroupByIdPreEvent(initiator, metalake,
groupId));
+ try {
+ boolean isExists = dispatcher.removeGroupById(metalake, groupId);
+ eventBus.dispatchEvent(new RemoveGroupByIdEvent(initiator, metalake,
groupId, isExists));
+
+ return isExists;
+ } catch (Exception e) {
+ eventBus.dispatchEvent(new RemoveGroupByIdFailureEvent(initiator,
metalake, e, groupId));
+ throw e;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Group alterGroupById(String metalake, long groupId, GroupChange...
changes)
+ throws NoSuchGroupException, NoSuchMetalakeException {
+ String initiator = PrincipalUtils.getCurrentUserName();
+
+ eventBus.dispatchEvent(new AlterGroupPreEvent(initiator, metalake,
groupId, changes));
+ try {
+ Group groupObject = dispatcher.alterGroupById(metalake, groupId,
changes);
+ eventBus.dispatchEvent(
+ new AlterGroupEvent(initiator, metalake, changes, new
GroupInfo(groupObject)));
+
+ return groupObject;
+ } catch (Exception e) {
+ eventBus.dispatchEvent(new AlterGroupFailureEvent(initiator, metalake,
groupId, changes, e));
+ throw e;
+ }
+ }
+
/** {@inheritDoc} */
@Override
public Group[] listGroups(String metalake) {
diff --git
a/core/src/main/java/org/apache/gravitino/listener/api/event/AlterGroupEvent.java
b/core/src/main/java/org/apache/gravitino/listener/api/event/AlterGroupEvent.java
new file mode 100644
index 0000000000..7e9d4922e6
--- /dev/null
+++
b/core/src/main/java/org/apache/gravitino/listener/api/event/AlterGroupEvent.java
@@ -0,0 +1,72 @@
+/*
+ * 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.listener.api.event;
+
+import javax.annotation.Nullable;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.authorization.AuthorizationUtils;
+import org.apache.gravitino.authorization.GroupChange;
+import org.apache.gravitino.listener.api.info.GroupInfo;
+
+/** Represents an event triggered upon the successful alteration of a group. */
+@DeveloperApi
+public final class AlterGroupEvent extends GroupEvent {
+ private final GroupInfo updatedGroupInfo;
+ private final GroupChange[] groupChanges;
+
+ /**
+ * Creates a new {@link AlterGroupEvent}.
+ *
+ * @param initiator The user who initiated the request.
+ * @param metalake The metalake name.
+ * @param groupChanges The changes applied to the group.
+ * @param updatedGroupInfo The post-alteration state of the group.
+ */
+ public AlterGroupEvent(
+ String initiator, String metalake, GroupChange[] groupChanges, GroupInfo
updatedGroupInfo) {
+ super(initiator, AuthorizationUtils.ofGroupId(metalake,
updatedGroupInfo.id()));
+ this.groupChanges = groupChanges != null ? groupChanges.clone() : null;
+ this.updatedGroupInfo = updatedGroupInfo;
+ }
+
+ /**
+ * Returns the final state of the group after successful alteration.
+ *
+ * @return The updated group information.
+ */
+ public GroupInfo updatedGroupInfo() {
+ return updatedGroupInfo;
+ }
+
+ /**
+ * Returns the specific changes that were made to the group.
+ *
+ * @return An array of {@link GroupChange}, or null.
+ */
+ @Nullable
+ public GroupChange[] changes() {
+ return groupChanges;
+ }
+
+ @Override
+ public OperationType operationType() {
+ return OperationType.ALTER_GROUP;
+ }
+}
diff --git
a/core/src/main/java/org/apache/gravitino/listener/api/event/AlterGroupFailureEvent.java
b/core/src/main/java/org/apache/gravitino/listener/api/event/AlterGroupFailureEvent.java
new file mode 100644
index 0000000000..790bfc8746
--- /dev/null
+++
b/core/src/main/java/org/apache/gravitino/listener/api/event/AlterGroupFailureEvent.java
@@ -0,0 +1,70 @@
+/*
+ * 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.listener.api.event;
+
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.authorization.AuthorizationUtils;
+import org.apache.gravitino.authorization.GroupChange;
+
+/** Represents an event triggered when an attempt to alter a group fails due
to an exception. */
+@DeveloperApi
+public class AlterGroupFailureEvent extends GroupFailureEvent {
+ private final long groupId;
+ private final GroupChange[] changes;
+
+ /**
+ * Creates a new {@link AlterGroupFailureEvent}.
+ *
+ * @param initiator The user who initiated the request.
+ * @param metalake The metalake name.
+ * @param groupId The Gravitino-assigned id of the group.
+ * @param changes The changes attempted to be made to the group.
+ * @param exception The exception that caused the failure.
+ */
+ public AlterGroupFailureEvent(
+ String initiator, String metalake, long groupId, GroupChange[] changes,
Exception exception) {
+ super(initiator, AuthorizationUtils.ofGroupId(metalake, groupId),
exception);
+ this.groupId = groupId;
+ this.changes = changes;
+ }
+
+ /**
+ * Returns the Gravitino-assigned id of the group.
+ *
+ * @return The group id.
+ */
+ public long groupId() {
+ return groupId;
+ }
+
+ /**
+ * Returns the changes attempted to be made to the group.
+ *
+ * @return An array of {@link GroupChange}.
+ */
+ public GroupChange[] changes() {
+ return changes;
+ }
+
+ @Override
+ public OperationType operationType() {
+ return OperationType.ALTER_GROUP;
+ }
+}
diff --git
a/core/src/main/java/org/apache/gravitino/listener/api/event/AlterGroupPreEvent.java
b/core/src/main/java/org/apache/gravitino/listener/api/event/AlterGroupPreEvent.java
new file mode 100644
index 0000000000..322c7422a6
--- /dev/null
+++
b/core/src/main/java/org/apache/gravitino/listener/api/event/AlterGroupPreEvent.java
@@ -0,0 +1,69 @@
+/*
+ * 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.listener.api.event;
+
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.authorization.AuthorizationUtils;
+import org.apache.gravitino.authorization.GroupChange;
+
+/** Represents an event triggered before altering a group by
Gravitino-assigned id. */
+@DeveloperApi
+public class AlterGroupPreEvent extends GroupPreEvent {
+ private final long groupId;
+ private final GroupChange[] changes;
+
+ /**
+ * Creates a new {@link AlterGroupPreEvent}.
+ *
+ * @param initiator The user who initiated the request.
+ * @param metalake The metalake name.
+ * @param groupId The Gravitino-assigned id of the group.
+ * @param changes The changes being applied to the group.
+ */
+ public AlterGroupPreEvent(
+ String initiator, String metalake, long groupId, GroupChange[] changes) {
+ super(initiator, AuthorizationUtils.ofGroupId(metalake, groupId));
+ this.groupId = groupId;
+ this.changes = changes;
+ }
+
+ /**
+ * Returns the Gravitino-assigned id of the group being altered.
+ *
+ * @return The group id.
+ */
+ public long groupId() {
+ return groupId;
+ }
+
+ /**
+ * Returns the changes being applied to the group.
+ *
+ * @return An array of {@link GroupChange}.
+ */
+ public GroupChange[] changes() {
+ return changes;
+ }
+
+ @Override
+ public OperationType operationType() {
+ return OperationType.ALTER_GROUP;
+ }
+}
diff --git
a/core/src/main/java/org/apache/gravitino/listener/api/event/GetGroupByIdEvent.java
b/core/src/main/java/org/apache/gravitino/listener/api/event/GetGroupByIdEvent.java
new file mode 100644
index 0000000000..4e7087c04b
--- /dev/null
+++
b/core/src/main/java/org/apache/gravitino/listener/api/event/GetGroupByIdEvent.java
@@ -0,0 +1,56 @@
+/*
+ * 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.listener.api.event;
+
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.authorization.AuthorizationUtils;
+import org.apache.gravitino.listener.api.info.GroupInfo;
+
+/** Represents an event triggered after successfully retrieving a group by
Gravitino-assigned id. */
+@DeveloperApi
+public class GetGroupByIdEvent extends GroupEvent {
+ private final GroupInfo loadedGroupInfo;
+
+ /**
+ * Creates a new {@link GetGroupByIdEvent}.
+ *
+ * @param initiator The user who initiated the request.
+ * @param metalake The metalake name.
+ * @param loadedGroupInfo The retrieved group information.
+ */
+ public GetGroupByIdEvent(String initiator, String metalake, GroupInfo
loadedGroupInfo) {
+ super(initiator, AuthorizationUtils.ofGroupId(metalake,
loadedGroupInfo.id()));
+ this.loadedGroupInfo = loadedGroupInfo;
+ }
+
+ /**
+ * Returns the retrieved group information.
+ *
+ * @return The group information.
+ */
+ public GroupInfo loadedGroupInfo() {
+ return loadedGroupInfo;
+ }
+
+ @Override
+ public OperationType operationType() {
+ return OperationType.GET_GROUP_BY_ID;
+ }
+}
diff --git
a/core/src/main/java/org/apache/gravitino/listener/api/event/GetGroupByIdFailureEvent.java
b/core/src/main/java/org/apache/gravitino/listener/api/event/GetGroupByIdFailureEvent.java
new file mode 100644
index 0000000000..35838405d9
--- /dev/null
+++
b/core/src/main/java/org/apache/gravitino/listener/api/event/GetGroupByIdFailureEvent.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.listener.api.event;
+
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.authorization.AuthorizationUtils;
+
+/** Represents an event triggered when retrieving a group by
Gravitino-assigned id fails. */
+@DeveloperApi
+public class GetGroupByIdFailureEvent extends GroupFailureEvent {
+ private final long groupId;
+
+ /**
+ * Creates a new {@link GetGroupByIdFailureEvent}.
+ *
+ * @param initiator The user who initiated the request.
+ * @param metalake The metalake name.
+ * @param exception The exception that caused the failure.
+ * @param groupId The Gravitino-assigned id of the group.
+ */
+ public GetGroupByIdFailureEvent(
+ String initiator, String metalake, Exception exception, long groupId) {
+ super(initiator, AuthorizationUtils.ofGroupId(metalake, groupId),
exception);
+ this.groupId = groupId;
+ }
+
+ /**
+ * Returns the Gravitino-assigned id of the group.
+ *
+ * @return The group id.
+ */
+ public long groupId() {
+ return groupId;
+ }
+
+ @Override
+ public OperationType operationType() {
+ return OperationType.GET_GROUP_BY_ID;
+ }
+}
diff --git
a/core/src/main/java/org/apache/gravitino/listener/api/event/GetGroupByIdPreEvent.java
b/core/src/main/java/org/apache/gravitino/listener/api/event/GetGroupByIdPreEvent.java
new file mode 100644
index 0000000000..b4c967900a
--- /dev/null
+++
b/core/src/main/java/org/apache/gravitino/listener/api/event/GetGroupByIdPreEvent.java
@@ -0,0 +1,55 @@
+/*
+ * 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.listener.api.event;
+
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.authorization.AuthorizationUtils;
+
+/** Represents an event triggered before retrieving a group by
Gravitino-assigned id. */
+@DeveloperApi
+public class GetGroupByIdPreEvent extends GroupPreEvent {
+ private final long groupId;
+
+ /**
+ * Creates a new {@link GetGroupByIdPreEvent}.
+ *
+ * @param initiator The user who initiated the request.
+ * @param metalake The metalake name.
+ * @param groupId The Gravitino-assigned id of the group.
+ */
+ public GetGroupByIdPreEvent(String initiator, String metalake, long groupId)
{
+ super(initiator, AuthorizationUtils.ofGroupId(metalake, groupId));
+ this.groupId = groupId;
+ }
+
+ /**
+ * Returns the Gravitino-assigned id of the group being retrieved.
+ *
+ * @return The group id.
+ */
+ public long groupId() {
+ return groupId;
+ }
+
+ @Override
+ public OperationType operationType() {
+ return OperationType.GET_GROUP_BY_ID;
+ }
+}
diff --git
a/core/src/main/java/org/apache/gravitino/listener/api/event/OperationType.java
b/core/src/main/java/org/apache/gravitino/listener/api/event/OperationType.java
index 2866195e4b..f64b9b3b0f 100644
---
a/core/src/main/java/org/apache/gravitino/listener/api/event/OperationType.java
+++
b/core/src/main/java/org/apache/gravitino/listener/api/event/OperationType.java
@@ -144,6 +144,9 @@ public enum OperationType {
GET_GROUP,
GET_GROUP_BY_EXTERNAL_ID,
REMOVE_GROUP_BY_EXTERNAL_ID,
+ GET_GROUP_BY_ID,
+ REMOVE_GROUP_BY_ID,
+ ALTER_GROUP,
LIST_GROUPS,
LIST_GROUP_NAMES,
GRANT_GROUP_ROLES,
diff --git
a/core/src/main/java/org/apache/gravitino/listener/api/event/RemoveGroupByIdEvent.java
b/core/src/main/java/org/apache/gravitino/listener/api/event/RemoveGroupByIdEvent.java
new file mode 100644
index 0000000000..cdb9e71477
--- /dev/null
+++
b/core/src/main/java/org/apache/gravitino/listener/api/event/RemoveGroupByIdEvent.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.listener.api.event;
+
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.authorization.AuthorizationUtils;
+
+/** Represents an event triggered after removing a group by Gravitino-assigned
id. */
+@DeveloperApi
+public class RemoveGroupByIdEvent extends GroupEvent {
+ private final long groupId;
+ private final boolean isExists;
+
+ /**
+ * Creates a new {@link RemoveGroupByIdEvent}.
+ *
+ * @param initiator The user who initiated the request.
+ * @param metalake The metalake name.
+ * @param groupId The Gravitino-assigned id of the group.
+ * @param isExists Whether the group existed and was removed.
+ */
+ public RemoveGroupByIdEvent(String initiator, String metalake, long groupId,
boolean isExists) {
+ super(initiator, AuthorizationUtils.ofGroupId(metalake, groupId));
+ this.groupId = groupId;
+ this.isExists = isExists;
+ }
+
+ /**
+ * Returns the Gravitino-assigned id of the removed group.
+ *
+ * @return The group id.
+ */
+ public long groupId() {
+ return groupId;
+ }
+
+ /**
+ * Returns whether the group existed and was removed.
+ *
+ * @return {@code true} if the group was removed.
+ */
+ public boolean isExists() {
+ return isExists;
+ }
+
+ @Override
+ public OperationType operationType() {
+ return OperationType.REMOVE_GROUP_BY_ID;
+ }
+}
diff --git
a/core/src/main/java/org/apache/gravitino/listener/api/event/RemoveGroupByIdFailureEvent.java
b/core/src/main/java/org/apache/gravitino/listener/api/event/RemoveGroupByIdFailureEvent.java
new file mode 100644
index 0000000000..d3d562bc27
--- /dev/null
+++
b/core/src/main/java/org/apache/gravitino/listener/api/event/RemoveGroupByIdFailureEvent.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.listener.api.event;
+
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.authorization.AuthorizationUtils;
+
+/** Represents an event triggered when removing a group by Gravitino-assigned
id fails. */
+@DeveloperApi
+public class RemoveGroupByIdFailureEvent extends GroupFailureEvent {
+ private final long groupId;
+
+ /**
+ * Creates a new {@link RemoveGroupByIdFailureEvent}.
+ *
+ * @param initiator The user who initiated the request.
+ * @param metalake The metalake name.
+ * @param exception The exception that caused the failure.
+ * @param groupId The Gravitino-assigned id of the group.
+ */
+ public RemoveGroupByIdFailureEvent(
+ String initiator, String metalake, Exception exception, long groupId) {
+ super(initiator, AuthorizationUtils.ofGroupId(metalake, groupId),
exception);
+ this.groupId = groupId;
+ }
+
+ /**
+ * Returns the Gravitino-assigned id of the group.
+ *
+ * @return The group id.
+ */
+ public long groupId() {
+ return groupId;
+ }
+
+ @Override
+ public OperationType operationType() {
+ return OperationType.REMOVE_GROUP_BY_ID;
+ }
+}
diff --git
a/core/src/main/java/org/apache/gravitino/listener/api/event/RemoveGroupByIdPreEvent.java
b/core/src/main/java/org/apache/gravitino/listener/api/event/RemoveGroupByIdPreEvent.java
new file mode 100644
index 0000000000..e689a23283
--- /dev/null
+++
b/core/src/main/java/org/apache/gravitino/listener/api/event/RemoveGroupByIdPreEvent.java
@@ -0,0 +1,55 @@
+/*
+ * 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.listener.api.event;
+
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.authorization.AuthorizationUtils;
+
+/** Represents an event triggered before removing a group by
Gravitino-assigned id. */
+@DeveloperApi
+public class RemoveGroupByIdPreEvent extends GroupPreEvent {
+ private final long groupId;
+
+ /**
+ * Creates a new {@link RemoveGroupByIdPreEvent}.
+ *
+ * @param initiator The user who initiated the request.
+ * @param metalake The metalake name.
+ * @param groupId The Gravitino-assigned id of the group.
+ */
+ public RemoveGroupByIdPreEvent(String initiator, String metalake, long
groupId) {
+ super(initiator, AuthorizationUtils.ofGroupId(metalake, groupId));
+ this.groupId = groupId;
+ }
+
+ /**
+ * Returns the Gravitino-assigned id of the group being removed.
+ *
+ * @return The group id.
+ */
+ public long groupId() {
+ return groupId;
+ }
+
+ @Override
+ public OperationType operationType() {
+ return OperationType.REMOVE_GROUP_BY_ID;
+ }
+}
diff --git
a/core/src/main/java/org/apache/gravitino/listener/api/info/GroupInfo.java
b/core/src/main/java/org/apache/gravitino/listener/api/info/GroupInfo.java
index 21c0cf4414..4f5f8c65ea 100644
--- a/core/src/main/java/org/apache/gravitino/listener/api/info/GroupInfo.java
+++ b/core/src/main/java/org/apache/gravitino/listener/api/info/GroupInfo.java
@@ -19,6 +19,7 @@
package org.apache.gravitino.listener.api.info;
+import com.google.common.base.Preconditions;
import java.util.List;
import java.util.Optional;
import org.apache.gravitino.annotation.DeveloperApi;
@@ -27,6 +28,7 @@ import org.apache.gravitino.authorization.Group;
/** Provides read-only access to group information for event listeners. */
@DeveloperApi
public class GroupInfo {
+ private final Long id;
private final String name;
private final Optional<String> externalId;
private List<String> roles;
@@ -37,11 +39,21 @@ public class GroupInfo {
* @param group the {@link Group} object from which to create the {@link
GroupInfo}.
*/
public GroupInfo(Group group) {
+ this.id = Preconditions.checkNotNull(group.id(), "group id");
this.name = group.name();
this.externalId = Optional.ofNullable(group.externalId());
this.roles = group.roles();
}
+ /**
+ * Returns the Gravitino-assigned id of the group.
+ *
+ * @return the group id
+ */
+ public Long id() {
+ return id;
+ }
+
/**
* Returns the name of the group.
*
@@ -61,7 +73,7 @@ public class GroupInfo {
}
/**
- * Returns the roles of the roles.
+ * Returns the roles of the group.
*
* @return The roles of the group.
*/
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
b/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
index d7e7a0e1a5..ada6e6160b 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
@@ -353,6 +353,11 @@ public class JDBCBackend implements RelationalBackend,
SupportsOrphanedRelationC
return (E)
UserMetaService.getInstance()
.getUserById(ident.namespace().level(0),
Long.parseLong(ident.name()));
+ case GROUP:
+ AuthorizationUtils.checkGroupId(ident);
+ return (E)
+ GroupMetaService.getInstance()
+ .getGroupById(ident.namespace().level(0),
Long.parseLong(ident.name()));
default:
throw new UnsupportedEntityTypeException(
"Unsupported entity type: %s for get by id operation", entityType);
@@ -369,6 +374,11 @@ public class JDBCBackend implements RelationalBackend,
SupportsOrphanedRelationC
return (E)
UserMetaService.getInstance()
.updateUserById(ident.namespace().level(0),
Long.parseLong(ident.name()), updater);
+ case GROUP:
+ AuthorizationUtils.checkGroupId(ident);
+ return (E)
+ GroupMetaService.getInstance()
+ .updateGroupById(ident.namespace().level(0),
Long.parseLong(ident.name()), updater);
default:
throw new UnsupportedEntityTypeException(
"Unsupported entity type: %s for update by id operation",
entityType);
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/GroupMetaMapper.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/GroupMetaMapper.java
index c86df5a90e..b74116fb24 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/GroupMetaMapper.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/GroupMetaMapper.java
@@ -108,4 +108,10 @@ public interface GroupMetaMapper {
method = "selectGroupMetaByMetalakeNameAndExternalId")
GroupPO selectGroupMetaByMetalakeNameAndExternalId(
@Param("metalakeName") String metalakeName, @Param("externalId") String
externalId);
+
+ @SelectProvider(
+ type = GroupMetaSQLProviderFactory.class,
+ method = "selectGroupMetaByMetalakeNameAndId")
+ GroupPO selectGroupMetaByMetalakeNameAndId(
+ @Param("metalakeName") String metalakeName, @Param("groupId") Long
groupId);
}
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/GroupMetaSQLProviderFactory.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/GroupMetaSQLProviderFactory.java
index 21d0d4df8e..0981d4b401 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/GroupMetaSQLProviderFactory.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/GroupMetaSQLProviderFactory.java
@@ -115,4 +115,9 @@ public class GroupMetaSQLProviderFactory {
@Param("metalakeName") String metalakeName, @Param("externalId") String
externalId) {
return
getProvider().selectGroupMetaByMetalakeNameAndExternalId(metalakeName,
externalId);
}
+
+ public static String selectGroupMetaByMetalakeNameAndId(
+ @Param("metalakeName") String metalakeName, @Param("groupId") Long
groupId) {
+ return getProvider().selectGroupMetaByMetalakeNameAndId(metalakeName,
groupId);
+ }
}
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/GroupMetaBaseSQLProvider.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/GroupMetaBaseSQLProvider.java
index 9e8229ec23..fa9085a429 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/GroupMetaBaseSQLProvider.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/GroupMetaBaseSQLProvider.java
@@ -106,6 +106,23 @@ public class GroupMetaBaseSQLProvider {
+ " AND gt.deleted_at = 0 AND mt.deleted_at = 0";
}
+ public String selectGroupMetaByMetalakeNameAndId(
+ @Param("metalakeName") String metalakeName, @Param("groupId") Long
groupId) {
+ return "SELECT gt.group_id as groupId, gt.group_name as groupName,"
+ + " gt.metalake_id as metalakeId,"
+ + " gt.external_id as externalId,"
+ + " gt.audit_info as auditInfo, gt.current_version as currentVersion,"
+ + " gt.last_version as lastVersion, gt.deleted_at as deletedAt"
+ + " FROM "
+ + GROUP_TABLE_NAME
+ + " gt JOIN "
+ + MetalakeMetaMapper.TABLE_NAME
+ + " mt ON gt.metalake_id = mt.metalake_id"
+ + " WHERE mt.metalake_name = #{metalakeName}"
+ + " AND gt.group_id = #{groupId}"
+ + " AND gt.deleted_at = 0 AND mt.deleted_at = 0";
+ }
+
public String listExtendedGroupPOsByMetalakeIdAndNames(
@Param("metalakeId") Long metalakeId, @Param("groupNames") List<String>
groupNames) {
return "<script>"
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/service/GroupMetaService.java
b/core/src/main/java/org/apache/gravitino/storage/relational/service/GroupMetaService.java
index 75c3a0f5c5..50b2003862 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/service/GroupMetaService.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/service/GroupMetaService.java
@@ -393,4 +393,95 @@ public class GroupMetaService {
return POConverters.fromGroupPO(
groupPO, rolePOs, AuthorizationUtils.ofGroupNamespace(metalake));
}
+
+ private GroupPO getGroupPOByMetalakeNameAndId(String metalakeName, Long
groupId) {
+ GroupPO groupPO =
+ SessionUtils.getWithoutCommit(
+ GroupMetaMapper.class,
+ mapper -> mapper.selectGroupMetaByMetalakeNameAndId(metalakeName,
groupId));
+
+ if (groupPO == null) {
+ throw new NoSuchEntityException(
+ NoSuchEntityException.NO_SUCH_ENTITY_MESSAGE,
+ Entity.EntityType.GROUP.name().toLowerCase(),
+ String.valueOf(groupId));
+ }
+ return groupPO;
+ }
+
+ @Monitored(
+ metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME,
+ baseMetricName = "getGroupById")
+ public GroupEntity getGroupById(String metalake, long groupId) {
+ GroupPO groupPO = getGroupPOByMetalakeNameAndId(metalake, groupId);
+ List<RolePO> rolePOs =
RoleMetaService.getInstance().listRolesByGroupId(groupPO.getGroupId());
+ return POConverters.fromGroupPO(
+ groupPO, rolePOs, AuthorizationUtils.ofGroupNamespace(metalake));
+ }
+
+ @Monitored(
+ metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME,
+ baseMetricName = "updateGroupById")
+ public <E extends Entity & HasIdentifier> GroupEntity updateGroupById(
+ String metalake, long groupId, Function<E, E> updater) throws
IOException {
+ GroupPO oldGroupPO = getGroupPOByMetalakeNameAndId(metalake, groupId);
+ List<RolePO> rolePOs =
+
RoleMetaService.getInstance().listRolesByGroupId(oldGroupPO.getGroupId());
+ GroupEntity oldEntity =
+ POConverters.fromGroupPO(
+ oldGroupPO, rolePOs,
AuthorizationUtils.ofGroupNamespace(metalake));
+ GroupEntity newEntity = (GroupEntity) updater.apply((E) oldEntity);
+ Preconditions.checkArgument(
+ Objects.equals(oldEntity.id(), newEntity.id()),
+ "The updated group entity id: %s should be same with the group entity
id before: %s",
+ newEntity.id(),
+ oldEntity.id());
+
+ try {
+ SessionUtils.doMultipleWithCommit(
+ () ->
+ SessionUtils.doWithoutCommit(
+ GroupMetaMapper.class,
+ mapper ->
+ mapper.updateGroupMeta(
+ POConverters.updateGroupPOWithVersion(oldGroupPO,
newEntity),
+ oldGroupPO)),
+ () ->
+ SessionUtils.doWithoutCommit(
+ GroupMetaMapper.class,
+ mapper ->
mapper.touchGroupUpdatedAt(oldGroupPO.getGroupId())));
+ } catch (RuntimeException re) {
+ ExceptionUtils.checkSQLException(
+ re, Entity.EntityType.GROUP, newEntity.nameIdentifier().toString());
+ throw re;
+ }
+ return newEntity;
+ }
+
+ @Monitored(
+ metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME,
+ baseMetricName = "deleteGroupById")
+ public boolean deleteGroupById(String metalake, long groupId) {
+ try {
+ getGroupPOByMetalakeNameAndId(metalake, groupId);
+ } catch (NoSuchEntityException e) {
+ return false;
+ }
+
+ SessionUtils.doMultipleWithCommit(
+ () ->
+ SessionUtils.doWithoutCommit(
+ GroupMetaMapper.class, mapper ->
mapper.softDeleteGroupMetaByGroupId(groupId)),
+ () ->
+ SessionUtils.doWithoutCommit(
+ GroupRoleRelMapper.class,
+ mapper -> mapper.softDeleteGroupRoleRelByGroupId(groupId)),
+ () ->
+ SessionUtils.doWithoutCommit(
+ OwnerMetaMapper.class,
+ mapper ->
+ mapper.softDeleteOwnerRelByOwnerIdAndType(
+ groupId, Entity.EntityType.GROUP.name())));
+ return true;
+ }
}
diff --git
a/core/src/test/java/org/apache/gravitino/audit/v2/TestCompatibilityUtils.java
b/core/src/test/java/org/apache/gravitino/audit/v2/TestCompatibilityUtils.java
index bf0bf7d2e4..7c6b42db62 100644
---
a/core/src/test/java/org/apache/gravitino/audit/v2/TestCompatibilityUtils.java
+++
b/core/src/test/java/org/apache/gravitino/audit/v2/TestCompatibilityUtils.java
@@ -139,6 +139,9 @@ public class TestCompatibilityUtils {
{OperationType.REMOVE_GROUP_BY_EXTERNAL_ID,
Operation.REMOVE_GROUP_BY_EXTERNAL_ID},
{OperationType.GET_GROUP, Operation.GET_GROUP},
{OperationType.GET_GROUP_BY_EXTERNAL_ID,
Operation.GET_GROUP_BY_EXTERNAL_ID},
+ {OperationType.GET_GROUP_BY_ID, Operation.GET_GROUP_BY_ID},
+ {OperationType.REMOVE_GROUP_BY_ID, Operation.REMOVE_GROUP_BY_ID},
+ {OperationType.ALTER_GROUP, Operation.ALTER_GROUP},
{OperationType.LIST_GROUPS, Operation.LIST_GROUPS},
{OperationType.LIST_GROUP_NAMES, Operation.LIST_GROUP_NAMES},
{OperationType.GRANT_GROUP_ROLES, Operation.GRANT_GROUP_ROLES},
diff --git
a/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManager.java
b/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManager.java
index c80b4abadc..182a184afd 100644
---
a/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManager.java
+++
b/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManager.java
@@ -659,4 +659,39 @@ public class TestAccessControlManager {
Assertions.assertFalse(testProps.containsKey(StringIdentifier.ID_KEY));
}
+
+ @Test
+ public void testGroupById() {
+ Group added = accessControlManager.addGroup(METALAKE, "id_group",
"ext-id-group");
+ long groupId = added.id();
+ Assertions.assertNotNull(groupId);
+
+ Group loaded = accessControlManager.getGroupById(METALAKE, groupId);
+ Assertions.assertEquals(groupId, loaded.id());
+ Assertions.assertEquals("id_group", loaded.name());
+ Assertions.assertEquals("ext-id-group", loaded.externalId());
+
+ Group updated =
+ accessControlManager.alterGroupById(
+ METALAKE, groupId, GroupChange.updateExternalId("ext-id-group-2"));
+ Assertions.assertEquals(groupId, updated.id());
+ Assertions.assertEquals("ext-id-group-2", updated.externalId());
+ Assertions.assertEquals(
+ "id_group", accessControlManager.getGroupByExternalId(METALAKE,
"ext-id-group-2").name());
+
+ Assertions.assertThrows(
+ NoSuchGroupException.class,
+ () ->
+ accessControlManager.alterGroupById(METALAKE, -1L,
GroupChange.updateExternalId("x")));
+
+ Assertions.assertTrue(accessControlManager.removeGroupById(METALAKE,
groupId));
+ Assertions.assertThrows(
+ NoSuchGroupException.class, () ->
accessControlManager.getGroupById(METALAKE, groupId));
+ Assertions.assertFalse(accessControlManager.removeGroupById(METALAKE,
groupId));
+ Assertions.assertThrows(
+ NoSuchGroupException.class,
+ () ->
+ accessControlManager.alterGroupById(
+ METALAKE, groupId, GroupChange.updateExternalId("x")));
+ }
}
diff --git
a/core/src/test/java/org/apache/gravitino/listener/api/event/TestGroupEvent.java
b/core/src/test/java/org/apache/gravitino/listener/api/event/TestGroupEvent.java
index 58c869933e..ab4e0ff146 100644
---
a/core/src/test/java/org/apache/gravitino/listener/api/event/TestGroupEvent.java
+++
b/core/src/test/java/org/apache/gravitino/listener/api/event/TestGroupEvent.java
@@ -19,6 +19,8 @@
package org.apache.gravitino.listener.api.event;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -31,6 +33,7 @@ import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.authorization.AccessControlDispatcher;
import org.apache.gravitino.authorization.AuthorizationUtils;
import org.apache.gravitino.authorization.Group;
+import org.apache.gravitino.authorization.GroupChange;
import org.apache.gravitino.exceptions.GravitinoRuntimeException;
import org.apache.gravitino.exceptions.NoSuchGroupException;
import org.apache.gravitino.exceptions.NoSuchMetalakeException;
@@ -51,6 +54,7 @@ public class TestGroupEvent {
private static final String METALAKE = "demo_metalake";
private static final String INEXIST_METALAKE = "inexist_metalake";
private static final String GROUP_EXT_ID = "ext-group-1";
+ private static final long GROUP_ID = 1L;
private AccessControlEventDispatcher dispatcher;
private AccessControlEventDispatcher failureDispatcher;
private DummyEventListener dummyEventListener;
@@ -591,8 +595,74 @@ public class TestGroupEvent {
Assertions.assertEquals(OperationType.GET_GROUP_BY_EXTERNAL_ID,
event.operationType());
}
+ @Test
+ void testGetGroupByIdEvent() {
+ dispatcher.getGroupById(METALAKE, GROUP_ID);
+
+ PreEvent preEvent = dummyEventListener.popPreEvent();
+ Assertions.assertEquals(GetGroupByIdPreEvent.class, preEvent.getClass());
+ Assertions.assertEquals(OperationType.GET_GROUP_BY_ID,
preEvent.operationType());
+ Assertions.assertEquals(GROUP_ID, ((GetGroupByIdPreEvent)
preEvent).groupId());
+
+ Event event = dummyEventListener.popPostEvent();
+ Assertions.assertEquals(GetGroupByIdEvent.class, event.getClass());
+ Assertions.assertEquals(OperationType.GET_GROUP_BY_ID,
event.operationType());
+ validateGroup(((GetGroupByIdEvent) event).loadedGroupInfo(), group);
+ }
+
+ @Test
+ void testRemoveGroupByIdEvent() {
+ dispatcher.removeGroupById(METALAKE, GROUP_ID);
+
+ PreEvent preEvent = dummyEventListener.popPreEvent();
+ Assertions.assertEquals(RemoveGroupByIdPreEvent.class,
preEvent.getClass());
+ Assertions.assertEquals(OperationType.REMOVE_GROUP_BY_ID,
preEvent.operationType());
+
+ Event event = dummyEventListener.popPostEvent();
+ Assertions.assertEquals(RemoveGroupByIdEvent.class, event.getClass());
+ Assertions.assertEquals(OperationType.REMOVE_GROUP_BY_ID,
event.operationType());
+ Assertions.assertTrue(((RemoveGroupByIdEvent) event).isExists());
+ }
+
+ @Test
+ void testAlterGroupEvent() {
+ GroupChange change = GroupChange.updateExternalId("ext-group-2");
+ dispatcher.alterGroupById(METALAKE, GROUP_ID, change);
+
+ PreEvent preEvent = dummyEventListener.popPreEvent();
+ Assertions.assertEquals(AlterGroupPreEvent.class, preEvent.getClass());
+ Assertions.assertEquals(OperationType.ALTER_GROUP,
preEvent.operationType());
+ AlterGroupPreEvent alterPreEvent = (AlterGroupPreEvent) preEvent;
+ Assertions.assertEquals(GROUP_ID, alterPreEvent.groupId());
+ Assertions.assertArrayEquals(new GroupChange[] {change},
alterPreEvent.changes());
+
+ Event event = dummyEventListener.popPostEvent();
+ Assertions.assertEquals(AlterGroupEvent.class, event.getClass());
+ Assertions.assertEquals(OperationType.ALTER_GROUP, event.operationType());
+ AlterGroupEvent alterEvent = (AlterGroupEvent) event;
+ Assertions.assertArrayEquals(new GroupChange[] {change},
alterEvent.changes());
+ validateGroup(alterEvent.updatedGroupInfo(), group);
+ }
+
+ @Test
+ void testAlterGroupFailureEvent() {
+ GroupChange change = GroupChange.updateExternalId("ext-group-2");
+ Assertions.assertThrowsExactly(
+ GravitinoRuntimeException.class,
+ () -> failureDispatcher.alterGroupById(METALAKE, GROUP_ID, change));
+
+ dummyEventListener.popPreEvent();
+ Event event = dummyEventListener.popPostEvent();
+ Assertions.assertEquals(AlterGroupFailureEvent.class, event.getClass());
+ Assertions.assertEquals(OperationType.ALTER_GROUP, event.operationType());
+ AlterGroupFailureEvent failureEvent = (AlterGroupFailureEvent) event;
+ Assertions.assertEquals(GROUP_ID, failureEvent.groupId());
+ Assertions.assertArrayEquals(new GroupChange[] {change},
failureEvent.changes());
+ }
+
private Group getMockGroup(String name, List<String> roles) {
Group mockGroup = mock(Group.class);
+ when(mockGroup.id()).thenReturn(GROUP_ID);
when(mockGroup.name()).thenReturn(name);
when(mockGroup.roles()).thenReturn(roles);
@@ -601,6 +671,7 @@ public class TestGroupEvent {
private Group getMockGroupWithExtId(String name, String externalId,
List<String> roles) {
Group mockGroup = mock(Group.class);
+ when(mockGroup.id()).thenReturn(GROUP_ID);
when(mockGroup.name()).thenReturn(name);
when(mockGroup.externalId()).thenReturn(externalId);
when(mockGroup.roles()).thenReturn(roles);
@@ -617,12 +688,16 @@ public class TestGroupEvent {
when(dispatcher.removeGroup(METALAKE, groupName)).thenReturn(true);
when(dispatcher.removeGroup(METALAKE, inExistGroupName)).thenReturn(false);
when(dispatcher.removeGroupByExternalId(METALAKE,
GROUP_EXT_ID)).thenReturn(true);
+ when(dispatcher.removeGroupById(METALAKE, GROUP_ID)).thenReturn(true);
when(dispatcher.listGroups(METALAKE)).thenReturn(new Group[] {group,
otherGroup});
when(dispatcher.listGroupNames(METALAKE)).thenReturn(new String[]
{groupName, otherGroupName});
when(dispatcher.getGroup(METALAKE, groupName)).thenReturn(group);
when(dispatcher.getGroupByExternalId(METALAKE,
GROUP_EXT_ID)).thenReturn(externalIdGroup);
+ when(dispatcher.getGroupById(METALAKE, GROUP_ID)).thenReturn(group);
+ when(dispatcher.alterGroupById(eq(METALAKE), eq(GROUP_ID),
any(GroupChange[].class)))
+ .thenReturn(group);
when(dispatcher.getGroup(METALAKE, inExistGroupName))
.thenThrow(new NoSuchGroupException("group not found"));
when(dispatcher.getGroup(INEXIST_METALAKE, groupName))
@@ -643,6 +718,7 @@ public class TestGroupEvent {
}
private void validateGroup(GroupInfo groupInfo, Group group) {
+ Assertions.assertEquals(group.id(), groupInfo.id());
Assertions.assertEquals(group.name(), groupInfo.name());
Assertions.assertEquals(Optional.ofNullable(group.externalId()),
groupInfo.externalId());
Assertions.assertEquals(group.roles(), groupInfo.roles());
diff --git
a/core/src/test/java/org/apache/gravitino/storage/memory/TestMemoryEntityStore.java
b/core/src/test/java/org/apache/gravitino/storage/memory/TestMemoryEntityStore.java
index ad7acc8959..f23b8655b7 100644
---
a/core/src/test/java/org/apache/gravitino/storage/memory/TestMemoryEntityStore.java
+++
b/core/src/test/java/org/apache/gravitino/storage/memory/TestMemoryEntityStore.java
@@ -240,14 +240,22 @@ public class TestMemoryEntityStore {
public <E extends Entity & HasIdentifier> E getById(
NameIdentifier ident, EntityType entityType, Class<E> type)
throws NoSuchEntityException, IOException {
- if (entityType != EntityType.USER) {
+ if (entityType != EntityType.USER && entityType != EntityType.GROUP) {
throw new UnsupportedOperationException(
"Get by id is not supported for entity type: " + entityType);
}
- AuthorizationUtils.checkUserId(ident);
- long entityId = Long.parseLong(ident.name());
- Namespace entityNamespace =
AuthorizationUtils.ofUserNamespace(ident.namespace().level(0));
+ long entityId;
+ Namespace entityNamespace;
+ if (entityType == EntityType.USER) {
+ AuthorizationUtils.checkUserId(ident);
+ entityId = Long.parseLong(ident.name());
+ entityNamespace =
AuthorizationUtils.ofUserNamespace(ident.namespace().level(0));
+ } else {
+ AuthorizationUtils.checkGroupId(ident);
+ entityId = Long.parseLong(ident.name());
+ entityNamespace =
AuthorizationUtils.ofGroupNamespace(ident.namespace().level(0));
+ }
for (Map.Entry<NameIdentifier, Entity> entry : entityMap.entrySet()) {
Entity entity = entry.getValue();
@@ -284,6 +292,9 @@ public class TestMemoryEntityStore {
if (entityType == EntityType.USER) {
UserEntity user = getById(ident, entityType, UserEntity.class);
return delete(user.nameIdentifier(), entityType);
+ } else if (entityType == EntityType.GROUP) {
+ GroupEntity group = getById(ident, entityType, GroupEntity.class);
+ return delete(group.nameIdentifier(), entityType);
}
throw new UnsupportedOperationException(
"Delete by id is not supported for entity type: " + entityType);