This is an automated email from the ASF dual-hosted git repository. gnodet pushed a commit to branch backport/28486-to-camel-4.18.x in repository https://gitbox.apache.org/repos/asf/camel.git
commit b3f0015f6646d7c7f8b5aa47beedd419deab76b2 Author: Guillaume Nodet <[email protected]> AuthorDate: Mon Oct 26 11:38:44 2026 +0000 [backport camel-4.18.x] CAMEL-26301: tokenizer EIP: wrap tokenizer call to prevent exchange hang on exception --- .../tokenizer/AbstractLangChain4JTokenizer.java | 5 ++ .../tokenizer/TokenizerBodyTypesTest.java | 56 ++++++++++++++++++++++ .../apache/camel/processor/TokenizerProcessor.java | 12 +++-- 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/components/camel-ai/camel-langchain4j-tokenizer/src/main/java/org/apache/camel/component/langchain4j/tokenizer/AbstractLangChain4JTokenizer.java b/components/camel-ai/camel-langchain4j-tokenizer/src/main/java/org/apache/camel/component/langchain4j/tokenizer/AbstractLangChain4JTokenizer.java index 6cadffa749b4..9a87ef5ebe17 100644 --- a/components/camel-ai/camel-langchain4j-tokenizer/src/main/java/org/apache/camel/component/langchain4j/tokenizer/AbstractLangChain4JTokenizer.java +++ b/components/camel-ai/camel-langchain4j-tokenizer/src/main/java/org/apache/camel/component/langchain4j/tokenizer/AbstractLangChain4JTokenizer.java @@ -19,6 +19,7 @@ package org.apache.camel.component.langchain4j.tokenizer; import dev.langchain4j.data.document.DocumentSplitter; import org.apache.camel.Exchange; +import org.apache.camel.RuntimeCamelException; import org.apache.camel.component.langchain4j.tokenizer.config.LangChain4JConfiguration; import org.apache.camel.component.langchain4j.tokenizer.util.SplitterUtil; import org.apache.camel.spi.Tokenizer; @@ -46,6 +47,10 @@ abstract class AbstractLangChain4JTokenizer<T extends LangChain4JConfiguration> public final String[] tokenize(Exchange exchange) { final String body = exchange.getIn().getBody(String.class); + if (body == null) { + throw new RuntimeCamelException("Cannot tokenize a null body"); + } + LOG.debug("Starting LangChain4j tokenizer for message: {}", body); final String[] split = SplitterUtil.split(splitter, body); diff --git a/components/camel-ai/camel-langchain4j-tokenizer/src/test/java/org/apache/camel/component/langchain4j/tokenizer/TokenizerBodyTypesTest.java b/components/camel-ai/camel-langchain4j-tokenizer/src/test/java/org/apache/camel/component/langchain4j/tokenizer/TokenizerBodyTypesTest.java new file mode 100644 index 000000000000..0b03485991bd --- /dev/null +++ b/components/camel-ai/camel-langchain4j-tokenizer/src/test/java/org/apache/camel/component/langchain4j/tokenizer/TokenizerBodyTypesTest.java @@ -0,0 +1,56 @@ +/* + * 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.tokenizer; + +import org.apache.camel.Exchange; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.model.tokenizer.LangChain4jTokenizerDefinition; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * Regression tests for the tokenizer EIP body handling. + */ +public class TokenizerBodyTypesTest extends LangChain4JTokenizerTestSupport { + + @Test + public void nullBodyCompletesWithException() { + // CAMEL-26301: a null body must not hang the exchange forever — the AsyncCallback must always be called. + // The tokenizer throws on null body; TokenizerProcessor must catch it, set it on the exchange, + // and signal the callback so the error handler can deal with it normally. + Exchange exchange = template.send("direct:start", e -> e.getMessage().setBody(null)); + assertNotNull(exchange.getException(), "exchange must have an exception, not hang"); + } + + @Override + protected RoutesBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:start") + .tokenize(tokenizer() + .byParagraph() + .maxSegmentSize(1024) + .maxOverlap(10) + .using(LangChain4jTokenizerDefinition.TokenizerType.OPEN_AI) + .end()); + } + }; + } +} diff --git a/core/camel-core-processor/src/main/java/org/apache/camel/processor/TokenizerProcessor.java b/core/camel-core-processor/src/main/java/org/apache/camel/processor/TokenizerProcessor.java index 457e4d3230f7..15a14a04fb44 100644 --- a/core/camel-core-processor/src/main/java/org/apache/camel/processor/TokenizerProcessor.java +++ b/core/camel-core-processor/src/main/java/org/apache/camel/processor/TokenizerProcessor.java @@ -51,11 +51,15 @@ public class TokenizerProcessor extends BaseProcessorSupport @Override public boolean process(Exchange exchange, AsyncCallback callback) { - final String[] tokenized = tokenizer.tokenize(exchange); - - exchange.getMessage().setBody(tokenized); + try { + final String[] tokenized = tokenizer.tokenize(exchange); + exchange.getMessage().setBody(tokenized); + } catch (Exception e) { + exchange.setException(e); + callback.done(true); + return true; + } return processor.process(exchange, callback); - } @Override
