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 c5ac5b0cbd [#12595] feat(core): Add event listener support for 
Semantic Model operations (#13458)
c5ac5b0cbd is described below

commit c5ac5b0cbd9264a31620eafac4c780a21ee25b9f
Author: Akshay Thorat <[email protected]>
AuthorDate: Fri Oct 9 22:40:38 2026 -0700

    [#12595] feat(core): Add event listener support for Semantic Model 
operations (#13458)
    
    ### What changes were proposed in this pull request?
    
    - Add `SemanticModelInfo` (immutable listener payload) under
    `org.apache.gravitino.listener.api.info`.
    - Add pre / success / failure event types under
    `org.apache.gravitino.listener.api.event.semantic` for **list**,
    **load**, **create**, **alter**, and **drop** Semantic Models, mirroring
    the existing view events. `ListSemanticModelEvent` implements
    `ListEvent` and reports the result count.
    - Add `SemanticModelEventDispatcher` to publish those events on the
    `EventBus`.
    - Wire `GravitinoEnv` so the chain is `SemanticModelEventDispatcher` ->
    `SemanticModelNormalizeDispatcher` ->
    `SemanticModelOperationDispatcher`, replacing the `TODO(#12595)`.
    - Add `CREATE/ALTER/DROP/LOAD/LIST_SEMANTIC_MODEL` operation types plus
    audit mappings on both audit paths: `AuditLog.Operation.fromEvent` (v1)
    and `CompatibilityUtils` (v2).
    - Add `TestSemanticModelEvent`; extend `TestOperation` and
    `TestCompatibilityUtils`.
    
    `semanticModelExists` gets no dedicated event, consistent with
    `viewExists` / function behavior. Ownership and privilege hooks are out
    of scope (#12594).
    
    ### Why are the changes needed?
    
    Semantic Model CRUD did not emit listener events, so
    `EventListenerPlugin` implementations could not observe those operations
    and audit classified them as `UNKNOWN_OPERATION`. The parent [design
    
doc](https://github.com/apache/gravitino/blob/main/design-docs/gravitino-semantic-model-design.md#events)
    specifies that list, load, create, alter, and drop emit pre, success,
    and failure events, with rename represented as an alter event.
    
    Fix: #12595
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes, for operators and integrations: event listeners and audit pipelines
    now see Semantic Model list/load/create/alter/drop and their failures
    with concrete audit operation types. No REST or Java client API changes.
    
    ### How was this patch tested?
    
    - `./gradlew :core:test --tests
    org.apache.gravitino.listener.api.event.TestSemanticModelEvent --tests
    org.apache.gravitino.audit.TestOperation --tests
    org.apache.gravitino.audit.v2.TestCompatibilityUtils -PskipITs` - 30
    tests, all pass.
    - Full `./gradlew :core:test -PskipITs` - 1823 tests, 0 failures.
    - `./gradlew :core:javadoc` and `:core:spotlessCheck` clean.
    
    The pre-existing `testAllKnownOperationTypesMapToConcreteAuditOperation`
    guard in `TestCompatibilityUtils` covers the new operation types, so the
    mapping cannot silently regress to `UNKNOWN_OPERATION`.
---
 .../java/org/apache/gravitino/GravitinoEnv.java    |   8 +-
 .../java/org/apache/gravitino/audit/AuditLog.java  |  35 ++
 .../gravitino/audit/v2/CompatibilityUtils.java     |   5 +
 .../listener/SemanticModelEventDispatcher.java     | 164 ++++++
 .../listener/api/event/OperationType.java          |   7 +
 .../event/semantic/AlterSemanticModelEvent.java    |  74 +++
 .../semantic/AlterSemanticModelFailureEvent.java   |  64 +++
 .../event/semantic/AlterSemanticModelPreEvent.java |  62 +++
 .../event/semantic/CreateSemanticModelEvent.java   |  58 +++
 .../semantic/CreateSemanticModelFailureEvent.java  |  62 +++
 .../semantic/CreateSemanticModelPreEvent.java      |  58 +++
 .../api/event/semantic/DropSemanticModelEvent.java |  56 +++
 .../semantic/DropSemanticModelFailureEvent.java    |  46 ++
 .../event/semantic/DropSemanticModelPreEvent.java  |  44 ++
 .../api/event/semantic/ListSemanticModelEvent.java |  70 +++
 .../semantic/ListSemanticModelFailureEvent.java    |  57 +++
 .../event/semantic/ListSemanticModelPreEvent.java  |  56 +++
 .../api/event/semantic/LoadSemanticModelEvent.java |  58 +++
 .../semantic/LoadSemanticModelFailureEvent.java    |  46 ++
 .../event/semantic/LoadSemanticModelPreEvent.java  |  44 ++
 .../api/event/semantic/SemanticModelEvent.java     |  45 ++
 .../event/semantic/SemanticModelFailureEvent.java  |  40 ++
 .../api/event/semantic/SemanticModelPreEvent.java  |  39 ++
 .../listener/api/info/SemanticModelInfo.java       | 124 +++++
 .../TestGravitinoEnvMetadataComponents.java        |   2 +
 .../org/apache/gravitino/audit/TestOperation.java  | 101 ++++
 .../gravitino/audit/v2/TestCompatibilityUtils.java |   5 +
 .../listener/api/event/TestSemanticModelEvent.java | 553 +++++++++++++++++++++
 28 files changed, 1980 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 e84d386533..482e42fa28 100644
--- a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
+++ b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
@@ -91,6 +91,7 @@ import org.apache.gravitino.listener.ModelEventDispatcher;
 import org.apache.gravitino.listener.PartitionEventDispatcher;
 import org.apache.gravitino.listener.PolicyEventDispatcher;
 import org.apache.gravitino.listener.SchemaEventDispatcher;
+import org.apache.gravitino.listener.SemanticModelEventDispatcher;
 import org.apache.gravitino.listener.StatisticEventDispatcher;
 import org.apache.gravitino.listener.TableEventDispatcher;
 import org.apache.gravitino.listener.TagEventDispatcher;
@@ -1145,8 +1146,7 @@ public class GravitinoEnv {
         new ViewNormalizeDispatcher(viewHookDispatcher, catalogManager);
     this.viewDispatcher = new ViewEventDispatcher(eventBus, 
viewNormalizeDispatcher);
 
-    // Semantic Model operation chain: Normalize -> Hook -> Operation.
-    // TODO(#12595): Add an outer SemanticModelEventDispatcher.
+    // Semantic Model operation chain: Event -> Normalize -> Hook -> Operation.
     SemanticModelOperationDispatcher semanticModelOperationDispatcher =
         new SemanticModelOperationDispatcher(
             catalogManager,
@@ -1157,8 +1157,10 @@ public class GravitinoEnv {
     SemanticModelHookDispatcher semanticModelHookDispatcher =
         new SemanticModelHookDispatcher(
             semanticModelOperationDispatcher, this::internalOwnerDispatcher);
-    this.semanticModelDispatcher =
+    SemanticModelNormalizeDispatcher semanticModelNormalizeDispatcher =
         new SemanticModelNormalizeDispatcher(semanticModelHookDispatcher, 
catalogManager);
+    this.semanticModelDispatcher =
+        new SemanticModelEventDispatcher(eventBus, 
semanticModelNormalizeDispatcher);
 
     this.statisticDispatcher = new StatisticEventDispatcher(eventBus, 
internalStatisticDispatcher);
 
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 42168a24e4..acf532f24c 100644
--- a/core/src/main/java/org/apache/gravitino/audit/AuditLog.java
+++ b/core/src/main/java/org/apache/gravitino/audit/AuditLog.java
@@ -109,6 +109,16 @@ import 
org.apache.gravitino.listener.api.event.PurgeTableEvent;
 import org.apache.gravitino.listener.api.event.PurgeTableFailureEvent;
 import org.apache.gravitino.listener.api.event.RemovePolicyFromTagEvent;
 import org.apache.gravitino.listener.api.event.RemovePolicyFromTagFailureEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.AlterSemanticModelEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.AlterSemanticModelFailureEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.CreateSemanticModelEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.CreateSemanticModelFailureEvent;
+import org.apache.gravitino.listener.api.event.semantic.DropSemanticModelEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.DropSemanticModelFailureEvent;
+import org.apache.gravitino.listener.api.event.semantic.ListSemanticModelEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.ListSemanticModelFailureEvent;
+import org.apache.gravitino.listener.api.event.semantic.LoadSemanticModelEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.LoadSemanticModelFailureEvent;
 import 
org.apache.gravitino.listener.api.event.server.AuthorizationDenialFailureEvent;
 import org.apache.gravitino.listener.api.event.view.AlterViewEvent;
 import org.apache.gravitino.listener.api.event.view.AlterViewFailureEvent;
@@ -508,6 +518,16 @@ public interface AuditLog {
 
     GET_POLICY_FOR_METADATA_OBJECT,
 
+    CREATE_SEMANTIC_MODEL,
+
+    ALTER_SEMANTIC_MODEL,
+
+    DROP_SEMANTIC_MODEL,
+
+    LOAD_SEMANTIC_MODEL,
+
+    LIST_SEMANTIC_MODEL,
+
     REGISTER_FUNCTION,
 
     GET_FUNCTION,
@@ -612,6 +632,21 @@ public interface AuditLog {
         return LOAD_VIEW;
       } else if (event instanceof ListViewEvent || event instanceof 
ListViewFailureEvent) {
         return LIST_VIEW;
+      } else if (event instanceof CreateSemanticModelEvent
+          || event instanceof CreateSemanticModelFailureEvent) {
+        return CREATE_SEMANTIC_MODEL;
+      } else if (event instanceof AlterSemanticModelEvent
+          || event instanceof AlterSemanticModelFailureEvent) {
+        return ALTER_SEMANTIC_MODEL;
+      } else if (event instanceof DropSemanticModelEvent
+          || event instanceof DropSemanticModelFailureEvent) {
+        return DROP_SEMANTIC_MODEL;
+      } else if (event instanceof LoadSemanticModelEvent
+          || event instanceof LoadSemanticModelFailureEvent) {
+        return LOAD_SEMANTIC_MODEL;
+      } else if (event instanceof ListSemanticModelEvent
+          || event instanceof ListSemanticModelFailureEvent) {
+        return LIST_SEMANTIC_MODEL;
       } else if (event instanceof CreateFilesetEvent
           || event instanceof CreateFilesetFailureEvent) {
         return CREATE_FILESET;
diff --git 
a/core/src/main/java/org/apache/gravitino/audit/v2/CompatibilityUtils.java 
b/core/src/main/java/org/apache/gravitino/audit/v2/CompatibilityUtils.java
index fea95fa9e5..bc220eff62 100644
--- a/core/src/main/java/org/apache/gravitino/audit/v2/CompatibilityUtils.java
+++ b/core/src/main/java/org/apache/gravitino/audit/v2/CompatibilityUtils.java
@@ -195,6 +195,11 @@ public class CompatibilityUtils {
           .put(
               OperationType.GET_POLICY_FOR_METADATA_OBJECT,
               Operation.GET_POLICY_FOR_METADATA_OBJECT)
+          .put(OperationType.CREATE_SEMANTIC_MODEL, 
Operation.CREATE_SEMANTIC_MODEL)
+          .put(OperationType.ALTER_SEMANTIC_MODEL, 
Operation.ALTER_SEMANTIC_MODEL)
+          .put(OperationType.DROP_SEMANTIC_MODEL, 
Operation.DROP_SEMANTIC_MODEL)
+          .put(OperationType.LOAD_SEMANTIC_MODEL, 
Operation.LOAD_SEMANTIC_MODEL)
+          .put(OperationType.LIST_SEMANTIC_MODEL, 
Operation.LIST_SEMANTIC_MODEL)
           .put(OperationType.REGISTER_FUNCTION, Operation.REGISTER_FUNCTION)
           .put(OperationType.GET_FUNCTION, Operation.GET_FUNCTION)
           .put(OperationType.ALTER_FUNCTION, Operation.ALTER_FUNCTION)
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/SemanticModelEventDispatcher.java
 
b/core/src/main/java/org/apache/gravitino/listener/SemanticModelEventDispatcher.java
new file mode 100644
index 0000000000..8eca83aef9
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/SemanticModelEventDispatcher.java
@@ -0,0 +1,164 @@
+/*
+ * 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.SemanticModelDispatcher;
+import org.apache.gravitino.catalog.SemanticModelNormalizeDispatcher;
+import org.apache.gravitino.exceptions.IllegalSemanticModelException;
+import org.apache.gravitino.exceptions.NoSuchSchemaException;
+import org.apache.gravitino.exceptions.NoSuchSemanticModelException;
+import org.apache.gravitino.exceptions.SemanticModelAlreadyExistsException;
+import 
org.apache.gravitino.listener.api.event.semantic.AlterSemanticModelEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.AlterSemanticModelFailureEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.AlterSemanticModelPreEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.CreateSemanticModelEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.CreateSemanticModelFailureEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.CreateSemanticModelPreEvent;
+import org.apache.gravitino.listener.api.event.semantic.DropSemanticModelEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.DropSemanticModelFailureEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.DropSemanticModelPreEvent;
+import org.apache.gravitino.listener.api.event.semantic.ListSemanticModelEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.ListSemanticModelFailureEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.ListSemanticModelPreEvent;
+import org.apache.gravitino.listener.api.event.semantic.LoadSemanticModelEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.LoadSemanticModelFailureEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.LoadSemanticModelPreEvent;
+import org.apache.gravitino.listener.api.info.SemanticModelInfo;
+import org.apache.gravitino.semantic.SemanticModel;
+import org.apache.gravitino.semantic.SemanticModelChange;
+import org.apache.gravitino.semantic.SemanticModelDefinition;
+import org.apache.gravitino.utils.PrincipalUtils;
+
+/**
+ * Decorates a {@link SemanticModelDispatcher} to dispatch pre/post/failure 
Semantic Model events to
+ * an {@link EventBus}, mirroring {@link ViewEventDispatcher}.
+ */
+public class SemanticModelEventDispatcher implements SemanticModelDispatcher {
+
+  private final EventBus eventBus;
+  private final SemanticModelDispatcher dispatcher;
+
+  /**
+   * Constructs a {@code SemanticModelEventDispatcher}.
+   *
+   * @param eventBus Event bus for listener plugins.
+   * @param dispatcher Underlying dispatcher, for example a {@link
+   *     SemanticModelNormalizeDispatcher}.
+   */
+  public SemanticModelEventDispatcher(EventBus eventBus, 
SemanticModelDispatcher dispatcher) {
+    this.eventBus = eventBus;
+    this.dispatcher = dispatcher;
+  }
+
+  @Override
+  public NameIdentifier[] listSemanticModels(Namespace namespace) throws 
NoSuchSchemaException {
+    String user = PrincipalUtils.getCurrentUserName();
+    eventBus.dispatchEvent(new ListSemanticModelPreEvent(user, namespace));
+    try {
+      NameIdentifier[] identifiers = dispatcher.listSemanticModels(namespace);
+      eventBus.dispatchEvent(
+          new ListSemanticModelEvent(
+              user, namespace, identifiers != null ? identifiers.length : -1));
+      return identifiers;
+    } catch (Exception e) {
+      eventBus.dispatchEvent(new ListSemanticModelFailureEvent(user, 
namespace, e));
+      throw e;
+    }
+  }
+
+  @Override
+  public SemanticModel loadSemanticModel(NameIdentifier ident) throws 
NoSuchSemanticModelException {
+    String user = PrincipalUtils.getCurrentUserName();
+    eventBus.dispatchEvent(new LoadSemanticModelPreEvent(user, ident));
+    try {
+      SemanticModel semanticModel = dispatcher.loadSemanticModel(ident);
+      eventBus.dispatchEvent(
+          new LoadSemanticModelEvent(user, ident, new 
SemanticModelInfo(semanticModel)));
+      return semanticModel;
+    } catch (Exception e) {
+      eventBus.dispatchEvent(new LoadSemanticModelFailureEvent(user, ident, 
e));
+      throw e;
+    }
+  }
+
+  @Override
+  public boolean semanticModelExists(NameIdentifier ident) {
+    return dispatcher.semanticModelExists(ident);
+  }
+
+  @Override
+  public SemanticModel createSemanticModel(
+      NameIdentifier ident,
+      @Nullable String comment,
+      SemanticModelDefinition definition,
+      Map<String, String> properties)
+      throws NoSuchSchemaException, SemanticModelAlreadyExistsException,
+          IllegalSemanticModelException {
+    String user = PrincipalUtils.getCurrentUserName();
+    SemanticModelInfo createRequest =
+        new SemanticModelInfo(ident.name(), comment, definition, properties, 
null);
+    eventBus.dispatchEvent(new CreateSemanticModelPreEvent(user, ident, 
createRequest));
+    try {
+      SemanticModel semanticModel =
+          dispatcher.createSemanticModel(ident, comment, definition, 
properties);
+      eventBus.dispatchEvent(
+          new CreateSemanticModelEvent(user, ident, new 
SemanticModelInfo(semanticModel)));
+      return semanticModel;
+    } catch (Exception e) {
+      eventBus.dispatchEvent(new CreateSemanticModelFailureEvent(user, ident, 
e, createRequest));
+      throw e;
+    }
+  }
+
+  @Override
+  public SemanticModel alterSemanticModel(NameIdentifier ident, 
SemanticModelChange... changes)
+      throws NoSuchSemanticModelException, SemanticModelAlreadyExistsException,
+          IllegalSemanticModelException {
+    String user = PrincipalUtils.getCurrentUserName();
+    eventBus.dispatchEvent(new AlterSemanticModelPreEvent(user, ident, 
changes));
+    try {
+      SemanticModel semanticModel = dispatcher.alterSemanticModel(ident, 
changes);
+      eventBus.dispatchEvent(
+          new AlterSemanticModelEvent(user, ident, changes, new 
SemanticModelInfo(semanticModel)));
+      return semanticModel;
+    } catch (Exception e) {
+      eventBus.dispatchEvent(new AlterSemanticModelFailureEvent(user, ident, 
e, changes));
+      throw e;
+    }
+  }
+
+  @Override
+  public boolean dropSemanticModel(NameIdentifier ident) {
+    String user = PrincipalUtils.getCurrentUserName();
+    eventBus.dispatchEvent(new DropSemanticModelPreEvent(user, ident));
+    try {
+      boolean existed = dispatcher.dropSemanticModel(ident);
+      eventBus.dispatchEvent(new DropSemanticModelEvent(user, ident, existed));
+      return existed;
+    } catch (Exception e) {
+      eventBus.dispatchEvent(new DropSemanticModelFailureEvent(user, ident, 
e));
+      throw e;
+    }
+  }
+}
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 1a2e7dd0ca..072dc00d42 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
@@ -209,6 +209,13 @@ public enum OperationType {
   ASSOCIATE_POLICIES_FOR_METADATA_OBJECT,
   GET_POLICY_FOR_METADATA_OBJECT,
 
+  // Semantic Model operations
+  CREATE_SEMANTIC_MODEL,
+  ALTER_SEMANTIC_MODEL,
+  DROP_SEMANTIC_MODEL,
+  LOAD_SEMANTIC_MODEL,
+  LIST_SEMANTIC_MODEL,
+
   // Function operations
   REGISTER_FUNCTION,
   GET_FUNCTION,
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/AlterSemanticModelEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/AlterSemanticModelEvent.java
new file mode 100644
index 0000000000..2db2d170b3
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/AlterSemanticModelEvent.java
@@ -0,0 +1,74 @@
+/*
+ * 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.semantic;
+
+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.SemanticModelInfo;
+import org.apache.gravitino.semantic.SemanticModelChange;
+
+/** Successful alter-Semantic-Model event. */
+@DeveloperApi
+public final class AlterSemanticModelEvent extends SemanticModelEvent {
+  private final SemanticModelChange[] semanticModelChanges;
+  private final SemanticModelInfo updatedSemanticModelInfo;
+
+  /**
+   * Constructs an instance of {@code AlterSemanticModelEvent}.
+   *
+   * @param user The username of the individual who initiated the alteration.
+   * @param identifier The identifier of the altered Semantic Model.
+   * @param semanticModelChanges The applied changes.
+   * @param updatedSemanticModelInfo The Semantic Model information after the 
alteration.
+   */
+  public AlterSemanticModelEvent(
+      String user,
+      NameIdentifier identifier,
+      SemanticModelChange[] semanticModelChanges,
+      SemanticModelInfo updatedSemanticModelInfo) {
+    super(user, identifier);
+    this.semanticModelChanges = semanticModelChanges.clone();
+    this.updatedSemanticModelInfo = updatedSemanticModelInfo;
+  }
+
+  /**
+   * Returns the applied Semantic Model changes.
+   *
+   * @return A copy of the Semantic Model changes.
+   */
+  public SemanticModelChange[] semanticModelChanges() {
+    return semanticModelChanges.clone();
+  }
+
+  /**
+   * Returns the Semantic Model information after the alteration.
+   *
+   * @return The updated Semantic Model information.
+   */
+  public SemanticModelInfo updatedSemanticModelInfo() {
+    return updatedSemanticModelInfo;
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.ALTER_SEMANTIC_MODEL;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/AlterSemanticModelFailureEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/AlterSemanticModelFailureEvent.java
new file mode 100644
index 0000000000..18edf9270c
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/AlterSemanticModelFailureEvent.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.semantic;
+
+import javax.annotation.Nullable;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.OperationType;
+import org.apache.gravitino.semantic.SemanticModelChange;
+
+/** Failure event for altering a Semantic Model. */
+@DeveloperApi
+public final class AlterSemanticModelFailureEvent extends 
SemanticModelFailureEvent {
+  @Nullable private final SemanticModelChange[] semanticModelChanges;
+
+  /**
+   * Constructs an instance of {@code AlterSemanticModelFailureEvent}.
+   *
+   * @param user The username of the individual who initiated the alteration.
+   * @param identifier The identifier of the Semantic Model being altered.
+   * @param exception The exception that caused the failure.
+   * @param semanticModelChanges The changes that failed to apply, or null if 
none were provided.
+   */
+  public AlterSemanticModelFailureEvent(
+      String user,
+      NameIdentifier identifier,
+      Exception exception,
+      @Nullable SemanticModelChange[] semanticModelChanges) {
+    super(user, identifier, exception);
+    this.semanticModelChanges = semanticModelChanges == null ? null : 
semanticModelChanges.clone();
+  }
+
+  /**
+   * Returns the Semantic Model changes that failed to apply.
+   *
+   * @return A copy of the Semantic Model changes, or null if none were 
provided.
+   */
+  @Nullable
+  public SemanticModelChange[] semanticModelChanges() {
+    return semanticModelChanges == null ? null : semanticModelChanges.clone();
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.ALTER_SEMANTIC_MODEL;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/AlterSemanticModelPreEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/AlterSemanticModelPreEvent.java
new file mode 100644
index 0000000000..588d991e1f
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/AlterSemanticModelPreEvent.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.semantic;
+
+import javax.annotation.Nullable;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.OperationType;
+import org.apache.gravitino.semantic.SemanticModelChange;
+
+/** Pre-event before altering a Semantic Model. */
+@DeveloperApi
+public final class AlterSemanticModelPreEvent extends SemanticModelPreEvent {
+  @Nullable private final SemanticModelChange[] semanticModelChanges;
+
+  /**
+   * Constructs an instance of {@code AlterSemanticModelPreEvent}.
+   *
+   * @param user The username of the individual who initiated the alteration.
+   * @param identifier The identifier of the Semantic Model being altered.
+   * @param semanticModelChanges The changes to apply, or null if none were 
provided.
+   */
+  public AlterSemanticModelPreEvent(
+      String user,
+      NameIdentifier identifier,
+      @Nullable SemanticModelChange[] semanticModelChanges) {
+    super(user, identifier);
+    this.semanticModelChanges = semanticModelChanges == null ? null : 
semanticModelChanges.clone();
+  }
+
+  /**
+   * Returns the changes to apply to the Semantic Model.
+   *
+   * @return A copy of the Semantic Model changes, or null if none were 
provided.
+   */
+  @Nullable
+  public SemanticModelChange[] semanticModelChanges() {
+    return semanticModelChanges == null ? null : semanticModelChanges.clone();
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.ALTER_SEMANTIC_MODEL;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/CreateSemanticModelEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/CreateSemanticModelEvent.java
new file mode 100644
index 0000000000..f642489d2a
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/CreateSemanticModelEvent.java
@@ -0,0 +1,58 @@
+/*
+ * 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.semantic;
+
+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.SemanticModelInfo;
+
+/** Successful create-Semantic-Model event. */
+@DeveloperApi
+public final class CreateSemanticModelEvent extends SemanticModelEvent {
+  private final SemanticModelInfo createdSemanticModelInfo;
+
+  /**
+   * Constructs an instance of {@code CreateSemanticModelEvent}.
+   *
+   * @param user The username of the individual who initiated the creation.
+   * @param identifier The identifier of the created Semantic Model.
+   * @param createdSemanticModelInfo The created Semantic Model information.
+   */
+  public CreateSemanticModelEvent(
+      String user, NameIdentifier identifier, SemanticModelInfo 
createdSemanticModelInfo) {
+    super(user, identifier);
+    this.createdSemanticModelInfo = createdSemanticModelInfo;
+  }
+
+  /**
+   * Returns the created Semantic Model information.
+   *
+   * @return The created Semantic Model information.
+   */
+  public SemanticModelInfo createdSemanticModelInfo() {
+    return createdSemanticModelInfo;
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.CREATE_SEMANTIC_MODEL;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/CreateSemanticModelFailureEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/CreateSemanticModelFailureEvent.java
new file mode 100644
index 0000000000..b1f57fe538
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/CreateSemanticModelFailureEvent.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.semantic;
+
+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.SemanticModelInfo;
+
+/** Failure event for creating a Semantic Model. */
+@DeveloperApi
+public final class CreateSemanticModelFailureEvent extends 
SemanticModelFailureEvent {
+  private final SemanticModelInfo createSemanticModelRequest;
+
+  /**
+   * Constructs an instance of {@code CreateSemanticModelFailureEvent}.
+   *
+   * @param user The username of the individual who initiated the creation.
+   * @param identifier The identifier of the Semantic Model being created.
+   * @param exception The exception that caused the failure.
+   * @param createSemanticModelRequest The requested Semantic Model 
information.
+   */
+  public CreateSemanticModelFailureEvent(
+      String user,
+      NameIdentifier identifier,
+      Exception exception,
+      SemanticModelInfo createSemanticModelRequest) {
+    super(user, identifier, exception);
+    this.createSemanticModelRequest = createSemanticModelRequest;
+  }
+
+  /**
+   * Returns the requested Semantic Model information.
+   *
+   * @return The requested Semantic Model information.
+   */
+  public SemanticModelInfo createSemanticModelRequest() {
+    return createSemanticModelRequest;
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.CREATE_SEMANTIC_MODEL;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/CreateSemanticModelPreEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/CreateSemanticModelPreEvent.java
new file mode 100644
index 0000000000..77ca3f06b4
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/CreateSemanticModelPreEvent.java
@@ -0,0 +1,58 @@
+/*
+ * 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.semantic;
+
+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.SemanticModelInfo;
+
+/** Pre-event before creating a Semantic Model. */
+@DeveloperApi
+public final class CreateSemanticModelPreEvent extends SemanticModelPreEvent {
+  private final SemanticModelInfo createSemanticModelRequest;
+
+  /**
+   * Constructs an instance of {@code CreateSemanticModelPreEvent}.
+   *
+   * @param user The username of the individual who initiated the creation.
+   * @param identifier The identifier of the Semantic Model being created.
+   * @param createSemanticModelRequest The requested Semantic Model 
information.
+   */
+  public CreateSemanticModelPreEvent(
+      String user, NameIdentifier identifier, SemanticModelInfo 
createSemanticModelRequest) {
+    super(user, identifier);
+    this.createSemanticModelRequest = createSemanticModelRequest;
+  }
+
+  /**
+   * Returns the requested Semantic Model information.
+   *
+   * @return The requested Semantic Model information.
+   */
+  public SemanticModelInfo createSemanticModelRequest() {
+    return createSemanticModelRequest;
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.CREATE_SEMANTIC_MODEL;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/DropSemanticModelEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/DropSemanticModelEvent.java
new file mode 100644
index 0000000000..38a16ed3b9
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/DropSemanticModelEvent.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.gravitino.listener.api.event.semantic;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.OperationType;
+
+/** Successful drop-Semantic-Model event. */
+@DeveloperApi
+public final class DropSemanticModelEvent extends SemanticModelEvent {
+  private final boolean isExists;
+
+  /**
+   * Constructs an instance of {@code DropSemanticModelEvent}.
+   *
+   * @param user The username of the individual who initiated the drop.
+   * @param identifier The identifier of the dropped Semantic Model.
+   * @param isExists Whether the Semantic Model existed when the drop was 
applied.
+   */
+  public DropSemanticModelEvent(String user, NameIdentifier identifier, 
boolean isExists) {
+    super(user, identifier);
+    this.isExists = isExists;
+  }
+
+  /**
+   * Returns whether the Semantic Model existed when the drop was applied.
+   *
+   * @return {@code true} if the Semantic Model existed, otherwise {@code 
false}.
+   */
+  public boolean isExists() {
+    return isExists;
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.DROP_SEMANTIC_MODEL;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/DropSemanticModelFailureEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/DropSemanticModelFailureEvent.java
new file mode 100644
index 0000000000..187ce73e83
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/DropSemanticModelFailureEvent.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.semantic;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.OperationType;
+
+/** Failure event for dropping a Semantic Model. */
+@DeveloperApi
+public final class DropSemanticModelFailureEvent extends 
SemanticModelFailureEvent {
+
+  /**
+   * Constructs an instance of {@code DropSemanticModelFailureEvent}.
+   *
+   * @param user The username of the individual who initiated the drop.
+   * @param identifier The identifier of the Semantic Model being dropped.
+   * @param exception The exception that caused the failure.
+   */
+  public DropSemanticModelFailureEvent(
+      String user, NameIdentifier identifier, Exception exception) {
+    super(user, identifier, exception);
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.DROP_SEMANTIC_MODEL;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/DropSemanticModelPreEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/DropSemanticModelPreEvent.java
new file mode 100644
index 0000000000..0e7de01277
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/DropSemanticModelPreEvent.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.semantic;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.OperationType;
+
+/** Pre-event before dropping a Semantic Model. */
+@DeveloperApi
+public final class DropSemanticModelPreEvent extends SemanticModelPreEvent {
+
+  /**
+   * Constructs an instance of {@code DropSemanticModelPreEvent}.
+   *
+   * @param user The username of the individual who initiated the drop.
+   * @param identifier The identifier of the Semantic Model being dropped.
+   */
+  public DropSemanticModelPreEvent(String user, NameIdentifier identifier) {
+    super(user, identifier);
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.DROP_SEMANTIC_MODEL;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/ListSemanticModelEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/ListSemanticModelEvent.java
new file mode 100644
index 0000000000..cac99ad216
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/ListSemanticModelEvent.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.gravitino.listener.api.event.semantic;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.ListEvent;
+import org.apache.gravitino.listener.api.event.OperationType;
+
+/**
+ * Successful list-Semantic-Models event. Like {@link
+ * org.apache.gravitino.listener.api.event.view.ListViewEvent}, listed 
identifiers are not stored on
+ * the event.
+ */
+@DeveloperApi
+public final class ListSemanticModelEvent extends SemanticModelEvent 
implements ListEvent {
+  private final Namespace namespace;
+  private final int semanticModelCount;
+
+  /**
+   * Constructs an instance of {@code ListSemanticModelEvent}.
+   *
+   * @param user The username of the individual who initiated the Semantic 
Model listing.
+   * @param namespace The namespace from which Semantic Models were listed.
+   * @param semanticModelCount The number of Semantic Models returned by the 
list operation.
+   */
+  public ListSemanticModelEvent(String user, Namespace namespace, int 
semanticModelCount) {
+    super(user, NameIdentifier.of(namespace.levels()));
+    this.namespace = namespace;
+    this.semanticModelCount = semanticModelCount;
+  }
+
+  /**
+   * Returns the namespace associated with this event.
+   *
+   * @return The namespace.
+   */
+  public Namespace namespace() {
+    return namespace;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public int resultCount() {
+    return semanticModelCount;
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.LIST_SEMANTIC_MODEL;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/ListSemanticModelFailureEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/ListSemanticModelFailureEvent.java
new file mode 100644
index 0000000000..c9eb779523
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/ListSemanticModelFailureEvent.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.gravitino.listener.api.event.semantic;
+
+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 Semantic Models. */
+@DeveloperApi
+public final class ListSemanticModelFailureEvent extends 
SemanticModelFailureEvent {
+  private final Namespace namespace;
+
+  /**
+   * Constructs an instance of {@code ListSemanticModelFailureEvent}.
+   *
+   * @param user The username of the individual who initiated the Semantic 
Model listing.
+   * @param namespace The namespace from which Semantic Models were listed.
+   * @param exception The exception that caused the failure.
+   */
+  public ListSemanticModelFailureEvent(String user, Namespace namespace, 
Exception exception) {
+    super(user, NameIdentifier.of(namespace.levels()), exception);
+    this.namespace = namespace;
+  }
+
+  /**
+   * Returns the namespace associated with this event.
+   *
+   * @return The namespace.
+   */
+  public Namespace namespace() {
+    return namespace;
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.LIST_SEMANTIC_MODEL;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/ListSemanticModelPreEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/ListSemanticModelPreEvent.java
new file mode 100644
index 0000000000..1abe2b7873
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/ListSemanticModelPreEvent.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.gravitino.listener.api.event.semantic;
+
+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 the Semantic Models in a namespace. */
+@DeveloperApi
+public final class ListSemanticModelPreEvent extends SemanticModelPreEvent {
+  private final Namespace namespace;
+
+  /**
+   * Constructs an instance of {@code ListSemanticModelPreEvent}.
+   *
+   * @param user The username of the individual who initiated the Semantic 
Model listing.
+   * @param namespace The namespace from which Semantic Models are listed.
+   */
+  public ListSemanticModelPreEvent(String user, Namespace namespace) {
+    super(user, NameIdentifier.of(namespace.levels()));
+    this.namespace = namespace;
+  }
+
+  /**
+   * Returns the namespace associated with this event.
+   *
+   * @return The namespace.
+   */
+  public Namespace namespace() {
+    return namespace;
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.LIST_SEMANTIC_MODEL;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/LoadSemanticModelEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/LoadSemanticModelEvent.java
new file mode 100644
index 0000000000..ec8fa44f73
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/LoadSemanticModelEvent.java
@@ -0,0 +1,58 @@
+/*
+ * 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.semantic;
+
+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.SemanticModelInfo;
+
+/** Successful load-Semantic-Model event. */
+@DeveloperApi
+public final class LoadSemanticModelEvent extends SemanticModelEvent {
+  private final SemanticModelInfo loadedSemanticModelInfo;
+
+  /**
+   * Constructs an instance of {@code LoadSemanticModelEvent}.
+   *
+   * @param user The username of the individual who initiated the load.
+   * @param identifier The identifier of the loaded Semantic Model.
+   * @param loadedSemanticModelInfo The loaded Semantic Model information.
+   */
+  public LoadSemanticModelEvent(
+      String user, NameIdentifier identifier, SemanticModelInfo 
loadedSemanticModelInfo) {
+    super(user, identifier);
+    this.loadedSemanticModelInfo = loadedSemanticModelInfo;
+  }
+
+  /**
+   * Returns the loaded Semantic Model information.
+   *
+   * @return The loaded Semantic Model information.
+   */
+  public SemanticModelInfo loadedSemanticModelInfo() {
+    return loadedSemanticModelInfo;
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.LOAD_SEMANTIC_MODEL;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/LoadSemanticModelFailureEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/LoadSemanticModelFailureEvent.java
new file mode 100644
index 0000000000..20ff0ab67f
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/LoadSemanticModelFailureEvent.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.semantic;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.OperationType;
+
+/** Failure event for loading a Semantic Model. */
+@DeveloperApi
+public final class LoadSemanticModelFailureEvent extends 
SemanticModelFailureEvent {
+
+  /**
+   * Constructs an instance of {@code LoadSemanticModelFailureEvent}.
+   *
+   * @param user The username of the individual who initiated the load.
+   * @param identifier The identifier of the Semantic Model being loaded.
+   * @param exception The exception that caused the failure.
+   */
+  public LoadSemanticModelFailureEvent(
+      String user, NameIdentifier identifier, Exception exception) {
+    super(user, identifier, exception);
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.LOAD_SEMANTIC_MODEL;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/LoadSemanticModelPreEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/LoadSemanticModelPreEvent.java
new file mode 100644
index 0000000000..1885d1ca95
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/LoadSemanticModelPreEvent.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.semantic;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.OperationType;
+
+/** Pre-event before loading a Semantic Model. */
+@DeveloperApi
+public final class LoadSemanticModelPreEvent extends SemanticModelPreEvent {
+
+  /**
+   * Constructs an instance of {@code LoadSemanticModelPreEvent}.
+   *
+   * @param user The username of the individual who initiated the load.
+   * @param identifier The identifier of the Semantic Model being loaded.
+   */
+  public LoadSemanticModelPreEvent(String user, NameIdentifier identifier) {
+    super(user, identifier);
+  }
+
+  @Override
+  public OperationType operationType() {
+    return OperationType.LOAD_SEMANTIC_MODEL;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/SemanticModelEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/SemanticModelEvent.java
new file mode 100644
index 0000000000..8c8ae32077
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/SemanticModelEvent.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.semantic;
+
+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 Semantic Model operation events. */
+@DeveloperApi
+public abstract class SemanticModelEvent extends Event {
+
+  /**
+   * Constructs a successful Semantic Model operation event.
+   *
+   * @param user The username of the individual who initiated the operation.
+   * @param identifier The identifier of the Semantic Model or namespace 
involved in the operation.
+   */
+  protected SemanticModelEvent(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/semantic/SemanticModelFailureEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/SemanticModelFailureEvent.java
new file mode 100644
index 0000000000..1b30d2fbec
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/SemanticModelFailureEvent.java
@@ -0,0 +1,40 @@
+/*
+ * 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.semantic;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.FailureEvent;
+
+/** Base class for Semantic Model operation failure events. */
+@DeveloperApi
+public abstract class SemanticModelFailureEvent extends FailureEvent {
+
+  /**
+   * Constructs a Semantic Model operation failure event.
+   *
+   * @param user The username of the individual who initiated the operation.
+   * @param identifier The identifier of the Semantic Model or namespace 
involved in the operation.
+   * @param exception The exception that caused the operation to fail.
+   */
+  protected SemanticModelFailureEvent(String user, NameIdentifier identifier, 
Exception exception) {
+    super(user, identifier, exception);
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/SemanticModelPreEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/SemanticModelPreEvent.java
new file mode 100644
index 0000000000..b74450c3e0
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/semantic/SemanticModelPreEvent.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.semantic;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+import org.apache.gravitino.listener.api.event.PreEvent;
+
+/** Base class for Semantic Model operation pre-events. */
+@DeveloperApi
+public abstract class SemanticModelPreEvent extends PreEvent {
+
+  /**
+   * Constructs a pre-event for a Semantic Model operation.
+   *
+   * @param user The username of the individual who initiated the operation.
+   * @param identifier The identifier of the Semantic Model or namespace 
involved in the operation.
+   */
+  protected SemanticModelPreEvent(String user, NameIdentifier identifier) {
+    super(user, identifier);
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/info/SemanticModelInfo.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/info/SemanticModelInfo.java
new file mode 100644
index 0000000000..626b7b901c
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/info/SemanticModelInfo.java
@@ -0,0 +1,124 @@
+/*
+ * 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.semantic.SemanticModel;
+import org.apache.gravitino.semantic.SemanticModelDefinition;
+
+/**
+ * SemanticModelInfo exposes Semantic Model information for event listeners; 
it is read-only. The
+ * definition is already an immutable value, so it is referenced directly.
+ */
+@DeveloperApi
+public final class SemanticModelInfo {
+  private final String name;
+  @Nullable private final String comment;
+  private final SemanticModelDefinition definition;
+  private final Map<String, String> properties;
+  @Nullable private final Audit auditInfo;
+
+  /**
+   * Constructs a SemanticModelInfo from a {@link SemanticModel}.
+   *
+   * @param semanticModel The Semantic Model to expose.
+   */
+  public SemanticModelInfo(SemanticModel semanticModel) {
+    this(
+        semanticModel.name(),
+        semanticModel.comment(),
+        semanticModel.definition(),
+        semanticModel.properties(),
+        semanticModel.auditInfo());
+  }
+
+  /**
+   * Constructs a SemanticModelInfo with the given fields.
+   *
+   * @param name Semantic Model name.
+   * @param comment Optional comment.
+   * @param definition The immutable Semantic Model definition.
+   * @param properties Gravitino-specific properties; copied defensively.
+   * @param auditInfo Optional audit information.
+   */
+  public SemanticModelInfo(
+      String name,
+      @Nullable String comment,
+      SemanticModelDefinition definition,
+      @Nullable Map<String, String> properties,
+      @Nullable Audit auditInfo) {
+    this.name = name;
+    this.comment = comment;
+    this.definition = definition;
+    this.properties = properties == null ? ImmutableMap.of() : 
ImmutableMap.copyOf(properties);
+    this.auditInfo = auditInfo;
+  }
+
+  /**
+   * Returns the Semantic Model name.
+   *
+   * @return The Semantic Model name.
+   */
+  public String name() {
+    return name;
+  }
+
+  /**
+   * Returns the optional comment for the Semantic Model.
+   *
+   * @return The comment, or {@code null} if it is not set.
+   */
+  @Nullable
+  public String comment() {
+    return comment;
+  }
+
+  /**
+   * Returns the Semantic Model definition.
+   *
+   * @return The Semantic Model definition.
+   */
+  public SemanticModelDefinition definition() {
+    return definition;
+  }
+
+  /**
+   * Returns the Gravitino-specific properties of the Semantic Model.
+   *
+   * @return The immutable Semantic Model properties.
+   */
+  public Map<String, String> properties() {
+    return properties;
+  }
+
+  /**
+   * Returns the optional audit information for the Semantic Model.
+   *
+   * @return The audit information, or {@code null} if it is not set.
+   */
+  @Nullable
+  public Audit auditInfo() {
+    return auditInfo;
+  }
+}
diff --git 
a/core/src/test/java/org/apache/gravitino/TestGravitinoEnvMetadataComponents.java
 
b/core/src/test/java/org/apache/gravitino/TestGravitinoEnvMetadataComponents.java
index 2b970f4876..9d2e182905 100644
--- 
a/core/src/test/java/org/apache/gravitino/TestGravitinoEnvMetadataComponents.java
+++ 
b/core/src/test/java/org/apache/gravitino/TestGravitinoEnvMetadataComponents.java
@@ -69,6 +69,7 @@ import org.apache.gravitino.listener.FunctionEventDispatcher;
 import org.apache.gravitino.listener.ModelEventDispatcher;
 import org.apache.gravitino.listener.PartitionEventDispatcher;
 import org.apache.gravitino.listener.SchemaEventDispatcher;
+import org.apache.gravitino.listener.SemanticModelEventDispatcher;
 import org.apache.gravitino.listener.StatisticEventDispatcher;
 import org.apache.gravitino.listener.TableEventDispatcher;
 import org.apache.gravitino.listener.TopicEventDispatcher;
@@ -279,6 +280,7 @@ class TestGravitinoEnvMetadataComponents {
           SemanticModelOperationDispatcher.class);
       assertDispatcherChain(
           env.semanticModelDispatcher(),
+          SemanticModelEventDispatcher.class,
           SemanticModelNormalizeDispatcher.class,
           SemanticModelHookDispatcher.class,
           SemanticModelOperationDispatcher.class);
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 df785a1b72..243cce5fdb 100644
--- a/core/src/test/java/org/apache/gravitino/audit/TestOperation.java
+++ b/core/src/test/java/org/apache/gravitino/audit/TestOperation.java
@@ -112,6 +112,16 @@ import 
org.apache.gravitino.listener.api.event.PurgePartitionFailureEvent;
 import org.apache.gravitino.listener.api.event.PurgeTableEvent;
 import org.apache.gravitino.listener.api.event.RemovePolicyFromTagEvent;
 import org.apache.gravitino.listener.api.event.RemovePolicyFromTagFailureEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.AlterSemanticModelEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.AlterSemanticModelFailureEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.CreateSemanticModelEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.CreateSemanticModelFailureEvent;
+import org.apache.gravitino.listener.api.event.semantic.DropSemanticModelEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.DropSemanticModelFailureEvent;
+import org.apache.gravitino.listener.api.event.semantic.ListSemanticModelEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.ListSemanticModelFailureEvent;
+import org.apache.gravitino.listener.api.event.semantic.LoadSemanticModelEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.LoadSemanticModelFailureEvent;
 import 
org.apache.gravitino.listener.api.event.server.AuthorizationDenialFailureEvent;
 import org.apache.gravitino.listener.api.event.server.HttpRequestFailureEvent;
 import org.apache.gravitino.listener.api.event.view.AlterViewEvent;
@@ -128,6 +138,7 @@ 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.SemanticModelInfo;
 import org.apache.gravitino.listener.api.info.TableInfo;
 import org.apache.gravitino.listener.api.info.TopicInfo;
 import org.apache.gravitino.listener.api.info.ViewInfo;
@@ -152,6 +163,9 @@ import 
org.apache.gravitino.rel.expressions.transforms.Transforms;
 import org.apache.gravitino.rel.indexes.Index;
 import org.apache.gravitino.rel.indexes.Indexes;
 import org.apache.gravitino.rel.types.Types;
+import org.apache.gravitino.semantic.Dataset;
+import org.apache.gravitino.semantic.SemanticModelChange;
+import org.apache.gravitino.semantic.SemanticModelDefinition;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
@@ -188,6 +202,10 @@ public class TestOperation {
 
   private ViewInfo viewInfo;
 
+  private NameIdentifier semanticModelIdentifier;
+
+  private SemanticModelInfo semanticModelInfo;
+
   private NameIdentifier partitionIdentifier;
 
   private PartitionInfo partitionInfo;
@@ -216,6 +234,9 @@ public class TestOperation {
     this.viewIdentifier = mockViewIdentifier();
     this.viewInfo = mockViewInfo();
 
+    this.semanticModelIdentifier = mockSemanticModelIdentifier();
+    this.semanticModelInfo = mockSemanticModelInfo();
+
     this.filesetIdentifier = mockFilesetIdentifier();
     this.filesetInfo = mockFilesetInfo();
 
@@ -280,6 +301,18 @@ public class TestOperation {
         new CreateViewFailureEvent(USER, viewIdentifier, new Exception(), 
viewInfo);
     Assertions.assertEquals(
         AuditLog.Operation.CREATE_VIEW, 
AuditLog.Operation.fromEvent(createViewFailureEvent));
+
+    Event createSemanticModelEvent =
+        new CreateSemanticModelEvent(USER, semanticModelIdentifier, 
semanticModelInfo);
+    Assertions.assertEquals(
+        AuditLog.Operation.CREATE_SEMANTIC_MODEL,
+        AuditLog.Operation.fromEvent(createSemanticModelEvent));
+    Event createSemanticModelFailureEvent =
+        new CreateSemanticModelFailureEvent(
+            USER, semanticModelIdentifier, new Exception(), semanticModelInfo);
+    Assertions.assertEquals(
+        AuditLog.Operation.CREATE_SEMANTIC_MODEL,
+        AuditLog.Operation.fromEvent(createSemanticModelFailureEvent));
   }
 
   @Test
@@ -348,6 +381,25 @@ public class TestOperation {
         new AlterViewFailureEvent(USER, viewIdentifier, new Exception(), new 
ViewChange[] {});
     Assertions.assertEquals(
         AuditLog.Operation.ALTER_VIEW, 
AuditLog.Operation.fromEvent(alterViewFailureEvent));
+
+    Event alterSemanticModelEvent =
+        new AlterSemanticModelEvent(
+            USER,
+            semanticModelIdentifier,
+            new SemanticModelChange[] {SemanticModelChange.setProperty("key", 
"value")},
+            semanticModelInfo);
+    Assertions.assertEquals(
+        AuditLog.Operation.ALTER_SEMANTIC_MODEL,
+        AuditLog.Operation.fromEvent(alterSemanticModelEvent));
+    Event alterSemanticModelFailureEvent =
+        new AlterSemanticModelFailureEvent(
+            USER,
+            semanticModelIdentifier,
+            new Exception(),
+            new SemanticModelChange[] {SemanticModelChange.setProperty("key", 
"value")});
+    Assertions.assertEquals(
+        AuditLog.Operation.ALTER_SEMANTIC_MODEL,
+        AuditLog.Operation.fromEvent(alterSemanticModelFailureEvent));
   }
 
   @Test
@@ -403,6 +455,16 @@ public class TestOperation {
     Event dropViewFailureEvent = new DropViewFailureEvent(USER, 
viewIdentifier, new Exception());
     Assertions.assertEquals(
         AuditLog.Operation.DROP_VIEW, 
AuditLog.Operation.fromEvent(dropViewFailureEvent));
+
+    Event dropSemanticModelEvent = new DropSemanticModelEvent(USER, 
semanticModelIdentifier, true);
+    Assertions.assertEquals(
+        AuditLog.Operation.DROP_SEMANTIC_MODEL,
+        AuditLog.Operation.fromEvent(dropSemanticModelEvent));
+    Event dropSemanticModelFailureEvent =
+        new DropSemanticModelFailureEvent(USER, semanticModelIdentifier, new 
Exception());
+    Assertions.assertEquals(
+        AuditLog.Operation.DROP_SEMANTIC_MODEL,
+        AuditLog.Operation.fromEvent(dropSemanticModelFailureEvent));
   }
 
   @Test
@@ -486,6 +548,16 @@ public class TestOperation {
     Assertions.assertEquals(
         AuditLog.Operation.LIST_VIEW, 
AuditLog.Operation.fromEvent(listViewFailureEvent));
 
+    Event listSemanticModelEvent = new ListSemanticModelEvent(USER, 
viewNamespace, 0);
+    Assertions.assertEquals(
+        AuditLog.Operation.LIST_SEMANTIC_MODEL,
+        AuditLog.Operation.fromEvent(listSemanticModelEvent));
+    Event listSemanticModelFailureEvent =
+        new ListSemanticModelFailureEvent(USER, viewNamespace, new 
Exception());
+    Assertions.assertEquals(
+        AuditLog.Operation.LIST_SEMANTIC_MODEL,
+        AuditLog.Operation.fromEvent(listSemanticModelFailureEvent));
+
     Event listFilesetEvent = new ListFilesetEvent(USER, namespace, 0);
     Assertions.assertEquals(
         AuditLog.Operation.LIST_FILESET, 
AuditLog.Operation.fromEvent(listFilesetEvent));
@@ -556,6 +628,17 @@ public class TestOperation {
     Event loadViewFailureEvent = new LoadViewFailureEvent(USER, 
viewIdentifier, new Exception());
     Assertions.assertEquals(
         AuditLog.Operation.LOAD_VIEW, 
AuditLog.Operation.fromEvent(loadViewFailureEvent));
+
+    Event loadSemanticModelEvent =
+        new LoadSemanticModelEvent(USER, semanticModelIdentifier, 
semanticModelInfo);
+    Assertions.assertEquals(
+        AuditLog.Operation.LOAD_SEMANTIC_MODEL,
+        AuditLog.Operation.fromEvent(loadSemanticModelEvent));
+    Event loadSemanticModelFailureEvent =
+        new LoadSemanticModelFailureEvent(USER, semanticModelIdentifier, new 
Exception());
+    Assertions.assertEquals(
+        AuditLog.Operation.LOAD_SEMANTIC_MODEL,
+        AuditLog.Operation.fromEvent(loadSemanticModelFailureEvent));
   }
 
   @Test
@@ -808,6 +891,24 @@ public class TestOperation {
         null);
   }
 
+  private NameIdentifier mockSemanticModelIdentifier() {
+    return NameIdentifier.of("metalake", "catalog", "schema", "sales_model");
+  }
+
+  private SemanticModelInfo mockSemanticModelInfo() {
+    Dataset dataset =
+        Dataset.builder()
+            .withName("orders")
+            .withSource(NameIdentifier.of("sales", "mart", "orders"))
+            .build();
+    return new SemanticModelInfo(
+        "sales_model",
+        "comment",
+        SemanticModelDefinition.builder().withDatasets(new Dataset[] 
{dataset}).build(),
+        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/audit/v2/TestCompatibilityUtils.java 
b/core/src/test/java/org/apache/gravitino/audit/v2/TestCompatibilityUtils.java
index 9793591cdd..fdfecbfb6b 100644
--- 
a/core/src/test/java/org/apache/gravitino/audit/v2/TestCompatibilityUtils.java
+++ 
b/core/src/test/java/org/apache/gravitino/audit/v2/TestCompatibilityUtils.java
@@ -198,6 +198,11 @@ public class TestCompatibilityUtils {
         Operation.ASSOCIATE_POLICIES_FOR_METADATA_OBJECT
       },
       {OperationType.GET_POLICY_FOR_METADATA_OBJECT, 
Operation.GET_POLICY_FOR_METADATA_OBJECT},
+      {OperationType.CREATE_SEMANTIC_MODEL, Operation.CREATE_SEMANTIC_MODEL},
+      {OperationType.ALTER_SEMANTIC_MODEL, Operation.ALTER_SEMANTIC_MODEL},
+      {OperationType.DROP_SEMANTIC_MODEL, Operation.DROP_SEMANTIC_MODEL},
+      {OperationType.LOAD_SEMANTIC_MODEL, Operation.LOAD_SEMANTIC_MODEL},
+      {OperationType.LIST_SEMANTIC_MODEL, Operation.LIST_SEMANTIC_MODEL},
       {OperationType.REGISTER_FUNCTION, Operation.REGISTER_FUNCTION},
       {OperationType.GET_FUNCTION, Operation.GET_FUNCTION},
       {OperationType.ALTER_FUNCTION, Operation.ALTER_FUNCTION},
diff --git 
a/core/src/test/java/org/apache/gravitino/listener/api/event/TestSemanticModelEvent.java
 
b/core/src/test/java/org/apache/gravitino/listener/api/event/TestSemanticModelEvent.java
new file mode 100644
index 0000000000..b6569dc91f
--- /dev/null
+++ 
b/core/src/test/java/org/apache/gravitino/listener/api/event/TestSemanticModelEvent.java
@@ -0,0 +1,553 @@
+/*
+ * 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.mockStatic;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.catalog.CatalogManager;
+import org.apache.gravitino.catalog.SemanticModelDispatcher;
+import org.apache.gravitino.catalog.SemanticModelNormalizeDispatcher;
+import org.apache.gravitino.exceptions.GravitinoRuntimeException;
+import org.apache.gravitino.listener.DummyEventListener;
+import org.apache.gravitino.listener.EventBus;
+import org.apache.gravitino.listener.SemanticModelEventDispatcher;
+import 
org.apache.gravitino.listener.api.event.semantic.AlterSemanticModelEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.AlterSemanticModelFailureEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.AlterSemanticModelPreEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.CreateSemanticModelEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.CreateSemanticModelFailureEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.CreateSemanticModelPreEvent;
+import org.apache.gravitino.listener.api.event.semantic.DropSemanticModelEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.DropSemanticModelFailureEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.DropSemanticModelPreEvent;
+import org.apache.gravitino.listener.api.event.semantic.ListSemanticModelEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.ListSemanticModelFailureEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.ListSemanticModelPreEvent;
+import org.apache.gravitino.listener.api.event.semantic.LoadSemanticModelEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.LoadSemanticModelFailureEvent;
+import 
org.apache.gravitino.listener.api.event.semantic.LoadSemanticModelPreEvent;
+import org.apache.gravitino.listener.api.info.SemanticModelInfo;
+import org.apache.gravitino.semantic.Dataset;
+import org.apache.gravitino.semantic.SemanticModel;
+import org.apache.gravitino.semantic.SemanticModelChange;
+import org.apache.gravitino.semantic.SemanticModelDefinition;
+import org.apache.gravitino.utils.PrincipalUtils;
+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;
+import org.mockito.MockedStatic;
+
+@TestInstance(Lifecycle.PER_CLASS)
+public class TestSemanticModelEvent {
+  private static final Namespace NAMESPACE = Namespace.of("metalake", 
"catalog", "schema");
+
+  private SemanticModelEventDispatcher dispatcher;
+  private SemanticModelEventDispatcher failureDispatcher;
+  private DummyEventListener dummyEventListener;
+  private SemanticModel semanticModel;
+
+  @BeforeAll
+  void init() {
+    this.semanticModel = mockSemanticModel();
+    this.dummyEventListener = new DummyEventListener();
+    EventBus eventBus = new EventBus(Arrays.asList(dummyEventListener));
+    this.dispatcher = new SemanticModelEventDispatcher(eventBus, 
mockSemanticModelDispatcher());
+    this.failureDispatcher =
+        new SemanticModelEventDispatcher(eventBus, 
mockExceptionSemanticModelDispatcher());
+  }
+
+  @Test
+  void testCreateSemanticModelEvent() {
+    NameIdentifier identifier = NameIdentifier.of(NAMESPACE, 
semanticModel.name());
+    dispatcher.createSemanticModel(
+        identifier,
+        semanticModel.comment(),
+        semanticModel.definition(),
+        semanticModel.properties());
+
+    Event event = dummyEventListener.popPostEvent();
+    Assertions.assertEquals(identifier, event.identifier());
+    Assertions.assertEquals(CreateSemanticModelEvent.class, event.getClass());
+    checkSemanticModelInfo(
+        ((CreateSemanticModelEvent) event).createdSemanticModelInfo(), 
semanticModel);
+    Assertions.assertEquals(OperationType.CREATE_SEMANTIC_MODEL, 
event.operationType());
+    Assertions.assertEquals(OperationStatus.SUCCESS, event.operationStatus());
+
+    PreEvent preEvent = dummyEventListener.popPreEvent();
+    Assertions.assertEquals(identifier, preEvent.identifier());
+    Assertions.assertEquals(CreateSemanticModelPreEvent.class, 
preEvent.getClass());
+    checkSemanticModelInfo(
+        ((CreateSemanticModelPreEvent) preEvent).createSemanticModelRequest(), 
semanticModel);
+    Assertions.assertEquals(OperationType.CREATE_SEMANTIC_MODEL, 
preEvent.operationType());
+    Assertions.assertEquals(OperationStatus.UNPROCESSED, 
preEvent.operationStatus());
+  }
+
+  @Test
+  void testLoadSemanticModelEvent() {
+    NameIdentifier identifier = NameIdentifier.of(NAMESPACE, 
semanticModel.name());
+    dispatcher.loadSemanticModel(identifier);
+
+    Event event = dummyEventListener.popPostEvent();
+    Assertions.assertEquals(identifier, event.identifier());
+    Assertions.assertEquals(LoadSemanticModelEvent.class, event.getClass());
+    checkSemanticModelInfo(
+        ((LoadSemanticModelEvent) event).loadedSemanticModelInfo(), 
semanticModel);
+    Assertions.assertEquals(OperationType.LOAD_SEMANTIC_MODEL, 
event.operationType());
+    Assertions.assertEquals(OperationStatus.SUCCESS, event.operationStatus());
+
+    PreEvent preEvent = dummyEventListener.popPreEvent();
+    Assertions.assertEquals(identifier, preEvent.identifier());
+    Assertions.assertEquals(LoadSemanticModelPreEvent.class, 
preEvent.getClass());
+    Assertions.assertEquals(OperationType.LOAD_SEMANTIC_MODEL, 
preEvent.operationType());
+    Assertions.assertEquals(OperationStatus.UNPROCESSED, 
preEvent.operationStatus());
+  }
+
+  @Test
+  void testAlterSemanticModelEvent() {
+    NameIdentifier identifier = NameIdentifier.of(NAMESPACE, 
semanticModel.name());
+    SemanticModelChange change = SemanticModelChange.setProperty("a", "b");
+    dispatcher.alterSemanticModel(identifier, change);
+
+    Event event = dummyEventListener.popPostEvent();
+    Assertions.assertEquals(identifier, event.identifier());
+    Assertions.assertEquals(AlterSemanticModelEvent.class, event.getClass());
+    checkSemanticModelInfo(
+        ((AlterSemanticModelEvent) event).updatedSemanticModelInfo(), 
semanticModel);
+    Assertions.assertArrayEquals(
+        new SemanticModelChange[] {change},
+        ((AlterSemanticModelEvent) event).semanticModelChanges());
+    Assertions.assertEquals(OperationType.ALTER_SEMANTIC_MODEL, 
event.operationType());
+    Assertions.assertEquals(OperationStatus.SUCCESS, event.operationStatus());
+
+    PreEvent preEvent = dummyEventListener.popPreEvent();
+    Assertions.assertEquals(identifier, preEvent.identifier());
+    Assertions.assertEquals(AlterSemanticModelPreEvent.class, 
preEvent.getClass());
+    Assertions.assertArrayEquals(
+        new SemanticModelChange[] {change},
+        ((AlterSemanticModelPreEvent) preEvent).semanticModelChanges());
+    Assertions.assertEquals(OperationType.ALTER_SEMANTIC_MODEL, 
preEvent.operationType());
+    Assertions.assertEquals(OperationStatus.UNPROCESSED, 
preEvent.operationStatus());
+  }
+
+  @Test
+  void testDropSemanticModelEvent() {
+    NameIdentifier identifier = NameIdentifier.of(NAMESPACE, 
semanticModel.name());
+    dispatcher.dropSemanticModel(identifier);
+
+    Event event = dummyEventListener.popPostEvent();
+    Assertions.assertEquals(identifier, event.identifier());
+    Assertions.assertEquals(DropSemanticModelEvent.class, event.getClass());
+    Assertions.assertTrue(((DropSemanticModelEvent) event).isExists());
+    Assertions.assertEquals(OperationType.DROP_SEMANTIC_MODEL, 
event.operationType());
+    Assertions.assertEquals(OperationStatus.SUCCESS, event.operationStatus());
+
+    PreEvent preEvent = dummyEventListener.popPreEvent();
+    Assertions.assertEquals(identifier, preEvent.identifier());
+    Assertions.assertEquals(DropSemanticModelPreEvent.class, 
preEvent.getClass());
+    Assertions.assertEquals(OperationType.DROP_SEMANTIC_MODEL, 
preEvent.operationType());
+    Assertions.assertEquals(OperationStatus.UNPROCESSED, 
preEvent.operationStatus());
+  }
+
+  @Test
+  void testListSemanticModelEvent() {
+    dispatcher.listSemanticModels(NAMESPACE);
+
+    Event event = dummyEventListener.popPostEvent();
+    Assertions.assertEquals(NAMESPACE.toString(), 
event.identifier().toString());
+    Assertions.assertEquals(ListSemanticModelEvent.class, event.getClass());
+    Assertions.assertEquals(NAMESPACE, ((ListSemanticModelEvent) 
event).namespace());
+    Assertions.assertEquals(2, ((ListSemanticModelEvent) event).resultCount());
+    Assertions.assertEquals(OperationType.LIST_SEMANTIC_MODEL, 
event.operationType());
+    Assertions.assertEquals(OperationStatus.SUCCESS, event.operationStatus());
+
+    PreEvent preEvent = dummyEventListener.popPreEvent();
+    Assertions.assertEquals(NAMESPACE.toString(), 
preEvent.identifier().toString());
+    Assertions.assertEquals(ListSemanticModelPreEvent.class, 
preEvent.getClass());
+    Assertions.assertEquals(NAMESPACE, ((ListSemanticModelPreEvent) 
preEvent).namespace());
+    Assertions.assertEquals(OperationType.LIST_SEMANTIC_MODEL, 
preEvent.operationType());
+    Assertions.assertEquals(OperationStatus.UNPROCESSED, 
preEvent.operationStatus());
+  }
+
+  @Test
+  void testCreateSemanticModelFailureEvent() {
+    NameIdentifier identifier = NameIdentifier.of(NAMESPACE, 
semanticModel.name());
+    Assertions.assertThrowsExactly(
+        GravitinoRuntimeException.class,
+        () ->
+            failureDispatcher.createSemanticModel(
+                identifier,
+                semanticModel.comment(),
+                semanticModel.definition(),
+                semanticModel.properties()));
+
+    Event event = dummyEventListener.popPostEvent();
+    Assertions.assertEquals(identifier, event.identifier());
+    Assertions.assertEquals(CreateSemanticModelFailureEvent.class, 
event.getClass());
+    Assertions.assertEquals(
+        GravitinoRuntimeException.class,
+        ((CreateSemanticModelFailureEvent) event).exception().getClass());
+    checkSemanticModelInfo(
+        ((CreateSemanticModelFailureEvent) 
event).createSemanticModelRequest(), semanticModel);
+    Assertions.assertEquals(OperationType.CREATE_SEMANTIC_MODEL, 
event.operationType());
+    Assertions.assertEquals(OperationStatus.FAILURE, event.operationStatus());
+  }
+
+  @Test
+  void testLoadSemanticModelFailureEvent() {
+    NameIdentifier identifier = NameIdentifier.of(NAMESPACE, 
semanticModel.name());
+    Assertions.assertThrowsExactly(
+        GravitinoRuntimeException.class, () -> 
failureDispatcher.loadSemanticModel(identifier));
+
+    Event event = dummyEventListener.popPostEvent();
+    Assertions.assertEquals(identifier, event.identifier());
+    Assertions.assertEquals(LoadSemanticModelFailureEvent.class, 
event.getClass());
+    Assertions.assertEquals(
+        GravitinoRuntimeException.class,
+        ((LoadSemanticModelFailureEvent) event).exception().getClass());
+    Assertions.assertEquals(OperationType.LOAD_SEMANTIC_MODEL, 
event.operationType());
+    Assertions.assertEquals(OperationStatus.FAILURE, event.operationStatus());
+  }
+
+  @Test
+  void testAlterSemanticModelFailureEvent() {
+    NameIdentifier identifier = NameIdentifier.of(NAMESPACE, 
semanticModel.name());
+    SemanticModelChange change = SemanticModelChange.setProperty("a", "b");
+    Assertions.assertThrowsExactly(
+        GravitinoRuntimeException.class,
+        () -> failureDispatcher.alterSemanticModel(identifier, change));
+
+    Event event = dummyEventListener.popPostEvent();
+    Assertions.assertEquals(identifier, event.identifier());
+    Assertions.assertEquals(AlterSemanticModelFailureEvent.class, 
event.getClass());
+    Assertions.assertEquals(
+        GravitinoRuntimeException.class,
+        ((AlterSemanticModelFailureEvent) event).exception().getClass());
+    Assertions.assertArrayEquals(
+        new SemanticModelChange[] {change},
+        ((AlterSemanticModelFailureEvent) event).semanticModelChanges());
+    Assertions.assertEquals(OperationType.ALTER_SEMANTIC_MODEL, 
event.operationType());
+    Assertions.assertEquals(OperationStatus.FAILURE, event.operationStatus());
+  }
+
+  @Test
+  void testDropSemanticModelFailureEvent() {
+    NameIdentifier identifier = NameIdentifier.of(NAMESPACE, 
semanticModel.name());
+    Assertions.assertThrowsExactly(
+        GravitinoRuntimeException.class, () -> 
failureDispatcher.dropSemanticModel(identifier));
+
+    Event event = dummyEventListener.popPostEvent();
+    Assertions.assertEquals(identifier, event.identifier());
+    Assertions.assertEquals(DropSemanticModelFailureEvent.class, 
event.getClass());
+    Assertions.assertEquals(
+        GravitinoRuntimeException.class,
+        ((DropSemanticModelFailureEvent) event).exception().getClass());
+    Assertions.assertEquals(OperationType.DROP_SEMANTIC_MODEL, 
event.operationType());
+    Assertions.assertEquals(OperationStatus.FAILURE, event.operationStatus());
+  }
+
+  @Test
+  void testListSemanticModelFailureEvent() {
+    Assertions.assertThrowsExactly(
+        GravitinoRuntimeException.class, () -> 
failureDispatcher.listSemanticModels(NAMESPACE));
+
+    Event event = dummyEventListener.popPostEvent();
+    Assertions.assertEquals(NAMESPACE.toString(), 
event.identifier().toString());
+    Assertions.assertEquals(ListSemanticModelFailureEvent.class, 
event.getClass());
+    Assertions.assertEquals(
+        GravitinoRuntimeException.class,
+        ((ListSemanticModelFailureEvent) event).exception().getClass());
+    Assertions.assertEquals(NAMESPACE, ((ListSemanticModelFailureEvent) 
event).namespace());
+    Assertions.assertEquals(OperationType.LIST_SEMANTIC_MODEL, 
event.operationType());
+    Assertions.assertEquals(OperationStatus.FAILURE, event.operationStatus());
+  }
+
+  @Test
+  void testSemanticModelExistsDoesNotEmitEvent() {
+    NameIdentifier identifier = NameIdentifier.of(NAMESPACE, 
semanticModel.name());
+    dummyEventListener.clear();
+
+    Assertions.assertTrue(dispatcher.semanticModelExists(identifier));
+
+    Assertions.assertTrue(dummyEventListener.getPostEvents().isEmpty());
+    Assertions.assertTrue(dummyEventListener.getPreEvents().isEmpty());
+  }
+
+  @Test
+  void testSemanticModelInfoPropertiesAreImmutable() {
+    Map<String, String> mutableProperties = new HashMap<>();
+    mutableProperties.put("a", "b");
+    SemanticModelInfo info =
+        new SemanticModelInfo("model", "comment", definition(), 
mutableProperties, null);
+
+    mutableProperties.put("c", "d");
+    Assertions.assertEquals(ImmutableMap.of("a", "b"), info.properties());
+    Assertions.assertThrows(
+        UnsupportedOperationException.class, () -> info.properties().put("e", 
"f"));
+  }
+
+  @Test
+  void testSemanticModelInfoDefaultsNullPropertiesToEmptyMap() {
+    SemanticModelInfo info = new SemanticModelInfo("model", null, 
definition(), null, null);
+
+    Assertions.assertEquals(ImmutableMap.of(), info.properties());
+    Assertions.assertNull(info.comment());
+    Assertions.assertNull(info.auditInfo());
+  }
+
+  @Test
+  void testAlterSemanticModelEventCopiesChanges() {
+    NameIdentifier identifier = NameIdentifier.of(NAMESPACE, 
semanticModel.name());
+    SemanticModelChange[] changes = {SemanticModelChange.setProperty("a", 
"b")};
+    AlterSemanticModelEvent event =
+        new AlterSemanticModelEvent(
+            "user", identifier, changes, new SemanticModelInfo(semanticModel));
+
+    changes[0] = SemanticModelChange.rename("renamed");
+    Assertions.assertEquals(
+        SemanticModelChange.setProperty("a", "b"), 
event.semanticModelChanges()[0]);
+  }
+
+  @Test
+  void testAlterSuccessChangesAreIsolatedBetweenListeners() {
+    SemanticModelChange[] changes = {SemanticModelChange.setProperty("a", 
"b")};
+    AlterSemanticModelEvent event =
+        new AlterSemanticModelEvent(
+            "user",
+            NameIdentifier.of(NAMESPACE, "model"),
+            changes,
+            new SemanticModelInfo(semanticModel));
+    changes[0] = SemanticModelChange.rename("caller_mutation");
+    assertChangesIsolatedBetweenListeners(
+        event, e -> ((AlterSemanticModelEvent) e).semanticModelChanges());
+  }
+
+  @Test
+  void testAlterPreChangesAreIsolatedBetweenListeners() {
+    SemanticModelChange[] changes = {SemanticModelChange.setProperty("a", 
"b")};
+    AlterSemanticModelPreEvent event =
+        new AlterSemanticModelPreEvent("user", NameIdentifier.of(NAMESPACE, 
"model"), changes);
+    changes[0] = SemanticModelChange.rename("caller_mutation");
+    assertChangesIsolatedBetweenListeners(
+        event, e -> ((AlterSemanticModelPreEvent) e).semanticModelChanges());
+  }
+
+  @Test
+  void testAlterFailureChangesAreIsolatedBetweenListeners() {
+    SemanticModelChange[] changes = {SemanticModelChange.setProperty("a", 
"b")};
+    AlterSemanticModelFailureEvent event =
+        new AlterSemanticModelFailureEvent(
+            "user",
+            NameIdentifier.of(NAMESPACE, "model"),
+            new GravitinoRuntimeException("alter failed"),
+            changes);
+    changes[0] = SemanticModelChange.rename("caller_mutation");
+    assertChangesIsolatedBetweenListeners(
+        event, e -> ((AlterSemanticModelFailureEvent) 
e).semanticModelChanges());
+  }
+
+  @Test
+  void testAlterNullChangesPreservesValidationAndFailureEvent() {
+    assertInvalidAlterChangesEmitFailure(null);
+  }
+
+  @Test
+  void testAlterEmptyChangesPreservesValidationAndFailureEvent() {
+    assertInvalidAlterChangesEmitFailure(new SemanticModelChange[0]);
+  }
+
+  @Test
+  void testNullListResultReportsUnknownCount() {
+    SemanticModelDispatcher underlying = mock(SemanticModelDispatcher.class);
+    when(underlying.listSemanticModels(NAMESPACE)).thenReturn(null);
+    DummyEventListener listener = new DummyEventListener();
+    SemanticModelEventDispatcher eventDispatcher =
+        new SemanticModelEventDispatcher(new 
EventBus(Arrays.asList(listener)), underlying);
+
+    Assertions.assertNull(eventDispatcher.listSemanticModels(NAMESPACE));
+    ListSemanticModelEvent event =
+        Assertions.assertInstanceOf(ListSemanticModelEvent.class, 
listener.popPostEvent());
+    Assertions.assertEquals(-1, event.resultCount());
+  }
+
+  @Test
+  void testSuccessEventsRetainInitiatingUser() {
+    assertEventsRetainInitiatingUser(false);
+  }
+
+  @Test
+  void testFailureEventsRetainInitiatingUser() {
+    assertEventsRetainInitiatingUser(true);
+  }
+
+  private void assertEventsRetainInitiatingUser(boolean fail) {
+    NameIdentifier identifier = NameIdentifier.of(NAMESPACE, "model");
+    for (Consumer<SemanticModelEventDispatcher> operation :
+        Arrays.<Consumer<SemanticModelEventDispatcher>>asList(
+            d -> d.listSemanticModels(NAMESPACE),
+            d -> d.loadSemanticModel(identifier),
+            d -> d.createSemanticModel(identifier, "comment", definition(), 
ImmutableMap.of()),
+            d -> d.alterSemanticModel(identifier, 
SemanticModelChange.setProperty("a", "b")),
+            d -> d.dropSemanticModel(identifier))) {
+      DummyEventListener listener = new DummyEventListener();
+      SemanticModelEventDispatcher eventDispatcher =
+          new SemanticModelEventDispatcher(
+              new EventBus(Arrays.asList(listener)),
+              fail ? mockExceptionSemanticModelDispatcher() : 
mockSemanticModelDispatcher());
+      try (MockedStatic<PrincipalUtils> principal = 
mockStatic(PrincipalUtils.class)) {
+        
principal.when(PrincipalUtils::getCurrentUserName).thenReturn("initiator", 
"changed_user");
+        if (fail) {
+          Assertions.assertThrows(
+              GravitinoRuntimeException.class, () -> 
operation.accept(eventDispatcher));
+        } else {
+          operation.accept(eventDispatcher);
+        }
+        Assertions.assertEquals("initiator", listener.popPreEvent().user());
+        Assertions.assertEquals("initiator", listener.popPostEvent().user());
+        principal.verify(PrincipalUtils::getCurrentUserName);
+      }
+    }
+  }
+
+  private void assertInvalidAlterChangesEmitFailure(SemanticModelChange[] 
changes) {
+    DummyEventListener listener = new DummyEventListener();
+    SemanticModelDispatcher underlyingDispatcher = 
mock(SemanticModelDispatcher.class);
+    CatalogManager catalogManager = mock(CatalogManager.class);
+    SemanticModelEventDispatcher eventDispatcher =
+        new SemanticModelEventDispatcher(
+            new EventBus(Arrays.asList(listener)),
+            new SemanticModelNormalizeDispatcher(underlyingDispatcher, 
catalogManager));
+    NameIdentifier identifier = NameIdentifier.of(NAMESPACE, "model");
+
+    IllegalArgumentException exception =
+        Assertions.assertThrows(
+            IllegalArgumentException.class,
+            () -> eventDispatcher.alterSemanticModel(identifier, changes));
+
+    Assertions.assertEquals("At least one change is required", 
exception.getMessage());
+    Assertions.assertEquals(1, listener.getPreEvents().size());
+    Assertions.assertEquals(1, listener.getPostEvents().size());
+    AlterSemanticModelPreEvent preEvent =
+        Assertions.assertInstanceOf(AlterSemanticModelPreEvent.class, 
listener.popPreEvent());
+    AlterSemanticModelFailureEvent failureEvent =
+        Assertions.assertInstanceOf(AlterSemanticModelFailureEvent.class, 
listener.popPostEvent());
+    Assertions.assertEquals(identifier, preEvent.identifier());
+    Assertions.assertEquals(identifier, failureEvent.identifier());
+    Assertions.assertSame(exception, failureEvent.exception());
+    Assertions.assertArrayEquals(changes, preEvent.semanticModelChanges());
+    Assertions.assertArrayEquals(changes, failureEvent.semanticModelChanges());
+    verifyNoInteractions(underlyingDispatcher, catalogManager);
+  }
+
+  private void assertChangesIsolatedBetweenListeners(
+      BaseEvent event, Function<BaseEvent, SemanticModelChange[]> 
changesAccessor) {
+    DummyEventListener mutatingListener =
+        new DummyEventListener() {
+          @Override
+          public void onPreEvent(PreEvent preEvent) {
+            changesAccessor.apply(preEvent)[0] = 
SemanticModelChange.rename("listener_mutation");
+          }
+
+          @Override
+          public void onPostEvent(Event postEvent) {
+            changesAccessor.apply(postEvent)[0] = 
SemanticModelChange.rename("listener_mutation");
+          }
+        };
+    DummyEventListener observingListener = new DummyEventListener();
+    EventBus eventBus = new EventBus(Arrays.asList(mutatingListener, 
observingListener));
+
+    eventBus.dispatchEvent(event);
+
+    BaseEvent observedEvent =
+        event instanceof PreEvent
+            ? observingListener.popPreEvent()
+            : observingListener.popPostEvent();
+    Assertions.assertSame(event, observedEvent);
+    Assertions.assertArrayEquals(
+        new SemanticModelChange[] {SemanticModelChange.setProperty("a", "b")},
+        changesAccessor.apply(observedEvent));
+  }
+
+  private void checkSemanticModelInfo(SemanticModelInfo info, SemanticModel 
semanticModel) {
+    Assertions.assertEquals(semanticModel.name(), info.name());
+    Assertions.assertEquals(semanticModel.comment(), info.comment());
+    Assertions.assertEquals(semanticModel.definition(), info.definition());
+    Assertions.assertEquals(semanticModel.properties(), info.properties());
+    Assertions.assertEquals(semanticModel.auditInfo(), info.auditInfo());
+  }
+
+  private static SemanticModelDefinition definition() {
+    Dataset dataset =
+        Dataset.builder()
+            .withName("orders")
+            .withSource(NameIdentifier.of("sales", "mart", "orders"))
+            .build();
+    return SemanticModelDefinition.builder().withDatasets(new Dataset[] 
{dataset}).build();
+  }
+
+  private SemanticModel mockSemanticModel() {
+    SemanticModel model = mock(SemanticModel.class);
+    when(model.name()).thenReturn("sales_model");
+    when(model.comment()).thenReturn("comment");
+    when(model.definition()).thenReturn(definition());
+    when(model.properties()).thenReturn(ImmutableMap.of("a", "b"));
+    when(model.auditInfo()).thenReturn(null);
+    return model;
+  }
+
+  private SemanticModelDispatcher mockSemanticModelDispatcher() {
+    SemanticModelDispatcher d = mock(SemanticModelDispatcher.class);
+    when(d.createSemanticModel(
+            any(NameIdentifier.class), any(), 
any(SemanticModelDefinition.class), any(Map.class)))
+        .thenReturn(semanticModel);
+    
when(d.loadSemanticModel(any(NameIdentifier.class))).thenReturn(semanticModel);
+    when(d.semanticModelExists(any(NameIdentifier.class))).thenReturn(true);
+    when(d.dropSemanticModel(any(NameIdentifier.class))).thenReturn(true);
+    when(d.listSemanticModels(any(Namespace.class)))
+        .thenReturn(
+            new NameIdentifier[] {
+              NameIdentifier.of(NAMESPACE, "model1"), 
NameIdentifier.of(NAMESPACE, "model2")
+            });
+    when(d.alterSemanticModel(any(NameIdentifier.class), 
any(SemanticModelChange.class)))
+        .thenReturn(semanticModel);
+    return d;
+  }
+
+  private SemanticModelDispatcher mockExceptionSemanticModelDispatcher() {
+    return mock(
+        SemanticModelDispatcher.class,
+        invocation -> {
+          throw new GravitinoRuntimeException("Exception for all methods");
+        });
+  }
+}

Reply via email to