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

jerryshao pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/branch-1.3 by this push:
     new b3eaaaa54b [Cherry-pick to branch-1.3] [#11172] fix(audit): skip audit 
events for internal Iceberg REST metadata operations    (#11188) (#11440)
b3eaaaa54b is described below

commit b3eaaaa54b7965fa6b6211e37be8c3d6c269b2c1
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Fri Jun 5 11:33:49 2026 +0800

    [Cherry-pick to branch-1.3] [#11172] fix(audit): skip audit events for 
internal Iceberg REST metadata operations    (#11188) (#11440)
    
    **Cherry-pick Information:**
    - Original commit: a2839877d3e833dc0b5ed852043c92504fe602bb
    - Target branch: `branch-1.3`
    - Status: ✅ Clean cherry-pick (no conflicts)
    
    Co-authored-by: Shane <[email protected]>
---
 .../java/org/apache/gravitino/GravitinoEnv.java    | 98 +++++++++++++++++++++
 .../catalog/TableOperationDispatcher.java          | 36 +++++++-
 .../gravitino/catalog/ViewOperationDispatcher.java | 35 +++++++-
 .../catalog/TestTableOperationDispatcher.java      | 38 +++++++++
 .../catalog/TestViewOperationDispatcher.java       | 36 ++++++++
 .../dispatcher/IcebergNamespaceHookDispatcher.java |  8 +-
 .../dispatcher/IcebergTableHookDispatcher.java     |  4 +-
 .../dispatcher/IcebergViewHookDispatcher.java      |  4 +-
 .../provider/DynamicIcebergConfigProvider.java     |  4 +-
 .../TestIcebergNamespaceHookDispatcher.java        | 99 +++++++++++++++++++---
 .../dispatcher/TestIcebergTableHookDispatcher.java | 69 ++++++++++++---
 .../dispatcher/TestIcebergViewHookDispatcher.java  | 41 +++++++--
 .../provider/TestDynamicIcebergConfigProvider.java | 64 ++++++++++----
 .../authorization/PassThroughAuthorizer.java       |  3 +-
 .../authorization/TestPassThroughAuthorizer.java   | 35 ++++++++
 .../jcasbin/TestJcasbinAuthorizer.java             |  6 ++
 16 files changed, 513 insertions(+), 67 deletions(-)

diff --git a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java 
b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
index df80fad2ca..c845c43c49 100644
--- a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
+++ b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
@@ -120,14 +120,17 @@ public class GravitinoEnv {
   private EntityStore entityStore;
 
   private CatalogDispatcher catalogDispatcher;
+  private CatalogDispatcher internalCatalogDispatcher;
 
   private CatalogManager catalogManager;
 
   private MetalakeManager metalakeManager;
 
   private SchemaDispatcher schemaDispatcher;
+  private SchemaDispatcher internalSchemaDispatcher;
 
   private TableDispatcher tableDispatcher;
+  private TableDispatcher internalTableDispatcher;
 
   private PartitionDispatcher partitionDispatcher;
 
@@ -140,6 +143,7 @@ public class GravitinoEnv {
   private FunctionDispatcher functionDispatcher;
 
   private ViewDispatcher viewDispatcher;
+  private ViewDispatcher internalViewDispatcher;
 
   private MetalakeDispatcher metalakeDispatcher;
 
@@ -150,6 +154,7 @@ public class GravitinoEnv {
   private PolicyDispatcher policyDispatcher;
 
   private AccessControlDispatcher accessControlDispatcher;
+  private AccessControlDispatcher internalAccessControlDispatcher;
 
   private IdGenerator idGenerator;
 
@@ -167,6 +172,7 @@ public class GravitinoEnv {
 
   private EventBus eventBus;
   private OwnerDispatcher ownerDispatcher;
+  private OwnerDispatcher internalOwnerDispatcher;
   private FutureGrantManager futureGrantManager;
   private GravitinoAuthorizer gravitinoAuthorizer;
   private StatisticDispatcher statisticDispatcher;
@@ -241,6 +247,19 @@ public class GravitinoEnv {
     return catalogDispatcher;
   }
 
+  /**
+   * Get the internal CatalogDispatcher associated with the Gravitino 
environment.
+   *
+   * <p>The internal dispatcher preserves normalization but skips hooks and 
event emission. It is
+   * intended for infrastructure catalog lookups that should not be recorded 
as user API audit
+   * events.
+   *
+   * @return The internal CatalogDispatcher instance.
+   */
+  public CatalogDispatcher internalCatalogDispatcher() {
+    return internalCatalogDispatcher;
+  }
+
   /**
    * Get the SchemaDispatcher associated with the Gravitino environment.
    *
@@ -250,6 +269,19 @@ public class GravitinoEnv {
     return schemaDispatcher;
   }
 
+  /**
+   * Get the internal SchemaDispatcher associated with the Gravitino 
environment.
+   *
+   * <p>The internal dispatcher preserves normalization but skips hooks and 
event emission. It is
+   * intended for infrastructure code that synchronizes metadata as part of 
another user-visible
+   * operation.
+   *
+   * @return The internal SchemaDispatcher instance.
+   */
+  public SchemaDispatcher internalSchemaDispatcher() {
+    return internalSchemaDispatcher;
+  }
+
   /**
    * Get the TableDispatcher associated with the Gravitino environment.
    *
@@ -259,6 +291,19 @@ public class GravitinoEnv {
     return tableDispatcher;
   }
 
+  /**
+   * Get the internal TableDispatcher associated with the Gravitino 
environment.
+   *
+   * <p>The internal dispatcher preserves normalization but skips hooks and 
event emission. It is
+   * intended for infrastructure code that synchronizes metadata as part of 
another user-visible
+   * operation.
+   *
+   * @return The internal TableDispatcher instance.
+   */
+  public TableDispatcher internalTableDispatcher() {
+    return internalTableDispatcher;
+  }
+
   /**
    * Get the ModelDispatcher associated with the Gravitino environment.
    *
@@ -286,6 +331,19 @@ public class GravitinoEnv {
     return viewDispatcher;
   }
 
+  /**
+   * Get the internal ViewDispatcher associated with the Gravitino environment.
+   *
+   * <p>The internal dispatcher preserves normalization but skips hook/event 
side effects from
+   * dependent schema lookups. It is intended for infrastructure code that 
synchronizes metadata as
+   * part of another user-visible operation.
+   *
+   * @return The internal ViewDispatcher instance.
+   */
+  public ViewDispatcher internalViewDispatcher() {
+    return internalViewDispatcher;
+  }
+
   /**
    * * Get the PartitionDispatcher associated with the Gravitino environment.
    *
@@ -382,6 +440,18 @@ public class GravitinoEnv {
     return accessControlDispatcher;
   }
 
+  /**
+   * Get the internal AccessControlDispatcher associated with the Gravitino 
environment.
+   *
+   * <p>The internal dispatcher skips hooks and event emission. It is intended 
for authorization
+   * infrastructure lookups that should not be recorded as user API audit 
events.
+   *
+   * @return The internal AccessControlDispatcher instance.
+   */
+  public AccessControlDispatcher internalAccessControlDispatcher() {
+    return internalAccessControlDispatcher;
+  }
+
   /**
    * Get the tagDispatcher associated with the Gravitino environment.
    *
@@ -409,6 +479,18 @@ public class GravitinoEnv {
     return ownerDispatcher;
   }
 
+  /**
+   * Get the internal OwnerDispatcher associated with the Gravitino 
environment.
+   *
+   * <p>The internal dispatcher skips event emission. It is intended for 
infrastructure ownership
+   * synchronization that happens as part of another user-visible operation.
+   *
+   * @return The internal OwnerDispatcher instance.
+   */
+  public OwnerDispatcher internalOwnerDispatcher() {
+    return internalOwnerDispatcher;
+  }
+
   /**
    * Get the FutureGrantManager associated with the Gravitino environment.
    *
@@ -569,6 +651,7 @@ public class GravitinoEnv {
     this.catalogManager = new CatalogManager(config, entityStore, idGenerator);
     CatalogNormalizeDispatcher catalogNormalizeDispatcher =
         new CatalogNormalizeDispatcher(catalogManager);
+    this.internalCatalogDispatcher = catalogNormalizeDispatcher;
     CatalogEventDispatcher catalogEventDispatcher =
         new CatalogEventDispatcher(eventBus, catalogNormalizeDispatcher);
     this.catalogDispatcher = new CatalogHookDispatcher(catalogEventDispatcher);
@@ -580,6 +663,7 @@ public class GravitinoEnv {
         new SchemaOperationDispatcher(catalogManager, entityStore, 
idGenerator);
     SchemaNormalizeDispatcher schemaNormalizeDispatcher =
         new SchemaNormalizeDispatcher(schemaOperationDispatcher, 
catalogManager);
+    this.internalSchemaDispatcher = schemaNormalizeDispatcher;
     SchemaEventDispatcher schemaEventDispatcher =
         new SchemaEventDispatcher(eventBus, schemaNormalizeDispatcher);
     this.schemaDispatcher = new SchemaHookDispatcher(schemaEventDispatcher);
@@ -588,6 +672,11 @@ public class GravitinoEnv {
         new TableOperationDispatcher(catalogManager, entityStore, idGenerator);
     TableNormalizeDispatcher tableNormalizeDispatcher =
         new TableNormalizeDispatcher(tableOperationDispatcher, catalogManager);
+    TableOperationDispatcher internalTableOperationDispatcher =
+        new TableOperationDispatcher(
+            catalogManager, entityStore, idGenerator, () -> 
internalSchemaDispatcher);
+    this.internalTableDispatcher =
+        new TableNormalizeDispatcher(internalTableOperationDispatcher, 
catalogManager);
     TableEventDispatcher tableEventDispatcher =
         new TableEventDispatcher(eventBus, tableNormalizeDispatcher);
     this.tableDispatcher = new TableHookDispatcher(tableEventDispatcher);
@@ -644,6 +733,11 @@ public class GravitinoEnv {
         new ViewOperationDispatcher(catalogManager, entityStore, idGenerator);
     ViewNormalizeDispatcher viewNormalizeDispatcher =
         new ViewNormalizeDispatcher(viewOperationDispatcher, catalogManager);
+    ViewOperationDispatcher internalViewOperationDispatcher =
+        new ViewOperationDispatcher(
+            catalogManager, entityStore, idGenerator, () -> 
internalSchemaDispatcher);
+    this.internalViewDispatcher =
+        new ViewNormalizeDispatcher(internalViewOperationDispatcher, 
catalogManager);
     ViewEventDispatcher viewEventDispatcher =
         new ViewEventDispatcher(eventBus, viewNormalizeDispatcher);
     this.viewDispatcher = viewEventDispatcher;
@@ -657,15 +751,19 @@ public class GravitinoEnv {
     if (enableAuthorization) {
       AccessControlManager accessControlManager =
           new AccessControlManager(entityStore, idGenerator, config);
+      this.internalAccessControlDispatcher = accessControlManager;
       AccessControlEventDispatcher accessControlEventDispatcher =
           new AccessControlEventDispatcher(eventBus, accessControlManager);
       this.accessControlDispatcher = new 
AccessControlHookDispatcher(accessControlEventDispatcher);
       OwnerDispatcher ownerManager = new OwnerManager(entityStore);
+      this.internalOwnerDispatcher = ownerManager;
       this.ownerDispatcher = new OwnerEventManager(eventBus, ownerManager);
       this.futureGrantManager = new FutureGrantManager(entityStore, 
ownerManager);
     } else {
       this.accessControlDispatcher = null;
+      this.internalAccessControlDispatcher = null;
       this.ownerDispatcher = null;
+      this.internalOwnerDispatcher = null;
       this.futureGrantManager = null;
     }
 
diff --git 
a/core/src/main/java/org/apache/gravitino/catalog/TableOperationDispatcher.java 
b/core/src/main/java/org/apache/gravitino/catalog/TableOperationDispatcher.java
index 244edec88d..34677f3e31 100644
--- 
a/core/src/main/java/org/apache/gravitino/catalog/TableOperationDispatcher.java
+++ 
b/core/src/main/java/org/apache/gravitino/catalog/TableOperationDispatcher.java
@@ -26,6 +26,7 @@ import static 
org.apache.gravitino.utils.NameIdentifierUtil.getCatalogIdentifier
 import static 
org.apache.gravitino.utils.NameIdentifierUtil.getSchemaIdentifier;
 
 import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
 import com.google.common.collect.Lists;
 import java.time.Instant;
 import java.util.Arrays;
@@ -33,6 +34,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Function;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 import org.apache.commons.lang3.tuple.Pair;
@@ -72,6 +74,8 @@ public class TableOperationDispatcher extends 
OperationDispatcher implements Tab
 
   private static final Logger LOG = 
LoggerFactory.getLogger(TableOperationDispatcher.class);
 
+  private final Supplier<SchemaDispatcher> schemaDispatcherSupplier;
+
   /**
    * Creates a new TableOperationDispatcher instance.
    *
@@ -81,7 +85,26 @@ public class TableOperationDispatcher extends 
OperationDispatcher implements Tab
    */
   public TableOperationDispatcher(
       CatalogManager catalogManager, EntityStore store, IdGenerator 
idGenerator) {
+    this(catalogManager, store, idGenerator, () -> 
GravitinoEnv.getInstance().schemaDispatcher());
+  }
+
+  /**
+   * Creates a new TableOperationDispatcher instance.
+   *
+   * @param catalogManager The CatalogManager instance to be used for table 
operations.
+   * @param store The EntityStore instance to be used for table operations.
+   * @param idGenerator The IdGenerator instance to be used for table 
operations.
+   * @param schemaDispatcherSupplier The SchemaDispatcher supplier to ensure 
schemas are imported.
+   */
+  public TableOperationDispatcher(
+      CatalogManager catalogManager,
+      EntityStore store,
+      IdGenerator idGenerator,
+      Supplier<SchemaDispatcher> schemaDispatcherSupplier) {
     super(catalogManager, store, idGenerator);
+    this.schemaDispatcherSupplier =
+        Preconditions.checkNotNull(
+            schemaDispatcherSupplier, "schemaDispatcherSupplier must not be 
null");
   }
 
   /**
@@ -118,7 +141,7 @@ public class TableOperationDispatcher extends 
OperationDispatcher implements Tab
 
     if (!entityCombinedTable.imported()) {
       // Load the schema to make sure the schema is imported.
-      SchemaDispatcher schemaDispatcher = 
GravitinoEnv.getInstance().schemaDispatcher();
+      SchemaDispatcher schemaDispatcher = getSchemaDispatcher();
       NameIdentifier schemaIdent = 
NameIdentifier.of(ident.namespace().levels());
       schemaDispatcher.loadSchema(schemaIdent);
 
@@ -180,7 +203,7 @@ public class TableOperationDispatcher extends 
OperationDispatcher implements Tab
       throws NoSuchSchemaException, TableAlreadyExistsException {
 
     // Load the schema to make sure the schema exists.
-    SchemaDispatcher schemaDispatcher = 
GravitinoEnv.getInstance().schemaDispatcher();
+    SchemaDispatcher schemaDispatcher = getSchemaDispatcher();
     NameIdentifier schemaIdent = NameIdentifier.of(ident.namespace().levels());
     schemaDispatcher.loadSchema(schemaIdent);
 
@@ -505,6 +528,15 @@ public class TableOperationDispatcher extends 
OperationDispatcher implements Tab
                 table.tableFromCatalog().properties()));
   }
 
+  private SchemaDispatcher getSchemaDispatcher() {
+    SchemaDispatcher schemaDispatcher = schemaDispatcherSupplier.get();
+    Preconditions.checkArgument(
+        schemaDispatcher != null,
+        "schemaDispatcherSupplier returned null. "
+            + "SchemaDispatcher must be available for table operations.");
+    return schemaDispatcher;
+  }
+
   private EntityCombinedTable internalLoadTable(NameIdentifier ident) {
     NameIdentifier catalogIdentifier = getCatalogIdentifier(ident);
     Table table =
diff --git 
a/core/src/main/java/org/apache/gravitino/catalog/ViewOperationDispatcher.java 
b/core/src/main/java/org/apache/gravitino/catalog/ViewOperationDispatcher.java
index a7b9ab1416..5b3937ee48 100644
--- 
a/core/src/main/java/org/apache/gravitino/catalog/ViewOperationDispatcher.java
+++ 
b/core/src/main/java/org/apache/gravitino/catalog/ViewOperationDispatcher.java
@@ -29,6 +29,7 @@ import java.time.Instant;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.function.Supplier;
 import javax.annotation.Nullable;
 import org.apache.gravitino.EntityAlreadyExistsException;
 import org.apache.gravitino.EntityStore;
@@ -60,6 +61,8 @@ public class ViewOperationDispatcher extends 
OperationDispatcher implements View
 
   private static final Logger LOG = 
LoggerFactory.getLogger(ViewOperationDispatcher.class);
 
+  private final Supplier<SchemaDispatcher> schemaDispatcherSupplier;
+
   /**
    * Creates a new ViewOperationDispatcher instance.
    *
@@ -69,7 +72,26 @@ public class ViewOperationDispatcher extends 
OperationDispatcher implements View
    */
   public ViewOperationDispatcher(
       CatalogManager catalogManager, EntityStore store, IdGenerator 
idGenerator) {
+    this(catalogManager, store, idGenerator, () -> 
GravitinoEnv.getInstance().schemaDispatcher());
+  }
+
+  /**
+   * Creates a new ViewOperationDispatcher instance.
+   *
+   * @param catalogManager The CatalogManager instance to be used for view 
operations.
+   * @param store The EntityStore instance to be used for view operations.
+   * @param idGenerator The IdGenerator instance to be used for view 
operations.
+   * @param schemaDispatcherSupplier The SchemaDispatcher supplier to ensure 
schemas are imported.
+   */
+  public ViewOperationDispatcher(
+      CatalogManager catalogManager,
+      EntityStore store,
+      IdGenerator idGenerator,
+      Supplier<SchemaDispatcher> schemaDispatcherSupplier) {
     super(catalogManager, store, idGenerator);
+    this.schemaDispatcherSupplier =
+        Preconditions.checkNotNull(
+            schemaDispatcherSupplier, "schemaDispatcherSupplier must not be 
null");
   }
 
   /**
@@ -107,7 +129,7 @@ public class ViewOperationDispatcher extends 
OperationDispatcher implements View
         TreeLockUtils.doWithTreeLock(ident, LockType.READ, () -> 
internalLoadView(ident));
 
     if (!entityCombinedView.imported()) {
-      SchemaDispatcher schemaDispatcher = 
GravitinoEnv.getInstance().schemaDispatcher();
+      SchemaDispatcher schemaDispatcher = getSchemaDispatcher();
       NameIdentifier schemaIdent = 
NameIdentifier.of(ident.namespace().levels());
       schemaDispatcher.loadSchema(schemaIdent);
 
@@ -148,7 +170,7 @@ public class ViewOperationDispatcher extends 
OperationDispatcher implements View
         "representations must not be null or empty");
 
     // Load the schema to make sure the schema exists.
-    SchemaDispatcher schemaDispatcher = 
GravitinoEnv.getInstance().schemaDispatcher();
+    SchemaDispatcher schemaDispatcher = getSchemaDispatcher();
     NameIdentifier schemaIdent = NameIdentifier.of(ident.namespace().levels());
     schemaDispatcher.loadSchema(schemaIdent);
 
@@ -527,6 +549,15 @@ public class ViewOperationDispatcher extends 
OperationDispatcher implements View
         .withImported(true);
   }
 
+  private SchemaDispatcher getSchemaDispatcher() {
+    SchemaDispatcher schemaDispatcher = schemaDispatcherSupplier.get();
+    Preconditions.checkArgument(
+        schemaDispatcher != null,
+        "schemaDispatcherSupplier returned null. "
+            + "SchemaDispatcher must be available for view operations.");
+    return schemaDispatcher;
+  }
+
   private ViewEntity applyChangesToEntity(
       ViewEntity current, View alteredView, ViewChange[] changes) {
     String name = alteredView.name();
diff --git 
a/core/src/test/java/org/apache/gravitino/catalog/TestTableOperationDispatcher.java
 
b/core/src/test/java/org/apache/gravitino/catalog/TestTableOperationDispatcher.java
index 9c44e46c7e..10b21dc41d 100644
--- 
a/core/src/test/java/org/apache/gravitino/catalog/TestTableOperationDispatcher.java
+++ 
b/core/src/test/java/org/apache/gravitino/catalog/TestTableOperationDispatcher.java
@@ -43,6 +43,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.function.Function;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 import org.apache.commons.lang3.reflect.FieldUtils;
@@ -258,6 +259,43 @@ public class TestTableOperationDispatcher extends 
TestOperationDispatcher {
     Assertions.assertEquals("test", loadedTable4.auditInfo().creator());
   }
 
+  @Test
+  public void 
testTableOperationDispatcherRejectsNullSchemaDispatcherSupplier() {
+    Assertions.assertThrows(
+        NullPointerException.class,
+        () -> new TableOperationDispatcher(catalogManager, entityStore, 
idGenerator, null));
+  }
+
+  @Test
+  public void 
testCreateTableFailsFastWhenSchemaDispatcherSupplierReturnsNull() throws 
IOException {
+    Namespace tableNs = Namespace.of(metalake, catalog, 
"schema-null-dispatcher");
+    Map<String, String> props = ImmutableMap.of("k1", "v1", "k2", "v2");
+    
schemaOperationDispatcher.createSchema(NameIdentifier.of(tableNs.levels()), 
"comment", props);
+
+    Supplier<SchemaDispatcher> nullSchemaDispatcherSupplier = () -> null;
+    TableOperationDispatcher dispatcher =
+        new TableOperationDispatcher(
+            catalogManager, entityStore, idGenerator, 
nullSchemaDispatcherSupplier);
+    NameIdentifier tableIdent = NameIdentifier.of(tableNs, 
"table_null_dispatcher");
+    Column[] columns =
+        new Column[] {
+          TestColumn.builder()
+              .withName("col1")
+              .withPosition(0)
+              .withType(Types.StringType.get())
+              .build()
+        };
+
+    IllegalArgumentException exception =
+        Assertions.assertThrows(
+            IllegalArgumentException.class,
+            () -> dispatcher.createTable(tableIdent, columns, "comment", 
props, new Transform[0]));
+    Assertions.assertEquals(
+        "schemaDispatcherSupplier returned null. "
+            + "SchemaDispatcher must be available for table operations.",
+        exception.getMessage());
+  }
+
   @Test
   public void testConcurrentImportTableReusesExistingEntity() throws 
IOException {
     Namespace tableNs = Namespace.of(metalake, catalog, "schema52");
diff --git 
a/core/src/test/java/org/apache/gravitino/catalog/TestViewOperationDispatcher.java
 
b/core/src/test/java/org/apache/gravitino/catalog/TestViewOperationDispatcher.java
index a3c4146e8a..5f28bcd20e 100644
--- 
a/core/src/test/java/org/apache/gravitino/catalog/TestViewOperationDispatcher.java
+++ 
b/core/src/test/java/org/apache/gravitino/catalog/TestViewOperationDispatcher.java
@@ -40,6 +40,7 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
 import org.apache.commons.lang3.reflect.FieldUtils;
 import org.apache.gravitino.Config;
 import org.apache.gravitino.Entity;
@@ -198,6 +199,41 @@ public class TestViewOperationDispatcher extends 
TestOperationDispatcher {
     }
   }
 
+  @Test
+  public void testViewOperationDispatcherRejectsNullSchemaDispatcherSupplier() 
{
+    Assertions.assertThrows(
+        NullPointerException.class,
+        () -> new ViewOperationDispatcher(catalogManager, entityStore, 
idGenerator, null));
+  }
+
+  @Test
+  public void testCreateViewFailsFastWhenSchemaDispatcherSupplierReturnsNull() 
throws IOException {
+    Namespace viewNs = Namespace.of(metalake, catalog, 
"schema-null-view-dispatcher");
+    Map<String, String> props = ImmutableMap.of("k1", "v1", "k2", "v2");
+    schemaOperationDispatcher.createSchema(NameIdentifier.of(viewNs.levels()), 
"comment", props);
+
+    Supplier<SchemaDispatcher> nullSchemaDispatcherSupplier = () -> null;
+    ViewOperationDispatcher dispatcher =
+        new ViewOperationDispatcher(
+            catalogManager, entityStore, idGenerator, 
nullSchemaDispatcherSupplier);
+    NameIdentifier viewIdent = NameIdentifier.of(viewNs, 
"view_null_dispatcher");
+    Representation[] representations =
+        new Representation[] {
+          SQLRepresentation.builder().withDialect("spark-sql").withSql("SELECT 
1").build()
+        };
+
+    IllegalArgumentException exception =
+        Assertions.assertThrows(
+            IllegalArgumentException.class,
+            () ->
+                dispatcher.createView(
+                    viewIdent, "comment", new Column[0], representations, 
null, null, props));
+    Assertions.assertEquals(
+        "schemaDispatcherSupplier returned null. "
+            + "SchemaDispatcher must be available for view operations.",
+        exception.getMessage());
+  }
+
   @Test
   public void testLoadViewAutoImportsIntoEntityStore() throws IOException {
     Namespace viewNs = Namespace.of(metalake, catalog, "schema63");
diff --git 
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergNamespaceHookDispatcher.java
 
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergNamespaceHookDispatcher.java
index 505b5dc87e..e316352339 100644
--- 
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergNamespaceHookDispatcher.java
+++ 
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergNamespaceHookDispatcher.java
@@ -96,7 +96,7 @@ public class IcebergNamespaceHookDispatcher implements 
IcebergNamespaceOperation
         catalogName,
         newlyOwned,
         context.userName(),
-        GravitinoEnv.getInstance().ownerDispatcher());
+        GravitinoEnv.getInstance().internalOwnerDispatcher());
     return createNamespaceResponse;
   }
 
@@ -228,13 +228,13 @@ public class IcebergNamespaceHookDispatcher implements 
IcebergNamespaceOperation
         namespace,
         registerTableRequest.name(),
         context.userName(),
-        GravitinoEnv.getInstance().ownerDispatcher());
+        GravitinoEnv.getInstance().internalOwnerDispatcher());
 
     return response;
   }
 
   private void importTable(String catalogName, Namespace namespace, String 
tableName) {
-    TableDispatcher tableDispatcher = 
GravitinoEnv.getInstance().tableDispatcher();
+    TableDispatcher tableDispatcher = 
GravitinoEnv.getInstance().internalTableDispatcher();
     if (tableDispatcher != null) {
       tableDispatcher.loadTable(
           IcebergIdentifierUtils.toGravitinoTableIdentifier(
@@ -246,7 +246,7 @@ public class IcebergNamespaceHookDispatcher implements 
IcebergNamespaceOperation
   }
 
   private void importSchema(String catalogName, Namespace namespace) {
-    SchemaDispatcher schemaDispatcher = 
GravitinoEnv.getInstance().schemaDispatcher();
+    SchemaDispatcher schemaDispatcher = 
GravitinoEnv.getInstance().internalSchemaDispatcher();
     if (schemaDispatcher != null) {
       schemaDispatcher.loadSchema(
           IcebergIdentifierUtils.toGravitinoSchemaIdentifier(
diff --git 
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergTableHookDispatcher.java
 
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergTableHookDispatcher.java
index 9f081571f5..d39d93f1ae 100644
--- 
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergTableHookDispatcher.java
+++ 
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergTableHookDispatcher.java
@@ -209,11 +209,11 @@ public class IcebergTableHookDispatcher implements 
IcebergTableOperationDispatch
         namespace,
         tableName,
         context.userName(),
-        GravitinoEnv.getInstance().ownerDispatcher());
+        GravitinoEnv.getInstance().internalOwnerDispatcher());
   }
 
   private void importTableEntity(String catalogName, Namespace namespace, 
String tableName) {
-    TableDispatcher tableDispatcher = 
GravitinoEnv.getInstance().tableDispatcher();
+    TableDispatcher tableDispatcher = 
GravitinoEnv.getInstance().internalTableDispatcher();
     if (tableDispatcher != null) {
       tableDispatcher.loadTable(
           IcebergIdentifierUtils.toGravitinoTableIdentifier(
diff --git 
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergViewHookDispatcher.java
 
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergViewHookDispatcher.java
index a6e35fef50..dcde32b92c 100644
--- 
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergViewHookDispatcher.java
+++ 
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/dispatcher/IcebergViewHookDispatcher.java
@@ -76,7 +76,7 @@ public class IcebergViewHookDispatcher implements 
IcebergViewOperationDispatcher
         namespace,
         createViewRequest.name(),
         context.userName(),
-        GravitinoEnv.getInstance().ownerDispatcher());
+        GravitinoEnv.getInstance().internalOwnerDispatcher());
 
     return response;
   }
@@ -181,7 +181,7 @@ public class IcebergViewHookDispatcher implements 
IcebergViewOperationDispatcher
    * @param viewName The name of the view.
    */
   private void importView(String catalogName, Namespace namespace, String 
viewName) {
-    ViewDispatcher viewDispatcher = 
GravitinoEnv.getInstance().viewDispatcher();
+    ViewDispatcher viewDispatcher = 
GravitinoEnv.getInstance().internalViewDispatcher();
     if (viewDispatcher != null) {
       try {
         viewDispatcher.loadView(
diff --git 
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/provider/DynamicIcebergConfigProvider.java
 
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/provider/DynamicIcebergConfigProvider.java
index 9194b4dc07..2bbefbade0 100644
--- 
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/provider/DynamicIcebergConfigProvider.java
+++ 
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/provider/DynamicIcebergConfigProvider.java
@@ -251,10 +251,10 @@ public class DynamicIcebergConfigProvider implements 
IcebergConfigProvider {
 
     InternalCatalogFetcher(String metalake) {
       this.metalake = metalake;
-      CatalogDispatcher dispatcher = 
GravitinoEnv.getInstance().catalogDispatcher();
+      CatalogDispatcher dispatcher = 
GravitinoEnv.getInstance().internalCatalogDispatcher();
       Preconditions.checkState(
           dispatcher != null,
-          "CatalogDispatcher is not available. "
+          "Internal CatalogDispatcher is not available. "
               + "Internal catalog fetcher requires running within Gravitino 
server.");
       this.catalogDispatcher = dispatcher;
     }
diff --git 
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergNamespaceHookDispatcher.java
 
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergNamespaceHookDispatcher.java
index eb212f8e4f..7d5e76922b 100644
--- 
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergNamespaceHookDispatcher.java
+++ 
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergNamespaceHookDispatcher.java
@@ -67,21 +67,65 @@ public class TestIcebergNamespaceHookDispatcher {
   private IcebergNamespaceHookDispatcher hookDispatcher;
   private IcebergNamespaceOperationDispatcher mockDispatcher;
   private OwnerDispatcher mockOwnerDispatcher;
+  private OwnerDispatcher mockInternalOwnerDispatcher;
+  private SchemaDispatcher mockSchemaDispatcher;
+  private SchemaDispatcher mockInternalSchemaDispatcher;
+  private TableDispatcher mockTableDispatcher;
+  private TableDispatcher mockInternalTableDispatcher;
   private EntityStore mockEntityStore;
   private LockManager mockLockManager;
   private IcebergRequestContext mockContext;
 
+  private OwnerDispatcher previousOwnerDispatcher;
+  private OwnerDispatcher previousInternalOwnerDispatcher;
+  private SchemaDispatcher previousSchemaDispatcher;
+  private SchemaDispatcher previousInternalSchemaDispatcher;
+  private TableDispatcher previousTableDispatcher;
+  private TableDispatcher previousInternalTableDispatcher;
+  private EntityStore previousEntityStore;
+  private LockManager previousLockManager;
+
   @BeforeEach
   public void setUp() throws IllegalAccessException {
     mockDispatcher = mock(IcebergNamespaceOperationDispatcher.class);
     mockOwnerDispatcher = mock(OwnerDispatcher.class);
-    SchemaDispatcher mockSchemaDispatcher = mock(SchemaDispatcher.class);
-    TableDispatcher mockTableDispatcher = mock(TableDispatcher.class);
+    mockInternalOwnerDispatcher = mock(OwnerDispatcher.class);
+    mockSchemaDispatcher = mock(SchemaDispatcher.class);
+    mockInternalSchemaDispatcher = mock(SchemaDispatcher.class);
+    mockTableDispatcher = mock(TableDispatcher.class);
+    mockInternalTableDispatcher = mock(TableDispatcher.class);
+
+    previousOwnerDispatcher =
+        (OwnerDispatcher) FieldUtils.readField(GravitinoEnv.getInstance(), 
"ownerDispatcher", true);
+    previousInternalOwnerDispatcher =
+        (OwnerDispatcher)
+            FieldUtils.readField(GravitinoEnv.getInstance(), 
"internalOwnerDispatcher", true);
+    previousSchemaDispatcher =
+        (SchemaDispatcher)
+            FieldUtils.readField(GravitinoEnv.getInstance(), 
"schemaDispatcher", true);
+    previousInternalSchemaDispatcher =
+        (SchemaDispatcher)
+            FieldUtils.readField(GravitinoEnv.getInstance(), 
"internalSchemaDispatcher", true);
+    previousTableDispatcher =
+        (TableDispatcher) FieldUtils.readField(GravitinoEnv.getInstance(), 
"tableDispatcher", true);
+    previousInternalTableDispatcher =
+        (TableDispatcher)
+            FieldUtils.readField(GravitinoEnv.getInstance(), 
"internalTableDispatcher", true);
+    previousEntityStore =
+        (EntityStore) FieldUtils.readField(GravitinoEnv.getInstance(), 
"entityStore", true);
+    previousLockManager =
+        (LockManager) FieldUtils.readField(GravitinoEnv.getInstance(), 
"lockManager", true);
 
     FieldUtils.writeField(GravitinoEnv.getInstance(), "ownerDispatcher", 
mockOwnerDispatcher, true);
+    FieldUtils.writeField(
+        GravitinoEnv.getInstance(), "internalOwnerDispatcher", 
mockInternalOwnerDispatcher, true);
     FieldUtils.writeField(
         GravitinoEnv.getInstance(), "schemaDispatcher", mockSchemaDispatcher, 
true);
+    FieldUtils.writeField(
+        GravitinoEnv.getInstance(), "internalSchemaDispatcher", 
mockInternalSchemaDispatcher, true);
     FieldUtils.writeField(GravitinoEnv.getInstance(), "tableDispatcher", 
mockTableDispatcher, true);
+    FieldUtils.writeField(
+        GravitinoEnv.getInstance(), "internalTableDispatcher", 
mockInternalTableDispatcher, true);
 
     mockEntityStore = mock(EntityStore.class);
     FieldUtils.writeField(GravitinoEnv.getInstance(), "entityStore", 
mockEntityStore, true);
@@ -105,11 +149,29 @@ public class TestIcebergNamespaceHookDispatcher {
 
   @AfterEach
   public void tearDown() throws IllegalAccessException {
-    FieldUtils.writeField(GravitinoEnv.getInstance(), "ownerDispatcher", null, 
true);
-    FieldUtils.writeField(GravitinoEnv.getInstance(), "schemaDispatcher", 
null, true);
-    FieldUtils.writeField(GravitinoEnv.getInstance(), "tableDispatcher", null, 
true);
-    FieldUtils.writeField(GravitinoEnv.getInstance(), "entityStore", null, 
true);
-    FieldUtils.writeField(GravitinoEnv.getInstance(), "lockManager", null, 
true);
+    FieldUtils.writeField(
+        GravitinoEnv.getInstance(), "ownerDispatcher", 
previousOwnerDispatcher, true);
+    FieldUtils.writeField(
+        GravitinoEnv.getInstance(),
+        "internalOwnerDispatcher",
+        previousInternalOwnerDispatcher,
+        true);
+    FieldUtils.writeField(
+        GravitinoEnv.getInstance(), "schemaDispatcher", 
previousSchemaDispatcher, true);
+    FieldUtils.writeField(
+        GravitinoEnv.getInstance(),
+        "internalSchemaDispatcher",
+        previousInternalSchemaDispatcher,
+        true);
+    FieldUtils.writeField(
+        GravitinoEnv.getInstance(), "tableDispatcher", 
previousTableDispatcher, true);
+    FieldUtils.writeField(
+        GravitinoEnv.getInstance(),
+        "internalTableDispatcher",
+        previousInternalTableDispatcher,
+        true);
+    FieldUtils.writeField(GravitinoEnv.getInstance(), "entityStore", 
previousEntityStore, true);
+    FieldUtils.writeField(GravitinoEnv.getInstance(), "lockManager", 
previousLockManager, true);
 
     Class<?> holderClass =
         Arrays.stream(IcebergRESTServerContext.class.getDeclaredClasses())
@@ -129,7 +191,7 @@ public class TestIcebergNamespaceHookDispatcher {
     when(mockDispatcher.createNamespace(mockContext, 
mockRequest)).thenReturn(mockResponse);
 
     doThrow(new RuntimeException("Set owner failed"))
-        .when(mockOwnerDispatcher)
+        .when(mockInternalOwnerDispatcher)
         .setOwners(any(), any(), any(), any());
 
     RuntimeException thrown =
@@ -137,6 +199,7 @@ public class TestIcebergNamespaceHookDispatcher {
             RuntimeException.class, () -> 
hookDispatcher.createNamespace(mockContext, mockRequest));
     Assertions.assertEquals("Set owner failed", thrown.getMessage());
     verify(mockDispatcher).createNamespace(mockContext, mockRequest);
+    verify(mockOwnerDispatcher, never()).setOwners(any(), any(), any(), any());
   }
 
   @Test
@@ -150,7 +213,7 @@ public class TestIcebergNamespaceHookDispatcher {
         .thenReturn(mockResponse);
 
     doThrow(new RuntimeException("Set owner failed"))
-        .when(mockOwnerDispatcher)
+        .when(mockInternalOwnerDispatcher)
         .setOwner(any(), any(), any(), any());
 
     RuntimeException thrown =
@@ -159,6 +222,7 @@ public class TestIcebergNamespaceHookDispatcher {
             () -> hookDispatcher.registerTable(mockContext, namespace, 
mockRequest));
     Assertions.assertEquals("Set owner failed", thrown.getMessage());
     verify(mockDispatcher).registerTable(mockContext, namespace, mockRequest);
+    verify(mockOwnerDispatcher, never()).setOwner(any(), any(), any(), any());
   }
 
   @Test
@@ -172,15 +236,18 @@ public class TestIcebergNamespaceHookDispatcher {
 
     // Schema import (loadSchema) throwing must propagate so the caller learns 
the namespace
     // exists in Iceberg but is not registered in Gravitino. setOwner is 
therefore unreachable.
-    SchemaDispatcher schemaDispatcher = 
GravitinoEnv.getInstance().schemaDispatcher();
-    doThrow(new RuntimeException("Import 
failed")).when(schemaDispatcher).loadSchema(any());
+    doThrow(new RuntimeException("Import failed"))
+        .when(mockInternalSchemaDispatcher)
+        .loadSchema(any());
 
     RuntimeException thrown =
         Assertions.assertThrows(
             RuntimeException.class, () -> 
hookDispatcher.createNamespace(mockContext, mockRequest));
 
     Assertions.assertEquals("Import failed", thrown.getMessage());
+    verify(mockInternalOwnerDispatcher, never()).setOwners(any(), any(), 
any(), any());
     verify(mockOwnerDispatcher, never()).setOwners(any(), any(), any(), any());
+    verify(mockSchemaDispatcher, never()).loadSchema(any());
   }
 
   @Test
@@ -195,8 +262,9 @@ public class TestIcebergNamespaceHookDispatcher {
 
     // Table import (loadTable) throwing must propagate so the caller learns 
the table exists in
     // Iceberg but is not registered in Gravitino. setOwner is therefore 
unreachable.
-    TableDispatcher tableDispatcher = 
GravitinoEnv.getInstance().tableDispatcher();
-    doThrow(new RuntimeException("Import 
failed")).when(tableDispatcher).loadTable(any());
+    doThrow(new RuntimeException("Import failed"))
+        .when(mockInternalTableDispatcher)
+        .loadTable(any());
 
     RuntimeException thrown =
         Assertions.assertThrows(
@@ -204,7 +272,9 @@ public class TestIcebergNamespaceHookDispatcher {
             () -> hookDispatcher.registerTable(mockContext, namespace, 
mockRequest));
 
     Assertions.assertEquals("Import failed", thrown.getMessage());
+    verify(mockInternalOwnerDispatcher, never()).setOwner(any(), any(), any(), 
any());
     verify(mockOwnerDispatcher, never()).setOwner(any(), any(), any(), any());
+    verify(mockTableDispatcher, never()).loadTable(any());
   }
 
   @Test
@@ -248,8 +318,9 @@ public class TestIcebergNamespaceHookDispatcher {
 
     @SuppressWarnings("unchecked")
     ArgumentCaptor<List<MetadataObject>> captor = 
ArgumentCaptor.forClass(List.class);
-    verify(mockOwnerDispatcher)
+    verify(mockInternalOwnerDispatcher)
         .setOwners(eq(TEST_METALAKE), captor.capture(), eq(TEST_USER), 
eq(Owner.Type.USER));
+    verify(mockOwnerDispatcher, never()).setOwners(any(), any(), any(), any());
     List<String> names =
         
captor.getValue().stream().map(MetadataObject::fullName).collect(Collectors.toList());
     Assertions.assertEquals(
diff --git 
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergTableHookDispatcher.java
 
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergTableHookDispatcher.java
index 2d4f2e125c..40a11ec015 100644
--- 
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergTableHookDispatcher.java
+++ 
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergTableHookDispatcher.java
@@ -76,13 +76,17 @@ public class TestIcebergTableHookDispatcher {
   private IcebergTableOperationDispatcher mockDispatcher;
   private EntityStore mockEntityStore;
   private TableDispatcher mockTableDispatcher;
+  private TableDispatcher mockInternalTableDispatcher;
   private OwnerDispatcher mockOwnerDispatcher;
+  private OwnerDispatcher mockInternalOwnerDispatcher;
   private IcebergRequestContext mockContext;
 
   private Config previousConfig;
   private EntityStore previousEntityStore;
   private TableDispatcher previousTableDispatcher;
+  private TableDispatcher previousInternalTableDispatcher;
   private OwnerDispatcher previousOwnerDispatcher;
+  private OwnerDispatcher previousInternalOwnerDispatcher;
 
   @BeforeEach
   public void setUp() throws IllegalAccessException {
@@ -92,7 +96,9 @@ public class TestIcebergTableHookDispatcher {
     // Mock GravitinoEnv components
     mockEntityStore = mock(EntityStore.class);
     mockTableDispatcher = mock(TableDispatcher.class);
+    mockInternalTableDispatcher = mock(TableDispatcher.class);
     mockOwnerDispatcher = mock(OwnerDispatcher.class);
+    mockInternalOwnerDispatcher = mock(OwnerDispatcher.class);
 
     Config mockConfig = mock(Config.class);
     when(mockConfig.get(Configs.SCHEMA_SEPARATOR)).thenReturn(":");
@@ -101,12 +107,22 @@ public class TestIcebergTableHookDispatcher {
         (EntityStore) FieldUtils.readField(GravitinoEnv.getInstance(), 
"entityStore", true);
     previousTableDispatcher =
         (TableDispatcher) FieldUtils.readField(GravitinoEnv.getInstance(), 
"tableDispatcher", true);
+    previousInternalTableDispatcher =
+        (TableDispatcher)
+            FieldUtils.readField(GravitinoEnv.getInstance(), 
"internalTableDispatcher", true);
     previousOwnerDispatcher =
         (OwnerDispatcher) FieldUtils.readField(GravitinoEnv.getInstance(), 
"ownerDispatcher", true);
+    previousInternalOwnerDispatcher =
+        (OwnerDispatcher)
+            FieldUtils.readField(GravitinoEnv.getInstance(), 
"internalOwnerDispatcher", true);
     FieldUtils.writeField(GravitinoEnv.getInstance(), "config", mockConfig, 
true);
     FieldUtils.writeField(GravitinoEnv.getInstance(), "entityStore", 
mockEntityStore, true);
     FieldUtils.writeField(GravitinoEnv.getInstance(), "tableDispatcher", 
mockTableDispatcher, true);
+    FieldUtils.writeField(
+        GravitinoEnv.getInstance(), "internalTableDispatcher", 
mockInternalTableDispatcher, true);
     FieldUtils.writeField(GravitinoEnv.getInstance(), "ownerDispatcher", 
mockOwnerDispatcher, true);
+    FieldUtils.writeField(
+        GravitinoEnv.getInstance(), "internalOwnerDispatcher", 
mockInternalOwnerDispatcher, true);
 
     // Create mock IcebergRESTServerContext
     IcebergConfigProvider mockConfigProvider = 
mock(IcebergConfigProvider.class);
@@ -131,8 +147,18 @@ public class TestIcebergTableHookDispatcher {
     FieldUtils.writeField(GravitinoEnv.getInstance(), "entityStore", 
previousEntityStore, true);
     FieldUtils.writeField(
         GravitinoEnv.getInstance(), "tableDispatcher", 
previousTableDispatcher, true);
+    FieldUtils.writeField(
+        GravitinoEnv.getInstance(),
+        "internalTableDispatcher",
+        previousInternalTableDispatcher,
+        true);
     FieldUtils.writeField(
         GravitinoEnv.getInstance(), "ownerDispatcher", 
previousOwnerDispatcher, true);
+    FieldUtils.writeField(
+        GravitinoEnv.getInstance(),
+        "internalOwnerDispatcher",
+        previousInternalOwnerDispatcher,
+        true);
 
     // Reset IcebergRESTServerContext singleton
     Class<?> holderClass =
@@ -161,12 +187,14 @@ public class TestIcebergTableHookDispatcher {
     NameIdentifier expectedIdentifier =
         IcebergIdentifierUtils.toGravitinoTableIdentifier(
             TEST_METALAKE, TEST_CATALOG, TableIdentifier.of(namespace, 
"test_table"), ":");
-    verify(mockTableDispatcher).loadTable(expectedIdentifier);
+    verify(mockInternalTableDispatcher).loadTable(expectedIdentifier);
+    verify(mockTableDispatcher, never()).loadTable(any());
 
     // Verify ownership was set
     ArgumentCaptor<String> userCaptor = ArgumentCaptor.forClass(String.class);
-    verify(mockOwnerDispatcher)
+    verify(mockInternalOwnerDispatcher)
         .setOwner(eq(TEST_METALAKE), any(), userCaptor.capture(), 
eq(Owner.Type.USER));
+    verify(mockOwnerDispatcher, never()).setOwner(any(), any(), any(), any());
     Assertions.assertEquals(TEST_USER, userCaptor.getValue());
   }
 
@@ -196,7 +224,8 @@ public class TestIcebergTableHookDispatcher {
         IcebergIdentifierUtils.toGravitinoTableIdentifier(
             TEST_METALAKE, TEST_CATALOG, tableId, ":");
     verify(mockEntityStore, never()).delete(expectedIdentifier, 
Entity.EntityType.TABLE);
-    verify(mockTableDispatcher).loadTable(expectedIdentifier);
+    verify(mockInternalTableDispatcher).loadTable(expectedIdentifier);
+    verify(mockTableDispatcher, never()).loadTable(any());
   }
 
   @Test
@@ -211,7 +240,8 @@ public class TestIcebergTableHookDispatcher {
         IcebergIdentifierUtils.toGravitinoTableIdentifier(
             TEST_METALAKE, TEST_CATALOG, tableId, ":");
     verify(mockEntityStore).delete(expectedIdentifier, 
Entity.EntityType.TABLE);
-    verify(mockTableDispatcher).loadTable(expectedIdentifier);
+    verify(mockInternalTableDispatcher).loadTable(expectedIdentifier);
+    verify(mockTableDispatcher, never()).loadTable(any());
   }
 
   @Test
@@ -293,7 +323,8 @@ public class TestIcebergTableHookDispatcher {
     NameIdentifier destIdentifier =
         IcebergIdentifierUtils.toGravitinoTableIdentifier(TEST_METALAKE, 
TEST_CATALOG, dest, ":");
     verify(mockEntityStore).delete(sourceIdentifier, Entity.EntityType.TABLE);
-    verify(mockTableDispatcher).loadTable(destIdentifier);
+    verify(mockInternalTableDispatcher).loadTable(destIdentifier);
+    verify(mockTableDispatcher, never()).loadTable(any());
   }
 
   @Test
@@ -312,12 +343,15 @@ public class TestIcebergTableHookDispatcher {
     when(mockEntityStore.update(any(), eq(TableEntity.class), 
eq(Entity.EntityType.TABLE), any()))
         .thenReturn(mockTableEntity);
     when(mockDispatcher.tableExists(mockContext, dest)).thenReturn(true);
-    doThrow(new RuntimeException("import 
failed")).when(mockTableDispatcher).loadTable(any());
+    doThrow(new RuntimeException("import failed"))
+        .when(mockInternalTableDispatcher)
+        .loadTable(any());
 
     Assertions.assertDoesNotThrow(() -> 
hookDispatcher.renameTable(mockContext, request));
 
     verify(mockDispatcher).renameTable(mockContext, request);
-    verify(mockTableDispatcher).loadTable(any());
+    verify(mockInternalTableDispatcher).loadTable(any());
+    verify(mockTableDispatcher, never()).loadTable(any());
   }
 
   @Test
@@ -364,7 +398,7 @@ public class TestIcebergTableHookDispatcher {
     when(mockDispatcher.createTable(mockContext, namespace, 
request)).thenReturn(mockResponse);
 
     doThrow(new RuntimeException("Set owner failed"))
-        .when(mockOwnerDispatcher)
+        .when(mockInternalOwnerDispatcher)
         .setOwner(any(), any(), any(), any());
 
     RuntimeException thrown =
@@ -373,6 +407,7 @@ public class TestIcebergTableHookDispatcher {
             () -> hookDispatcher.createTable(mockContext, namespace, request));
     Assertions.assertEquals("Set owner failed", thrown.getMessage());
     verify(mockDispatcher).createTable(mockContext, namespace, request);
+    verify(mockOwnerDispatcher, never()).setOwner(any(), any(), any(), any());
   }
 
   @Test
@@ -394,9 +429,11 @@ public class TestIcebergTableHookDispatcher {
     verify(mockDispatcher).createTable(mockContext, namespace, request);
 
     // Verify table import was NOT called for staged create
+    verify(mockInternalTableDispatcher, never()).loadTable(any());
     verify(mockTableDispatcher, never()).loadTable(any());
 
     // Verify ownership was NOT set for staged create
+    verify(mockInternalOwnerDispatcher, never()).setOwner(any(), any(), any(), 
any());
     verify(mockOwnerDispatcher, never()).setOwner(any(), any(), any(), any());
   }
 
@@ -419,11 +456,13 @@ public class TestIcebergTableHookDispatcher {
     NameIdentifier gravitinoTableId =
         IcebergIdentifierUtils.toGravitinoTableIdentifier(
             TEST_METALAKE, TEST_CATALOG, tableId, ":");
-    verify(mockTableDispatcher).loadTable(gravitinoTableId);
+    verify(mockInternalTableDispatcher).loadTable(gravitinoTableId);
+    verify(mockTableDispatcher, never()).loadTable(any());
 
     ArgumentCaptor<String> userCaptor = ArgumentCaptor.forClass(String.class);
-    verify(mockOwnerDispatcher)
+    verify(mockInternalOwnerDispatcher)
         .setOwner(eq(TEST_METALAKE), any(), userCaptor.capture(), 
eq(Owner.Type.USER));
+    verify(mockOwnerDispatcher, never()).setOwner(any(), any(), any(), any());
     Assertions.assertEquals(TEST_USER, userCaptor.getValue());
   }
 
@@ -444,9 +483,11 @@ public class TestIcebergTableHookDispatcher {
     verify(mockDispatcher).updateTable(mockContext, tableId, request);
 
     // Verify table import was NOT called for a regular update
+    verify(mockInternalTableDispatcher, never()).loadTable(any());
     verify(mockTableDispatcher, never()).loadTable(any());
 
     // Verify ownership was NOT set
+    verify(mockInternalOwnerDispatcher, never()).setOwner(any(), any(), any(), 
any());
     verify(mockOwnerDispatcher, never()).setOwner(any(), any(), any(), any());
   }
 
@@ -474,7 +515,9 @@ public class TestIcebergTableHookDispatcher {
 
     // Import failure (the loadTable call) must propagate so the caller learns 
the table exists in
     // Iceberg but is not registered in Gravitino. setOwner is therefore 
unreachable.
-    doThrow(new RuntimeException("Import 
failed")).when(mockTableDispatcher).loadTable(any());
+    doThrow(new RuntimeException("Import failed"))
+        .when(mockInternalTableDispatcher)
+        .loadTable(any());
 
     RuntimeException thrown =
         Assertions.assertThrows(
@@ -483,7 +526,9 @@ public class TestIcebergTableHookDispatcher {
 
     Assertions.assertEquals("Import failed", thrown.getMessage());
     verify(mockDispatcher).createTable(mockContext, namespace, request);
-    verify(mockTableDispatcher).loadTable(any());
+    verify(mockInternalTableDispatcher).loadTable(any());
+    verify(mockTableDispatcher, never()).loadTable(any());
+    verify(mockInternalOwnerDispatcher, never()).setOwner(any(), any(), any(), 
any());
     verify(mockOwnerDispatcher, never()).setOwner(any(), any(), any(), any());
   }
 }
diff --git 
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergViewHookDispatcher.java
 
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergViewHookDispatcher.java
index 4fdf1426b9..929aeac842 100644
--- 
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergViewHookDispatcher.java
+++ 
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/dispatcher/TestIcebergViewHookDispatcher.java
@@ -74,21 +74,27 @@ public class TestIcebergViewHookDispatcher {
   private IcebergViewOperationDispatcher mockExecutor;
   private EntityStore mockEntityStore;
   private ViewDispatcher mockViewDispatcher;
+  private ViewDispatcher mockInternalViewDispatcher;
   private OwnerDispatcher mockOwnerDispatcher;
+  private OwnerDispatcher mockInternalOwnerDispatcher;
   private IcebergRequestContext mockContext;
   private GravitinoEnv gravitinoEnv;
 
   private Config previousConfig;
   private EntityStore previousEntityStore;
   private ViewDispatcher previousViewDispatcher;
+  private ViewDispatcher previousInternalViewDispatcher;
   private OwnerDispatcher previousOwnerDispatcher;
+  private OwnerDispatcher previousInternalOwnerDispatcher;
 
   @BeforeEach
   public void setUp() {
     mockExecutor = mock(IcebergViewOperationDispatcher.class);
     mockEntityStore = mock(EntityStore.class);
     mockViewDispatcher = mock(ViewDispatcher.class);
+    mockInternalViewDispatcher = mock(ViewDispatcher.class);
     mockOwnerDispatcher = mock(OwnerDispatcher.class);
+    mockInternalOwnerDispatcher = mock(OwnerDispatcher.class);
     mockContext = mock(IcebergRequestContext.class);
 
     when(mockContext.catalogName()).thenReturn(CATALOG);
@@ -103,12 +109,20 @@ public class TestIcebergViewHookDispatcher {
       previousEntityStore = (EntityStore) FieldUtils.readField(gravitinoEnv, 
"entityStore", true);
       previousViewDispatcher =
           (ViewDispatcher) FieldUtils.readField(gravitinoEnv, 
"viewDispatcher", true);
+      previousInternalViewDispatcher =
+          (ViewDispatcher) FieldUtils.readField(gravitinoEnv, 
"internalViewDispatcher", true);
       previousOwnerDispatcher =
           (OwnerDispatcher) FieldUtils.readField(gravitinoEnv, 
"ownerDispatcher", true);
+      previousInternalOwnerDispatcher =
+          (OwnerDispatcher) FieldUtils.readField(gravitinoEnv, 
"internalOwnerDispatcher", true);
       FieldUtils.writeField(gravitinoEnv, "config", mockConfig, true);
       FieldUtils.writeField(gravitinoEnv, "entityStore", mockEntityStore, 
true);
       FieldUtils.writeField(gravitinoEnv, "viewDispatcher", 
mockViewDispatcher, true);
+      FieldUtils.writeField(
+          gravitinoEnv, "internalViewDispatcher", mockInternalViewDispatcher, 
true);
       FieldUtils.writeField(gravitinoEnv, "ownerDispatcher", 
mockOwnerDispatcher, true);
+      FieldUtils.writeField(
+          gravitinoEnv, "internalOwnerDispatcher", 
mockInternalOwnerDispatcher, true);
     } catch (Exception e) {
       throw new RuntimeException("Failed to setup test", e);
     }
@@ -124,7 +138,11 @@ public class TestIcebergViewHookDispatcher {
       FieldUtils.writeField(gravitinoEnv, "config", previousConfig, true);
       FieldUtils.writeField(gravitinoEnv, "entityStore", previousEntityStore, 
true);
       FieldUtils.writeField(gravitinoEnv, "viewDispatcher", 
previousViewDispatcher, true);
+      FieldUtils.writeField(
+          gravitinoEnv, "internalViewDispatcher", 
previousInternalViewDispatcher, true);
       FieldUtils.writeField(gravitinoEnv, "ownerDispatcher", 
previousOwnerDispatcher, true);
+      FieldUtils.writeField(
+          gravitinoEnv, "internalOwnerDispatcher", 
previousInternalOwnerDispatcher, true);
     } catch (Exception e) {
       // Ignore cleanup errors
     }
@@ -161,10 +179,12 @@ public class TestIcebergViewHookDispatcher {
 
     // Verify view was imported into Gravitino
     NameIdentifier expectedIdent = NameIdentifier.of(METALAKE, CATALOG, 
SCHEMA_NAME, VIEW_NAME);
-    verify(mockViewDispatcher, times(1)).loadView(eq(expectedIdent));
+    verify(mockInternalViewDispatcher, times(1)).loadView(eq(expectedIdent));
+    verify(mockViewDispatcher, never()).loadView(any());
 
     // Verify ownership was set
-    verify(mockOwnerDispatcher, times(1)).setOwner(any(), any(), eq(USER), 
any());
+    verify(mockInternalOwnerDispatcher, times(1)).setOwner(any(), any(), 
eq(USER), any());
+    verify(mockOwnerDispatcher, never()).setOwner(any(), any(), any(), any());
 
     assertEquals(mockResponse, response);
   }
@@ -195,7 +215,7 @@ public class TestIcebergViewHookDispatcher {
 
     // Simulate import failure
     doThrow(new RuntimeException("Import failed"))
-        .when(mockViewDispatcher)
+        .when(mockInternalViewDispatcher)
         .loadView(any(NameIdentifier.class));
 
     // Should not throw - import is best-effort
@@ -230,7 +250,7 @@ public class TestIcebergViewHookDispatcher {
     when(mockExecutor.createView(mockContext, namespace, 
createRequest)).thenReturn(mockResponse);
 
     doThrow(new RuntimeException("Set owner failed"))
-        .when(mockOwnerDispatcher)
+        .when(mockInternalOwnerDispatcher)
         .setOwner(any(), any(), any(), any());
 
     RuntimeException thrown =
@@ -239,7 +259,9 @@ public class TestIcebergViewHookDispatcher {
             () -> hookDispatcher.createView(mockContext, namespace, 
createRequest));
     assertEquals("Set owner failed", thrown.getMessage());
     verify(mockExecutor, times(1)).createView(mockContext, namespace, 
createRequest);
-    verify(mockViewDispatcher, times(1)).loadView(any(NameIdentifier.class));
+    verify(mockInternalViewDispatcher, 
times(1)).loadView(any(NameIdentifier.class));
+    verify(mockViewDispatcher, never()).loadView(any());
+    verify(mockOwnerDispatcher, never()).setOwner(any(), any(), any(), any());
   }
 
   @Test
@@ -268,7 +290,8 @@ public class TestIcebergViewHookDispatcher {
     NameIdentifier expectedIdent =
         IcebergIdentifierUtils.toGravitinoTableIdentifier(METALAKE, CATALOG, 
viewIdent, ":");
     verify(mockEntityStore, never()).delete(eq(expectedIdent), 
eq(Entity.EntityType.VIEW));
-    verify(mockViewDispatcher, times(1)).loadView(eq(expectedIdent));
+    verify(mockInternalViewDispatcher, times(1)).loadView(eq(expectedIdent));
+    verify(mockViewDispatcher, never()).loadView(any());
   }
 
   @Test
@@ -282,7 +305,8 @@ public class TestIcebergViewHookDispatcher {
     NameIdentifier expectedIdent =
         IcebergIdentifierUtils.toGravitinoTableIdentifier(METALAKE, CATALOG, 
viewIdent, ":");
     verify(mockEntityStore, times(1)).delete(eq(expectedIdent), 
eq(Entity.EntityType.VIEW));
-    verify(mockViewDispatcher, times(1)).loadView(eq(expectedIdent));
+    verify(mockInternalViewDispatcher, times(1)).loadView(eq(expectedIdent));
+    verify(mockViewDispatcher, never()).loadView(any());
   }
 
   @Test
@@ -354,7 +378,8 @@ public class TestIcebergViewHookDispatcher {
     NameIdentifier destGravitinoIdent =
         IcebergIdentifierUtils.toGravitinoTableIdentifier(METALAKE, CATALOG, 
destIdent, ":");
     verify(mockEntityStore, times(1)).delete(eq(sourceGravitinoIdent), 
eq(Entity.EntityType.VIEW));
-    verify(mockViewDispatcher, times(1)).loadView(eq(destGravitinoIdent));
+    verify(mockInternalViewDispatcher, 
times(1)).loadView(eq(destGravitinoIdent));
+    verify(mockViewDispatcher, never()).loadView(any());
   }
 
   @Test
diff --git 
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/provider/TestDynamicIcebergConfigProvider.java
 
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/provider/TestDynamicIcebergConfigProvider.java
index 669e41a24d..2177778734 100644
--- 
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/provider/TestDynamicIcebergConfigProvider.java
+++ 
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/service/provider/TestDynamicIcebergConfigProvider.java
@@ -63,6 +63,7 @@ public class TestDynamicIcebergConfigProvider {
   public void tearDown() throws IllegalAccessException {
     // Clean up GravitinoEnv and IcebergRESTServerContext state after each test
     FieldUtils.writeField(GravitinoEnv.getInstance(), "catalogDispatcher", 
null, true);
+    FieldUtils.writeField(GravitinoEnv.getInstance(), 
"internalCatalogDispatcher", null, true);
     resetServerContext();
   }
 
@@ -265,12 +266,13 @@ public class TestDynamicIcebergConfigProvider {
     // Enable authorization to use internal fetcher
     createMockServerContext(true);
 
-    // Mock CatalogDispatcher
+    // Mock CatalogDispatchers
     CatalogDispatcher mockCatalogDispatcher = 
Mockito.mock(CatalogDispatcher.class);
+    CatalogDispatcher mockInternalCatalogDispatcher = 
Mockito.mock(CatalogDispatcher.class);
     Catalog mockCatalog = Mockito.mock(Catalog.class);
 
     NameIdentifier catalogIdent = NameIdentifierUtil.ofCatalog(metalakeName, 
catalogName);
-    
Mockito.when(mockCatalogDispatcher.loadCatalog(catalogIdent)).thenReturn(mockCatalog);
+    
Mockito.when(mockInternalCatalogDispatcher.loadCatalog(catalogIdent)).thenReturn(mockCatalog);
     Mockito.when(mockCatalog.provider()).thenReturn("lakehouse-iceberg");
     Mockito.when(mockCatalog.properties())
         .thenReturn(
@@ -281,9 +283,14 @@ public class TestDynamicIcebergConfigProvider {
               }
             });
 
-    // Set the mock CatalogDispatcher to GravitinoEnv
+    // Set the mock CatalogDispatchers to GravitinoEnv
     FieldUtils.writeField(
         GravitinoEnv.getInstance(), "catalogDispatcher", 
mockCatalogDispatcher, true);
+    FieldUtils.writeField(
+        GravitinoEnv.getInstance(),
+        "internalCatalogDispatcher",
+        mockInternalCatalogDispatcher,
+        true);
 
     // Initialize provider with required properties
     Map<String, String> properties = new HashMap<>();
@@ -292,11 +299,12 @@ public class TestDynamicIcebergConfigProvider {
     DynamicIcebergConfigProvider provider = new DynamicIcebergConfigProvider();
     provider.initialize(properties);
 
-    // Test that internal interface is used (CatalogDispatcher should be 
called)
+    // Test that internal interface is used (internal CatalogDispatcher should 
be called)
     Optional<IcebergConfig> icebergConfig = 
provider.getIcebergCatalogConfig(catalogName);
 
     Assertions.assertTrue(icebergConfig.isPresent());
-    Mockito.verify(mockCatalogDispatcher).loadCatalog(catalogIdent);
+    Mockito.verify(mockInternalCatalogDispatcher).loadCatalog(catalogIdent);
+    Mockito.verify(mockCatalogDispatcher, 
Mockito.never()).loadCatalog(catalogIdent);
   }
 
   @Test
@@ -345,8 +353,9 @@ public class TestDynamicIcebergConfigProvider {
     // Enable authorization to use internal fetcher
     createMockServerContext(true);
 
-    // Ensure CatalogDispatcher is null (simulating GravitinoEnv not 
initialized)
+    // Ensure internal CatalogDispatcher is null (simulating GravitinoEnv not 
initialized)
     FieldUtils.writeField(GravitinoEnv.getInstance(), "catalogDispatcher", 
null, true);
+    FieldUtils.writeField(GravitinoEnv.getInstance(), 
"internalCatalogDispatcher", null, true);
 
     // Initialize provider with required properties
     Map<String, String> properties = new HashMap<>();
@@ -355,9 +364,13 @@ public class TestDynamicIcebergConfigProvider {
     DynamicIcebergConfigProvider provider = new DynamicIcebergConfigProvider();
     provider.initialize(properties);
 
-    // Test that IllegalStateException is thrown when CatalogDispatcher is null
-    Assertions.assertThrows(
-        IllegalStateException.class, () -> 
provider.getIcebergCatalogConfig(catalogName));
+    IllegalStateException exception =
+        Assertions.assertThrows(
+            IllegalStateException.class, () -> 
provider.getIcebergCatalogConfig(catalogName));
+    Assertions.assertEquals(
+        "Internal CatalogDispatcher is not available. "
+            + "Internal catalog fetcher requires running within Gravitino 
server.",
+        exception.getMessage());
   }
 
   @Test
@@ -368,16 +381,22 @@ public class TestDynamicIcebergConfigProvider {
     // Enable authorization to use internal fetcher
     createMockServerContext(true);
 
-    // Mock CatalogDispatcher to throw NoSuchCatalogException
+    // Mock internal CatalogDispatcher to throw NoSuchCatalogException
     CatalogDispatcher mockCatalogDispatcher = 
Mockito.mock(CatalogDispatcher.class);
+    CatalogDispatcher mockInternalCatalogDispatcher = 
Mockito.mock(CatalogDispatcher.class);
     NameIdentifier catalogIdent =
         NameIdentifierUtil.ofCatalog(metalakeName, nonExistentCatalogName);
-    Mockito.when(mockCatalogDispatcher.loadCatalog(catalogIdent))
+    Mockito.when(mockInternalCatalogDispatcher.loadCatalog(catalogIdent))
         .thenThrow(new NoSuchCatalogException("Catalog not found: %s", 
nonExistentCatalogName));
 
-    // Set the mock CatalogDispatcher to GravitinoEnv
+    // Set the mock CatalogDispatchers to GravitinoEnv
     FieldUtils.writeField(
         GravitinoEnv.getInstance(), "catalogDispatcher", 
mockCatalogDispatcher, true);
+    FieldUtils.writeField(
+        GravitinoEnv.getInstance(),
+        "internalCatalogDispatcher",
+        mockInternalCatalogDispatcher,
+        true);
 
     // Initialize provider with required properties
     Map<String, String> properties = new HashMap<>();
@@ -390,7 +409,8 @@ public class TestDynamicIcebergConfigProvider {
     Optional<IcebergConfig> result = 
provider.getIcebergCatalogConfig(nonExistentCatalogName);
 
     Assertions.assertFalse(result.isPresent());
-    Mockito.verify(mockCatalogDispatcher).loadCatalog(catalogIdent);
+    Mockito.verify(mockInternalCatalogDispatcher).loadCatalog(catalogIdent);
+    Mockito.verify(mockCatalogDispatcher, 
Mockito.never()).loadCatalog(catalogIdent);
   }
 
   @Test
@@ -474,12 +494,13 @@ public class TestDynamicIcebergConfigProvider {
     // Enable authorization to use internal fetcher
     createMockServerContext(true);
 
-    // Mock CatalogDispatcher
+    // Mock CatalogDispatchers
     CatalogDispatcher mockCatalogDispatcher = 
Mockito.mock(CatalogDispatcher.class);
+    CatalogDispatcher mockInternalCatalogDispatcher = 
Mockito.mock(CatalogDispatcher.class);
     Catalog mockCatalog = Mockito.mock(Catalog.class);
 
     NameIdentifier catalogIdent = NameIdentifierUtil.ofCatalog(metalakeName, 
catalogName);
-    
Mockito.when(mockCatalogDispatcher.loadCatalog(catalogIdent)).thenReturn(mockCatalog);
+    
Mockito.when(mockInternalCatalogDispatcher.loadCatalog(catalogIdent)).thenReturn(mockCatalog);
     Mockito.when(mockCatalog.provider()).thenReturn("lakehouse-iceberg");
     Mockito.when(mockCatalog.properties())
         .thenReturn(
@@ -490,9 +511,14 @@ public class TestDynamicIcebergConfigProvider {
               }
             });
 
-    // Set the mock CatalogDispatcher to GravitinoEnv
+    // Set the mock CatalogDispatchers to GravitinoEnv
     FieldUtils.writeField(
         GravitinoEnv.getInstance(), "catalogDispatcher", 
mockCatalogDispatcher, true);
+    FieldUtils.writeField(
+        GravitinoEnv.getInstance(),
+        "internalCatalogDispatcher",
+        mockInternalCatalogDispatcher,
+        true);
 
     // Initialize provider with required properties
     Map<String, String> properties = new HashMap<>();
@@ -535,8 +561,10 @@ public class TestDynamicIcebergConfigProvider {
       Assertions.assertTrue(result.isPresent(), "Each thread should get a 
valid config");
     }
 
-    // Verify CatalogDispatcher was called (at least once, possibly more due 
to concurrency)
-    Mockito.verify(mockCatalogDispatcher, 
Mockito.atLeastOnce()).loadCatalog(catalogIdent);
+    // Verify internal CatalogDispatcher was called (at least once, possibly 
more due to
+    // concurrency)
+    Mockito.verify(mockInternalCatalogDispatcher, 
Mockito.atLeastOnce()).loadCatalog(catalogIdent);
+    Mockito.verify(mockCatalogDispatcher, 
Mockito.never()).loadCatalog(catalogIdent);
 
     executor.shutdown();
     executor.awaitTermination(5, TimeUnit.SECONDS);
diff --git 
a/server-common/src/main/java/org/apache/gravitino/server/authorization/PassThroughAuthorizer.java
 
b/server-common/src/main/java/org/apache/gravitino/server/authorization/PassThroughAuthorizer.java
index c58ffea12e..deab84b743 100644
--- 
a/server-common/src/main/java/org/apache/gravitino/server/authorization/PassThroughAuthorizer.java
+++ 
b/server-common/src/main/java/org/apache/gravitino/server/authorization/PassThroughAuthorizer.java
@@ -83,7 +83,8 @@ public class PassThroughAuthorizer implements 
GravitinoAuthorizer {
 
   @Override
   public boolean isMetalakeUser(String metalake, AuthorizationRequestContext 
requestContext) {
-    AccessControlDispatcher dispatcher = 
GravitinoEnv.getInstance().accessControlDispatcher();
+    AccessControlDispatcher dispatcher =
+        GravitinoEnv.getInstance().internalAccessControlDispatcher();
     if (dispatcher != null) {
       try {
         dispatcher.getUser(metalake, PrincipalUtils.getCurrentUserName());
diff --git 
a/server-common/src/test/java/org/apache/gravitino/server/authorization/TestPassThroughAuthorizer.java
 
b/server-common/src/test/java/org/apache/gravitino/server/authorization/TestPassThroughAuthorizer.java
index e6099a6c47..4f7efb875e 100644
--- 
a/server-common/src/test/java/org/apache/gravitino/server/authorization/TestPassThroughAuthorizer.java
+++ 
b/server-common/src/test/java/org/apache/gravitino/server/authorization/TestPassThroughAuthorizer.java
@@ -18,15 +18,22 @@
 package org.apache.gravitino.server.authorization;
 
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import java.io.IOException;
 import java.security.Principal;
+import org.apache.commons.lang3.reflect.FieldUtils;
 import org.apache.gravitino.Entity;
+import org.apache.gravitino.GravitinoEnv;
 import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.MetadataObjects;
+import org.apache.gravitino.UserPrincipal;
+import org.apache.gravitino.authorization.AccessControlDispatcher;
 import org.apache.gravitino.authorization.AuthorizationRequestContext;
 import org.apache.gravitino.authorization.Privilege;
+import org.apache.gravitino.authorization.User;
+import org.apache.gravitino.utils.PrincipalUtils;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
@@ -73,4 +80,32 @@ public class TestPassThroughAuthorizer {
               "metalake", "type", "fullName", new 
AuthorizationRequestContext()));
     }
   }
+
+  @Test
+  public void testIsMetalakeUserUsesInternalAccessControlDispatcher() throws 
Exception {
+    AccessControlDispatcher dispatcher = mock(AccessControlDispatcher.class);
+    User user = mock(User.class);
+    when(dispatcher.getUser("metalake", "testUser")).thenReturn(user);
+
+    AccessControlDispatcher previousDispatcher =
+        (AccessControlDispatcher)
+            FieldUtils.readField(
+                GravitinoEnv.getInstance(), "internalAccessControlDispatcher", 
true);
+    FieldUtils.writeField(
+        GravitinoEnv.getInstance(), "internalAccessControlDispatcher", 
dispatcher, true);
+    try (PassThroughAuthorizer passThroughAuthorizer = new 
PassThroughAuthorizer()) {
+      PrincipalUtils.doAs(
+          new UserPrincipal("testUser"),
+          () -> {
+            Assertions.assertTrue(
+                passThroughAuthorizer.isMetalakeUser(
+                    "metalake", new AuthorizationRequestContext()));
+            return null;
+          });
+      verify(dispatcher).getUser("metalake", "testUser");
+    } finally {
+      FieldUtils.writeField(
+          GravitinoEnv.getInstance(), "internalAccessControlDispatcher", 
previousDispatcher, true);
+    }
+  }
 }
diff --git 
a/server-common/src/test/java/org/apache/gravitino/server/authorization/jcasbin/TestJcasbinAuthorizer.java
 
b/server-common/src/test/java/org/apache/gravitino/server/authorization/jcasbin/TestJcasbinAuthorizer.java
index 67f84a6f99..5f28bfc7dd 100644
--- 
a/server-common/src/test/java/org/apache/gravitino/server/authorization/jcasbin/TestJcasbinAuthorizer.java
+++ 
b/server-common/src/test/java/org/apache/gravitino/server/authorization/jcasbin/TestJcasbinAuthorizer.java
@@ -372,6 +372,12 @@ public class TestJcasbinAuthorizer {
     }
   }
 
+  @Test
+  public void testIsMetalakeUserUsesUserInfoCache() {
+    assertTrue(jcasbinAuthorizer.isMetalakeUser(METALAKE, new 
AuthorizationRequestContext()));
+    verify(userMetaMapper).getUserUpdatedAt(METALAKE, USERNAME);
+  }
+
   @Test
   public void testAuthorize() throws Exception {
     makeCompletableFutureUseCurrentThread(jcasbinAuthorizer);

Reply via email to