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

oscerd pushed a commit to branch camel-4.22.x
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/camel-4.22.x by this push:
     new 6c8fe7302dc8 [backport camel-4.22.x] CAMEL-26343: camel-aws - set the 
AWS SDK HTTP client explicitly instead of the classpath lookup (#28564)
6c8fe7302dc8 is described below

commit 6c8fe7302dc8c76d17a0011ef6bbd518d4229462
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Oct 29 15:16:36 2026 +0100

    [backport camel-4.22.x] CAMEL-26343: camel-aws - set the AWS SDK HTTP 
client explicitly instead of the classpath lookup (#28564)
    
    AwsClientBuilderUtil set an HTTP client on the SDK client builder only
    with a proxy or trustAllCertificates. Otherwise the AWS SDK looked the
    client up with ServiceLoader, and that lookup fails as a whole when one
    registration cannot be loaded, as when a Kafka Connect plugin sees the
    apache-client registration of the worker's own AWS SDK
    ("ApacheSdkHttpService not a subtype").
    
    Set Apache5HttpClient (sync) and NettyNioAsyncHttpClient (async), the
    implementations the lookup selects from Camel's dependencies, unless the
    AWS SDK setting software.amazon.awssdk.http.service.impl (or
    software.amazon.awssdk.http.async.service.impl) names one.
    
    Signed-off-by: Andrea Cosentino <[email protected]>
    Co-authored-by: Claude Opus 5.5 <[email protected]>
---
 components/camel-aws/camel-aws-common/pom.xml      |   6 +
 .../component/aws/common/AwsClientBuilderUtil.java |  24 ++-
 .../aws/common/AwsClientBuilderUtilTest.java       | 162 +++++++++++++++++++++
 .../software.amazon.awssdk.http.SdkHttpService     |  19 +++
 ...re.amazon.awssdk.http.async.SdkAsyncHttpService |  19 +++
 5 files changed, 228 insertions(+), 2 deletions(-)

diff --git a/components/camel-aws/camel-aws-common/pom.xml 
b/components/camel-aws/camel-aws-common/pom.xml
index 7bfc97803226..61cf1e5f42e0 100644
--- a/components/camel-aws/camel-aws-common/pom.xml
+++ b/components/camel-aws/camel-aws-common/pom.xml
@@ -72,6 +72,12 @@
             <artifactId>netty-nio-client</artifactId>
             <version>${aws-java-sdk2-version}</version>
         </dependency>
+        <dependency>
+            <groupId>software.amazon.awssdk</groupId>
+            <artifactId>sts</artifactId>
+            <version>${aws-java-sdk2-version}</version>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.junit.jupiter</groupId>
             <artifactId>junit-jupiter</artifactId>
diff --git 
a/components/camel-aws/camel-aws-common/src/main/java/org/apache/camel/component/aws/common/AwsClientBuilderUtil.java
 
b/components/camel-aws/camel-aws-common/src/main/java/org/apache/camel/component/aws/common/AwsClientBuilderUtil.java
index b2f678d8a1cf..210987a30ba9 100644
--- 
a/components/camel-aws/camel-aws-common/src/main/java/org/apache/camel/component/aws/common/AwsClientBuilderUtil.java
+++ 
b/components/camel-aws/camel-aws-common/src/main/java/org/apache/camel/component/aws/common/AwsClientBuilderUtil.java
@@ -31,6 +31,7 @@ import 
software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
 import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
 import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder;
 import software.amazon.awssdk.core.SdkClient;
+import software.amazon.awssdk.core.SdkSystemSetting;
 import software.amazon.awssdk.core.client.builder.SdkAsyncClientBuilder;
 import software.amazon.awssdk.core.client.builder.SdkSyncClientBuilder;
 import software.amazon.awssdk.http.SdkHttpClient;
@@ -76,7 +77,7 @@ public final class AwsClientBuilderUtil {
         Apache5HttpClient.Builder httpClientBuilder = null;
         boolean httpClientConfigured = false;
 
-        // 1. Configure proxy
+        // 1. Configure proxy, or else the default HTTP client
         if (ObjectHelper.isNotEmpty(config.getProxyHost())
                 && ObjectHelper.isNotEmpty(config.getProxyPort())) {
             LOG.trace("Configuring proxy: {}:{}", config.getProxyHost(), 
config.getProxyPort());
@@ -87,6 +88,9 @@ public final class AwsClientBuilderUtil {
                     .build();
             httpClientBuilder = 
Apache5HttpClient.builder().proxyConfiguration(proxyConfig);
             httpClientConfigured = true;
+        } else if 
(useDefaultHttpClient(SdkSystemSetting.SYNC_HTTP_SERVICE_IMPL)) {
+            httpClientBuilder = Apache5HttpClient.builder();
+            httpClientConfigured = true;
         }
 
         // 2. Configure credentials
@@ -167,7 +171,7 @@ public final class AwsClientBuilderUtil {
         NettyNioAsyncHttpClient.Builder httpClientBuilder = null;
         boolean httpClientConfigured = false;
 
-        // 1. Configure proxy
+        // 1. Configure proxy, or else the default HTTP client
         if (ObjectHelper.isNotEmpty(config.getProxyHost())
                 && ObjectHelper.isNotEmpty(config.getProxyPort())) {
             LOG.trace("Configuring async proxy: {}:{}", config.getProxyHost(), 
config.getProxyPort());
@@ -179,6 +183,9 @@ public final class AwsClientBuilderUtil {
                             .build();
             httpClientBuilder = 
NettyNioAsyncHttpClient.builder().proxyConfiguration(proxyConfig);
             httpClientConfigured = true;
+        } else if 
(useDefaultHttpClient(SdkSystemSetting.ASYNC_HTTP_SERVICE_IMPL)) {
+            httpClientBuilder = NettyNioAsyncHttpClient.builder();
+            httpClientConfigured = true;
         }
 
         // 2. Configure credentials
@@ -238,6 +245,19 @@ public final class AwsClientBuilderUtil {
         return buildAsyncClient(config, builderSupplier, null);
     }
 
+    /**
+     * Whether to set the default HTTP client on the client builder: the one 
the AWS SDK would select itself. Without an
+     * HTTP client on the builder, the SDK looks up every implementation 
registered on the classpath, and fails if any
+     * of them cannot be loaded, for example when a Kafka Connect plugin sees 
the registrations of the AWS SDK of the
+     * worker. When the given AWS SDK setting names an implementation, the 
choice is left to the SDK.
+     *
+     * @param  implSetting the AWS SDK setting that names the sync or async 
HTTP client implementation
+     * @return             true to set the default HTTP client, false when the 
setting names an implementation
+     */
+    private static boolean useDefaultHttpClient(SdkSystemSetting implSetting) {
+        return implSetting.getStringValue().isEmpty();
+    }
+
     /**
      * Resolve the appropriate credentials provider based on configuration.
      * <p>
diff --git 
a/components/camel-aws/camel-aws-common/src/test/java/org/apache/camel/component/aws/common/AwsClientBuilderUtilTest.java
 
b/components/camel-aws/camel-aws-common/src/test/java/org/apache/camel/component/aws/common/AwsClientBuilderUtilTest.java
new file mode 100644
index 000000000000..e0943bdaa68a
--- /dev/null
+++ 
b/components/camel-aws/camel-aws-common/src/test/java/org/apache/camel/component/aws/common/AwsClientBuilderUtilTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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.camel.component.aws.common;
+
+import java.util.ServiceConfigurationError;
+
+import org.junit.jupiter.api.Test;
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.core.Protocol;
+import software.amazon.awssdk.core.SdkSystemSetting;
+import software.amazon.awssdk.core.exception.SdkClientException;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.sts.StsAsyncClient;
+import software.amazon.awssdk.services.sts.StsClient;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * The test resources register {@link ForeignHttpService} as a sync and an 
async HTTP client implementation of the AWS
+ * SDK, which it is not. The SDK lookup of the HTTP client on the classpath 
then fails, as when a Kafka Connect plugin
+ * sees the registrations of the AWS SDK of the worker.
+ */
+class AwsClientBuilderUtilTest {
+
+    private static final AwsCommonConfiguration CONFIGURATION = new 
StaticCredentialsConfiguration();
+
+    @Test
+    void sdkLookupFailsOnTheForeignRegistration() {
+        StaticCredentialsProvider credentials
+                = 
StaticCredentialsProvider.create(AwsBasicCredentials.create("accessKey", 
"secretKey"));
+
+        assertThatThrownBy(() -> 
StsClient.builder().region(Region.US_EAST_1).credentialsProvider(credentials).build())
+                .isInstanceOf(ServiceConfigurationError.class)
+                .hasMessageContaining("not a subtype");
+        assertThatThrownBy(() -> 
StsAsyncClient.builder().region(Region.US_EAST_1).credentialsProvider(credentials).build())
+                .isInstanceOf(ServiceConfigurationError.class)
+                .hasMessageContaining("not a subtype");
+    }
+
+    @Test
+    void buildClientDoesNotUseTheSdkLookup() {
+        try (StsClient client = 
AwsClientBuilderUtil.buildClient(CONFIGURATION, StsClient::builder)) {
+            assertThat(client).isNotNull();
+        }
+    }
+
+    @Test
+    void buildAsyncClientDoesNotUseTheSdkLookup() {
+        try (StsAsyncClient client = 
AwsClientBuilderUtil.buildAsyncClient(CONFIGURATION, StsAsyncClient::builder)) {
+            assertThat(client).isNotNull();
+        }
+    }
+
+    @Test
+    void sdkSettingLeavesTheChoiceToTheSdk() {
+        // the SDK fails to load the implementation the setting names, so 
Camel did not set its own HTTP client
+        String property = SdkSystemSetting.SYNC_HTTP_SERVICE_IMPL.property();
+        System.setProperty(property, 
"org.apache.camel.component.aws.common.MissingHttpService");
+        try {
+            assertThatThrownBy(() -> 
AwsClientBuilderUtil.buildClient(CONFIGURATION, StsClient::builder))
+                    .isInstanceOf(SdkClientException.class)
+                    .hasMessageContaining(property);
+        } finally {
+            System.clearProperty(property);
+        }
+    }
+
+    /**
+     * Registered in the test resources as an SdkHttpService and an 
SdkAsyncHttpService, without being either.
+     */
+    public static class ForeignHttpService {
+    }
+
+    private static final class StaticCredentialsConfiguration implements 
AwsCommonConfiguration {
+
+        @Override
+        public String getAccessKey() {
+            return "accessKey";
+        }
+
+        @Override
+        public String getSecretKey() {
+            return "secretKey";
+        }
+
+        @Override
+        public String getSessionToken() {
+            return null;
+        }
+
+        @Override
+        public boolean isUseDefaultCredentialsProvider() {
+            return false;
+        }
+
+        @Override
+        public boolean isUseProfileCredentialsProvider() {
+            return false;
+        }
+
+        @Override
+        public boolean isUseSessionCredentials() {
+            return false;
+        }
+
+        @Override
+        public String getProfileCredentialsName() {
+            return null;
+        }
+
+        @Override
+        public Protocol getProxyProtocol() {
+            return null;
+        }
+
+        @Override
+        public String getProxyHost() {
+            return null;
+        }
+
+        @Override
+        public Integer getProxyPort() {
+            return null;
+        }
+
+        @Override
+        public String getRegion() {
+            return Region.US_EAST_1.id();
+        }
+
+        @Override
+        public boolean isOverrideEndpoint() {
+            return false;
+        }
+
+        @Override
+        public String getUriEndpointOverride() {
+            return null;
+        }
+
+        @Override
+        public boolean isTrustAllCertificates() {
+            return false;
+        }
+    }
+}
diff --git 
a/components/camel-aws/camel-aws-common/src/test/resources/META-INF/services/software.amazon.awssdk.http.SdkHttpService
 
b/components/camel-aws/camel-aws-common/src/test/resources/META-INF/services/software.amazon.awssdk.http.SdkHttpService
new file mode 100644
index 000000000000..366f708cf2a4
--- /dev/null
+++ 
b/components/camel-aws/camel-aws-common/src/test/resources/META-INF/services/software.amazon.awssdk.http.SdkHttpService
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+
+# Not an HTTP client implementation: the AWS SDK lookup fails on it, see 
AwsClientBuilderUtilTest
+org.apache.camel.component.aws.common.AwsClientBuilderUtilTest$ForeignHttpService
diff --git 
a/components/camel-aws/camel-aws-common/src/test/resources/META-INF/services/software.amazon.awssdk.http.async.SdkAsyncHttpService
 
b/components/camel-aws/camel-aws-common/src/test/resources/META-INF/services/software.amazon.awssdk.http.async.SdkAsyncHttpService
new file mode 100644
index 000000000000..366f708cf2a4
--- /dev/null
+++ 
b/components/camel-aws/camel-aws-common/src/test/resources/META-INF/services/software.amazon.awssdk.http.async.SdkAsyncHttpService
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+
+# Not an HTTP client implementation: the AWS SDK lookup fails on it, see 
AwsClientBuilderUtilTest
+org.apache.camel.component.aws.common.AwsClientBuilderUtilTest$ForeignHttpService

Reply via email to