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

jt2594838 pushed a commit to branch dev/1.3
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/dev/1.3 by this push:
     new a550448d613 Pipe: Fixed the bug that the parent class loader may 
wrongly load ext pipe plugins (#17580) (#17600)
a550448d613 is described below

commit a550448d6136d12ac47f3d96fa8cb570687913cc
Author: Caideyipi <[email protected]>
AuthorDate: Wed May 6 17:34:49 2026 +0800

    Pipe: Fixed the bug that the parent class loader may wrongly load ext pipe 
plugins (#17580) (#17600)
---
 .../plugin/service/PipePluginClassLoader.java      |  42 ++++-
 .../plugin/service/PipePluginClassLoaderTest.java  | 175 +++++++++++++++++++++
 2 files changed, 216 insertions(+), 1 deletion(-)

diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/plugin/service/PipePluginClassLoader.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/plugin/service/PipePluginClassLoader.java
index a2bc1df1a27..745d93566fc 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/plugin/service/PipePluginClassLoader.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/plugin/service/PipePluginClassLoader.java
@@ -35,6 +35,10 @@ import java.util.stream.Stream;
 @ThreadSafe
 public class PipePluginClassLoader extends URLClassLoader {
 
+  private static final String[] PARENT_FIRST_CLASS_PREFIXES = {
+    "java.", "javax.", "jdk.", "sun.", "org.slf4j.", 
"org.apache.iotdb.pipe.api."
+  };
+
   private final String libRoot;
 
   /**
@@ -50,7 +54,11 @@ public class PipePluginClassLoader extends URLClassLoader {
   private volatile boolean deprecated;
 
   public PipePluginClassLoader(String libRoot) throws IOException {
-    super(new URL[0]);
+    this(libRoot, ClassLoader.getSystemClassLoader());
+  }
+
+  PipePluginClassLoader(String libRoot, ClassLoader parent) throws IOException 
{
+    super(new URL[0], parent);
     this.libRoot = libRoot;
     activeInstanceCount = new AtomicLong(0);
     deprecated = false;
@@ -82,6 +90,38 @@ public class PipePluginClassLoader extends URLClassLoader {
     closeIfPossible();
   }
 
+  @Override
+  protected Class<?> loadClass(String name, boolean resolve) throws 
ClassNotFoundException {
+    synchronized (getClassLoadingLock(name)) {
+      Class<?> loadedClass = findLoadedClass(name);
+      if (loadedClass == null) {
+        loadedClass =
+            shouldLoadFromParentFirst(name) ? super.loadClass(name, false) : 
loadClassLocally(name);
+      }
+      if (resolve) {
+        resolveClass(loadedClass);
+      }
+      return loadedClass;
+    }
+  }
+
+  private Class<?> loadClassLocally(String name) throws ClassNotFoundException 
{
+    try {
+      return findClass(name);
+    } catch (ClassNotFoundException e) {
+      return super.loadClass(name, false);
+    }
+  }
+
+  private boolean shouldLoadFromParentFirst(String name) {
+    for (String prefix : PARENT_FIRST_CLASS_PREFIXES) {
+      if (name.startsWith(prefix)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   private void closeIfPossible() throws IOException {
     if (deprecated && activeInstanceCount.get() == 0) {
       close();
diff --git 
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/agent/plugin/service/PipePluginClassLoaderTest.java
 
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/agent/plugin/service/PipePluginClassLoaderTest.java
new file mode 100644
index 00000000000..39126656fc1
--- /dev/null
+++ 
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/agent/plugin/service/PipePluginClassLoaderTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.iotdb.commons.pipe.agent.plugin.service;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import javax.tools.JavaCompiler;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.ToolProvider;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.stream.Stream;
+
+public class PipePluginClassLoaderTest {
+
+  @Test
+  public void testPluginClassesShouldOverrideParentClasses() throws Exception {
+    final Path tempDir = 
Files.createTempDirectory("pipe-plugin-classloader-test");
+    try {
+      final Path parentSources = 
Files.createDirectory(tempDir.resolve("parent-sources"));
+      final Path parentClasses = 
Files.createDirectory(tempDir.resolve("parent-classes"));
+      final Path childSources = 
Files.createDirectory(tempDir.resolve("child-sources"));
+      final Path childClasses = 
Files.createDirectory(tempDir.resolve("child-classes"));
+
+      final String sampleSource =
+          "package test.plugin;"
+              + "public class Sample {"
+              + "  public String ping() {"
+              + "    return test.dep.Helper.value();"
+              + "  }"
+              + "}";
+      final String parentHelperSource =
+          "package test.dep;"
+              + "public class Helper {"
+              + "  public static String value() {"
+              + "    return \"parent\";"
+              + "  }"
+              + "}";
+      final String childHelperSource =
+          "package test.dep;"
+              + "public class Helper {"
+              + "  public static String value() {"
+              + "    return \"child\";"
+              + "  }"
+              + "}";
+
+      compile(
+          parentSources,
+          parentClasses,
+          createSources(sampleSource, false),
+          createSources(parentHelperSource, true));
+      compile(
+          childSources,
+          childClasses,
+          createSources(sampleSource, false),
+          createSources(childHelperSource, true));
+
+      final Path parentJar = tempDir.resolve("parent.jar");
+      final Path childJar = tempDir.resolve("child.jar");
+      createJar(parentJar, parentClasses, 
Arrays.asList("test/plugin/Sample.class"));
+      createJar(
+          childJar,
+          childClasses,
+          Arrays.asList("test/plugin/Sample.class", "test/dep/Helper.class"));
+
+      try (final URLClassLoader parentClassLoader =
+              new URLClassLoader(new URL[] {parentJar.toUri().toURL()}, null);
+          final PipePluginClassLoader pluginClassLoader =
+              new PipePluginClassLoader(childJar.toString(), 
parentClassLoader)) {
+        final Class<?> sampleClass = Class.forName("test.plugin.Sample", true, 
pluginClassLoader);
+        Assert.assertSame(pluginClassLoader, sampleClass.getClassLoader());
+        final Object sample = 
sampleClass.getDeclaredConstructor().newInstance();
+        Assert.assertEquals("child", 
sampleClass.getMethod("ping").invoke(sample));
+      }
+    } finally {
+      deleteRecursively(tempDir);
+    }
+  }
+
+  private static Map<String, String> createSources(
+      final String source, final boolean helperSource) {
+    final Map<String, String> sources = new LinkedHashMap<>();
+    sources.put(helperSource ? "test.dep.Helper" : "test.plugin.Sample", 
source);
+    return sources;
+  }
+
+  private static void compile(
+      final Path sourcesDir, final Path classesDir, final Map<String, 
String>... sourceGroups)
+      throws IOException {
+    final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+    Assert.assertNotNull("A JDK is required to run this test.", compiler);
+
+    final List<File> sourceFiles = new ArrayList<>();
+    for (final Map<String, String> sourceGroup : sourceGroups) {
+      for (final Map.Entry<String, String> entry : sourceGroup.entrySet()) {
+        final Path sourceFile = sourcesDir.resolve(entry.getKey().replace('.', 
'/') + ".java");
+        Files.createDirectories(sourceFile.getParent());
+        Files.write(sourceFile, 
entry.getValue().getBytes(StandardCharsets.UTF_8));
+        sourceFiles.add(sourceFile.toFile());
+      }
+    }
+
+    try (final StandardJavaFileManager fileManager =
+        compiler.getStandardFileManager(null, null, StandardCharsets.UTF_8)) {
+      final boolean success =
+          compiler
+              .getTask(
+                  null,
+                  fileManager,
+                  null,
+                  Arrays.asList("-d", classesDir.toString()),
+                  null,
+                  fileManager.getJavaFileObjectsFromFiles(sourceFiles))
+              .call();
+      Assert.assertTrue(success);
+    }
+  }
+
+  private static void createJar(
+      final Path jarPath, final Path classesDir, final List<String> 
classEntries)
+      throws IOException {
+    try (final JarOutputStream jarOutputStream =
+        new JarOutputStream(Files.newOutputStream(jarPath))) {
+      for (final String classEntry : classEntries) {
+        jarOutputStream.putNextEntry(new JarEntry(classEntry));
+        
jarOutputStream.write(Files.readAllBytes(classesDir.resolve(classEntry)));
+        jarOutputStream.closeEntry();
+      }
+    }
+  }
+
+  private static void deleteRecursively(final Path path) throws IOException {
+    if (path == null || !Files.exists(path)) {
+      return;
+    }
+    try (final Stream<Path> stream = Files.walk(path)) {
+      for (final Path subPath :
+          (Iterable<Path>) stream.sorted(Comparator.reverseOrder())::iterator) 
{
+        Files.deleteIfExists(subPath);
+      }
+    }
+  }
+}

Reply via email to