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

JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new 948933b779 [jindo] Unify the Jindo User-Agent format (#10321)
948933b779 is described below

commit 948933b779cb0601b8b194710205652a7912c47b
Author: Dapeng Sun(孙大鹏) <[email protected]>
AuthorDate: Fri Oct 2 09:40:49 2026 +0800

    [jindo] Unify the Jindo User-Agent format (#10321)
---
 paimon-filesystems/paimon-jindo/pom.xml            |  13 +++
 .../apache/paimon/jindo/JindoBuildVersions.java    |  27 +++++
 .../java/org/apache/paimon/jindo/JindoFileIO.java  |  16 +--
 .../org/apache/paimon/jindo/JindoUserAgent.java    | 101 ++++++++++++++++++
 .../apache/paimon/jindo/TestJindoUserAgent.java    | 113 +++++++++++++++++++++
 pom.xml                                            |   6 ++
 6 files changed, 261 insertions(+), 15 deletions(-)

diff --git a/paimon-filesystems/paimon-jindo/pom.xml 
b/paimon-filesystems/paimon-jindo/pom.xml
index f70fe56d91..cb1a5c2978 100644
--- a/paimon-filesystems/paimon-jindo/pom.xml
+++ b/paimon-filesystems/paimon-jindo/pom.xml
@@ -163,6 +163,19 @@
 
     <build>
         <plugins>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>templating-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>filter-sources</id>
+                        <goals>
+                            <goal>filter-sources</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-jar-plugin</artifactId>
diff --git 
a/paimon-filesystems/paimon-jindo/src/main/java-templates/org/apache/paimon/jindo/JindoBuildVersions.java
 
b/paimon-filesystems/paimon-jindo/src/main/java-templates/org/apache/paimon/jindo/JindoBuildVersions.java
new file mode 100644
index 0000000000..912757080f
--- /dev/null
+++ 
b/paimon-filesystems/paimon-jindo/src/main/java-templates/org/apache/paimon/jindo/JindoBuildVersions.java
@@ -0,0 +1,27 @@
+/*
+ * 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.paimon.jindo;
+
+/** Versions written in at build time, so reading them needs no class loader 
lookup. */
+final class JindoBuildVersions {
+
+    static final String PAIMON = "${project.version}";
+
+    private JindoBuildVersions() {}
+}
diff --git 
a/paimon-filesystems/paimon-jindo/src/main/java/org/apache/paimon/jindo/JindoFileIO.java
 
b/paimon-filesystems/paimon-jindo/src/main/java/org/apache/paimon/jindo/JindoFileIO.java
index e5e2ede687..71468672af 100644
--- 
a/paimon-filesystems/paimon-jindo/src/main/java/org/apache/paimon/jindo/JindoFileIO.java
+++ 
b/paimon-filesystems/paimon-jindo/src/main/java/org/apache/paimon/jindo/JindoFileIO.java
@@ -29,7 +29,6 @@ import org.apache.paimon.plugin.PluginLoader;
 import org.apache.paimon.utils.IOUtils;
 import org.apache.paimon.utils.Pair;
 import org.apache.paimon.utils.SensitiveConfigUtils;
-import org.apache.paimon.utils.StringUtils;
 
 import com.aliyun.jindodata.common.JindoHadoopSystem;
 import com.aliyun.jindodata.dls.JindoDlsFileSystem;
@@ -72,10 +71,7 @@ public class JindoFileIO extends HadoopCompliantFileIO 
implements HadoopOptionsP
     private static final String OSS_ACCESS_KEY_ID = "fs.oss.accessKeyId";
     private static final String OSS_ACCESS_KEY_SECRET = 
"fs.oss.accessKeySecret";
     private static final String OSS_SECURITY_TOKEN = "fs.oss.securityToken";
-    private static final String OSS_USER_AGENT_EXTENDED = 
"fs.oss.user.agent.extended";
     private static final String OSS_SHOW_DIR_TIMESTAMP = 
"fs.oss.show-dir-timestamp";
-    private static final String DLF_ACCESS_TRACKING_EXTENDED_INFO =
-            "dlf.access-tracking.extended-info";
 
     private static final Map<String, String> CASE_SENSITIVE_KEYS =
             new HashMap<String, String>() {
@@ -154,17 +150,7 @@ public class JindoFileIO extends HadoopCompliantFileIO 
implements HadoopOptionsP
             hadoopOptions.set(OSS_SHOW_DIR_TIMESTAMP, "false");
         }
 
-        String dlfAccessTrackingExtendedInfo =
-                context.options().get(DLF_ACCESS_TRACKING_EXTENDED_INFO);
-        if 
(!StringUtils.isNullOrWhitespaceOnly(dlfAccessTrackingExtendedInfo)) {
-            LOG.info("Adding DLF access tracking extended info: {}", 
dlfAccessTrackingExtendedInfo);
-            String existedUserAgentExtended = 
hadoopOptions.get(OSS_USER_AGENT_EXTENDED);
-            hadoopOptions.set(
-                    OSS_USER_AGENT_EXTENDED,
-                    
StringUtils.isNullOrWhitespaceOnly(existedUserAgentExtended)
-                            ? dlfAccessTrackingExtendedInfo
-                            : existedUserAgentExtended + " " + 
dlfAccessTrackingExtendedInfo);
-        }
+        JindoUserAgent.apply(context.options(), hadoopOptions);
 
         // another config when enable cache
         hadoopOptionsWithCache = new Options(hadoopOptions.toMap());
diff --git 
a/paimon-filesystems/paimon-jindo/src/main/java/org/apache/paimon/jindo/JindoUserAgent.java
 
b/paimon-filesystems/paimon-jindo/src/main/java/org/apache/paimon/jindo/JindoUserAgent.java
new file mode 100644
index 0000000000..9d22354cd9
--- /dev/null
+++ 
b/paimon-filesystems/paimon-jindo/src/main/java/org/apache/paimon/jindo/JindoUserAgent.java
@@ -0,0 +1,101 @@
+/*
+ * 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.paimon.jindo;
+
+import org.apache.paimon.options.Options;
+import org.apache.paimon.utils.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Fills Paimon's unified User-Agent {@code module(transport;features) 
extended} into the {@code
+ * fs.<scheme>.user.agent.*} keys, from which Jindo builds the header.
+ */
+final class JindoUserAgent {
+
+    static final String MODULE = "user-agent.module";
+    static final String FEATURES = "user-agent.features";
+    static final String EXTENDED = "user-agent.extended";
+    static final String DLF_ACCESS_TRACKING_EXTENDED_INFO = 
"dlf.access-tracking.extended-info";
+
+    static final String PAIMON = "Paimon/" + JindoBuildVersions.PAIMON;
+
+    private static final String[] SCHEMES = {"oss", "dls"};
+
+    private JindoUserAgent() {}
+
+    /**
+     * Per part, {@code fs.<scheme>.user.agent.*} wins over the catalog-wide 
{@code user-agent.*}.
+     */
+    static void apply(Options catalogOptions, Options hadoopOptions) {
+        for (String scheme : SCHEMES) {
+            String prefix = "fs." + scheme + ".user.agent.";
+            String module = first(hadoopOptions.get(prefix + "module"), 
catalogOptions.get(MODULE));
+            if (module != null) {
+                hadoopOptions.set(prefix + "module", module);
+            }
+            hadoopOptions.set(
+                    prefix + "features",
+                    features(
+                            first(
+                                    hadoopOptions.get(prefix + "features"),
+                                    catalogOptions.get(FEATURES))));
+            // Access tracking info is appended, so a user-set extended value 
is kept.
+            String extended =
+                    join(
+                            first(
+                                    hadoopOptions.get(prefix + "extended"),
+                                    catalogOptions.get(EXTENDED)),
+                            
catalogOptions.get(DLF_ACCESS_TRACKING_EXTENDED_INFO));
+            if (!extended.isEmpty()) {
+                hadoopOptions.set(prefix + "extended", extended);
+            }
+        }
+    }
+
+    private static String features(String configured) {
+        List<String> features = new ArrayList<>();
+        if (configured != null) {
+            features.addAll(Arrays.asList(configured.split("\\s+")));
+        }
+        if (features.stream().noneMatch(f -> f.equals("Paimon") || 
f.startsWith("Paimon/"))) {
+            features.add(0, PAIMON);
+        }
+        return String.join(" ", features);
+    }
+
+    private static String first(String preferred, String fallback) {
+        if (!StringUtils.isNullOrWhitespaceOnly(preferred)) {
+            return preferred.trim();
+        }
+        return StringUtils.isNullOrWhitespaceOnly(fallback) ? null : 
fallback.trim();
+    }
+
+    private static String join(String first, String second) {
+        List<String> parts = new ArrayList<>();
+        for (String part : Arrays.asList(first, second)) {
+            if (!StringUtils.isNullOrWhitespaceOnly(part)) {
+                parts.add(part.trim());
+            }
+        }
+        return String.join(" ", parts);
+    }
+}
diff --git 
a/paimon-filesystems/paimon-jindo/src/test/java/org/apache/paimon/jindo/TestJindoUserAgent.java
 
b/paimon-filesystems/paimon-jindo/src/test/java/org/apache/paimon/jindo/TestJindoUserAgent.java
new file mode 100644
index 0000000000..99e0e123b3
--- /dev/null
+++ 
b/paimon-filesystems/paimon-jindo/src/test/java/org/apache/paimon/jindo/TestJindoUserAgent.java
@@ -0,0 +1,113 @@
+/*
+ * 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.paimon.jindo;
+
+import org.apache.paimon.catalog.CatalogContext;
+import org.apache.paimon.options.Options;
+
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Field;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Tests for {@link JindoUserAgent}. */
+public class TestJindoUserAgent {
+
+    private static final String[] SCHEMES = {"oss", "dls"};
+
+    @Test
+    public void testDefault() {
+        assertThat(JindoBuildVersions.PAIMON).matches("\\d+\\.\\d+\\S*");
+        Options hadoopOptions = configure(new Options());
+        for (String scheme : SCHEMES) {
+            assertThat(hadoopOptions.get(key(scheme, 
"features"))).isEqualTo(JindoUserAgent.PAIMON);
+            assertThat(hadoopOptions.get(key(scheme, "module"))).isNull();
+            assertThat(hadoopOptions.get(key(scheme, "extended"))).isNull();
+        }
+    }
+
+    @Test
+    public void testCatalogWideKeys() {
+        Options options = new Options();
+        options.set(JindoUserAgent.MODULE, "MyApp/1.0");
+        options.set(JindoUserAgent.FEATURES, " Flink  Spark ");
+        options.set(JindoUserAgent.EXTENDED, "vvr");
+        Options hadoopOptions = configure(options);
+        for (String scheme : SCHEMES) {
+            assertThat(hadoopOptions.get(key(scheme, 
"module"))).isEqualTo("MyApp/1.0");
+            assertThat(hadoopOptions.get(key(scheme, "features")))
+                    .isEqualTo(JindoUserAgent.PAIMON + " Flink Spark");
+            assertThat(hadoopOptions.get(key(scheme, 
"extended"))).isEqualTo("vvr");
+        }
+    }
+
+    @Test
+    public void testSchemeKeysOverrideCatalogWideKeys() {
+        Options options = new Options();
+        options.set(JindoUserAgent.FEATURES, "Flink");
+        options.set(JindoUserAgent.EXTENDED, "vvr");
+        options.set(key("oss", "features"), "morax/2.6.0");
+        options.set(key("oss", "extended"), "bennett/2.7.0");
+        Options hadoopOptions = configure(options);
+        assertThat(hadoopOptions.get(key("oss", "features")))
+                .isEqualTo(JindoUserAgent.PAIMON + " morax/2.6.0");
+        assertThat(hadoopOptions.get(key("oss", 
"extended"))).isEqualTo("bennett/2.7.0");
+        assertThat(hadoopOptions.get(key("dls", "features")))
+                .isEqualTo(JindoUserAgent.PAIMON + " Flink");
+        assertThat(hadoopOptions.get(key("dls", "extended"))).isEqualTo("vvr");
+    }
+
+    @Test
+    public void testAccessTrackingIsAppendedToCatalogWideExtended() {
+        Options options = new Options();
+        options.set(JindoUserAgent.EXTENDED, "vvr");
+        options.set(JindoUserAgent.DLF_ACCESS_TRACKING_EXTENDED_INFO, "uid/123 
user/alice");
+        Options hadoopOptions = configure(options);
+        for (String scheme : SCHEMES) {
+            assertThat(hadoopOptions.get(key(scheme, "extended")))
+                    .isEqualTo("vvr uid/123 user/alice");
+        }
+    }
+
+    @Test
+    public void testPaimonFeatureIsNotDuplicated() {
+        Options options = new Options();
+        options.set(JindoUserAgent.FEATURES, "Flink Paimon");
+        assertThat(configure(options).get(key("oss", 
"features"))).isEqualTo("Flink Paimon");
+    }
+
+    private static String key(String scheme, String part) {
+        return "fs." + scheme + ".user.agent." + part;
+    }
+
+    private static Options configure(Options options) {
+        options.set("fs.oss.accessKeyId", "testAk");
+        options.set("fs.oss.accessKeySecret", "testSk");
+        JindoFileIO fileIO = new JindoFileIO();
+        fileIO.configure(CatalogContext.create(options));
+        try {
+            Field field = JindoFileIO.class.getDeclaredField("hadoopOptions");
+            field.setAccessible(true);
+            return (Options) field.get(fileIO);
+        } catch (ReflectiveOperationException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/pom.xml b/pom.xml
index 0b5512bd31..ee9ba05399 100644
--- a/pom.xml
+++ b/pom.xml
@@ -980,6 +980,12 @@ under the License.
                     <version>1.7</version>
                 </plugin>
 
+                <plugin>
+                    <groupId>org.codehaus.mojo</groupId>
+                    <artifactId>templating-maven-plugin</artifactId>
+                    <version>3.0.0</version>
+                </plugin>
+
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-compiler-plugin</artifactId>

Reply via email to