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 ef639b3b38 [#13845] fix(server): Backport authorized view existence 
probing to branch-1.3 (#14191)
ef639b3b38 is described below

commit ef639b3b38d40675f48caf4830076455efd0d4e5
Author: jarred0214 <[email protected]>
AuthorDate: Mon Oct 26 14:57:36 2026 +0800

    [#13845] fix(server): Backport authorized view existence probing to 
branch-1.3 (#14191)
    
    ### What changes were proposed in this pull request?
    
    Backport #13846 (commit 9f8197917b6918e7f62ae7e2eac4c825d537de60) to
    `branch-1.3`, resolving the conflicts in #14074.
    
    Add authorized existence probing for view loads and use internal
    dispatchers for table/view probes. Return a terminal 404 for absent
    objects, preventing concurrent creation from exposing metadata. Adapt
    the branch-specific imports and test mocks.
    
    ### Why are the changes needed?
    
    Clients need to distinguish absent views from inaccessible existing
    views when they have permission to probe existence. Probe results must
    not allow a subsequent unauthorized load.
    
    Refs #13845. Supersedes #14074.
    
    ### Does this PR introduce _any_ user-facing change?
    
    Authorized probes of absent views return 404; existing views remain
    forbidden without load permission. No API or configuration changes.
    
    ### How was this patch tested?
    
    - Passed 33 tests: `./gradlew :server:spotlessApply :server:test
    -PskipITs --tests '*TestLoadTableAuthorizationExecutor' --tests
    '*TestLoadViewAuthorizationExecutor' --tests
    '*TestGravitinoInterceptionService'`.
    - `git diff --check` passed.
    - Full `./gradlew spotlessApply` was attempted; the unrelated
    `:mcp-server:formatApplyPython` task failed because its installed Black
    extension is x86_64 on an ARM host. The affected Java formatting/checks
    passed.
---
 .../annotations/AuthorizationRequest.java          |   2 +
 .../web/filter/GravitinoInterceptionService.java   |  26 ++
 .../authorization/AuthorizeExecutorFactory.java    |   2 +
 .../LoadTableAuthorizationExecutor.java            |   6 +-
 .../LoadViewAuthorizationExecutor.java             |  79 ++++++
 .../gravitino/server/web/rest/ViewOperations.java  |   8 +-
 .../filter/TestGravitinoInterceptionService.java   | 297 ++++++++++++++++++++-
 .../TestLoadTableAuthorizationExecutor.java        |  83 ++++++
 .../TestLoadViewAuthorizationExecutor.java         | 170 ++++++++++++
 9 files changed, 658 insertions(+), 15 deletions(-)

diff --git 
a/server-common/src/main/java/org/apache/gravitino/server/authorization/annotations/AuthorizationRequest.java
 
b/server-common/src/main/java/org/apache/gravitino/server/authorization/annotations/AuthorizationRequest.java
index f90679324f..8587a2aa99 100644
--- 
a/server-common/src/main/java/org/apache/gravitino/server/authorization/annotations/AuthorizationRequest.java
+++ 
b/server-common/src/main/java/org/apache/gravitino/server/authorization/annotations/AuthorizationRequest.java
@@ -35,6 +35,8 @@ public @interface AuthorizationRequest {
     RUN_JOB,
     LINEAGE,
     LOAD_TABLE,
+    /** Loads a view with authorized existence probing. */
+    LOAD_VIEW,
     CREATE_SCHEMA,
     TEST_CATALOG_CONNECTION
   }
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/filter/GravitinoInterceptionService.java
 
b/server/src/main/java/org/apache/gravitino/server/web/filter/GravitinoInterceptionService.java
index a21fd32e01..e5440fdd70 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/filter/GravitinoInterceptionService.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/filter/GravitinoInterceptionService.java
@@ -48,6 +48,8 @@ import org.apache.gravitino.exceptions.ForbiddenException;
 import org.apache.gravitino.exceptions.IllegalMetadataObjectException;
 import org.apache.gravitino.exceptions.IllegalNameIdentifierException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
+import org.apache.gravitino.exceptions.NoSuchTableException;
+import org.apache.gravitino.exceptions.NoSuchViewException;
 import org.apache.gravitino.lineage.source.rest.LineageOperations;
 import 
org.apache.gravitino.listener.api.event.server.AuthorizationDenialFailureEvent;
 import 
org.apache.gravitino.server.authorization.annotations.AuthorizationExpression;
@@ -57,6 +59,7 @@ import org.apache.gravitino.server.web.Utils;
 import 
org.apache.gravitino.server.web.filter.authorization.AuthorizationExecutor;
 import 
org.apache.gravitino.server.web.filter.authorization.AuthorizeExecutorFactory;
 import org.apache.gravitino.server.web.rest.CatalogOperations;
+import org.apache.gravitino.server.web.rest.ExceptionHandlers;
 import org.apache.gravitino.server.web.rest.FilesetOperations;
 import org.apache.gravitino.server.web.rest.FunctionOperations;
 import org.apache.gravitino.server.web.rest.GroupOperations;
@@ -66,6 +69,7 @@ import 
org.apache.gravitino.server.web.rest.MetadataObjectPolicyOperations;
 import org.apache.gravitino.server.web.rest.MetadataObjectTagOperations;
 import org.apache.gravitino.server.web.rest.MetalakeOperations;
 import org.apache.gravitino.server.web.rest.ModelOperations;
+import org.apache.gravitino.server.web.rest.OperationType;
 import org.apache.gravitino.server.web.rest.OwnerOperations;
 import org.apache.gravitino.server.web.rest.PartitionOperations;
 import org.apache.gravitino.server.web.rest.PermissionOperations;
@@ -78,6 +82,7 @@ import org.apache.gravitino.server.web.rest.TagOperations;
 import org.apache.gravitino.server.web.rest.TopicOperations;
 import org.apache.gravitino.server.web.rest.UserOperations;
 import org.apache.gravitino.server.web.rest.ViewOperations;
+import org.apache.gravitino.utils.NameIdentifierUtil;
 import org.apache.gravitino.utils.PrincipalUtils;
 import org.glassfish.hk2.api.Descriptor;
 import org.glassfish.hk2.api.Filter;
@@ -230,6 +235,27 @@ public class GravitinoInterceptionService implements 
InterceptionService {
             boolean authorizeResult;
             try {
               authorizeResult = executor.execute(authorizationRequestContext);
+            } catch (NoSuchTableException exception) {
+              NameIdentifier tableIdent = 
metadataContext.get(Entity.EntityType.TABLE);
+              if (tableIdent == null) {
+                throw exception;
+              }
+              return ExceptionHandlers.handleTableException(
+                  OperationType.LOAD,
+                  tableIdent.name(),
+                  NameIdentifierUtil.getSchemaIdentifier(tableIdent).name(),
+                  exception);
+            } catch (NoSuchViewException exception) {
+              NameIdentifier viewIdent = 
metadataContext.get(Entity.EntityType.VIEW);
+              if (viewIdent == null) {
+                throw exception;
+              }
+              // An authorized absence probe must terminate before the 
resource can load a view.
+              return ExceptionHandlers.handleViewException(
+                  OperationType.LOAD,
+                  viewIdent.name(),
+                  NameIdentifierUtil.getSchemaIdentifier(viewIdent).name(),
+                  exception);
             } catch (BadRequestException exception) {
               LOG.warn("Invalid authorization request", exception);
               return Utils.illegalArguments(exception.getMessage(), exception);
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/filter/authorization/AuthorizeExecutorFactory.java
 
b/server/src/main/java/org/apache/gravitino/server/web/filter/authorization/AuthorizeExecutorFactory.java
index 12a526cb2b..e4f5be3749 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/filter/authorization/AuthorizeExecutorFactory.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/filter/authorization/AuthorizeExecutorFactory.java
@@ -58,6 +58,8 @@ public class AuthorizeExecutorFactory {
           secondaryExpression,
           secondaryExpressionCondition,
           allowCheckExistenceExpression);
+      case LOAD_VIEW -> new LoadViewAuthorizationExecutor(
+          expression, metadataContext, pathParams, entityType, 
allowCheckExistenceExpression);
       case CREATE_SCHEMA -> new CreateSchemaAuthorizationExecutor(
           parameters, args, expression, metadataContext, pathParams, 
entityType);
       case TEST_CATALOG_CONNECTION -> new 
CatalogConnectionTestAuthorizationExecutor(
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/filter/authorization/LoadTableAuthorizationExecutor.java
 
b/server/src/main/java/org/apache/gravitino/server/web/filter/authorization/LoadTableAuthorizationExecutor.java
index a0a99b94e4..36b49e43e1 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/filter/authorization/LoadTableAuthorizationExecutor.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/filter/authorization/LoadTableAuthorizationExecutor.java
@@ -87,8 +87,10 @@ public class LoadTableAuthorizationExecutor extends 
CommonAuthorizerExecutor {
     }
 
     NameIdentifier tableIdentifier = 
metadataContext.get(Entity.EntityType.TABLE);
-    return tableIdentifier != null
-        && 
!GravitinoEnv.getInstance().tableDispatcher().tableExists(tableIdentifier);
+    if (tableIdentifier != null) {
+      
GravitinoEnv.getInstance().internalTableDispatcher().loadTable(tableIdentifier);
+    }
+    return false;
   }
 
   private static boolean shouldCheckModifyTablePrivilege(
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/filter/authorization/LoadViewAuthorizationExecutor.java
 
b/server/src/main/java/org/apache/gravitino/server/web/filter/authorization/LoadViewAuthorizationExecutor.java
new file mode 100644
index 0000000000..5a594f744f
--- /dev/null
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/filter/authorization/LoadViewAuthorizationExecutor.java
@@ -0,0 +1,79 @@
+/*
+ * 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.server.web.filter.authorization;
+
+import java.util.Map;
+import java.util.Optional;
+import javax.annotation.Nullable;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.GravitinoEnv;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.authorization.AuthorizationRequestContext;
+import 
org.apache.gravitino.server.authorization.expression.AuthorizationExpressionEvaluator;
+
+/** Authorizes view loads and permits authorized probes of absent views. */
+public class LoadViewAuthorizationExecutor extends CommonAuthorizerExecutor {
+  @Nullable private final AuthorizationExpressionEvaluator 
allowCheckExistenceEvaluator;
+
+  /**
+   * Creates an executor for loading views.
+   *
+   * @param expression the primary authorization expression
+   * @param metadataContext the metadata identifiers
+   * @param pathParams the request path parameters
+   * @param entityType the optional entity type
+   * @param allowCheckExistenceExpression the expression permitting existence 
probes
+   */
+  public LoadViewAuthorizationExecutor(
+      String expression,
+      Map<Entity.EntityType, NameIdentifier> metadataContext,
+      Map<String, Object> pathParams,
+      Optional<String> entityType,
+      String allowCheckExistenceExpression) {
+    super(expression, metadataContext, pathParams, entityType);
+    allowCheckExistenceEvaluator =
+        StringUtils.isBlank(allowCheckExistenceExpression)
+            ? null
+            : new 
AuthorizationExpressionEvaluator(allowCheckExistenceExpression);
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public boolean execute(AuthorizationRequestContext 
authorizationRequestContext) throws Exception {
+    if (super.execute(authorizationRequestContext)) {
+      return true;
+    }
+
+    if (allowCheckExistenceEvaluator == null
+        || !allowCheckExistenceEvaluator.evaluate(
+            metadataContext, pathParams, authorizationRequestContext, 
entityType)) {
+      return false;
+    }
+
+    NameIdentifier viewIdentifier = 
metadataContext.get(Entity.EntityType.VIEW);
+    if (viewIdentifier == null) {
+      return false;
+    }
+    try {
+      
GravitinoEnv.getInstance().internalViewDispatcher().loadView(viewIdentifier);
+    } catch (UnsupportedOperationException e) {
+      // Preserve the primary denial when the connector cannot perform the 
existence probe.
+    }
+    return false;
+  }
+}
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/ViewOperations.java 
b/server/src/main/java/org/apache/gravitino/server/web/rest/ViewOperations.java
index 794298b25f..786d41ea29 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/rest/ViewOperations.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/ViewOperations.java
@@ -49,6 +49,7 @@ import org.apache.gravitino.rel.ViewChange;
 import org.apache.gravitino.server.authorization.MetadataAuthzHelper;
 import 
org.apache.gravitino.server.authorization.annotations.AuthorizationExpression;
 import 
org.apache.gravitino.server.authorization.annotations.AuthorizationMetadata;
+import 
org.apache.gravitino.server.authorization.annotations.AuthorizationRequest;
 import 
org.apache.gravitino.server.authorization.expression.AuthorizationExpressionConstants;
 import org.apache.gravitino.server.web.Utils;
 import org.apache.gravitino.utils.NameIdentifierUtil;
@@ -163,13 +164,18 @@ public class ViewOperations {
   @ResponseMetered(name = "load-view", absolute = true)
   @AuthorizationExpression(
       expression = 
AuthorizationExpressionConstants.LOAD_VIEW_AUTHORIZATION_EXPRESSION,
+      allowCheckExistence =
+          
AuthorizationExpressionConstants.PROBE_TABLE_LIKE_AUTHORIZATION_EXPRESSION,
       accessMetadataType = MetadataObject.Type.VIEW)
   public Response loadView(
       @PathParam("metalake") @AuthorizationMetadata(type = 
Entity.EntityType.METALAKE)
           String metalake,
       @PathParam("catalog") @AuthorizationMetadata(type = 
Entity.EntityType.CATALOG) String catalog,
       @PathParam("schema") @AuthorizationMetadata(type = 
Entity.EntityType.SCHEMA) String schema,
-      @PathParam("view") @AuthorizationMetadata(type = Entity.EntityType.VIEW) 
String view) {
+      @PathParam("view")
+          @AuthorizationMetadata(type = Entity.EntityType.VIEW)
+          @AuthorizationRequest(type = 
AuthorizationRequest.RequestType.LOAD_VIEW)
+          String view) {
     LOG.info("Received load view request for view: {}.{}.{}.{}", metalake, 
catalog, schema, view);
     try {
       return Utils.doAs(
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/filter/TestGravitinoInterceptionService.java
 
b/server/src/test/java/org/apache/gravitino/server/web/filter/TestGravitinoInterceptionService.java
index d29ade70ca..33ffa50053 100644
--- 
a/server/src/test/java/org/apache/gravitino/server/web/filter/TestGravitinoInterceptionService.java
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/filter/TestGravitinoInterceptionService.java
@@ -34,8 +34,11 @@ import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.lang.reflect.Method;
 import java.security.Principal;
+import java.security.PrivilegedExceptionAction;
+import java.time.Instant;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.core.Response;
 import org.aopalliance.intercept.MethodInterceptor;
@@ -61,9 +64,21 @@ import org.apache.gravitino.dto.responses.ErrorConstants;
 import org.apache.gravitino.dto.responses.ErrorResponse;
 import org.apache.gravitino.exceptions.ForbiddenException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
+import org.apache.gravitino.exceptions.NoSuchTableException;
+import org.apache.gravitino.exceptions.NoSuchViewException;
 import org.apache.gravitino.listener.EventBus;
 import 
org.apache.gravitino.listener.api.event.server.AuthorizationDenialFailureEvent;
+import org.apache.gravitino.meta.AuditInfo;
 import org.apache.gravitino.metalake.MetalakeManager;
+import org.apache.gravitino.rel.Column;
+import org.apache.gravitino.rel.Representation;
+import org.apache.gravitino.rel.SQLRepresentation;
+import org.apache.gravitino.rel.Table;
+import org.apache.gravitino.rel.View;
+import org.apache.gravitino.rel.expressions.distributions.Distributions;
+import org.apache.gravitino.rel.expressions.sorts.SortOrder;
+import org.apache.gravitino.rel.expressions.transforms.Transform;
+import org.apache.gravitino.rel.indexes.Index;
 import org.apache.gravitino.server.authorization.GravitinoAuthorizerProvider;
 import 
org.apache.gravitino.server.authorization.annotations.AuthorizationExpression;
 import 
org.apache.gravitino.server.authorization.annotations.AuthorizationFullName;
@@ -633,8 +648,7 @@ public class TestGravitinoInterceptionService {
   }
 
   @Test
-  public void 
testLoadTableAuthorizationProceedsWhenProbeAllowedTableDoesNotExist()
-      throws Throwable {
+  void testLoadTableAuthorizationTerminatesWhenProbeAllowedTableDoesNotExist() 
throws Throwable {
     try (MockedStatic<PrincipalUtils> principalUtilsMocked = 
mockStatic(PrincipalUtils.class);
         MockedStatic<GravitinoAuthorizerProvider> authorizerMocked =
             mockStatic(GravitinoAuthorizerProvider.class);
@@ -661,9 +675,10 @@ public class TestGravitinoInterceptionService {
       TableDispatcher tableDispatcher = mock(TableDispatcher.class);
       EventBus mockEventBus = mock(EventBus.class);
       envMocked.when(GravitinoEnv::getInstance).thenReturn(mockEnv);
-      when(mockEnv.tableDispatcher()).thenReturn(tableDispatcher);
+      when(mockEnv.internalTableDispatcher()).thenReturn(tableDispatcher);
       when(mockEnv.eventBus()).thenReturn(mockEventBus);
-      
when(tableDispatcher.tableExists(ArgumentMatchers.any())).thenReturn(false);
+      when(tableDispatcher.loadTable(ArgumentMatchers.any()))
+          .thenThrow(new NoSuchTableException("Table is absent"));
 
       MethodInterceptor interceptor = tableLoadInterceptor();
       MethodInvocation invocation = tableLoadInvocation();
@@ -673,8 +688,11 @@ public class TestGravitinoInterceptionService {
       Response response = (Response) interceptor.invoke(invocation);
 
       assertEquals(Response.Status.NOT_FOUND.getStatusCode(), 
response.getStatus());
-      verify(invocation).proceed();
-      verify(tableDispatcher).tableExists(ArgumentMatchers.any());
+      verify(invocation, never()).proceed();
+      assertEquals(
+          NoSuchTableException.class.getSimpleName(),
+          ((ErrorResponse) response.getEntity()).getType());
+      verify(tableDispatcher).loadTable(ArgumentMatchers.any());
       verify(mockEventBus, never()).dispatchEvent(ArgumentMatchers.any());
     }
   }
@@ -708,9 +726,9 @@ public class TestGravitinoInterceptionService {
       TableDispatcher tableDispatcher = mock(TableDispatcher.class);
       EventBus mockEventBus = spy(new EventBus(Collections.emptyList()));
       envMocked.when(GravitinoEnv::getInstance).thenReturn(mockEnv);
-      when(mockEnv.tableDispatcher()).thenReturn(tableDispatcher);
+      when(mockEnv.internalTableDispatcher()).thenReturn(tableDispatcher);
       when(mockEnv.eventBus()).thenReturn(mockEventBus);
-      
when(tableDispatcher.tableExists(ArgumentMatchers.any())).thenReturn(true);
+      
when(tableDispatcher.loadTable(ArgumentMatchers.any())).thenReturn(mock(Table.class));
 
       MethodInterceptor interceptor = tableLoadInterceptor();
       MethodInvocation invocation = tableLoadInvocation();
@@ -719,12 +737,264 @@ public class TestGravitinoInterceptionService {
 
       assertEquals(Response.Status.FORBIDDEN.getStatusCode(), 
response.getStatus());
       verify(invocation, never()).proceed();
-      verify(tableDispatcher).tableExists(ArgumentMatchers.any());
+      verify(tableDispatcher).loadTable(ArgumentMatchers.any());
+      verify(mockEventBus)
+          
.dispatchEvent(ArgumentMatchers.any(AuthorizationDenialFailureEvent.class));
+    }
+  }
+
+  /**
+   * Unsupported view probes preserve the primary denial and its authorization 
event.
+   *
+   * @throws Throwable If authorization interception fails.
+   */
+  @Test
+  public void 
testLoadViewAuthorizationReturnsForbiddenWhenViewsAreUnsupported() throws 
Throwable {
+    try (MockedStatic<PrincipalUtils> principalUtilsMocked = 
mockStatic(PrincipalUtils.class);
+        MockedStatic<GravitinoAuthorizerProvider> authorizerMocked =
+            mockStatic(GravitinoAuthorizerProvider.class);
+        MockedStatic<AuthorizationUtils> authUtilsMocked = 
mockStatic(AuthorizationUtils.class);
+        MockedStatic<GravitinoEnv> envMocked = mockStatic(GravitinoEnv.class)) 
{
+      principalUtilsMocked
+          .when(PrincipalUtils::getCurrentPrincipal)
+          .thenReturn(new UserPrincipal("tester"));
+      
principalUtilsMocked.when(PrincipalUtils::getCurrentUserName).thenReturn("tester");
+
+      authUtilsMocked
+          .when(
+              () ->
+                  AuthorizationUtils.checkCurrentUser(
+                      ArgumentMatchers.any(), ArgumentMatchers.any(), 
ArgumentMatchers.any()))
+          .thenAnswer(invocation -> null);
+
+      GravitinoAuthorizerProvider mockedProvider = 
mock(GravitinoAuthorizerProvider.class);
+      GravitinoAuthorizer authorizer = tableProbeAuthorizer();
+      
authorizerMocked.when(GravitinoAuthorizerProvider::getInstance).thenReturn(mockedProvider);
+      when(mockedProvider.getGravitinoAuthorizer()).thenReturn(authorizer);
+
+      GravitinoEnv mockEnv = mock(GravitinoEnv.class);
+      ViewDispatcher viewDispatcher = mock(ViewDispatcher.class);
+      EventBus mockEventBus = spy(new EventBus(Collections.emptyList()));
+      envMocked.when(GravitinoEnv::getInstance).thenReturn(mockEnv);
+      when(mockEnv.internalViewDispatcher()).thenReturn(viewDispatcher);
+      when(mockEnv.eventBus()).thenReturn(mockEventBus);
+      when(viewDispatcher.loadView(ArgumentMatchers.any()))
+          .thenThrow(new UnsupportedOperationException("Catalog does not 
support view operations"));
+
+      Method method =
+          ViewOperations.class.getMethod(
+              "loadView", String.class, String.class, String.class, 
String.class);
+      MethodInterceptor interceptor =
+          new 
GravitinoInterceptionService().getMethodInterceptors(method).get(0);
+      MethodInvocation invocation = mock(MethodInvocation.class);
+      when(invocation.getMethod()).thenReturn(method);
+      when(invocation.getArguments())
+          .thenReturn(new Object[] {"testMetalake", "testCatalog", 
"testSchema", "missingView"});
+
+      Response response = (Response) interceptor.invoke(invocation);
+
+      assertEquals(Response.Status.FORBIDDEN.getStatusCode(), 
response.getStatus());
+      verify(invocation, never()).proceed();
+      verify(viewDispatcher).loadView(ArgumentMatchers.any());
       verify(mockEventBus)
           
.dispatchEvent(ArgumentMatchers.any(AuthorizationDenialFailureEvent.class));
     }
   }
 
+  /**
+   * An absent view probe is terminal even when a concurrent create makes the 
view loadable.
+   *
+   * @throws Throwable If authorization interception fails.
+   */
+  @Test
+  public void testMissingViewProbeDoesNotLoadConcurrentlyCreatedView() throws 
Throwable {
+    try (MockedStatic<PrincipalUtils> principalUtilsMocked = 
mockStatic(PrincipalUtils.class);
+        MockedStatic<GravitinoAuthorizerProvider> authorizerMocked =
+            mockStatic(GravitinoAuthorizerProvider.class);
+        MockedStatic<AuthorizationUtils> authUtilsMocked = 
mockStatic(AuthorizationUtils.class);
+        MockedStatic<GravitinoEnv> envMocked = mockStatic(GravitinoEnv.class)) 
{
+      principalUtilsMocked
+          .when(PrincipalUtils::getCurrentPrincipal)
+          .thenReturn(new UserPrincipal("tester"));
+      
principalUtilsMocked.when(PrincipalUtils::getCurrentUserName).thenReturn("tester");
+
+      authUtilsMocked
+          .when(
+              () ->
+                  AuthorizationUtils.checkCurrentUser(
+                      ArgumentMatchers.any(), ArgumentMatchers.any(), 
ArgumentMatchers.any()))
+          .thenAnswer(invocation -> null);
+
+      GravitinoAuthorizerProvider mockedProvider = 
mock(GravitinoAuthorizerProvider.class);
+      GravitinoAuthorizer authorizer = tableProbeAuthorizer();
+      
authorizerMocked.when(GravitinoAuthorizerProvider::getInstance).thenReturn(mockedProvider);
+      when(mockedProvider.getGravitinoAuthorizer()).thenReturn(authorizer);
+
+      GravitinoEnv mockEnv = mock(GravitinoEnv.class);
+      ViewDispatcher viewDispatcher = mock(ViewDispatcher.class);
+      EventBus mockEventBus = spy(new EventBus(Collections.emptyList()));
+      envMocked.when(GravitinoEnv::getInstance).thenReturn(mockEnv);
+      when(mockEnv.internalViewDispatcher()).thenReturn(viewDispatcher);
+      when(mockEnv.eventBus()).thenReturn(mockEventBus);
+      AtomicReference<View> created = new AtomicReference<>();
+      View view = mock(View.class);
+      when(view.name()).thenReturn("missingView");
+      when(view.columns()).thenReturn(new Column[0]);
+      when(view.representations())
+          .thenReturn(
+              new Representation[] {
+                SQLRepresentation.builder()
+                    .withDialect("spark")
+                    .withSql("SELECT secret FROM private_table")
+                    .build()
+              });
+      when(view.properties()).thenReturn(Collections.emptyMap());
+      when(view.auditInfo())
+          .thenReturn(
+              
AuditInfo.builder().withCreator("owner").withCreateTime(Instant.now()).build());
+      when(viewDispatcher.loadView(ArgumentMatchers.any()))
+          .thenAnswer(
+              ignored -> {
+                if (created.get() == null) {
+                  // The load observed absence; another request creates the 
view before the handler
+                  // can run.
+                  created.set(view);
+                  throw new NoSuchViewException("View is absent");
+                }
+                return created.get();
+              });
+      ViewOperations operations = new ViewOperations(viewDispatcher);
+      FieldUtils.writeField(operations, "httpRequest", 
mock(HttpServletRequest.class), true);
+      principalUtilsMocked
+          .when(() -> PrincipalUtils.doAs(any(), any()))
+          .thenAnswer(
+              invocation -> ((PrivilegedExceptionAction<?>) 
invocation.getArgument(1)).run());
+
+      Method method =
+          ViewOperations.class.getMethod(
+              "loadView", String.class, String.class, String.class, 
String.class);
+      MethodInterceptor interceptor =
+          new 
GravitinoInterceptionService().getMethodInterceptors(method).get(0);
+      MethodInvocation invocation = mock(MethodInvocation.class);
+      when(invocation.getMethod()).thenReturn(method);
+      when(invocation.getArguments())
+          .thenReturn(new Object[] {"testMetalake", "testCatalog", 
"testSchema", "missingView"});
+
+      when(invocation.proceed())
+          .thenAnswer(
+              ignored ->
+                  operations.loadView("testMetalake", "testCatalog", 
"testSchema", "missingView"));
+      Response response = (Response) interceptor.invoke(invocation);
+
+      assertEquals(Response.Status.NOT_FOUND.getStatusCode(), 
response.getStatus());
+      verify(invocation, never()).proceed();
+      verify(viewDispatcher).loadView(ArgumentMatchers.any());
+      Assertions.assertSame(view, created.get());
+      Assertions.assertTrue(response.getEntity() instanceof ErrorResponse);
+      assertEquals(
+          NoSuchViewException.class.getSimpleName(),
+          ((ErrorResponse) response.getEntity()).getType());
+
+      verify(mockEventBus, never()).dispatchEvent(ArgumentMatchers.any());
+    }
+  }
+
+  /**
+   * An absent table probe is terminal even when a concurrent create makes the 
table loadable.
+   *
+   * @throws Throwable If authorization interception fails.
+   */
+  @Test
+  public void testMissingTableProbeDoesNotLoadConcurrentlyCreatedTable() 
throws Throwable {
+    try (MockedStatic<PrincipalUtils> principalUtilsMocked = 
mockStatic(PrincipalUtils.class);
+        MockedStatic<GravitinoAuthorizerProvider> authorizerMocked =
+            mockStatic(GravitinoAuthorizerProvider.class);
+        MockedStatic<AuthorizationUtils> authUtilsMocked = 
mockStatic(AuthorizationUtils.class);
+        MockedStatic<GravitinoEnv> envMocked = mockStatic(GravitinoEnv.class)) 
{
+      principalUtilsMocked
+          .when(PrincipalUtils::getCurrentPrincipal)
+          .thenReturn(new UserPrincipal("tester"));
+      
principalUtilsMocked.when(PrincipalUtils::getCurrentUserName).thenReturn("tester");
+
+      authUtilsMocked
+          .when(
+              () ->
+                  AuthorizationUtils.checkCurrentUser(
+                      ArgumentMatchers.any(), ArgumentMatchers.any(), 
ArgumentMatchers.any()))
+          .thenAnswer(invocation -> null);
+
+      GravitinoAuthorizerProvider mockedProvider = 
mock(GravitinoAuthorizerProvider.class);
+      GravitinoAuthorizer authorizer = tableProbeAuthorizer();
+      
authorizerMocked.when(GravitinoAuthorizerProvider::getInstance).thenReturn(mockedProvider);
+      when(mockedProvider.getGravitinoAuthorizer()).thenReturn(authorizer);
+
+      GravitinoEnv mockEnv = mock(GravitinoEnv.class);
+      TableDispatcher tableDispatcher = mock(TableDispatcher.class);
+      EventBus mockEventBus = spy(new EventBus(Collections.emptyList()));
+      envMocked.when(GravitinoEnv::getInstance).thenReturn(mockEnv);
+      when(mockEnv.internalTableDispatcher()).thenReturn(tableDispatcher);
+      when(mockEnv.eventBus()).thenReturn(mockEventBus);
+      AtomicReference<Table> created = new AtomicReference<>();
+      Table table = mock(Table.class);
+      when(table.name()).thenReturn("missingTable");
+      when(table.columns()).thenReturn(new Column[0]);
+      when(table.sortOrder()).thenReturn(new SortOrder[0]);
+      when(table.partitioning()).thenReturn(new Transform[0]);
+      when(table.index()).thenReturn(new Index[0]);
+      when(table.distribution()).thenReturn(Distributions.NONE);
+      when(table.properties()).thenReturn(Collections.emptyMap());
+      when(table.auditInfo())
+          .thenReturn(
+              
AuditInfo.builder().withCreator("owner").withCreateTime(Instant.now()).build());
+      when(tableDispatcher.loadTable(ArgumentMatchers.any()))
+          .thenAnswer(
+              ignored -> {
+                if (created.get() == null) {
+                  // The load observed absence; another request creates the 
table before the handler
+                  // can run.
+                  created.set(table);
+                  throw new NoSuchTableException("Table is absent");
+                }
+                return created.get();
+              });
+      TableOperations operations = new TableOperations(tableDispatcher);
+      FieldUtils.writeField(operations, "httpRequest", 
mock(HttpServletRequest.class), true);
+      principalUtilsMocked
+          .when(() -> PrincipalUtils.doAs(any(), any()))
+          .thenAnswer(
+              invocation -> ((PrivilegedExceptionAction<?>) 
invocation.getArgument(1)).run());
+
+      Method method =
+          TableOperations.class.getMethod(
+              "loadTable", String.class, String.class, String.class, 
String.class, String.class);
+      MethodInterceptor interceptor =
+          new 
GravitinoInterceptionService().getMethodInterceptors(method).get(0);
+      MethodInvocation invocation = mock(MethodInvocation.class);
+      when(invocation.getMethod()).thenReturn(method);
+      when(invocation.getArguments())
+          .thenReturn(
+              new Object[] {"testMetalake", "testCatalog", "testSchema", 
"missingTable", null});
+
+      when(invocation.proceed())
+          .thenAnswer(
+              ignored ->
+                  operations.loadTable(
+                      "testMetalake", "testCatalog", "testSchema", 
"missingTable", null));
+      Response response = (Response) interceptor.invoke(invocation);
+
+      assertEquals(Response.Status.NOT_FOUND.getStatusCode(), 
response.getStatus());
+      verify(invocation, never()).proceed();
+      verify(tableDispatcher).loadTable(ArgumentMatchers.any());
+      Assertions.assertSame(table, created.get());
+      Assertions.assertTrue(response.getEntity() instanceof ErrorResponse);
+      assertEquals(
+          NoSuchTableException.class.getSimpleName(),
+          ((ErrorResponse) response.getEntity()).getType());
+
+      verify(mockEventBus, never()).dispatchEvent(ArgumentMatchers.any());
+    }
+  }
+
   /**
    * When {@code checkCurrentUser} throws {@link NoSuchMetalakeException}, the 
403 is a
    * resource-not-found masquerading as forbidden — no {@link 
AuthorizationDenialFailureEvent} is
@@ -984,7 +1254,8 @@ public class TestGravitinoInterceptionService {
             ArgumentMatchers.eq("testMetalake"),
             ArgumentMatchers.argThat(
                 metadataObject ->
-                    metadataObject.type() == MetadataObject.Type.CATALOG
+                    metadataObject != null
+                        && metadataObject.type() == MetadataObject.Type.CATALOG
                         && "testCatalog".equals(metadataObject.name())),
             ArgumentMatchers.eq(Privilege.Name.USE_CATALOG),
             ArgumentMatchers.any()))
@@ -994,7 +1265,8 @@ public class TestGravitinoInterceptionService {
             ArgumentMatchers.eq("testMetalake"),
             ArgumentMatchers.argThat(
                 metadataObject ->
-                    metadataObject.type() == MetadataObject.Type.SCHEMA
+                    metadataObject != null
+                        && metadataObject.type() == MetadataObject.Type.SCHEMA
                         && "testSchema".equals(metadataObject.name())),
             ArgumentMatchers.eq(Privilege.Name.USE_SCHEMA),
             ArgumentMatchers.any()))
@@ -1004,7 +1276,8 @@ public class TestGravitinoInterceptionService {
             ArgumentMatchers.eq("testMetalake"),
             ArgumentMatchers.argThat(
                 metadataObject ->
-                    metadataObject.type() == MetadataObject.Type.METALAKE
+                    metadataObject != null
+                        && metadataObject.type() == 
MetadataObject.Type.METALAKE
                         && "testMetalake".equals(metadataObject.name())),
             ArgumentMatchers.eq(Privilege.Name.CREATE_TABLE),
             ArgumentMatchers.any()))
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/filter/authorization/TestLoadTableAuthorizationExecutor.java
 
b/server/src/test/java/org/apache/gravitino/server/web/filter/authorization/TestLoadTableAuthorizationExecutor.java
index 15bc76ce8f..6e58a850a4 100644
--- 
a/server/src/test/java/org/apache/gravitino/server/web/filter/authorization/TestLoadTableAuthorizationExecutor.java
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/filter/authorization/TestLoadTableAuthorizationExecutor.java
@@ -20,17 +20,41 @@
 package org.apache.gravitino.server.web.filter.authorization;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyMap;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.util.Collections;
+import java.util.Map;
 import java.util.Optional;
+import org.apache.commons.lang3.reflect.FieldUtils;
 import org.apache.gravitino.Entity;
+import org.apache.gravitino.GravitinoEnv;
 import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.authorization.AuthorizationRequestContext;
+import org.apache.gravitino.catalog.TableDispatcher;
+import org.apache.gravitino.exceptions.NoSuchTableException;
+import org.apache.gravitino.rel.Table;
 import 
org.apache.gravitino.server.authorization.annotations.AuthorizationRequest;
 import 
org.apache.gravitino.server.authorization.annotations.ExpressionCondition;
 import 
org.apache.gravitino.server.authorization.expression.AuthorizationExpressionConstants;
+import 
org.apache.gravitino.server.authorization.expression.AuthorizationExpressionEvaluator;
 import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
 
 public class TestLoadTableAuthorizationExecutor {
   private static final String PRIMARY_EXPRESSION =
@@ -63,6 +87,65 @@ public class TestLoadTableAuthorizationExecutor {
     assertEquals(PRIMARY_EXPRESSION, expression(executor));
   }
 
+  @Test
+  void testExistenceProbeUsesInternalTableDispatcher() throws Exception {
+    Method method = TestOperations.class.getMethod("loadTable", String.class);
+    NameIdentifier tableIdentifier = NameIdentifier.of("metalake", "catalog", 
"schema", "table");
+    LoadTableAuthorizationExecutor executor =
+        new LoadTableAuthorizationExecutor(
+            method.getParameters(),
+            new Object[] {"SELECT_TABLE"},
+            PRIMARY_EXPRESSION,
+            Map.of(Entity.EntityType.TABLE, tableIdentifier),
+            Collections.emptyMap(),
+            Optional.empty(),
+            SECONDARY_EXPRESSION,
+            ExpressionCondition.NEVER,
+            
AuthorizationExpressionConstants.PROBE_TABLE_LIKE_AUTHORIZATION_EXPRESSION);
+
+    AuthorizationExpressionEvaluator primaryEvaluator =
+        mock(AuthorizationExpressionEvaluator.class);
+    AuthorizationExpressionEvaluator existenceEvaluator =
+        mock(AuthorizationExpressionEvaluator.class);
+    when(primaryEvaluator.evaluate(
+            anyMap(), anyMap(), any(AuthorizationRequestContext.class), any()))
+        .thenReturn(false);
+    when(existenceEvaluator.evaluate(
+            anyMap(), anyMap(), any(AuthorizationRequestContext.class), any()))
+        .thenReturn(true);
+    executor.authorizationExpressionEvaluator = primaryEvaluator;
+    FieldUtils.writeField(executor, "allowCheckExistenceEvaluator", 
existenceEvaluator, true);
+
+    GravitinoEnv env = mock(GravitinoEnv.class);
+    TableDispatcher internalTableDispatcher = mock(TableDispatcher.class);
+    when(env.internalTableDispatcher()).thenReturn(internalTableDispatcher);
+    when(internalTableDispatcher.loadTable(tableIdentifier))
+        .thenThrow(new NoSuchTableException("Table is absent"));
+
+    try (MockedStatic<GravitinoEnv> mockedEnv = 
mockStatic(GravitinoEnv.class)) {
+      mockedEnv.when(GravitinoEnv::getInstance).thenReturn(env);
+      assertThrows(
+          NoSuchTableException.class, () -> executor.execute(new 
AuthorizationRequestContext()));
+      
doReturn(mock(Table.class)).when(internalTableDispatcher).loadTable(tableIdentifier);
+      assertFalse(executor.execute(new AuthorizationRequestContext()));
+      IllegalStateException failure = new IllegalStateException("Table backend 
unavailable");
+      
doThrow(failure).when(internalTableDispatcher).loadTable(tableIdentifier);
+      assertSame(
+          failure,
+          assertThrows(
+              IllegalStateException.class,
+              () -> executor.execute(new AuthorizationRequestContext())));
+      verify(internalTableDispatcher, times(3)).loadTable(tableIdentifier);
+      reset(internalTableDispatcher);
+      when(existenceEvaluator.evaluate(anyMap(), anyMap(), any(), 
any())).thenReturn(false);
+      assertFalse(executor.execute(new AuthorizationRequestContext()));
+      verifyNoInteractions(internalTableDispatcher);
+      when(primaryEvaluator.evaluate(anyMap(), anyMap(), any(), 
any())).thenReturn(true);
+      assertTrue(executor.execute(new AuthorizationRequestContext()));
+      verifyNoInteractions(internalTableDispatcher);
+    }
+  }
+
   private static LoadTableAuthorizationExecutor createExecutor(
       String privileges, ExpressionCondition condition) throws Exception {
     Method method = TestOperations.class.getMethod("loadTable", String.class);
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/filter/authorization/TestLoadViewAuthorizationExecutor.java
 
b/server/src/test/java/org/apache/gravitino/server/web/filter/authorization/TestLoadViewAuthorizationExecutor.java
new file mode 100644
index 0000000000..dbc767c4e1
--- /dev/null
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/filter/authorization/TestLoadViewAuthorizationExecutor.java
@@ -0,0 +1,170 @@
+/*
+ * 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.server.web.filter.authorization;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyMap;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+import java.util.Map;
+import java.util.Optional;
+import javax.annotation.Nullable;
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.GravitinoEnv;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.authorization.AuthorizationRequestContext;
+import org.apache.gravitino.catalog.ViewDispatcher;
+import org.apache.gravitino.exceptions.NoSuchViewException;
+import org.apache.gravitino.rel.View;
+import 
org.apache.gravitino.server.authorization.annotations.AuthorizationExpression;
+import 
org.apache.gravitino.server.authorization.annotations.AuthorizationRequest;
+import 
org.apache.gravitino.server.authorization.annotations.ExpressionCondition;
+import 
org.apache.gravitino.server.authorization.expression.AuthorizationExpressionConstants;
+import 
org.apache.gravitino.server.authorization.expression.AuthorizationExpressionEvaluator;
+import org.apache.gravitino.server.web.rest.ViewOperations;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+
+/** Tests authorization and existence probing for view loads. */
+public class TestLoadViewAuthorizationExecutor {
+  @Test
+  void testAuthorizedMissingViewProbe() throws Exception {
+    assertThrows(NoSuchViewException.class, () -> check(false, true, false, 
true, true));
+  }
+
+  @Test
+  void testExistingViewRemainsForbidden() throws Exception {
+    check(false, true, true, false, true);
+  }
+
+  @Test
+  void testNoProbePermissionDoesNotCheckExistence() throws Exception {
+    check(false, false, false, false, false);
+  }
+
+  @Test
+  void testPrimaryPermissionDoesNotProbe() throws Exception {
+    check(true, false, true, true, false);
+  }
+
+  @Test
+  void testViewEndpointSelectsProbeExecutor() throws Exception {
+    var method =
+        ViewOperations.class.getMethod(
+            "loadView", String.class, String.class, String.class, 
String.class);
+    var annotation = method.getAnnotation(AuthorizationExpression.class);
+    var request = 
method.getParameters()[3].getAnnotation(AuthorizationRequest.class);
+    assertEquals(AuthorizationRequest.RequestType.LOAD_VIEW, request.type());
+    assertEquals(
+        
AuthorizationExpressionConstants.PROBE_TABLE_LIKE_AUTHORIZATION_EXPRESSION,
+        annotation.allowCheckExistence());
+    assertInstanceOf(
+        LoadViewAuthorizationExecutor.class,
+        AuthorizeExecutorFactory.create(
+            annotation.expression(),
+            request.type(),
+            Map.of(),
+            Map.of(),
+            Optional.empty(),
+            method.getParameters(),
+            new Object[] {"m", "c", "s", "v"},
+            "",
+            ExpressionCondition.NEVER,
+            annotation.allowCheckExistence()));
+  }
+
+  @Test
+  void testUnsupportedViewOperationsRemainForbidden() throws Exception {
+    check(
+        false,
+        true,
+        false,
+        false,
+        true,
+        new UnsupportedOperationException("Catalog does not support view 
operations"));
+  }
+
+  @Test
+  void testProbeBackendFailureIsNotTreatedAsMissingView() {
+    IllegalStateException failure = new IllegalStateException("View backend 
unavailable");
+    assertSame(
+        failure,
+        assertThrows(
+            IllegalStateException.class, () -> check(false, true, false, 
false, true, failure)));
+  }
+
+  private void check(
+      boolean primary, boolean probe, boolean exists, boolean expected, 
boolean shouldProbe)
+      throws Exception {
+    check(primary, probe, exists, expected, shouldProbe, null);
+  }
+
+  private void check(
+      boolean primary,
+      boolean probe,
+      boolean exists,
+      boolean expected,
+      boolean shouldProbe,
+      @Nullable RuntimeException probeFailure)
+      throws Exception {
+    NameIdentifier ident = NameIdentifier.of("m", "c", "s", "v");
+    var executor =
+        new LoadViewAuthorizationExecutor(
+            
AuthorizationExpressionConstants.LOAD_VIEW_AUTHORIZATION_EXPRESSION,
+            Map.of(Entity.EntityType.VIEW, ident),
+            Map.of(),
+            Optional.empty(),
+            
AuthorizationExpressionConstants.PROBE_TABLE_LIKE_AUTHORIZATION_EXPRESSION);
+    var primaryEvaluator = mock(AuthorizationExpressionEvaluator.class);
+    var probeEvaluator = mock(AuthorizationExpressionEvaluator.class);
+    when(primaryEvaluator.evaluate(anyMap(), anyMap(), any(), 
any())).thenReturn(primary);
+    when(probeEvaluator.evaluate(anyMap(), anyMap(), any(), 
any())).thenReturn(probe);
+    executor.authorizationExpressionEvaluator = primaryEvaluator;
+    FieldUtils.writeField(executor, "allowCheckExistenceEvaluator", 
probeEvaluator, true);
+    var env = mock(GravitinoEnv.class);
+    var dispatcher = mock(ViewDispatcher.class);
+    when(env.internalViewDispatcher()).thenReturn(dispatcher);
+    if (probeFailure != null) {
+      when(dispatcher.loadView(ident)).thenThrow(probeFailure);
+    } else if (exists) {
+      when(dispatcher.loadView(ident)).thenReturn(mock(View.class));
+    } else {
+      when(dispatcher.loadView(ident)).thenThrow(new NoSuchViewException("View 
is absent"));
+    }
+    try (MockedStatic<GravitinoEnv> mocked = mockStatic(GravitinoEnv.class)) {
+      mocked.when(GravitinoEnv::getInstance).thenReturn(env);
+      assertEquals(expected, executor.execute(new 
AuthorizationRequestContext()));
+    }
+    if (shouldProbe) {
+      verify(dispatcher).loadView(ident);
+    } else {
+      verifyNoInteractions(dispatcher);
+    }
+  }
+}

Reply via email to