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

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


The following commit(s) were added to refs/heads/main by this push:
     new 1cf2c4864c5b Camel 22510 avoid leaking internal data types (#21704)
1cf2c4864c5b is described below

commit 1cf2c4864c5ba70d94dd56807010638759aa7a52
Author: Chandrashekhar G <[email protected]>
AuthorDate: Wed Mar 11 12:25:44 2026 +0530

    Camel 22510 avoid leaking internal data types (#21704)
    
    * Implementation to avoid leaking internal data types.
    
    * Implementation to avoid leaking internal data types.
    
    * Implementation to avoid leaking internal data types.
    
    * Implementation to avoid leaking internal data types.
    
    * Fixed review comments.
    
    * Fixed review comments.
    
    * Formatting issues fixed.
    
    * More review comments fixed.
    
    * Fix for failing tests.
---
 .../component/langchain4j/agent/api/Agent.java     |  20 +--
 .../agent/LangChain4jAgentConverterLoader.java     |   8 ++
 .../agent/LangChain4jAgentConverter.java           |  93 ++++++++-----
 .../agent/LangChain4jAgentProducer.java            |   2 +-
 .../LangChain4jAgentAutoConversionTest.java        | 151 +++++++++++++++++++++
 5 files changed, 234 insertions(+), 40 deletions(-)

diff --git 
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/Agent.java
 
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/Agent.java
index 532f73cd59f0..7d52dfdaa4b9 100644
--- 
a/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/Agent.java
+++ 
b/components/camel-ai/camel-langchain4j-agent-api/src/main/java/org/apache/camel/component/langchain4j/agent/api/Agent.java
@@ -82,15 +82,19 @@ public interface Agent {
      * request body regardless of how the original message was formatted.
      * </p>
      *
-     * @param  messagePayload                 the message payload from the 
exchange body; must be either an
-     *                                        {@link AiAgentBody} or a {@link 
String}
-     * @param  exchange                       the Camel exchange containing 
headers and context information
-     * @return                                an {@link AiAgentBody} instance 
ready for agent processing; returns the
-     *                                        original payload if it's already 
an {@link AiAgentBody}, or creates a new
-     *                                        one from a string payload and 
relevant headers
-     * @throws InvalidPayloadRuntimeException if the payload is neither an 
{@link AiAgentBody} nor a {@link String}
-     * @throws Exception                      if any other error occurs during 
payload processing
+     * @param      messagePayload                 the message payload from the 
exchange body; must be either an
+     *                                            {@link AiAgentBody} or a 
{@link String}
+     * @param      exchange                       the Camel exchange 
containing headers and context information
+     * @return                                    an {@link AiAgentBody} 
instance ready for agent processing; returns
+     *                                            the original payload if it's 
already an {@link AiAgentBody}, or
+     *                                            creates a new one from a 
string payload and relevant headers
+     * @throws     InvalidPayloadRuntimeException if the payload is neither an 
{@link AiAgentBody} nor a {@link String}
+     * @throws     Exception                      if any other error occurs 
during payload processing
+     *
+     * @deprecated                                This method is no longer 
used by {@code LangChain4jAgentProducer}.
+     *                                            Body conversion is now 
handled via Camel TypeConverters.
      */
+    @Deprecated(since = "4.19.0")
     default AiAgentBody<?> processBody(Object messagePayload, Exchange 
exchange) throws Exception {
         if (messagePayload instanceof AiAgentBody<?> payload) {
             return payload;
diff --git 
a/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConverterLoader.java
 
b/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConverterLoader.java
index 1186963e3e9c..240733da94ce 100644
--- 
a/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConverterLoader.java
+++ 
b/components/camel-ai/camel-langchain4j-agent/src/generated/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConverterLoader.java
@@ -60,6 +60,14 @@ public final class LangChain4jAgentConverterLoader 
implements TypeConverterLoade
                 }
                 return answer;
             });
+        addTypeConverter(registry, 
org.apache.camel.component.langchain4j.agent.api.AiAgentBody.class, 
java.lang.String.class, false,
+            (type, exchange, value) -> {
+                Object answer = 
org.apache.camel.component.langchain4j.agent.LangChain4jAgentConverter.textToAiAgentBody((java.lang.String)
 value, exchange);
+                if (false && answer == null) {
+                    answer = Void.class;
+                }
+                return answer;
+            });
         addTypeConverter(registry, 
org.apache.camel.component.langchain4j.agent.api.AiAgentBody.class, 
org.apache.camel.WrappedFile.class, false,
             (type, exchange, value) -> {
                 Object answer = 
org.apache.camel.component.langchain4j.agent.LangChain4jAgentConverter.toAiAgentBody((org.apache.camel.WrappedFile)
 value, exchange);
diff --git 
a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConverter.java
 
b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConverter.java
index 07ec8ca41e80..5207757f98d7 100644
--- 
a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConverter.java
+++ 
b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentConverter.java
@@ -19,6 +19,7 @@ package org.apache.camel.component.langchain4j.agent;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.URLConnection;
 import java.nio.file.Files;
 import java.util.Base64;
 
@@ -34,6 +35,7 @@ import dev.langchain4j.data.pdf.PdfFile;
 import dev.langchain4j.data.video.Video;
 import org.apache.camel.Converter;
 import org.apache.camel.Exchange;
+import org.apache.camel.Message;
 import org.apache.camel.WrappedFile;
 import org.apache.camel.component.langchain4j.agent.api.AiAgentBody;
 import org.slf4j.Logger;
@@ -108,17 +110,7 @@ public final class LangChain4jAgentConverter {
         byte[] fileData = readFileBytes(file);
         Content content = createContent(fileData, mimeType);
 
-        String userMessage = exchange.getIn().getHeader(USER_MESSAGE, 
String.class);
-        String systemMessage = exchange.getIn().getHeader(SYSTEM_MESSAGE, 
String.class);
-        Object memoryId = exchange.getIn().getHeader(MEMORY_ID);
-
-        AiAgentBody<Content> body = new AiAgentBody<>();
-        body.setUserMessage(userMessage != null ? userMessage : "");
-        body.setSystemMessage(systemMessage);
-        body.setMemoryId(memoryId);
-        body.setContent(content);
-
-        return body;
+        return buildAiAgentBody(exchange, content, "");
     }
 
     /**
@@ -153,17 +145,7 @@ public final class LangChain4jAgentConverter {
         String mimeType = detectMimeTypeFromHeaders(exchange);
         Content content = createContent(data, mimeType);
 
-        String userMessage = exchange.getIn().getHeader(USER_MESSAGE, 
String.class);
-        String systemMessage = exchange.getIn().getHeader(SYSTEM_MESSAGE, 
String.class);
-        Object memoryId = exchange.getIn().getHeader(MEMORY_ID);
-
-        AiAgentBody<Content> body = new AiAgentBody<>();
-        body.setUserMessage(userMessage != null ? userMessage : "");
-        body.setSystemMessage(systemMessage);
-        body.setMemoryId(memoryId);
-        body.setContent(content);
-
-        return body;
+        return buildAiAgentBody(exchange, content, "");
     }
 
     /**
@@ -191,6 +173,21 @@ public final class LangChain4jAgentConverter {
         }
     }
 
+    /**
+     * Converts a {@link String} to an {@link AiAgentBody} with the 
appropriate {@link Content} type.
+     * <p>
+     * This converter is useful for the text components that return Text Body.
+     * </p>
+     *
+     * @param  text     String as message
+     * @param  exchange the Camel exchange containing headers
+     * @return          an AiAgentBody with the appropriate Content type
+     */
+    @Converter
+    public static AiAgentBody<?> textToAiAgentBody(String text, Exchange 
exchange) {
+        return buildAiAgentBody(exchange, null, text);
+    }
+
     /**
      * Creates the appropriate LangChain4j Content object based on the MIME 
type.
      */
@@ -242,13 +239,13 @@ public final class LangChain4jAgentConverter {
      */
     private static String detectMimeType(File file, Exchange exchange) {
         // Check agent-specific header first (highest priority)
-        String mediaType = exchange.getIn().getHeader(MEDIA_TYPE, 
String.class);
+        String mediaType = exchange.getMessage().getHeader(MEDIA_TYPE, 
String.class);
         if (mediaType != null) {
             return mediaType;
         }
 
         // Check file component's content type header
-        String fileContentType = 
exchange.getIn().getHeader(Exchange.FILE_CONTENT_TYPE, String.class);
+        String fileContentType = 
exchange.getMessage().getHeader(Exchange.FILE_CONTENT_TYPE, String.class);
         if (fileContentType != null) {
             return fileContentType;
         }
@@ -273,11 +270,12 @@ public final class LangChain4jAgentConverter {
      */
     private static String detectMimeTypeFromHeaders(Exchange exchange) {
         // Check agent-specific header first (highest priority)
-        String mediaType = exchange.getIn().getHeader(MEDIA_TYPE, 
String.class);
+        Message message = exchange.getMessage();
+
+        String mediaType = message.getHeader(MEDIA_TYPE, String.class);
         if (mediaType != null) {
             return normalizeContentType(mediaType);
         }
-
         // Cloud storage component content type headers
         String[] cloudContentTypeHeaders = {
                 "CamelAwsS3ContentType",                  // AWS S3
@@ -289,24 +287,30 @@ public final class LangChain4jAgentConverter {
         };
 
         for (String header : cloudContentTypeHeaders) {
-            String cloudContentType = exchange.getIn().getHeader(header, 
String.class);
+            String cloudContentType = message.getHeader(header, String.class);
             if (cloudContentType != null) {
                 return normalizeContentType(cloudContentType);
             }
         }
-
         // Check standard content type header
-        String contentType = exchange.getIn().getHeader(Exchange.CONTENT_TYPE, 
String.class);
+        String contentType = message.getHeader(Exchange.CONTENT_TYPE, 
String.class);
         if (contentType != null) {
             return normalizeContentType(contentType);
         }
-
         // Check file component's content type header
-        String fileContentType = 
exchange.getIn().getHeader(Exchange.FILE_CONTENT_TYPE, String.class);
+        String fileContentType = message.getHeader(Exchange.FILE_CONTENT_TYPE, 
String.class);
         if (fileContentType != null) {
             return normalizeContentType(fileContentType);
         }
 
+        String fileName = message.getHeader(Exchange.FILE_NAME, String.class);
+        if (fileName != null) {
+            String mime = URLConnection.guessContentTypeFromName(fileName);
+            if (mime != null) {
+                return normalizeContentType(mime);
+            }
+        }
+
         throw new IllegalArgumentException(
                 "MIME type is required for byte[] or InputStream input. "
                                            + "Please set the 
CamelLangChain4jAgentMediaType header.");
@@ -405,4 +409,31 @@ public final class LangChain4jAgentConverter {
             throw new IllegalArgumentException("Failed to read file: " + 
file.getAbsolutePath(), e);
         }
     }
+
+    /**
+     * Utility method to build agent body.
+     *
+     * @param  exchange           the Camel exchange containing message headers
+     * @param  content            the LangChain4j content to attach to the 
agent body
+     * @param  defaultUserMessage the fallback user message if the 
corresponding header is absent
+     * @return                    a fully populated {@link AiAgentBody} 
instance
+     * @param  <T>                the type of LangChain4j {@link Content}
+     */
+    private static <
+            T extends Content> AiAgentBody<T> buildAiAgentBody(Exchange 
exchange, T content, String defaultUserMessage) {
+
+        Message message = exchange.getMessage();
+
+        String userMessage = message.getHeader(USER_MESSAGE, String.class);
+        String systemMessage = message.getHeader(SYSTEM_MESSAGE, String.class);
+        Object memoryId = message.getHeader(MEMORY_ID);
+
+        AiAgentBody<T> body = new AiAgentBody<>();
+        body.setUserMessage(userMessage != null ? userMessage : 
defaultUserMessage);
+        body.setSystemMessage(systemMessage);
+        body.setMemoryId(memoryId);
+        body.setContent(content);
+
+        return body;
+    }
 }
diff --git 
a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentProducer.java
 
b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentProducer.java
index afc1ff6e8a73..4002071fbea2 100644
--- 
a/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentProducer.java
+++ 
b/components/camel-ai/camel-langchain4j-agent/src/main/java/org/apache/camel/component/langchain4j/agent/LangChain4jAgentProducer.java
@@ -79,7 +79,7 @@ public class LangChain4jAgentProducer extends DefaultProducer 
{
             agent = agentFactory.createAgent(exchange);
         }
 
-        AiAgentBody<?> aiAgentBody = agent.processBody(messagePayload, 
exchange);
+        AiAgentBody<?> aiAgentBody = 
exchange.getMessage().getMandatoryBody(AiAgentBody.class);
 
         ToolProvider toolProvider = createComposedToolProvider(tags, exchange);
         String response = agent.chat(aiAgentBody, toolProvider);
diff --git 
a/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentAutoConversionTest.java
 
b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentAutoConversionTest.java
new file mode 100644
index 000000000000..a89be750949e
--- /dev/null
+++ 
b/components/camel-ai/camel-langchain4j-agent/src/test/java/org/apache/camel/component/langchain4j/agent/integration/LangChain4jAgentAutoConversionTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.langchain4j.agent.integration;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.nio.file.Files;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.file.GenericFile;
+import org.apache.camel.component.langchain4j.agent.api.Agent;
+import org.apache.camel.component.langchain4j.agent.api.AiAgentBody;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class LangChain4jAgentAutoConversionTest extends CamelTestSupport {
+
+    @Override
+    protected RoutesBuilder createRouteBuilder() throws Exception {
+
+        Agent mockAgent = (body, exchange) -> "Processed";
+
+        context.getRegistry().bind("mockAgent", mockAgent);
+
+        return new RouteBuilder() {
+            public void configure() {
+                from("direct:start")
+                        .to("langchain4j-agent:test?agent=#mockAgent")
+                        .to("mock:result");
+            }
+        };
+
+    }
+
+    @Test
+    void shouldAutoConvertPlainString() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:result");
+        mock.expectedMessageCount(1);
+
+        template.sendBody("direct:start", "Hello world");
+
+        mock.assertIsSatisfied();
+
+        String response = mock.getExchanges().get(0)
+                .getMessage()
+                .getMandatoryBody(String.class);
+
+        assertNotNull(response);
+    }
+
+    @Test
+    void shouldAutoConvertInputStream() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:result");
+        mock.expectedMessageCount(1);
+        context.setStreamCaching(true);
+        template.send("direct:start", exchange -> {
+            exchange.getMessage().setBody(
+                    new ByteArrayInputStream("Hello stream".getBytes()));
+            exchange.getMessage().setHeader(Exchange.CONTENT_TYPE, 
"text/plain");
+        });
+
+        mock.assertIsSatisfied();
+        AiAgentBody<?> body = mock.getExchanges().get(0)
+                .getMessage()
+                .getBody(AiAgentBody.class);
+
+        assertNotNull(body);
+    }
+
+    @Test
+    void shouldConvertWrappedFile() throws Exception {
+        Exchange exchange = 
context.getEndpoint("direct:test").createExchange();
+
+        File file = File.createTempFile("camel", ".txt");
+        Files.writeString(file.toPath(), "Hello file");
+
+        GenericFile<File> genericFile = new GenericFile<>();
+        genericFile.setFile(file);
+        genericFile.setFileName(file.getName());
+
+        AiAgentBody<?> body = context.getTypeConverter()
+                .convertTo(AiAgentBody.class, exchange, genericFile);
+
+        assertNotNull(body);
+    }
+
+    @Test
+    void shouldConvertByteArray() {
+        Exchange exchange = 
context.getEndpoint("direct:test").createExchange();
+
+        exchange.getMessage().setHeader(
+                "CamelLangChain4jAgentMediaType",
+                "text/plain");
+
+        AiAgentBody<?> body = context.getTypeConverter()
+                .convertTo(AiAgentBody.class, exchange, "Hello".getBytes());
+
+        assertNotNull(body);
+    }
+
+    @Test
+    void shouldConvertInputStream() {
+        Exchange exchange = 
context.getEndpoint("direct:test").createExchange();
+
+        exchange.getMessage().setHeader(
+                "CamelLangChain4jAgentMediaType",
+                "text/plain");
+
+        ByteArrayInputStream stream = new 
ByteArrayInputStream("Hello".getBytes());
+
+        AiAgentBody<?> body = context.getTypeConverter()
+                .convertTo(AiAgentBody.class, exchange, stream);
+
+        assertNotNull(body);
+    }
+
+    @Test
+    void shouldFailForUnsupportedMimeType() {
+        Exchange exchange = 
context.getEndpoint("direct:test").createExchange();
+
+        exchange.getMessage().setHeader(
+                "CamelLangChain4jAgentMediaType",
+                "application/zip");
+
+        assertThrows(
+                org.apache.camel.TypeConversionException.class,
+                () -> context.getTypeConverter()
+                        .convertTo(AiAgentBody.class, exchange, 
"data".getBytes()));
+    }
+
+}

Reply via email to