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");
+ });
+ }
+}