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

yuqi1129 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new 7239078b10 [#13401] fix(core): unwrap InvocationTargetException in 
OperationsProxy.invoke (#13402)
7239078b10 is described below

commit 7239078b1072742984b7b0411022e2fd4d4992c1
Author: YangJie <[email protected]>
AuthorDate: Tue Sep 22 05:53:57 2026 -0400

    [#13401] fix(core): unwrap InvocationTargetException in 
OperationsProxy.invoke (#13402)
    
    ### What changes were proposed in this pull request?
    
    `OperationsProxy.invoke` now rethrows the target exception from the
    `InvocationTargetException` that reflection produces, matching the
    standard proxy-handler idiom already used in `DynMethods`. Callers see
    the operation's own exception, the same one a direct call would throw.
    
    ### Why are the changes needed?
    
    When a catalog supplies a `ProxyPlugin`, the reflective wrapper escaped
    instead of the real exception, so exception-type dispatch such as the
    REST layer's status mapping matched none of its `instanceof` branches
    and returned a generic 500 rather than the correct 404 or 409.
    
    Fix: #13401
    
    ### Does this PR introduce _any_ user-facing change?
    
    No.
    
    ### How was this patch tested?
    
    Added `TestOperationsProxy.testExceptionsAreUnwrapped` (a typed
    exception thrown through a proxied operation reaches the caller
    unwrapped) and `testNormalInvocationPassesThrough` (a normal call is
    unaffected). The unwrap assertion fails against the pre-fix code.
---
 .../gravitino/connector/OperationsProxy.java       | 16 +++-
 .../gravitino/connector/TestOperationsProxy.java   | 97 ++++++++++++++++++++++
 2 files changed, 109 insertions(+), 4 deletions(-)

diff --git 
a/core/src/main/java/org/apache/gravitino/connector/OperationsProxy.java 
b/core/src/main/java/org/apache/gravitino/connector/OperationsProxy.java
index 14ba65ca06..d19054063f 100644
--- a/core/src/main/java/org/apache/gravitino/connector/OperationsProxy.java
+++ b/core/src/main/java/org/apache/gravitino/connector/OperationsProxy.java
@@ -19,6 +19,7 @@
 package org.apache.gravitino.connector;
 
 import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 import java.util.Collections;
@@ -54,9 +55,16 @@ public class OperationsProxy<T> implements InvocationHandler 
{
 
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws 
Throwable {
-    return plugin.doAs(
-        PrincipalUtils.getCurrentPrincipal(),
-        () -> method.invoke(ops, args),
-        Collections.emptyMap());
+    try {
+      return plugin.doAs(
+          PrincipalUtils.getCurrentPrincipal(),
+          () -> method.invoke(ops, args),
+          Collections.emptyMap());
+    } catch (InvocationTargetException e) {
+      // Surface the operation's own exception instead of the reflective 
wrapper, so
+      // callers dispatching on exception type (e.g. REST status mapping) keep 
working.
+      Throwable cause = e.getCause();
+      throw cause != null ? cause : e;
+    }
   }
 }
diff --git 
a/core/src/test/java/org/apache/gravitino/connector/TestOperationsProxy.java 
b/core/src/test/java/org/apache/gravitino/connector/TestOperationsProxy.java
new file mode 100644
index 0000000000..53a968614f
--- /dev/null
+++ b/core/src/test/java/org/apache/gravitino/connector/TestOperationsProxy.java
@@ -0,0 +1,97 @@
+/*
+ * 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.connector;
+
+import java.security.Principal;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.exceptions.NoSuchTableException;
+import org.apache.gravitino.utils.Executable;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/** Tests for {@link OperationsProxy}. */
+public class TestOperationsProxy {
+
+  private static class PassthroughProxyPlugin implements ProxyPlugin {
+    @Override
+    public Object doAs(
+        Principal principal, Executable<Object, Exception> action, Map<String, 
String> properties)
+        throws Throwable {
+      return action.execute();
+    }
+
+    @Override
+    public void bindCatalogOperation(CatalogOperations ops) {}
+  }
+
+  @Test
+  public void testExceptionsAreUnwrapped() {
+    CatalogOperations ops =
+        new CatalogOperations() {
+          @Override
+          public void initialize(
+              Map<String, String> config,
+              CatalogInfo catalogInfo,
+              HasPropertyMetadata hasPropertyMetadata) {}
+
+          @Override
+          public void testConnection(NameIdentifier catalogIdent) throws 
Exception {
+            throw new NoSuchTableException("table does not exist");
+          }
+
+          @Override
+          public void close() {}
+        };
+
+    CatalogOperations proxy = OperationsProxy.createProxy(ops, new 
PassthroughProxyPlugin());
+
+    // The caller must see the operation's own exception, not the reflective
+    // InvocationTargetException wrapper, so exception-type dispatch keeps 
working.
+    Assertions.assertThrows(
+        NoSuchTableException.class, () -> 
proxy.testConnection(NameIdentifier.of("catalog")));
+  }
+
+  @Test
+  public void testNormalInvocationPassesThrough() throws Exception {
+    AtomicInteger calls = new AtomicInteger();
+    CatalogOperations ops =
+        new CatalogOperations() {
+          @Override
+          public void initialize(
+              Map<String, String> config,
+              CatalogInfo catalogInfo,
+              HasPropertyMetadata hasPropertyMetadata) {}
+
+          @Override
+          public void testConnection(NameIdentifier catalogIdent) {
+            calls.incrementAndGet();
+          }
+
+          @Override
+          public void close() {}
+        };
+
+    CatalogOperations proxy = OperationsProxy.createProxy(ops, new 
PassthroughProxyPlugin());
+    proxy.testConnection(NameIdentifier.of("catalog"));
+
+    Assertions.assertEquals(1, calls.get());
+  }
+}

Reply via email to