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

mchades 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 fb6f8a2fda [#11002] improvement(view): Add view CRUD event 
dispatching, hooks, audit mapping, and tests (#11028)
fb6f8a2fda is described below

commit fb6f8a2fdace3eb07f39c8ef8c3f38fd48e6fde0
Author: Raushan Prabhakar <[email protected]>
AuthorDate: Tue May 26 07:26:37 2026 +0530

    [#11002] improvement(view): Add view CRUD event dispatching, hooks, audit 
mapping, and tests (#11028)
    
    ### What changes were proposed in this pull request?
    
    - Add `ViewInfo` and view listener event types under
    `org.apache.gravitino.listener.api.event.view` (pre / success / failure)
    for **list**, **load**, **create**, **alter**, and **drop** views,
    consistent with existing table-style events.
    - Add `ViewEventDispatcher` to publish those events on the `EventBus`
    around view operations.
    - Wire `GravitinoEnv` so the view stack is **`ViewEventDispatcher` →
    `ViewNormalizeDispatcher` → `ViewOperationDispatcher`**.
    `ViewHookDispatcher` is **not** included; ownership / privilege hooks
    for views are deferred (see TODO referencing #11007).
    - Extend `AuditLog.Operation.fromEvent` so view events map to the
    existing view audit operations (`CREATE_VIEW`, `ALTER_VIEW`, etc.)
    instead of `UNKNOWN_OPERATION`.
    - Add `TestViewEvent` and extend `TestOperation` for event emission and
    audit mapping.
    
    **Out of scope (removed or never merged here, per review):**
    `AuthorizationUtils.getMetadataObjectLocation` handling for `VIEW`,
    Ranger fileset IT changes, and view hook dispatching—those can land in
    separate PRs if still desired.
    
    ### Why are the changes needed?
    
    Relational view CRUD did not emit the same listener events as tables, so
    `EventListenerPlugin` implementations could not observe view operations
    and audit could not classify them. This closes the listener / audit gap
    for the view operations listed above (aligned with #11002).
    
    Fix: #11002
    
    ### Does this PR introduce _any_ user-facing change?
    
    **Yes, for operators and integrations:** event listeners and audit
    pipelines now see view list/load/create/alter/drop (and failures) with
    concrete audit operation types. **No** REST or Java client API changes.
    **`viewExists`** still has no dedicated events (same idea as
    `tableExists` for tables).
    
    ### How was this patch tested?
    
    - `./gradlew :core:spotlessApply :core:test --tests
    org.apache.gravitino.listener.api.event.TestViewEvent --tests
    org.apache.gravitino.audit.TestOperation -PskipITs`
    
    ---------
    
    Co-authored-by: Raushan Prabhakar <[email protected]>
    Co-authored-by: Copilot Autofix powered by AI 
<[email protected]>
---
 .../java/org/apache/gravitino/GravitinoEnv.java    |  11 +-
 .../java/org/apache/gravitino/audit/AuditLog.java  |  20 ++
 .../gravitino/listener/ViewEventDispatcher.java    | 174 +++++++++++
 .../listener/api/event/view/AlterViewEvent.java    |  53 ++++
 .../api/event/view/AlterViewFailureEvent.java      |  46 +++
 .../listener/api/event/view/AlterViewPreEvent.java |  45 +++
 .../listener/api/event/view/CreateViewEvent.java   |  45 +++
 .../api/event/view/CreateViewFailureEvent.java     |  46 +++
 .../api/event/view/CreateViewPreEvent.java         |  45 +++
 .../listener/api/event/view/DropViewEvent.java     |  44 +++
 .../api/event/view/DropViewFailureEvent.java       |  37 +++
 .../listener/api/event/view/DropViewPreEvent.java  |  37 +++
 .../listener/api/event/view/ListViewEvent.java     |  48 +++
 .../api/event/view/ListViewFailureEvent.java       |  45 +++
 .../listener/api/event/view/ListViewPreEvent.java  |  45 +++
 .../listener/api/event/view/LoadViewEvent.java     |  50 ++++
 .../api/event/view/LoadViewFailureEvent.java       |  37 +++
 .../listener/api/event/view/LoadViewPreEvent.java  |  37 +++
 .../listener/api/event/view/ViewEvent.java         |  39 +++
 .../listener/api/event/view/ViewFailureEvent.java  |  33 +++
 .../listener/api/event/view/ViewPreEvent.java      |  32 ++
 .../gravitino/listener/api/info/ViewInfo.java      | 134 +++++++++
 .../org/apache/gravitino/audit/TestOperation.java  |  77 +++++
 .../listener/api/event/TestViewEvent.java          | 326 +++++++++++++++++++++
 24 files changed, 1503 insertions(+), 3 deletions(-)

diff --git a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java 
b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
index fd00c2a0e0..7514d2b930 100644
--- a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
+++ b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
@@ -88,6 +88,7 @@ import org.apache.gravitino.listener.StatisticEventDispatcher;
 import org.apache.gravitino.listener.TableEventDispatcher;
 import org.apache.gravitino.listener.TagEventDispatcher;
 import org.apache.gravitino.listener.TopicEventDispatcher;
+import org.apache.gravitino.listener.ViewEventDispatcher;
 import org.apache.gravitino.lock.LockManager;
 import org.apache.gravitino.metalake.MetalakeDispatcher;
 import org.apache.gravitino.metalake.MetalakeManager;
@@ -633,13 +634,17 @@ public class GravitinoEnv {
         new FunctionEventDispatcher(eventBus, functionNormalizeDispatcher);
     this.functionDispatcher = new 
FunctionHookDispatcher(functionEventDispatcher);
 
-    // TODO: Add ViewHookDispatcher and ViewEventDispatcher when needed for 
view-specific hooks
-    //  and event handling.
+    // View operation chain: ViewEventDispatcher -> ViewNormalizeDispatcher ->
+    // ViewOperationDispatcher.
+    // TODO(#11007): Add ViewHookDispatcher for view ownership and privilege 
hooks when view
+    // privilege support is finalized.
     ViewOperationDispatcher viewOperationDispatcher =
         new ViewOperationDispatcher(catalogManager, entityStore, idGenerator);
     ViewNormalizeDispatcher viewNormalizeDispatcher =
         new ViewNormalizeDispatcher(viewOperationDispatcher, catalogManager);
-    this.viewDispatcher = viewNormalizeDispatcher;
+    ViewEventDispatcher viewEventDispatcher =
+        new ViewEventDispatcher(eventBus, viewNormalizeDispatcher);
+    this.viewDispatcher = viewEventDispatcher;
 
     this.statisticDispatcher =
         new StatisticEventDispatcher(
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 1d03be7d3e..bfd76e84cc 100644
--- a/core/src/main/java/org/apache/gravitino/audit/AuditLog.java
+++ b/core/src/main/java/org/apache/gravitino/audit/AuditLog.java
@@ -105,6 +105,16 @@ import 
org.apache.gravitino.listener.api.event.PurgePartitionEvent;
 import org.apache.gravitino.listener.api.event.PurgePartitionFailureEvent;
 import org.apache.gravitino.listener.api.event.PurgeTableEvent;
 import org.apache.gravitino.listener.api.event.PurgeTableFailureEvent;
+import org.apache.gravitino.listener.api.event.view.AlterViewEvent;
+import org.apache.gravitino.listener.api.event.view.AlterViewFailureEvent;
+import org.apache.gravitino.listener.api.event.view.CreateViewEvent;
+import org.apache.gravitino.listener.api.event.view.CreateViewFailureEvent;
+import org.apache.gravitino.listener.api.event.view.DropViewEvent;
+import org.apache.gravitino.listener.api.event.view.DropViewFailureEvent;
+import org.apache.gravitino.listener.api.event.view.ListViewEvent;
+import org.apache.gravitino.listener.api.event.view.ListViewFailureEvent;
+import org.apache.gravitino.listener.api.event.view.LoadViewEvent;
+import org.apache.gravitino.listener.api.event.view.LoadViewFailureEvent;
 
 /** The interface define unified audit log schema. */
 public interface AuditLog {
@@ -551,6 +561,16 @@ public interface AuditLog {
         return LOAD_TOPIC;
       } else if (event instanceof ListTopicEvent || event instanceof 
ListTopicFailureEvent) {
         return LIST_TOPIC;
+      } else if (event instanceof CreateViewEvent || event instanceof 
CreateViewFailureEvent) {
+        return CREATE_VIEW;
+      } else if (event instanceof AlterViewEvent || event instanceof 
AlterViewFailureEvent) {
+        return ALTER_VIEW;
+      } else if (event instanceof DropViewEvent || event instanceof 
DropViewFailureEvent) {
+        return DROP_VIEW;
+      } else if (event instanceof LoadViewEvent || event instanceof 
LoadViewFailureEvent) {
+        return LOAD_VIEW;
+      } else if (event instanceof ListViewEvent || event instanceof 
ListViewFailureEvent) {
+        return LIST_VIEW;
       } else if (event instanceof CreateFilesetEvent
           || event instanceof CreateFilesetFailureEvent) {
         return CREATE_FILESET;
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/ViewEventDispatcher.java 
b/core/src/main/java/org/apache/gravitino/listener/ViewEventDispatcher.java
new file mode 100644
index 0000000000..764e989c63
--- /dev/null
+++ b/core/src/main/java/org/apache/gravitino/listener/ViewEventDispatcher.java
@@ -0,0 +1,174 @@
+/*
+ * 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;
+
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.catalog.ViewDispatcher;
+import org.apache.gravitino.catalog.ViewOperationDispatcher;
+import org.apache.gravitino.exceptions.NoSuchSchemaException;
+import org.apache.gravitino.exceptions.NoSuchViewException;
+import org.apache.gravitino.exceptions.ViewAlreadyExistsException;
+import org.apache.gravitino.listener.api.event.view.AlterViewEvent;
+import org.apache.gravitino.listener.api.event.view.AlterViewFailureEvent;
+import org.apache.gravitino.listener.api.event.view.AlterViewPreEvent;
+import org.apache.gravitino.listener.api.event.view.CreateViewEvent;
+import org.apache.gravitino.listener.api.event.view.CreateViewFailureEvent;
+import org.apache.gravitino.listener.api.event.view.CreateViewPreEvent;
+import org.apache.gravitino.listener.api.event.view.DropViewEvent;
+import org.apache.gravitino.listener.api.event.view.DropViewFailureEvent;
+import org.apache.gravitino.listener.api.event.view.DropViewPreEvent;
+import org.apache.gravitino.listener.api.event.view.ListViewEvent;
+import org.apache.gravitino.listener.api.event.view.ListViewFailureEvent;
+import org.apache.gravitino.listener.api.event.view.ListViewPreEvent;
+import org.apache.gravitino.listener.api.event.view.LoadViewEvent;
+import org.apache.gravitino.listener.api.event.view.LoadViewFailureEvent;
+import org.apache.gravitino.listener.api.event.view.LoadViewPreEvent;
+import org.apache.gravitino.listener.api.info.ViewInfo;
+import org.apache.gravitino.rel.Column;
+import org.apache.gravitino.rel.Representation;
+import org.apache.gravitino.rel.View;
+import org.apache.gravitino.rel.ViewChange;
+import org.apache.gravitino.utils.PrincipalUtils;
+
+/**
+ * Decorates a {@link ViewDispatcher} to dispatch pre/post/failure view events 
to an {@link
+ * EventBus}, mirroring {@link TableEventDispatcher}.
+ */
+public class ViewEventDispatcher implements ViewDispatcher {
+
+  private final EventBus eventBus;
+  private final ViewDispatcher dispatcher;
+
+  /**
+   * @param eventBus event bus for listener plugins
+   * @param dispatcher underlying dispatcher (for example {@link 
ViewOperationDispatcher} behind a
+   *     normalize layer)
+   */
+  public ViewEventDispatcher(EventBus eventBus, ViewDispatcher dispatcher) {
+    this.eventBus = eventBus;
+    this.dispatcher = dispatcher;
+  }
+
+  @Override
+  public NameIdentifier[] listViews(Namespace namespace) throws 
NoSuchSchemaException {
+    eventBus.dispatchEvent(new 
ListViewPreEvent(PrincipalUtils.getCurrentUserName(), namespace));
+    try {
+      NameIdentifier[] identifiers = dispatcher.listViews(namespace);
+      eventBus.dispatchEvent(new 
ListViewEvent(PrincipalUtils.getCurrentUserName(), namespace));
+      return identifiers;
+    } catch (Exception e) {
+      eventBus.dispatchEvent(
+          new ListViewFailureEvent(PrincipalUtils.getCurrentUserName(), 
namespace, e));
+      throw e;
+    }
+  }
+
+  @Override
+  public View loadView(NameIdentifier ident) throws NoSuchViewException {
+    eventBus.dispatchEvent(new 
LoadViewPreEvent(PrincipalUtils.getCurrentUserName(), ident));
+    try {
+      View view = dispatcher.loadView(ident);
+      eventBus.dispatchEvent(
+          new LoadViewEvent(PrincipalUtils.getCurrentUserName(), ident, new 
ViewInfo(view)));
+      return view;
+    } catch (Exception e) {
+      eventBus.dispatchEvent(
+          new LoadViewFailureEvent(PrincipalUtils.getCurrentUserName(), ident, 
e));
+      throw e;
+    }
+  }
+
+  @Override
+  public boolean viewExists(NameIdentifier ident) {
+    return dispatcher.viewExists(ident);
+  }
+
+  @Override
+  public View createView(
+      NameIdentifier ident,
+      @Nullable String comment,
+      Column[] columns,
+      Representation[] representations,
+      @Nullable String defaultCatalog,
+      @Nullable String defaultSchema,
+      Map<String, String> properties)
+      throws NoSuchSchemaException, ViewAlreadyExistsException {
+    ViewInfo createRequest =
+        new ViewInfo(
+            ident.name(),
+            columns,
+            comment,
+            representations,
+            defaultCatalog,
+            defaultSchema,
+            properties,
+            null);
+    eventBus.dispatchEvent(
+        new CreateViewPreEvent(PrincipalUtils.getCurrentUserName(), ident, 
createRequest));
+    try {
+      View view =
+          dispatcher.createView(
+              ident, comment, columns, representations, defaultCatalog, 
defaultSchema, properties);
+      eventBus.dispatchEvent(
+          new CreateViewEvent(PrincipalUtils.getCurrentUserName(), ident, new 
ViewInfo(view)));
+      return view;
+    } catch (Exception e) {
+      eventBus.dispatchEvent(
+          new CreateViewFailureEvent(PrincipalUtils.getCurrentUserName(), 
ident, e, createRequest));
+      throw e;
+    }
+  }
+
+  @Override
+  public View alterView(NameIdentifier ident, ViewChange... changes)
+      throws NoSuchViewException, IllegalArgumentException {
+    eventBus.dispatchEvent(
+        new AlterViewPreEvent(PrincipalUtils.getCurrentUserName(), ident, 
changes));
+    try {
+      View view = dispatcher.alterView(ident, changes);
+      eventBus.dispatchEvent(
+          new AlterViewEvent(
+              PrincipalUtils.getCurrentUserName(), ident, changes, new 
ViewInfo(view)));
+      return view;
+    } catch (Exception e) {
+      eventBus.dispatchEvent(
+          new AlterViewFailureEvent(PrincipalUtils.getCurrentUserName(), 
ident, e, changes));
+      throw e;
+    }
+  }
+
+  @Override
+  public boolean dropView(NameIdentifier ident) {
+    eventBus.dispatchEvent(new 
DropViewPreEvent(PrincipalUtils.getCurrentUserName(), ident));
+    try {
+      boolean existed = dispatcher.dropView(ident);
+      eventBus.dispatchEvent(
+          new DropViewEvent(PrincipalUtils.getCurrentUserName(), ident, 
existed));
+      return existed;
+    } catch (Exception e) {
+      eventBus.dispatchEvent(
+          new DropViewFailureEvent(PrincipalUtils.getCurrentUserName(), ident, 
e));
+      throw e;
+    }
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/view/AlterViewEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/AlterViewEvent.java
new file mode 100644
index 0000000000..d9c73ca331
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/AlterViewEvent.java
@@ -0,0 +1,53 @@
+/*
+ * 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.view;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.OperationType;
+import org.apache.gravitino.listener.api.info.ViewInfo;
+import org.apache.gravitino.rel.ViewChange;
+
+/** Successful alter-view event. */
+@DeveloperApi
+public final class AlterViewEvent extends ViewEvent {
+  private final ViewInfo updatedViewInfo;
+  private final ViewChange[] viewChanges;
+
+  public AlterViewEvent(
+      String user, NameIdentifier identifier, ViewChange[] viewChanges, 
ViewInfo updatedViewInfo) {
+    super(user, identifier);
+    this.viewChanges = viewChanges.clone();
+    this.updatedViewInfo = updatedViewInfo;
+  }
+
+  public ViewInfo updatedViewInfo() {
+    return updatedViewInfo;
+  }
+
+  public ViewChange[] viewChanges() {
+    return viewChanges;
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.ALTER_VIEW;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/view/AlterViewFailureEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/AlterViewFailureEvent.java
new file mode 100644
index 0000000000..0a8a6d9e74
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/AlterViewFailureEvent.java
@@ -0,0 +1,46 @@
+/*
+ * 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.view;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.OperationType;
+import org.apache.gravitino.rel.ViewChange;
+
+/** Failure event for altering a view. */
+@DeveloperApi
+public final class AlterViewFailureEvent extends ViewFailureEvent {
+  private final ViewChange[] viewChanges;
+
+  public AlterViewFailureEvent(
+      String user, NameIdentifier identifier, Exception exception, 
ViewChange[] viewChanges) {
+    super(user, identifier, exception);
+    this.viewChanges = viewChanges.clone();
+  }
+
+  public ViewChange[] viewChanges() {
+    return viewChanges;
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.ALTER_VIEW;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/view/AlterViewPreEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/AlterViewPreEvent.java
new file mode 100644
index 0000000000..022ad33026
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/AlterViewPreEvent.java
@@ -0,0 +1,45 @@
+/*
+ *  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.view;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.OperationType;
+import org.apache.gravitino.rel.ViewChange;
+
+/** Pre-event before altering a view. */
+@DeveloperApi
+public class AlterViewPreEvent extends ViewPreEvent {
+  private final ViewChange[] viewChanges;
+
+  public AlterViewPreEvent(String user, NameIdentifier identifier, 
ViewChange[] viewChanges) {
+    super(user, identifier);
+    this.viewChanges = viewChanges;
+  }
+
+  public ViewChange[] viewChanges() {
+    return viewChanges;
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.ALTER_VIEW;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/view/CreateViewEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/CreateViewEvent.java
new file mode 100644
index 0000000000..df6f490db4
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/CreateViewEvent.java
@@ -0,0 +1,45 @@
+/*
+ * 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.view;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.OperationType;
+import org.apache.gravitino.listener.api.info.ViewInfo;
+
+/** Successful create-view event. */
+@DeveloperApi
+public final class CreateViewEvent extends ViewEvent {
+  private final ViewInfo createdViewInfo;
+
+  public CreateViewEvent(String user, NameIdentifier identifier, ViewInfo 
createdViewInfo) {
+    super(user, identifier);
+    this.createdViewInfo = createdViewInfo;
+  }
+
+  public ViewInfo createdViewInfo() {
+    return createdViewInfo;
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.CREATE_VIEW;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/view/CreateViewFailureEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/CreateViewFailureEvent.java
new file mode 100644
index 0000000000..e40f0fa325
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/CreateViewFailureEvent.java
@@ -0,0 +1,46 @@
+/*
+ * 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.view;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.OperationType;
+import org.apache.gravitino.listener.api.info.ViewInfo;
+
+/** Failure event for creating a view. */
+@DeveloperApi
+public final class CreateViewFailureEvent extends ViewFailureEvent {
+  private final ViewInfo createViewRequest;
+
+  public CreateViewFailureEvent(
+      String user, NameIdentifier identifier, Exception exception, ViewInfo 
createViewRequest) {
+    super(user, identifier, exception);
+    this.createViewRequest = createViewRequest;
+  }
+
+  public ViewInfo createViewRequest() {
+    return createViewRequest;
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.CREATE_VIEW;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/view/CreateViewPreEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/CreateViewPreEvent.java
new file mode 100644
index 0000000000..16599a57a8
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/CreateViewPreEvent.java
@@ -0,0 +1,45 @@
+/*
+ *  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.view;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.OperationType;
+import org.apache.gravitino.listener.api.info.ViewInfo;
+
+/** Pre-event before creating a view. */
+@DeveloperApi
+public class CreateViewPreEvent extends ViewPreEvent {
+  private final ViewInfo createViewRequest;
+
+  public CreateViewPreEvent(String user, NameIdentifier identifier, ViewInfo 
createViewRequest) {
+    super(user, identifier);
+    this.createViewRequest = createViewRequest;
+  }
+
+  public ViewInfo createViewRequest() {
+    return createViewRequest;
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.CREATE_VIEW;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/view/DropViewEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/DropViewEvent.java
new file mode 100644
index 0000000000..0772f4323a
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/DropViewEvent.java
@@ -0,0 +1,44 @@
+/*
+ * 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.view;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.OperationType;
+
+/** Successful drop-view event. */
+@DeveloperApi
+public final class DropViewEvent extends ViewEvent {
+  private final boolean isExists;
+
+  public DropViewEvent(String user, NameIdentifier identifier, boolean 
isExists) {
+    super(user, identifier);
+    this.isExists = isExists;
+  }
+
+  public boolean isExists() {
+    return isExists;
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.DROP_VIEW;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/view/DropViewFailureEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/DropViewFailureEvent.java
new file mode 100644
index 0000000000..07ec06f029
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/DropViewFailureEvent.java
@@ -0,0 +1,37 @@
+/*
+ * 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.view;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.OperationType;
+
+/** Failure event for dropping a view. */
+@DeveloperApi
+public final class DropViewFailureEvent extends ViewFailureEvent {
+  public DropViewFailureEvent(String user, NameIdentifier identifier, 
Exception exception) {
+    super(user, identifier, exception);
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.DROP_VIEW;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/view/DropViewPreEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/DropViewPreEvent.java
new file mode 100644
index 0000000000..3acb2fe433
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/DropViewPreEvent.java
@@ -0,0 +1,37 @@
+/*
+ *  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.view;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.OperationType;
+
+/** Pre-event before dropping a view. */
+@DeveloperApi
+public class DropViewPreEvent extends ViewPreEvent {
+  public DropViewPreEvent(String user, NameIdentifier identifier) {
+    super(user, identifier);
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.DROP_VIEW;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/view/ListViewEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/ListViewEvent.java
new file mode 100644
index 0000000000..a4afbb59eb
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/ListViewEvent.java
@@ -0,0 +1,48 @@
+/*
+ * 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.view;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.OperationType;
+
+/**
+ * Successful list-views event. Like {@link 
org.apache.gravitino.listener.api.event.ListTableEvent},
+ * listed identifiers are not stored on the event.
+ */
+@DeveloperApi
+public final class ListViewEvent extends ViewEvent {
+  private final Namespace namespace;
+
+  public ListViewEvent(String user, Namespace namespace) {
+    super(user, NameIdentifier.of(namespace.levels()));
+    this.namespace = namespace;
+  }
+
+  public Namespace namespace() {
+    return namespace;
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.LIST_VIEW;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/view/ListViewFailureEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/ListViewFailureEvent.java
new file mode 100644
index 0000000000..bf4b482dd6
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/ListViewFailureEvent.java
@@ -0,0 +1,45 @@
+/*
+ * 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.view;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.OperationType;
+
+/** Failure event for listing views. */
+@DeveloperApi
+public final class ListViewFailureEvent extends ViewFailureEvent {
+  private final Namespace namespace;
+
+  public ListViewFailureEvent(String user, Namespace namespace, Exception 
exception) {
+    super(user, NameIdentifier.of(namespace.levels()), exception);
+    this.namespace = namespace;
+  }
+
+  public Namespace namespace() {
+    return namespace;
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.LIST_VIEW;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/view/ListViewPreEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/ListViewPreEvent.java
new file mode 100644
index 0000000000..3bd2acdf91
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/ListViewPreEvent.java
@@ -0,0 +1,45 @@
+/*
+ *  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.view;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.OperationType;
+
+/** Pre-event before listing views in a namespace. */
+@DeveloperApi
+public class ListViewPreEvent extends ViewPreEvent {
+  private final Namespace namespace;
+
+  public ListViewPreEvent(String user, Namespace namespace) {
+    super(user, NameIdentifier.of(namespace.levels()));
+    this.namespace = namespace;
+  }
+
+  public Namespace namespace() {
+    return namespace;
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.LIST_VIEW;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/view/LoadViewEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/LoadViewEvent.java
new file mode 100644
index 0000000000..a8120131c9
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/LoadViewEvent.java
@@ -0,0 +1,50 @@
+/*
+ * 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.view;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.OperationType;
+import org.apache.gravitino.listener.api.info.ViewInfo;
+
+/** Successful load-view event. */
+@DeveloperApi
+public final class LoadViewEvent extends ViewEvent {
+  private final ViewInfo loadedViewInfo;
+
+  public LoadViewEvent(String user, NameIdentifier identifier, ViewInfo 
loadedViewInfo) {
+    super(user, identifier);
+    this.loadedViewInfo = loadedViewInfo;
+  }
+
+  /**
+   * Returns the loaded view information.
+   *
+   * @return the loaded view information
+   */
+  public ViewInfo loadedViewInfo() {
+    return loadedViewInfo;
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.LOAD_VIEW;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/view/LoadViewFailureEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/LoadViewFailureEvent.java
new file mode 100644
index 0000000000..100a978dce
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/LoadViewFailureEvent.java
@@ -0,0 +1,37 @@
+/*
+ * 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.view;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.OperationType;
+
+/** Failure event for loading a view. */
+@DeveloperApi
+public final class LoadViewFailureEvent extends ViewFailureEvent {
+  public LoadViewFailureEvent(String user, NameIdentifier identifier, 
Exception exception) {
+    super(user, identifier, exception);
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.LOAD_VIEW;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/view/LoadViewPreEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/LoadViewPreEvent.java
new file mode 100644
index 0000000000..3f4ca91746
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/LoadViewPreEvent.java
@@ -0,0 +1,37 @@
+/*
+ *  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.view;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.OperationType;
+
+/** Pre-event before loading a view. */
+@DeveloperApi
+public class LoadViewPreEvent extends ViewPreEvent {
+  public LoadViewPreEvent(String user, NameIdentifier identifier) {
+    super(user, identifier);
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.LOAD_VIEW;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/view/ViewEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/ViewEvent.java
new file mode 100644
index 0000000000..8fcdc1e279
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/ViewEvent.java
@@ -0,0 +1,39 @@
+/*
+ * 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.view;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.Event;
+import org.apache.gravitino.listener.api.event.OperationStatus;
+
+/** Base class for successful view operation events. */
+@DeveloperApi
+public abstract class ViewEvent extends Event {
+
+  protected ViewEvent(String user, NameIdentifier identifier) {
+    super(user, identifier);
+  }
+
+  @Override
+  public OperationStatus operationStatus() {
+    return OperationStatus.SUCCESS;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/view/ViewFailureEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/ViewFailureEvent.java
new file mode 100644
index 0000000000..b14ce130e8
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/ViewFailureEvent.java
@@ -0,0 +1,33 @@
+/*
+ * 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.view;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.FailureEvent;
+
+/** Base class for view operation failure events. */
+@DeveloperApi
+public abstract class ViewFailureEvent extends FailureEvent {
+
+  protected ViewFailureEvent(String user, NameIdentifier identifier, Exception 
exception) {
+    super(user, identifier, exception);
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/view/ViewPreEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/ViewPreEvent.java
new file mode 100644
index 0000000000..0d0a64da45
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/view/ViewPreEvent.java
@@ -0,0 +1,32 @@
+/*
+ *  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.view;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.PreEvent;
+
+/** Pre-event base class for view operations. */
+@DeveloperApi
+public abstract class ViewPreEvent extends PreEvent {
+  protected ViewPreEvent(String user, NameIdentifier identifier) {
+    super(user, identifier);
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/info/ViewInfo.java 
b/core/src/main/java/org/apache/gravitino/listener/api/info/ViewInfo.java
new file mode 100644
index 0000000000..e9b31e843f
--- /dev/null
+++ b/core/src/main/java/org/apache/gravitino/listener/api/info/ViewInfo.java
@@ -0,0 +1,134 @@
+/*
+ * 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.info;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.apache.gravitino.Audit;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.rel.Column;
+import org.apache.gravitino.rel.Representation;
+import org.apache.gravitino.rel.View;
+
+/**
+ * ViewInfo exposes view information for event listeners; it is read-only. 
Most fields are shallow
+ * copies internally, not deep copies, for performance.
+ */
+@DeveloperApi
+public final class ViewInfo {
+  private final String name;
+  private final Column[] columns;
+  @Nullable private final String comment;
+  private final Representation[] representations;
+  @Nullable private final String defaultCatalog;
+  @Nullable private final String defaultSchema;
+  private final Map<String, String> properties;
+  @Nullable private final Audit auditInfo;
+
+  /** Constructs a ViewInfo from a {@link View}. */
+  public ViewInfo(View view) {
+    this(
+        view.name(),
+        view.columns(),
+        view.comment(),
+        view.representations(),
+        view.defaultCatalog(),
+        view.defaultSchema(),
+        view.properties(),
+        view.auditInfo());
+  }
+
+  /**
+   * Constructs ViewInfo with the given fields.
+   *
+   * @param name View name
+   * @param columns Output columns
+   * @param comment Optional comment
+   * @param representations View representations (at least one in a valid 
create request)
+   * @param defaultCatalog Optional default catalog for the definition
+   * @param defaultSchema Optional default schema for the definition
+   * @param properties View properties; copied defensively
+   * @param auditInfo Optional audit information
+   */
+  public ViewInfo(
+      String name,
+      Column[] columns,
+      @Nullable String comment,
+      Representation[] representations,
+      @Nullable String defaultCatalog,
+      @Nullable String defaultSchema,
+      Map<String, String> properties,
+      @Nullable Audit auditInfo) {
+    this.name = name;
+    this.columns = columns == null ? new Column[0] : columns.clone();
+    this.comment = comment;
+    this.representations =
+        representations == null ? new Representation[0] : 
representations.clone();
+    this.defaultCatalog = defaultCatalog;
+    this.defaultSchema = defaultSchema;
+    this.properties = properties == null ? ImmutableMap.of() : 
ImmutableMap.copyOf(properties);
+    this.auditInfo = auditInfo;
+  }
+
+  /** Returns the view name. */
+  public String name() {
+    return name;
+  }
+
+  /** Returns the output columns of the view. */
+  public Column[] columns() {
+    return columns;
+  }
+
+  /** Returns the optional comment for the view. */
+  @Nullable
+  public String comment() {
+    return comment;
+  }
+
+  /** Returns the view representations. */
+  public Representation[] representations() {
+    return representations;
+  }
+
+  /** Returns the optional default catalog used by the view definition. */
+  @Nullable
+  public String defaultCatalog() {
+    return defaultCatalog;
+  }
+
+  /** Returns the optional default schema used by the view definition. */
+  @Nullable
+  public String defaultSchema() {
+    return defaultSchema;
+  }
+
+  /** Returns the view properties. */
+  public Map<String, String> properties() {
+    return properties;
+  }
+
+  /** Returns the optional audit information for the view. */
+  @Nullable
+  public Audit auditInfo() {
+    return auditInfo;
+  }
+}
diff --git a/core/src/test/java/org/apache/gravitino/audit/TestOperation.java 
b/core/src/test/java/org/apache/gravitino/audit/TestOperation.java
index 3a9bedb983..2a7032e45e 100644
--- a/core/src/test/java/org/apache/gravitino/audit/TestOperation.java
+++ b/core/src/test/java/org/apache/gravitino/audit/TestOperation.java
@@ -107,17 +107,31 @@ import 
org.apache.gravitino.listener.api.event.PartitionExistsEvent;
 import org.apache.gravitino.listener.api.event.PurgePartitionEvent;
 import org.apache.gravitino.listener.api.event.PurgePartitionFailureEvent;
 import org.apache.gravitino.listener.api.event.PurgeTableEvent;
+import org.apache.gravitino.listener.api.event.view.AlterViewEvent;
+import org.apache.gravitino.listener.api.event.view.AlterViewFailureEvent;
+import org.apache.gravitino.listener.api.event.view.CreateViewEvent;
+import org.apache.gravitino.listener.api.event.view.CreateViewFailureEvent;
+import org.apache.gravitino.listener.api.event.view.DropViewEvent;
+import org.apache.gravitino.listener.api.event.view.DropViewFailureEvent;
+import org.apache.gravitino.listener.api.event.view.ListViewEvent;
+import org.apache.gravitino.listener.api.event.view.ListViewFailureEvent;
+import org.apache.gravitino.listener.api.event.view.LoadViewEvent;
+import org.apache.gravitino.listener.api.event.view.LoadViewFailureEvent;
 import org.apache.gravitino.listener.api.info.CatalogInfo;
 import org.apache.gravitino.listener.api.info.FilesetInfo;
 import org.apache.gravitino.listener.api.info.MetalakeInfo;
 import org.apache.gravitino.listener.api.info.SchemaInfo;
 import org.apache.gravitino.listener.api.info.TableInfo;
 import org.apache.gravitino.listener.api.info.TopicInfo;
+import org.apache.gravitino.listener.api.info.ViewInfo;
 import org.apache.gravitino.listener.api.info.partitions.IdentityPartitionInfo;
 import org.apache.gravitino.listener.api.info.partitions.PartitionInfo;
 import org.apache.gravitino.messaging.TopicChange;
 import org.apache.gravitino.rel.Column;
+import org.apache.gravitino.rel.Representation;
+import org.apache.gravitino.rel.SQLRepresentation;
 import org.apache.gravitino.rel.TableChange;
+import org.apache.gravitino.rel.ViewChange;
 import org.apache.gravitino.rel.expressions.NamedReference;
 import org.apache.gravitino.rel.expressions.distributions.Distributions;
 import org.apache.gravitino.rel.expressions.distributions.Strategy;
@@ -162,6 +176,10 @@ public class TestOperation {
 
   private TopicInfo topicInfo;
 
+  private NameIdentifier viewIdentifier;
+
+  private ViewInfo viewInfo;
+
   private NameIdentifier partitionIdentifier;
 
   private PartitionInfo partitionInfo;
@@ -187,6 +205,9 @@ public class TestOperation {
     this.topicIdentifier = mockTopicIdentifier();
     this.topicInfo = mockTopicInfo();
 
+    this.viewIdentifier = mockViewIdentifier();
+    this.viewInfo = mockViewInfo();
+
     this.filesetIdentifier = mockFilesetIdentifier();
     this.filesetInfo = mockFilesetInfo();
 
@@ -243,6 +264,14 @@ public class TestOperation {
         new CreateTopicFailureEvent(USER, topicIdentifier, new Exception(), 
topicInfo);
     Assertions.assertEquals(
         AuditLog.Operation.fromEvent(createTopicFailureEvent), 
AuditLog.Operation.CREATE_TOPIC);
+
+    Event createViewEvent = new CreateViewEvent(USER, viewIdentifier, 
viewInfo);
+    Assertions.assertEquals(
+        AuditLog.Operation.fromEvent(createViewEvent), 
AuditLog.Operation.CREATE_VIEW);
+    Event createViewFailureEvent =
+        new CreateViewFailureEvent(USER, viewIdentifier, new Exception(), 
viewInfo);
+    Assertions.assertEquals(
+        AuditLog.Operation.fromEvent(createViewFailureEvent), 
AuditLog.Operation.CREATE_VIEW);
   }
 
   @Test
@@ -303,6 +332,14 @@ public class TestOperation {
         new AlterTopicFailureEvent(USER, topicIdentifier, new Exception(), new 
TopicChange[] {});
     Assertions.assertEquals(
         AuditLog.Operation.fromEvent(alterTopicFailureEvent), 
AuditLog.Operation.ALTER_TOPIC);
+
+    Event alterViewEvent = new AlterViewEvent(USER, viewIdentifier, new 
ViewChange[] {}, viewInfo);
+    Assertions.assertEquals(
+        AuditLog.Operation.fromEvent(alterViewEvent), 
AuditLog.Operation.ALTER_VIEW);
+    Event alterViewFailureEvent =
+        new AlterViewFailureEvent(USER, viewIdentifier, new Exception(), new 
ViewChange[] {});
+    Assertions.assertEquals(
+        AuditLog.Operation.fromEvent(alterViewFailureEvent), 
AuditLog.Operation.ALTER_VIEW);
   }
 
   @Test
@@ -351,6 +388,13 @@ public class TestOperation {
     Event dropTopicFailureEvent = new DropTopicFailureEvent(USER, 
topicIdentifier, new Exception());
     Assertions.assertEquals(
         AuditLog.Operation.fromEvent(dropTopicFailureEvent), 
AuditLog.Operation.DROP_TOPIC);
+
+    Event dropViewEvent = new DropViewEvent(USER, viewIdentifier, true);
+    Assertions.assertEquals(
+        AuditLog.Operation.fromEvent(dropViewEvent), 
AuditLog.Operation.DROP_VIEW);
+    Event dropViewFailureEvent = new DropViewFailureEvent(USER, 
viewIdentifier, new Exception());
+    Assertions.assertEquals(
+        AuditLog.Operation.fromEvent(dropViewFailureEvent), 
AuditLog.Operation.DROP_VIEW);
   }
 
   @Test
@@ -426,6 +470,14 @@ public class TestOperation {
     Assertions.assertEquals(
         AuditLog.Operation.fromEvent(listTopicFailureEvent), 
AuditLog.Operation.LIST_TOPIC);
 
+    Namespace viewNamespace = Namespace.of("metalake", "catalog", "schema");
+    Event listViewEvent = new ListViewEvent(USER, viewNamespace);
+    Assertions.assertEquals(
+        AuditLog.Operation.fromEvent(listViewEvent), 
AuditLog.Operation.LIST_VIEW);
+    Event listViewFailureEvent = new ListViewFailureEvent(USER, viewNamespace, 
new Exception());
+    Assertions.assertEquals(
+        AuditLog.Operation.fromEvent(listViewFailureEvent), 
AuditLog.Operation.LIST_VIEW);
+
     Event listFilesetEvent = new ListFilesetEvent(USER, namespace);
     Assertions.assertEquals(
         AuditLog.Operation.fromEvent(listFilesetEvent), 
AuditLog.Operation.LIST_FILESET);
@@ -489,6 +541,13 @@ public class TestOperation {
     Event loadTopicFailureEvent = new LoadTopicFailureEvent(USER, 
topicIdentifier, new Exception());
     Assertions.assertEquals(
         AuditLog.Operation.fromEvent(loadTopicFailureEvent), 
AuditLog.Operation.LOAD_TOPIC);
+
+    Event loadViewEvent = new LoadViewEvent(USER, viewIdentifier, viewInfo);
+    Assertions.assertEquals(
+        AuditLog.Operation.fromEvent(loadViewEvent), 
AuditLog.Operation.LOAD_VIEW);
+    Event loadViewFailureEvent = new LoadViewFailureEvent(USER, 
viewIdentifier, new Exception());
+    Assertions.assertEquals(
+        AuditLog.Operation.fromEvent(loadViewFailureEvent), 
AuditLog.Operation.LOAD_VIEW);
   }
 
   @Test
@@ -602,6 +661,24 @@ public class TestOperation {
     return new TopicInfo("topic", "comment", ImmutableMap.of("a", "b"), null);
   }
 
+  private NameIdentifier mockViewIdentifier() {
+    return NameIdentifier.of("metalake", "catalog", "schema", "view");
+  }
+
+  private ViewInfo mockViewInfo() {
+    return new ViewInfo(
+        "view",
+        new Column[] {Column.of("a", Types.IntegerType.get())},
+        "comment",
+        new Representation[] {
+          SQLRepresentation.builder().withDialect("trino").withSql("SELECT 
1").build()
+        },
+        "dc",
+        "ds",
+        ImmutableMap.of("a", "b"),
+        null);
+  }
+
   private NameIdentifier mockPartitionIdentifier() {
     return NameIdentifier.of("metalake", "catalog", "schema", "table", 
PARTITION_NAME);
   }
diff --git 
a/core/src/test/java/org/apache/gravitino/listener/api/event/TestViewEvent.java 
b/core/src/test/java/org/apache/gravitino/listener/api/event/TestViewEvent.java
new file mode 100644
index 0000000000..1d9529ce22
--- /dev/null
+++ 
b/core/src/test/java/org/apache/gravitino/listener/api/event/TestViewEvent.java
@@ -0,0 +1,326 @@
+/*
+ * 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.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.Map;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.catalog.ViewDispatcher;
+import org.apache.gravitino.exceptions.GravitinoRuntimeException;
+import org.apache.gravitino.listener.DummyEventListener;
+import org.apache.gravitino.listener.EventBus;
+import org.apache.gravitino.listener.ViewEventDispatcher;
+import org.apache.gravitino.listener.api.event.view.AlterViewEvent;
+import org.apache.gravitino.listener.api.event.view.AlterViewFailureEvent;
+import org.apache.gravitino.listener.api.event.view.AlterViewPreEvent;
+import org.apache.gravitino.listener.api.event.view.CreateViewEvent;
+import org.apache.gravitino.listener.api.event.view.CreateViewFailureEvent;
+import org.apache.gravitino.listener.api.event.view.CreateViewPreEvent;
+import org.apache.gravitino.listener.api.event.view.DropViewEvent;
+import org.apache.gravitino.listener.api.event.view.DropViewFailureEvent;
+import org.apache.gravitino.listener.api.event.view.DropViewPreEvent;
+import org.apache.gravitino.listener.api.event.view.ListViewEvent;
+import org.apache.gravitino.listener.api.event.view.ListViewFailureEvent;
+import org.apache.gravitino.listener.api.event.view.ListViewPreEvent;
+import org.apache.gravitino.listener.api.event.view.LoadViewEvent;
+import org.apache.gravitino.listener.api.event.view.LoadViewFailureEvent;
+import org.apache.gravitino.listener.api.event.view.LoadViewPreEvent;
+import org.apache.gravitino.listener.api.info.ViewInfo;
+import org.apache.gravitino.rel.Column;
+import org.apache.gravitino.rel.Representation;
+import org.apache.gravitino.rel.SQLRepresentation;
+import org.apache.gravitino.rel.View;
+import org.apache.gravitino.rel.ViewChange;
+import org.apache.gravitino.rel.types.Types;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.TestInstance.Lifecycle;
+
+@TestInstance(Lifecycle.PER_CLASS)
+public class TestViewEvent {
+  private ViewEventDispatcher dispatcher;
+  private ViewEventDispatcher failureDispatcher;
+  private DummyEventListener dummyEventListener;
+  private View view;
+
+  @BeforeAll
+  void init() {
+    this.view = mockView();
+    this.dummyEventListener = new DummyEventListener();
+    EventBus eventBus = new EventBus(Arrays.asList(dummyEventListener));
+    ViewDispatcher viewDispatcher = mockViewDispatcher();
+    this.dispatcher = new ViewEventDispatcher(eventBus, viewDispatcher);
+    ViewDispatcher exceptionDispatcher = mockExceptionViewDispatcher();
+    this.failureDispatcher = new ViewEventDispatcher(eventBus, 
exceptionDispatcher);
+  }
+
+  @Test
+  void testCreateViewEvent() {
+    NameIdentifier identifier = NameIdentifier.of("metalake", "catalog", 
"schema", view.name());
+    dispatcher.createView(
+        identifier,
+        view.comment(),
+        view.columns(),
+        view.representations(),
+        view.defaultCatalog(),
+        view.defaultSchema(),
+        view.properties());
+
+    Event event = dummyEventListener.popPostEvent();
+    Assertions.assertEquals(identifier, event.identifier());
+    Assertions.assertEquals(CreateViewEvent.class, event.getClass());
+    checkViewInfo(((CreateViewEvent) event).createdViewInfo(), view);
+    Assertions.assertEquals(OperationType.CREATE_VIEW, event.operationType());
+    Assertions.assertEquals(OperationStatus.SUCCESS, event.operationStatus());
+
+    PreEvent preEvent = dummyEventListener.popPreEvent();
+    Assertions.assertEquals(identifier, preEvent.identifier());
+    Assertions.assertEquals(CreateViewPreEvent.class, preEvent.getClass());
+    checkViewInfo(((CreateViewPreEvent) preEvent).createViewRequest(), view);
+    Assertions.assertEquals(OperationType.CREATE_VIEW, 
preEvent.operationType());
+    Assertions.assertEquals(OperationStatus.UNPROCESSED, 
preEvent.operationStatus());
+  }
+
+  @Test
+  void testLoadViewEvent() {
+    NameIdentifier identifier = NameIdentifier.of("metalake", "catalog", 
"schema", view.name());
+    dispatcher.loadView(identifier);
+
+    Event event = dummyEventListener.popPostEvent();
+    Assertions.assertEquals(identifier, event.identifier());
+    Assertions.assertEquals(LoadViewEvent.class, event.getClass());
+    checkViewInfo(((LoadViewEvent) event).loadedViewInfo(), view);
+    Assertions.assertEquals(OperationType.LOAD_VIEW, event.operationType());
+    Assertions.assertEquals(OperationStatus.SUCCESS, event.operationStatus());
+
+    PreEvent preEvent = dummyEventListener.popPreEvent();
+    Assertions.assertEquals(identifier, preEvent.identifier());
+    Assertions.assertEquals(LoadViewPreEvent.class, preEvent.getClass());
+    Assertions.assertEquals(OperationType.LOAD_VIEW, preEvent.operationType());
+    Assertions.assertEquals(OperationStatus.UNPROCESSED, 
preEvent.operationStatus());
+  }
+
+  @Test
+  void testAlterViewEvent() {
+    NameIdentifier identifier = NameIdentifier.of("metalake", "catalog", 
"schema", view.name());
+    ViewChange change = ViewChange.setProperty("a", "b");
+    dispatcher.alterView(identifier, change);
+
+    Event event = dummyEventListener.popPostEvent();
+    Assertions.assertEquals(identifier, event.identifier());
+    Assertions.assertEquals(AlterViewEvent.class, event.getClass());
+    checkViewInfo(((AlterViewEvent) event).updatedViewInfo(), view);
+    Assertions.assertEquals(1, ((AlterViewEvent) event).viewChanges().length);
+    Assertions.assertEquals(change, ((AlterViewEvent) event).viewChanges()[0]);
+    Assertions.assertEquals(OperationType.ALTER_VIEW, event.operationType());
+    Assertions.assertEquals(OperationStatus.SUCCESS, event.operationStatus());
+
+    PreEvent preEvent = dummyEventListener.popPreEvent();
+    Assertions.assertEquals(identifier, preEvent.identifier());
+    Assertions.assertEquals(AlterViewPreEvent.class, preEvent.getClass());
+    Assertions.assertEquals(1, ((AlterViewPreEvent) 
preEvent).viewChanges().length);
+    Assertions.assertEquals(change, ((AlterViewPreEvent) 
preEvent).viewChanges()[0]);
+    Assertions.assertEquals(OperationType.ALTER_VIEW, 
preEvent.operationType());
+    Assertions.assertEquals(OperationStatus.UNPROCESSED, 
preEvent.operationStatus());
+  }
+
+  @Test
+  void testDropViewEvent() {
+    NameIdentifier identifier = NameIdentifier.of("metalake", "catalog", 
"schema", view.name());
+    dispatcher.dropView(identifier);
+
+    Event event = dummyEventListener.popPostEvent();
+    Assertions.assertEquals(identifier, event.identifier());
+    Assertions.assertEquals(DropViewEvent.class, event.getClass());
+    Assertions.assertTrue(((DropViewEvent) event).isExists());
+    Assertions.assertEquals(OperationType.DROP_VIEW, event.operationType());
+    Assertions.assertEquals(OperationStatus.SUCCESS, event.operationStatus());
+
+    PreEvent preEvent = dummyEventListener.popPreEvent();
+    Assertions.assertEquals(identifier, preEvent.identifier());
+    Assertions.assertEquals(DropViewPreEvent.class, preEvent.getClass());
+    Assertions.assertEquals(OperationType.DROP_VIEW, preEvent.operationType());
+    Assertions.assertEquals(OperationStatus.UNPROCESSED, 
preEvent.operationStatus());
+  }
+
+  @Test
+  void testListViewEvent() {
+    Namespace namespace = Namespace.of("metalake", "catalog", "schema");
+    dispatcher.listViews(namespace);
+
+    Event event = dummyEventListener.popPostEvent();
+    Assertions.assertEquals(namespace.toString(), 
event.identifier().toString());
+    Assertions.assertEquals(ListViewEvent.class, event.getClass());
+    Assertions.assertEquals(namespace, ((ListViewEvent) event).namespace());
+    Assertions.assertEquals(OperationType.LIST_VIEW, event.operationType());
+    Assertions.assertEquals(OperationStatus.SUCCESS, event.operationStatus());
+
+    PreEvent preEvent = dummyEventListener.popPreEvent();
+    Assertions.assertEquals(namespace.toString(), 
preEvent.identifier().toString());
+    Assertions.assertEquals(ListViewPreEvent.class, preEvent.getClass());
+    Assertions.assertEquals(namespace, ((ListViewPreEvent) 
preEvent).namespace());
+    Assertions.assertEquals(OperationType.LIST_VIEW, preEvent.operationType());
+    Assertions.assertEquals(OperationStatus.UNPROCESSED, 
preEvent.operationStatus());
+  }
+
+  @Test
+  void testCreateViewFailureEvent() {
+    NameIdentifier identifier = NameIdentifier.of("metalake", "catalog", 
"schema", view.name());
+    Assertions.assertThrowsExactly(
+        GravitinoRuntimeException.class,
+        () ->
+            failureDispatcher.createView(
+                identifier,
+                view.comment(),
+                view.columns(),
+                view.representations(),
+                view.defaultCatalog(),
+                view.defaultSchema(),
+                view.properties()));
+    Event event = dummyEventListener.popPostEvent();
+    Assertions.assertEquals(identifier, event.identifier());
+    Assertions.assertEquals(CreateViewFailureEvent.class, event.getClass());
+    Assertions.assertEquals(
+        GravitinoRuntimeException.class, ((CreateViewFailureEvent) 
event).exception().getClass());
+    checkViewInfo(((CreateViewFailureEvent) event).createViewRequest(), view);
+    Assertions.assertEquals(OperationType.CREATE_VIEW, event.operationType());
+    Assertions.assertEquals(OperationStatus.FAILURE, event.operationStatus());
+  }
+
+  @Test
+  void testLoadViewFailureEvent() {
+    NameIdentifier identifier = NameIdentifier.of("metalake", "catalog", 
"schema", view.name());
+    Assertions.assertThrowsExactly(
+        GravitinoRuntimeException.class, () -> 
failureDispatcher.loadView(identifier));
+    Event event = dummyEventListener.popPostEvent();
+    Assertions.assertEquals(identifier, event.identifier());
+    Assertions.assertEquals(LoadViewFailureEvent.class, event.getClass());
+    Assertions.assertEquals(
+        GravitinoRuntimeException.class, ((LoadViewFailureEvent) 
event).exception().getClass());
+    Assertions.assertEquals(OperationType.LOAD_VIEW, event.operationType());
+    Assertions.assertEquals(OperationStatus.FAILURE, event.operationStatus());
+  }
+
+  @Test
+  void testAlterViewFailureEvent() {
+    NameIdentifier identifier = NameIdentifier.of("metalake", "catalog", 
"schema", view.name());
+    ViewChange change = ViewChange.setProperty("a", "b");
+    Assertions.assertThrowsExactly(
+        GravitinoRuntimeException.class, () -> 
failureDispatcher.alterView(identifier, change));
+    Event event = dummyEventListener.popPostEvent();
+    Assertions.assertEquals(identifier, event.identifier());
+    Assertions.assertEquals(AlterViewFailureEvent.class, event.getClass());
+    Assertions.assertEquals(
+        GravitinoRuntimeException.class, ((AlterViewFailureEvent) 
event).exception().getClass());
+    Assertions.assertEquals(1, ((AlterViewFailureEvent) 
event).viewChanges().length);
+    Assertions.assertEquals(change, ((AlterViewFailureEvent) 
event).viewChanges()[0]);
+    Assertions.assertEquals(OperationType.ALTER_VIEW, event.operationType());
+    Assertions.assertEquals(OperationStatus.FAILURE, event.operationStatus());
+  }
+
+  @Test
+  void testDropViewFailureEvent() {
+    NameIdentifier identifier = NameIdentifier.of("metalake", "catalog", 
"schema", view.name());
+    Assertions.assertThrowsExactly(
+        GravitinoRuntimeException.class, () -> 
failureDispatcher.dropView(identifier));
+    Event event = dummyEventListener.popPostEvent();
+    Assertions.assertEquals(identifier, event.identifier());
+    Assertions.assertEquals(DropViewFailureEvent.class, event.getClass());
+    Assertions.assertEquals(
+        GravitinoRuntimeException.class, ((DropViewFailureEvent) 
event).exception().getClass());
+    Assertions.assertEquals(OperationType.DROP_VIEW, event.operationType());
+    Assertions.assertEquals(OperationStatus.FAILURE, event.operationStatus());
+  }
+
+  @Test
+  void testListViewFailureEvent() {
+    Namespace namespace = Namespace.of("metalake", "catalog", "schema");
+    Assertions.assertThrowsExactly(
+        GravitinoRuntimeException.class, () -> 
failureDispatcher.listViews(namespace));
+    Event event = dummyEventListener.popPostEvent();
+    Assertions.assertEquals(namespace.toString(), 
event.identifier().toString());
+    Assertions.assertEquals(ListViewFailureEvent.class, event.getClass());
+    Assertions.assertEquals(
+        GravitinoRuntimeException.class, ((ListViewFailureEvent) 
event).exception().getClass());
+    Assertions.assertEquals(namespace, ((ListViewFailureEvent) 
event).namespace());
+    Assertions.assertEquals(OperationType.LIST_VIEW, event.operationType());
+    Assertions.assertEquals(OperationStatus.FAILURE, event.operationStatus());
+  }
+
+  private void checkViewInfo(ViewInfo viewInfo, View view) {
+    Assertions.assertEquals(view.name(), viewInfo.name());
+    Assertions.assertEquals(view.comment(), viewInfo.comment());
+    Assertions.assertEquals(view.defaultCatalog(), viewInfo.defaultCatalog());
+    Assertions.assertEquals(view.defaultSchema(), viewInfo.defaultSchema());
+    Assertions.assertEquals(view.properties(), viewInfo.properties());
+    Assertions.assertArrayEquals(view.columns(), viewInfo.columns());
+    Assertions.assertArrayEquals(view.representations(), 
viewInfo.representations());
+    Assertions.assertEquals(view.auditInfo(), viewInfo.auditInfo());
+  }
+
+  private View mockView() {
+    View v = mock(View.class);
+    Representation rep =
+        SQLRepresentation.builder().withDialect("trino").withSql("SELECT 
1").build();
+    when(v.name()).thenReturn("view");
+    when(v.comment()).thenReturn("comment");
+    when(v.properties()).thenReturn(ImmutableMap.of("a", "b"));
+    when(v.columns()).thenReturn(new Column[] {Column.of("a", 
Types.IntegerType.get())});
+    when(v.representations()).thenReturn(new Representation[] {rep});
+    when(v.defaultCatalog()).thenReturn("dc");
+    when(v.defaultSchema()).thenReturn("ds");
+    when(v.auditInfo()).thenReturn(null);
+    return v;
+  }
+
+  private ViewDispatcher mockViewDispatcher() {
+    ViewDispatcher d = mock(ViewDispatcher.class);
+    when(d.createView(
+            any(NameIdentifier.class),
+            any(),
+            any(Column[].class),
+            any(Representation[].class),
+            any(),
+            any(),
+            any(Map.class)))
+        .thenReturn(view);
+    when(d.loadView(any(NameIdentifier.class))).thenReturn(view);
+    when(d.dropView(any(NameIdentifier.class))).thenReturn(true);
+    when(d.listViews(any(Namespace.class))).thenReturn(null);
+    when(d.alterView(any(NameIdentifier.class), 
any(ViewChange.class))).thenReturn(view);
+    return d;
+  }
+
+  private ViewDispatcher mockExceptionViewDispatcher() {
+    return mock(
+        ViewDispatcher.class,
+        invocation -> {
+          throw new GravitinoRuntimeException("Exception for all methods");
+        });
+  }
+}

Reply via email to