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

fanng 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 df433b0192 [#6758] feat(core): Support role pre-event to Gravitino 
server (#6761)
df433b0192 is described below

commit df433b019232830721e5b0de064f8c0c851951e4
Author: Lord of Abyss <[email protected]>
AuthorDate: Mon Apr 7 11:13:29 2025 +0800

    [#6758] feat(core): Support role pre-event to Gravitino server (#6761)
    
    ### What changes were proposed in this pull request?
    
    Support role pre-event to Gravitino server
    
    
    
![image](https://github.com/user-attachments/assets/b3c5f3f3-c2a6-4bbb-9fdf-2cda4ec4496d)
    
    
    ### Why are the changes needed?
    
    Fix: #6758
    
    ### Does this PR introduce _any_ user-facing change?
    
    No.
    
    ### How was this patch tested?
    
    local test.
---
 .../api/event/AccessControlEventDispatcher.java    |  24 ++
 .../listener/api/event/CreateRolePreEvent.java     |  97 +++++++
 .../listener/api/event/DeleteRolePreEvent.java     |  62 +++++
 .../listener/api/event/GetRolePreEvent.java        |  64 +++++
 .../api/event/GrantPrivilegesPreEvent.java         |  97 +++++++
 .../listener/api/event/ListRoleNamesPreEvent.java  |  80 ++++++
 .../listener/api/event/OperationType.java          |   7 +-
 .../api/event/RevokePrivilegesPreEvent.java        |  96 +++++++
 .../gravitino/listener/api/event/RolePreEvent.java |  42 +++
 .../apache/gravitino/utils/NameIdentifierUtil.java |  11 +
 .../org/apache/gravitino/utils/NamespaceUtil.java  |  10 +
 .../listener/api/event/TestRoleEvent.java          | 291 +++++++++++++++++++++
 .../gravitino/utils/TestNameIdentifierUtil.java    |  14 +
 .../apache/gravitino/utils/TestNamespaceUtil.java  |  11 +
 docs/gravitino-server-config.md                    |   1 +
 15 files changed, 906 insertions(+), 1 deletion(-)

diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/AccessControlEventDispatcher.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/AccessControlEventDispatcher.java
index f9d478243b..d6db8bfe89 100644
--- 
a/core/src/main/java/org/apache/gravitino/listener/api/event/AccessControlEventDispatcher.java
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/AccessControlEventDispatcher.java
@@ -335,6 +335,10 @@ public class AccessControlEventDispatcher implements 
AccessControlDispatcher {
       Map<String, String> properties,
       List<SecurableObject> securableObjects)
       throws RoleAlreadyExistsException, NoSuchMetalakeException {
+    String initiator = PrincipalUtils.getCurrentUserName();
+
+    eventBus.dispatchEvent(
+        new CreateRolePreEvent(initiator, metalake, role, properties, 
securableObjects));
     try {
       // TODO: add Event
       return dispatcher.createRole(metalake, role, properties, 
securableObjects);
@@ -348,6 +352,9 @@ public class AccessControlEventDispatcher implements 
AccessControlDispatcher {
   @Override
   public Role getRole(String metalake, String role)
       throws NoSuchRoleException, NoSuchMetalakeException {
+    String initiator = PrincipalUtils.getCurrentUserName();
+
+    eventBus.dispatchEvent(new GetRolePreEvent(initiator, metalake, role));
     try {
       // TODO: add Event
       return dispatcher.getRole(metalake, role);
@@ -360,6 +367,9 @@ public class AccessControlEventDispatcher implements 
AccessControlDispatcher {
   /** {@inheritDoc} */
   @Override
   public boolean deleteRole(String metalake, String role) throws 
NoSuchMetalakeException {
+    String initiator = PrincipalUtils.getCurrentUserName();
+
+    eventBus.dispatchEvent(new DeleteRolePreEvent(initiator, metalake, role));
     try {
       // TODO: add Event
       return dispatcher.deleteRole(metalake, role);
@@ -372,6 +382,9 @@ public class AccessControlEventDispatcher implements 
AccessControlDispatcher {
   /** {@inheritDoc} */
   @Override
   public String[] listRoleNames(String metalake) throws 
NoSuchMetalakeException {
+    String initiator = PrincipalUtils.getCurrentUserName();
+
+    eventBus.dispatchEvent(new ListRoleNamesPreEvent(initiator, metalake));
     try {
       // TODO: add Event
       return dispatcher.listRoleNames(metalake);
@@ -385,6 +398,9 @@ public class AccessControlEventDispatcher implements 
AccessControlDispatcher {
   @Override
   public String[] listRoleNamesByObject(String metalake, MetadataObject object)
       throws NoSuchMetalakeException, NoSuchMetadataObjectException {
+    String initiator = PrincipalUtils.getCurrentUserName();
+
+    eventBus.dispatchEvent(new ListRoleNamesPreEvent(initiator, metalake, 
object));
     try {
       // TODO: add Event
       return dispatcher.listRoleNamesByObject(metalake, object);
@@ -399,6 +415,10 @@ public class AccessControlEventDispatcher implements 
AccessControlDispatcher {
   public Role grantPrivilegeToRole(
       String metalake, String role, MetadataObject object, Set<Privilege> 
privileges)
       throws NoSuchGroupException, NoSuchRoleException {
+    String initiator = PrincipalUtils.getCurrentUserName();
+
+    eventBus.dispatchEvent(
+        new GrantPrivilegesPreEvent(initiator, metalake, role, object, 
privileges));
     try {
       // TODO: add Event
       return dispatcher.grantPrivilegeToRole(metalake, role, object, 
privileges);
@@ -413,6 +433,10 @@ public class AccessControlEventDispatcher implements 
AccessControlDispatcher {
   public Role revokePrivilegesFromRole(
       String metalake, String role, MetadataObject object, Set<Privilege> 
privileges)
       throws NoSuchMetalakeException, NoSuchRoleException {
+    String initiator = PrincipalUtils.getCurrentUserName();
+
+    eventBus.dispatchEvent(
+        new RevokePrivilegesPreEvent(initiator, metalake, role, object, 
privileges));
     try {
       // TODO: add Event
       return dispatcher.revokePrivilegesFromRole(metalake, role, object, 
privileges);
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/CreateRolePreEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/CreateRolePreEvent.java
new file mode 100644
index 0000000000..0fe009fd9f
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/CreateRolePreEvent.java
@@ -0,0 +1,97 @@
+/*
+ * 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 java.util.List;
+import java.util.Map;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.authorization.SecurableObject;
+import org.apache.gravitino.utils.NameIdentifierUtil;
+
+/**
+ * Represents an event generated before a role is successfully created. This 
class encapsulates the
+ * details of the role creation event prior to its completion.
+ */
+@DeveloperApi
+public class CreateRolePreEvent extends RolePreEvent {
+  private final String roleName;
+  private final Map<String, String> properties;
+  private final List<SecurableObject> securableObjects;
+
+  /**
+   * Constructs a new {@link CreateRolePreEvent} instance with the specified 
initiator, identifier,
+   * role, properties, and securable objects.
+   *
+   * @param initiator The user who initiated the event.
+   * @param metalake The name of the metalake.
+   * @param roleName The name of the role being created.
+   * @param properties The properties of the role being created.
+   * @param securableObjects The list of securable objects belonging to the 
role.
+   */
+  protected CreateRolePreEvent(
+      String initiator,
+      String metalake,
+      String roleName,
+      Map<String, String> properties,
+      List<SecurableObject> securableObjects) {
+    super(initiator, NameIdentifierUtil.ofRole(metalake, roleName));
+
+    this.roleName = roleName;
+    this.properties = properties;
+    this.securableObjects = securableObjects;
+  }
+
+  /**
+   * Returns the name of the role being created.
+   *
+   * @return The name of the role being created.
+   */
+  public String roleName() {
+    return roleName;
+  }
+
+  /**
+   * Returns the properties of the role being created.
+   *
+   * @return The properties of the role being created.
+   */
+  protected Map<String, String> properties() {
+    return properties;
+  }
+
+  /**
+   * Returns the list of securable objects that belong to the role.
+   *
+   * @return The list of securable objects that belong to the role.
+   */
+  protected List<SecurableObject> securableObjects() {
+    return securableObjects;
+  }
+
+  /**
+   * Returns the operation type of this event.
+   *
+   * @return the operation type.
+   */
+  @Override
+  public OperationType operationType() {
+    return OperationType.CREATE_ROLE;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/DeleteRolePreEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/DeleteRolePreEvent.java
new file mode 100644
index 0000000000..1c13410e0e
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/DeleteRolePreEvent.java
@@ -0,0 +1,62 @@
+/*
+ * 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.utils.NameIdentifierUtil;
+
+/** Represents an event that is generated before a role is successfully 
deleted. */
+@DeveloperApi
+public class DeleteRolePreEvent extends RolePreEvent {
+  private final String roleName;
+
+  /**
+   * Constructs a new {@link DeleteRolePreEvent} instance with specified 
initiator, identifier and
+   * role name.
+   *
+   * @param initiator the user who initiated the event.
+   * @param metalake The name of the metalake.
+   * @param roleName the name of the role to be deleted.
+   */
+  public DeleteRolePreEvent(String initiator, String metalake, String 
roleName) {
+    super(initiator, NameIdentifierUtil.ofRole(metalake, roleName));
+
+    this.roleName = roleName;
+  }
+
+  /**
+   * Returns the name of the role to be deleted.
+   *
+   * @return The name of the role to be deleted.
+   */
+  public String roleName() {
+    return roleName;
+  }
+
+  /**
+   * Returns the operation type of this event.
+   *
+   * @return the operation type.
+   */
+  @Override
+  public OperationType operationType() {
+    return OperationType.DELETE_ROLE;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/GetRolePreEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/GetRolePreEvent.java
new file mode 100644
index 0000000000..6aa0f632ed
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/GetRolePreEvent.java
@@ -0,0 +1,64 @@
+/*
+ * 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.utils.NameIdentifierUtil;
+
+/**
+ * Represents an event triggered before retrieving a role from a specific 
metalake. This class
+ * encapsulates the details of the role retrieval event prior to its execution.
+ */
+@DeveloperApi
+public class GetRolePreEvent extends RolePreEvent {
+  private final String roleName;
+
+  /**
+   * Constructs a new {@link GetRolePreEvent} instance with the specified 
initiator, identifier, and
+   * role name.
+   *
+   * @param initiator The user who initiated the event.
+   * @param metalake The name of the metalake where the role is being 
retrieved from.
+   * @param roleName The name of the role being retrieved.
+   */
+  public GetRolePreEvent(String initiator, String metalake, String roleName) {
+    super(initiator, NameIdentifierUtil.ofRole(metalake, roleName));
+    this.roleName = roleName;
+  }
+
+  /**
+   * Returns the name of the role.
+   *
+   * @return The name of the role being retrieved.
+   */
+  public String roleName() {
+    return roleName;
+  }
+
+  /**
+   * Returns the operation type of this event.
+   *
+   * @return the operation type.
+   */
+  @Override
+  public OperationType operationType() {
+    return OperationType.GET_ROLE;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/GrantPrivilegesPreEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/GrantPrivilegesPreEvent.java
new file mode 100644
index 0000000000..3f33c8cce4
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/GrantPrivilegesPreEvent.java
@@ -0,0 +1,97 @@
+/*
+ * 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 java.util.Set;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.authorization.Privilege;
+import org.apache.gravitino.utils.NameIdentifierUtil;
+
+/**
+ * Represents an event generated before granting a set of privileges to a 
role. This class
+ * encapsulates the details of the privilege granting event prior to its 
execution.
+ */
+@DeveloperApi
+public class GrantPrivilegesPreEvent extends RolePreEvent {
+  private final String roleName;
+  private final MetadataObject object;
+  private final Set<Privilege> privileges;
+
+  /**
+   * Constructs a new {@link GrantPrivilegesPreEvent} instance with the 
specified initiator,
+   * identifier, role name, object, and privileges.
+   *
+   * @param initiator The name of the user who initiated the event.
+   * @param metalake The name of the metalake.
+   * @param roleName The name of the role to which privileges will be granted.
+   * @param object The {@link MetadataObject} instance related to the role.
+   * @param privileges The set of privileges to grant to the role.
+   */
+  public GrantPrivilegesPreEvent(
+      String initiator,
+      String metalake,
+      String roleName,
+      MetadataObject object,
+      Set<Privilege> privileges) {
+    super(initiator, NameIdentifierUtil.ofRole(metalake, roleName));
+
+    this.roleName = roleName;
+    this.object = object;
+    this.privileges = privileges;
+  }
+
+  /**
+   * Returns the name of the role.
+   *
+   * @return The name of the role to which privileges will be granted.
+   */
+  public String roleName() {
+    return roleName;
+  }
+
+  /**
+   * Returns the {@link MetadataObject} instance.
+   *
+   * @return The {@link MetadataObject} instance related to the role.
+   */
+  public MetadataObject object() {
+    return object;
+  }
+
+  /**
+   * Returns the set of privileges to grant.
+   *
+   * @return The set of privileges to grant to the role.
+   */
+  public Set<Privilege> privileges() {
+    return privileges;
+  }
+
+  /**
+   * Returns the operation type of this event.
+   *
+   * @return the operation type.
+   */
+  @Override
+  public OperationType operationType() {
+    return OperationType.GRANT_PRIVILEGES;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/ListRoleNamesPreEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/ListRoleNamesPreEvent.java
new file mode 100644
index 0000000000..34642a74ae
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/ListRoleNamesPreEvent.java
@@ -0,0 +1,80 @@
+/*
+ * 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 java.util.Optional;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.utils.NameIdentifierUtil;
+
+/**
+ * Represents an event generated before listing the role names of a metalake. 
This class
+ * encapsulates the details of the role name listing event prior to its 
execution.
+ */
+@DeveloperApi
+public class ListRoleNamesPreEvent extends RolePreEvent {
+  private final Optional<MetadataObject> object;
+
+  /**
+   * Constructs a new {@link ListRoleNamesPreEvent} instance with the 
specified initiator and
+   * identifier.
+   *
+   * @param initiator The user who initiated the event.
+   * @param metalake The name of the metalake for which role names are being 
listed.
+   */
+  public ListRoleNamesPreEvent(String initiator, String metalake) {
+    super(initiator, NameIdentifierUtil.ofMetalake(metalake));
+
+    this.object = Optional.empty();
+  }
+
+  /**
+   * Constructs a new {@link ListRoleNamesPreEvent} instance with the 
specified initiator,
+   * identifier, and {@link MetadataObject} instance.
+   *
+   * @param initiator The user who initiated the event.
+   * @param metalake The name of the metalake for which role names are being 
listed.
+   * @param object The {@link MetadataObject} instance related to the role 
names.
+   */
+  public ListRoleNamesPreEvent(String initiator, String metalake, 
MetadataObject object) {
+    super(initiator, NameIdentifierUtil.ofMetalake(metalake));
+
+    this.object = Optional.ofNullable(object);
+  }
+
+  /**
+   * Returns the {@link MetadataObject} instance related to the role names.
+   *
+   * @return The {@link MetadataObject} instance, if present.
+   */
+  public Optional<MetadataObject> object() {
+    return object;
+  }
+
+  /**
+   * Returns the operation type of this event.
+   *
+   * @return the operation type.
+   */
+  @Override
+  public OperationType operationType() {
+    return OperationType.LIST_ROLE_NAMES;
+  }
+}
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 7905109421..aebf43c3ba 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
@@ -131,7 +131,12 @@ public enum OperationType {
   GRANT_GROUP_ROLES,
   REVOKE_GROUP_ROLES,
 
-  // TODO ROLE
+  CREATE_ROLE,
+  DELETE_ROLE,
+  GET_ROLE,
+  LIST_ROLE_NAMES,
+  GRANT_PRIVILEGES,
+  REVOKE_PRIVILEGES,
 
   UNKNOWN,
 }
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/RevokePrivilegesPreEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/RevokePrivilegesPreEvent.java
new file mode 100644
index 0000000000..260eb374f0
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/RevokePrivilegesPreEvent.java
@@ -0,0 +1,96 @@
+/*
+ * 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 java.util.Set;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.authorization.Privilege;
+import org.apache.gravitino.utils.NameIdentifierUtil;
+
+/**
+ * Represents an event generated before revoking a set of privileges from a 
role. This class
+ * encapsulates the details of the privilege revocation event prior to its 
execution.
+ */
+@DeveloperApi
+public class RevokePrivilegesPreEvent extends RolePreEvent {
+  private final String roleName;
+  private final MetadataObject object;
+  private final Set<Privilege> privileges;
+
+  /**
+   * Constructs a new {@link RevokePrivilegesPreEvent} instance with the 
specified initiator,
+   * identifier, role name, object, and privileges.
+   *
+   * @param initiator The user who initiated the event.
+   * @param metalake The name of the metalake.
+   * @param roleName The name of the role from which privileges will be 
revoked.
+   * @param object The {@link MetadataObject} instance related to the role.
+   * @param privileges The set of privileges to revoke from the role.
+   */
+  public RevokePrivilegesPreEvent(
+      String initiator,
+      String metalake,
+      String roleName,
+      MetadataObject object,
+      Set<Privilege> privileges) {
+    super(initiator, NameIdentifierUtil.ofRole(metalake, roleName));
+    this.roleName = roleName;
+    this.object = object;
+    this.privileges = privileges;
+  }
+
+  /**
+   * Returns the name of the role.
+   *
+   * @return The name of the role from which privileges will be revoked.
+   */
+  public String roleName() {
+    return roleName;
+  }
+
+  /**
+   * Returns the {@link MetadataObject} instance related to the role.
+   *
+   * @return The {@link MetadataObject} instance associated with the role.
+   */
+  public MetadataObject object() {
+    return object;
+  }
+
+  /**
+   * Returns the set of privileges to revoke.
+   *
+   * @return The set of privileges to revoke from the role.
+   */
+  public Set<Privilege> privileges() {
+    return privileges;
+  }
+
+  /**
+   * Returns the operation type of this event.
+   *
+   * @return the operation type.
+   */
+  @Override
+  public OperationType operationType() {
+    return OperationType.REVOKE_PRIVILEGES;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/RolePreEvent.java 
b/core/src/main/java/org/apache/gravitino/listener/api/event/RolePreEvent.java
new file mode 100644
index 0000000000..0a22ea377f
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/RolePreEvent.java
@@ -0,0 +1,42 @@
+/*
+ * 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.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+
+/**
+ * Represents a pre-event for a role operation request. This class serves as a 
base class for events
+ * triggered before a role operation takes place.
+ */
+@DeveloperApi
+public abstract class RolePreEvent extends PreEvent {
+
+  /**
+   * Constructs a new {@link RolePreEvent} instance with the specified 
initiator and identifier for
+   * a role operation.
+   *
+   * @param initiator The user who triggered the event.
+   * @param identifier The identifier of the metalake being operated on.
+   */
+  protected RolePreEvent(String initiator, NameIdentifier identifier) {
+    super(initiator, identifier);
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/utils/NameIdentifierUtil.java 
b/core/src/main/java/org/apache/gravitino/utils/NameIdentifierUtil.java
index b2b3d83bed..e092b85ca8 100644
--- a/core/src/main/java/org/apache/gravitino/utils/NameIdentifierUtil.java
+++ b/core/src/main/java/org/apache/gravitino/utils/NameIdentifierUtil.java
@@ -115,6 +115,17 @@ public class NameIdentifierUtil {
     return NameIdentifier.of(NamespaceUtil.ofUser(metalake), userName);
   }
 
+  /**
+   * Create the role {@link NameIdentifier} with the given metalake and role 
name.
+   *
+   * @param metalake The metalake name
+   * @param roleName The role name
+   * @return the created role {@link NameIdentifier}
+   */
+  public static NameIdentifier ofRole(String metalake, String roleName) {
+    return NameIdentifier.of(NamespaceUtil.ofRole(metalake), roleName);
+  }
+
   /**
    * Create the column {@link NameIdentifier} with the given metalake, 
catalog, schema, table and
    * column name.
diff --git a/core/src/main/java/org/apache/gravitino/utils/NamespaceUtil.java 
b/core/src/main/java/org/apache/gravitino/utils/NamespaceUtil.java
index 76670b0066..f904592789 100644
--- a/core/src/main/java/org/apache/gravitino/utils/NamespaceUtil.java
+++ b/core/src/main/java/org/apache/gravitino/utils/NamespaceUtil.java
@@ -92,6 +92,16 @@ public class NamespaceUtil {
     return Namespace.of(metalake, Entity.SYSTEM_CATALOG_RESERVED_NAME, 
Entity.USER_SCHEMA_NAME);
   }
 
+  /**
+   * Create a namespace for role.
+   *
+   * @param metalake The metalake name
+   * @return A namespace for role
+   */
+  public static Namespace ofRole(String metalake) {
+    return Namespace.of(metalake, Entity.SYSTEM_CATALOG_RESERVED_NAME, 
Entity.ROLE_SCHEMA_NAME);
+  }
+
   /**
    * Create a namespace for group.
    *
diff --git 
a/core/src/test/java/org/apache/gravitino/listener/api/event/TestRoleEvent.java 
b/core/src/test/java/org/apache/gravitino/listener/api/event/TestRoleEvent.java
new file mode 100644
index 0000000000..e4220e037a
--- /dev/null
+++ 
b/core/src/test/java/org/apache/gravitino/listener/api/event/TestRoleEvent.java
@@ -0,0 +1,291 @@
+/*
+ * 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 static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.authorization.Group;
+import org.apache.gravitino.authorization.Privilege;
+import org.apache.gravitino.authorization.Privileges;
+import org.apache.gravitino.authorization.Role;
+import org.apache.gravitino.authorization.SecurableObject;
+import org.apache.gravitino.authorization.User;
+import org.apache.gravitino.exceptions.GravitinoRuntimeException;
+import org.apache.gravitino.listener.DummyEventListener;
+import org.apache.gravitino.listener.EventBus;
+import org.apache.gravitino.utils.NameIdentifierUtil;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+public class TestRoleEvent {
+  private static final String METALAKE = "demo_metalake";
+  private String groupName;
+  private String userName;
+  private AccessControlEventDispatcher dispatcher;
+  private AccessControlEventDispatcher failureDispatcher;
+  private DummyEventListener dummyEventListener;
+  private String roleName;
+  private String otherRoleName;
+  private NameIdentifier identifier;
+  private Map<String, String> properties;
+  private List<SecurableObject> securableObjects;
+  private Set<Privilege> privileges;
+  private MetadataObject metadataObject;
+  private Role role;
+
+  @BeforeAll
+  void init() {
+    this.groupName = "demo_group";
+    this.userName = "demo_user";
+    this.roleName = "admin";
+    this.otherRoleName = "user";
+    this.identifier = NameIdentifier.of(METALAKE);
+    this.properties = ImmutableMap.of("comment", "test comment");
+    this.securableObjects =
+        ImmutableList.of(
+            getMockSecurableObjects(Privilege.Name.CREATE_TABLE, 
Privilege.Name.MODIFY_TABLE));
+    this.role = getMockRole(roleName, properties, securableObjects);
+    this.privileges =
+        Sets.newHashSet(
+            Privileges.allow(Privilege.Name.CREATE_TABLE),
+            Privileges.allow(Privilege.Name.MODIFY_TABLE));
+    this.metadataObject = getMockMetadataObject("test_metalake", 
MetadataObject.Type.METALAKE);
+
+    this.dummyEventListener = new DummyEventListener();
+    EventBus eventBus = new 
EventBus(Collections.singletonList(dummyEventListener));
+    this.dispatcher = new AccessControlEventDispatcher(eventBus, 
mockRoleDispatcher());
+    this.failureDispatcher =
+        new AccessControlEventDispatcher(eventBus, 
mockExceptionRoleDispatcher());
+
+    System.out.println(failureDispatcher);
+  }
+
+  @Test
+  void testCreateRole() {
+    dispatcher.createRole(
+        METALAKE, roleName, ImmutableMap.of("comment", "test comment"), 
securableObjects);
+
+    // validate pre-event
+    PreEvent preEvent = dummyEventListener.popPreEvent();
+    Assertions.assertEquals(CreateRolePreEvent.class, preEvent.getClass());
+    Assertions.assertEquals(OperationStatus.UNPROCESSED, 
preEvent.operationStatus());
+    Assertions.assertEquals(OperationType.CREATE_ROLE, 
preEvent.operationType());
+
+    CreateRolePreEvent createRolePreEvent = (CreateRolePreEvent) preEvent;
+    Assertions.assertEquals(
+        NameIdentifierUtil.ofRole(METALAKE, roleName), 
createRolePreEvent.identifier());
+    Assertions.assertEquals(roleName, createRolePreEvent.roleName());
+    Assertions.assertEquals(properties, createRolePreEvent.properties());
+    Assertions.assertEquals(securableObjects, 
createRolePreEvent.securableObjects());
+  }
+
+  @Test
+  void testDeleteRolePreEvent() {
+    dispatcher.deleteRole(METALAKE, roleName);
+
+    // validate pre-event
+    PreEvent preEvent = dummyEventListener.popPreEvent();
+    Assertions.assertEquals(DeleteRolePreEvent.class, preEvent.getClass());
+    Assertions.assertEquals(OperationStatus.UNPROCESSED, 
preEvent.operationStatus());
+    Assertions.assertEquals(OperationType.DELETE_ROLE, 
preEvent.operationType());
+
+    DeleteRolePreEvent deleteRolePreEvent = (DeleteRolePreEvent) preEvent;
+    Assertions.assertEquals(
+        NameIdentifierUtil.ofRole(METALAKE, roleName), 
deleteRolePreEvent.identifier());
+    Assertions.assertEquals(roleName, deleteRolePreEvent.roleName());
+  }
+
+  @Test
+  void testGetRolePreEvent() {
+    dispatcher.getRole(METALAKE, roleName);
+
+    // validate pre-event
+    PreEvent preEvent = dummyEventListener.popPreEvent();
+    Assertions.assertEquals(GetRolePreEvent.class, preEvent.getClass());
+    Assertions.assertEquals(OperationStatus.UNPROCESSED, 
preEvent.operationStatus());
+    Assertions.assertEquals(OperationType.GET_ROLE, preEvent.operationType());
+
+    GetRolePreEvent getRolePreEvent = (GetRolePreEvent) preEvent;
+    Assertions.assertEquals(
+        NameIdentifierUtil.ofRole(METALAKE, roleName), 
getRolePreEvent.identifier());
+    Assertions.assertEquals(roleName, getRolePreEvent.roleName());
+  }
+
+  @Test
+  void testListRolesEvent() {
+    dispatcher.listRoleNames(METALAKE);
+
+    // validate pre-event
+    PreEvent preEvent = dummyEventListener.popPreEvent();
+    Assertions.assertEquals(ListRoleNamesPreEvent.class, preEvent.getClass());
+    Assertions.assertEquals(OperationStatus.UNPROCESSED, 
preEvent.operationStatus());
+    Assertions.assertEquals(OperationType.LIST_ROLE_NAMES, 
preEvent.operationType());
+
+    ListRoleNamesPreEvent listRoleNamesPreEvent = (ListRoleNamesPreEvent) 
preEvent;
+    Assertions.assertEquals(identifier, listRoleNamesPreEvent.identifier());
+    Assertions.assertFalse(listRoleNamesPreEvent.object().isPresent());
+  }
+
+  @Test
+  void testListRolesFromObject() {
+    dispatcher.listRoleNamesByObject(METALAKE, securableObjects.get(0));
+
+    // validate pre-event
+    PreEvent preEvent = dummyEventListener.popPreEvent();
+    Assertions.assertEquals(ListRoleNamesPreEvent.class, preEvent.getClass());
+    Assertions.assertEquals(OperationStatus.UNPROCESSED, 
preEvent.operationStatus());
+    Assertions.assertEquals(OperationType.LIST_ROLE_NAMES, 
preEvent.operationType());
+
+    ListRoleNamesPreEvent listRoleNamesPreEvent = (ListRoleNamesPreEvent) 
preEvent;
+    Assertions.assertEquals(identifier, listRoleNamesPreEvent.identifier());
+    Assertions.assertTrue(listRoleNamesPreEvent.object().isPresent());
+    Assertions.assertEquals(securableObjects.get(0), 
listRoleNamesPreEvent.object().get());
+  }
+
+  @Test
+  void testGrantPrivilegesToRole() {
+    dispatcher.grantPrivilegeToRole(METALAKE, roleName, metadataObject, 
privileges);
+
+    // validate pre-event
+    PreEvent preEvent = dummyEventListener.popPreEvent();
+    Assertions.assertEquals(GrantPrivilegesPreEvent.class, 
preEvent.getClass());
+    Assertions.assertEquals(OperationStatus.UNPROCESSED, 
preEvent.operationStatus());
+    Assertions.assertEquals(OperationType.GRANT_PRIVILEGES, 
preEvent.operationType());
+
+    GrantPrivilegesPreEvent grantPrivilegesPreEvent = 
(GrantPrivilegesPreEvent) preEvent;
+    Assertions.assertEquals(
+        NameIdentifierUtil.ofRole(METALAKE, roleName), 
grantPrivilegesPreEvent.identifier());
+    Assertions.assertEquals(roleName, grantPrivilegesPreEvent.roleName());
+    Assertions.assertEquals(metadataObject, grantPrivilegesPreEvent.object());
+    Assertions.assertEquals(privileges, grantPrivilegesPreEvent.privileges());
+  }
+
+  @Test
+  void testRevokePrivilegesFromRole() {
+    dispatcher.revokePrivilegesFromRole(METALAKE, roleName, metadataObject, 
privileges);
+
+    // validate pre-event
+    PreEvent preEvent = dummyEventListener.popPreEvent();
+    Assertions.assertEquals(RevokePrivilegesPreEvent.class, 
preEvent.getClass());
+    Assertions.assertEquals(OperationStatus.UNPROCESSED, 
preEvent.operationStatus());
+    Assertions.assertEquals(OperationType.REVOKE_PRIVILEGES, 
preEvent.operationType());
+
+    RevokePrivilegesPreEvent revokePrivilegesPreEvent = 
(RevokePrivilegesPreEvent) preEvent;
+    Assertions.assertEquals(
+        NameIdentifierUtil.ofRole(METALAKE, roleName), 
revokePrivilegesPreEvent.identifier());
+    Assertions.assertEquals(roleName, revokePrivilegesPreEvent.roleName());
+    Assertions.assertEquals(metadataObject, revokePrivilegesPreEvent.object());
+    Assertions.assertEquals(privileges, revokePrivilegesPreEvent.privileges());
+  }
+
+  private AccessControlEventDispatcher mockRoleDispatcher() {
+    AccessControlEventDispatcher dispatcher = 
mock(AccessControlEventDispatcher.class);
+    Group mockGroup = getMockGroup(groupName, ImmutableList.of(roleName, 
otherRoleName));
+    User mockUser = getMockUser(userName, ImmutableList.of(roleName, 
otherRoleName));
+
+    when(dispatcher.createRole(METALAKE, roleName, properties, 
securableObjects)).thenReturn(role);
+    when(dispatcher.deleteRole(METALAKE, roleName)).thenReturn(true);
+    when(dispatcher.deleteRole(METALAKE, otherRoleName)).thenReturn(false);
+    when(dispatcher.getRole(METALAKE, roleName)).thenReturn(role);
+    when(dispatcher.grantRolesToGroup(
+            METALAKE, ImmutableList.of(roleName, otherRoleName), groupName))
+        .thenReturn(mockGroup);
+    when(dispatcher.grantRolesToUser(METALAKE, ImmutableList.of(roleName, 
otherRoleName), userName))
+        .thenReturn(mockUser);
+    when(dispatcher.revokeRolesFromGroup(
+            METALAKE, ImmutableList.of(roleName, otherRoleName), groupName))
+        .thenReturn(mockGroup);
+    when(dispatcher.listRoleNames(METALAKE)).thenReturn(new String[] 
{roleName, otherRoleName});
+    when(dispatcher.listRoleNamesByObject(METALAKE, securableObjects.get(0)))
+        .thenReturn(new String[] {roleName, otherRoleName});
+    when(dispatcher.grantPrivilegeToRole(METALAKE, roleName, metadataObject, 
privileges))
+        .thenReturn(role);
+    when(dispatcher.revokePrivilegesFromRole(METALAKE, roleName, 
metadataObject, privileges))
+        .thenReturn(role);
+
+    return dispatcher;
+  }
+
+  private AccessControlEventDispatcher mockExceptionRoleDispatcher() {
+    return mock(
+        AccessControlEventDispatcher.class,
+        invocation -> {
+          throw new GravitinoRuntimeException("Exception for all methods");
+        });
+  }
+
+  public SecurableObject getMockSecurableObjects(Privilege.Name... names) {
+    SecurableObject mockSecurableObject = mock(SecurableObject.class);
+    List<Privilege> privileges =
+        
Arrays.stream(names).map(Privileges::allow).collect(Collectors.toList());
+    when(mockSecurableObject.privileges()).thenReturn(privileges);
+
+    return mockSecurableObject;
+  }
+
+  public Role getMockRole(
+      String roleName, Map<String, String> properties, List<SecurableObject> 
securableObjects) {
+    Role mockRole = mock(Role.class);
+    when(mockRole.name()).thenReturn(roleName);
+    when(mockRole.properties()).thenReturn(properties);
+    when(mockRole.securableObjects()).thenReturn(securableObjects);
+
+    return mockRole;
+  }
+
+  private Group getMockGroup(String name, List<String> roles) {
+    Group mockGroup = mock(Group.class);
+    when(mockGroup.name()).thenReturn(name);
+    when(mockGroup.roles()).thenReturn(roles);
+
+    return mockGroup;
+  }
+
+  private User getMockUser(String name, List<String> roles) {
+    User user = mock(User.class);
+    when(user.name()).thenReturn(name);
+    when(user.roles()).thenReturn(roles);
+
+    return user;
+  }
+
+  private MetadataObject getMockMetadataObject(String name, 
MetadataObject.Type type) {
+    MetadataObject mockMetadataObject = mock(MetadataObject.class);
+    when(mockMetadataObject.name()).thenReturn(name);
+    when(mockMetadataObject.type()).thenReturn(type);
+
+    return mockMetadataObject;
+  }
+}
diff --git 
a/core/src/test/java/org/apache/gravitino/utils/TestNameIdentifierUtil.java 
b/core/src/test/java/org/apache/gravitino/utils/TestNameIdentifierUtil.java
index fcd7455f7c..e13ec72211 100644
--- a/core/src/test/java/org/apache/gravitino/utils/TestNameIdentifierUtil.java
+++ b/core/src/test/java/org/apache/gravitino/utils/TestNameIdentifierUtil.java
@@ -183,4 +183,18 @@ public class TestNameIdentifierUtil {
     Assertions.assertThrows(
         IllegalArgumentException.class, () -> NameIdentifierUtil.ofGroup(null, 
groupName));
   }
+
+  @Test
+  void testOfRole() {
+    String roleName = "roleA";
+    String metalake = "demo_metalake";
+
+    NameIdentifier nameIdentifier = NameIdentifierUtil.ofRole(metalake, 
roleName);
+    Assertions.assertEquals(
+        Joiner.on(".")
+            .join(metalake, Entity.SYSTEM_CATALOG_RESERVED_NAME, 
Entity.ROLE_SCHEMA_NAME, roleName),
+        nameIdentifier.toString());
+    Assertions.assertThrows(
+        IllegalArgumentException.class, () -> NameIdentifierUtil.ofRole(null, 
roleName));
+  }
 }
diff --git 
a/core/src/test/java/org/apache/gravitino/utils/TestNamespaceUtil.java 
b/core/src/test/java/org/apache/gravitino/utils/TestNamespaceUtil.java
index 9c26043a12..58703b23b9 100644
--- a/core/src/test/java/org/apache/gravitino/utils/TestNamespaceUtil.java
+++ b/core/src/test/java/org/apache/gravitino/utils/TestNamespaceUtil.java
@@ -106,4 +106,15 @@ public class TestNamespaceUtil {
         namespace.toString());
     Assertions.assertThrows(IllegalArgumentException.class, () -> 
NamespaceUtil.ofGroup(null));
   }
+
+  @Test
+  void testOfRole() {
+    String metalake = "metalake";
+    Namespace namespace = NamespaceUtil.ofRole(metalake);
+
+    Assertions.assertEquals(
+        Joiner.on(".").join(metalake, Entity.SYSTEM_CATALOG_RESERVED_NAME, 
Entity.ROLE_SCHEMA_NAME),
+        namespace.toString());
+    Assertions.assertThrows(IllegalArgumentException.class, () -> 
NamespaceUtil.ofRole(null));
+  }
 }
diff --git a/docs/gravitino-server-config.md b/docs/gravitino-server-config.md
index 95d50c117e..db0d5f3132 100644
--- a/docs/gravitino-server-config.md
+++ b/docs/gravitino-server-config.md
@@ -149,6 +149,7 @@ Gravitino triggers a pre-event before the operation, a 
post-event after the comp
 | Gravitino server tag operation       | `ListTagsPreEvent`, 
`ListTagsInfoPreEvent`, `CreateTagPreEvent`, `GetTagPreEvent`, 
`AlterTagPreEvent`, `DeleteTagPreEvent`, `ListMetadataObjectsForTagPreEvent`, 
`ListTagsForMetadataObjectPreEvent`, `ListTagsInfoForMetadataObjectPreEvent`, 
`AssociateTagsForMetadataObjectPreEvent`, `GetTagForMetadataObjectPreEvent` | 
0.9.0-incubating |
 | Gravitino server user operation      | `AddUserPreEvent`, `GetUserPreEvent`, 
`ListUserNamesPreEvent`, `ListUsersPreEvent`, `RemoveUserPreEvent`, 
`GrantUserRolesPreEvent`, `RevokeUserRolesPreEvent`                             
                                                                                
                                                 | 0.9.0-incubating |
 | Gravitino server group operation     | `AddGroupPreEvent`, 
`GetGroupPreEvent`, `ListGroupNamesPreEvent`, `ListGroupsPreEvent`, 
`RemoveGroupPreEvent`, `GrantGroupRolesPreEvent`, `RevokeGroupRolesPreEvent`    
                                                                                
                                                                   | 
0.9.0-incubating |
+| Gravitino server role operation      | `CreateRolePreEvent`, 
`DeleteRolePreEvent`, `GetRolePreEvent`, `GrantPrivilegesPreEvent`, 
`ListRoleNamesPreEvent`, `RevokePrivilegesPreEvent`                             
                                                                                
                                                                 | 
0.9.0-incubating |
 
 #### Event listener plugin
 


Reply via email to