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

davsclaus pushed a commit to branch CAMEL-26193-lpr-mandatory-body
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 238937ddbc79ad56160b2f1c10b9c3975b8d5dce
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Nov 3 00:00:25 2026 +0100

    CAMEL-26193: camel-printer - a body that does not convert to a stream fails 
the exchange
    
    The lpr producer converted the body with the non-mandatory convertTo, so a 
Map, a
    POJO, a number or no body gave a null stream and a print job with no data, 
without
    an error. It now reads the body with getMandatoryBody(InputStream.class), 
which
    fails with an InvalidPayloadException, and the conversion uses the 
exchange, so a
    text body is encoded in the charset of the exchange.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../org/apache/camel/catalog/components/lpr.json   |   2 +-
 .../org/apache/camel/component/printer/lpr.json    |   2 +-
 .../camel/component/printer/PrinterEndpoint.java   |   2 +-
 .../camel/component/printer/PrinterProducer.java   |   4 +-
 .../component/printer/PrinterProducerBodyTest.java | 154 +++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |   6 +
 6 files changed, 165 insertions(+), 5 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/lpr.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/lpr.json
index 6c8879855c81..42524976db0b 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/lpr.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/lpr.json
@@ -31,7 +31,7 @@
     "PrinterJobName": { "index": 0, "kind": "header", "displayName": "", 
"group": "producer", "label": "producer", "required": false, "javaType": 
"String", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "The name of the job", "constantName": 
"org.apache.camel.component.printer.PrinterEndpoint#JOB_NAME" }
   },
   "bodyTypes": {
-    "producerIn": { "carriers": [ "text", "bytes", "stream", "file" ], 
"description": "The document to print, read as a stream: text, bytes, a stream 
or a file, in the format of the mimeType and flavor options. A Map, a POJO, a 
number or no body is not converted: the print job gets no data" },
+    "producerIn": { "carriers": [ "text", "bytes", "stream", "file" ], 
"description": "The document to print, read as a stream: text, bytes, a stream 
or a file, in the format of the mimeType and flavor options. A Map, a POJO, a 
number or no body fails with an InvalidPayloadException" },
     "producerOut": { "carriers": [ "unchanged" ] }
   },
   "properties": {
diff --git 
a/components/camel-printer/src/generated/resources/META-INF/org/apache/camel/component/printer/lpr.json
 
b/components/camel-printer/src/generated/resources/META-INF/org/apache/camel/component/printer/lpr.json
index 6c8879855c81..42524976db0b 100644
--- 
a/components/camel-printer/src/generated/resources/META-INF/org/apache/camel/component/printer/lpr.json
+++ 
b/components/camel-printer/src/generated/resources/META-INF/org/apache/camel/component/printer/lpr.json
@@ -31,7 +31,7 @@
     "PrinterJobName": { "index": 0, "kind": "header", "displayName": "", 
"group": "producer", "label": "producer", "required": false, "javaType": 
"String", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "The name of the job", "constantName": 
"org.apache.camel.component.printer.PrinterEndpoint#JOB_NAME" }
   },
   "bodyTypes": {
-    "producerIn": { "carriers": [ "text", "bytes", "stream", "file" ], 
"description": "The document to print, read as a stream: text, bytes, a stream 
or a file, in the format of the mimeType and flavor options. A Map, a POJO, a 
number or no body is not converted: the print job gets no data" },
+    "producerIn": { "carriers": [ "text", "bytes", "stream", "file" ], 
"description": "The document to print, read as a stream: text, bytes, a stream 
or a file, in the format of the mimeType and flavor options. A Map, a POJO, a 
number or no body fails with an InvalidPayloadException" },
     "producerOut": { "carriers": [ "unchanged" ] }
   },
   "properties": {
diff --git 
a/components/camel-printer/src/main/java/org/apache/camel/component/printer/PrinterEndpoint.java
 
b/components/camel-printer/src/main/java/org/apache/camel/component/printer/PrinterEndpoint.java
index 00e47f9554f1..08784d1b1a81 100644
--- 
a/components/camel-printer/src/main/java/org/apache/camel/component/printer/PrinterEndpoint.java
+++ 
b/components/camel-printer/src/main/java/org/apache/camel/component/printer/PrinterEndpoint.java
@@ -44,7 +44,7 @@ import org.apache.camel.support.DefaultEndpoint;
 @BodyTypes(producerIn = @BodyType(value = { Carrier.TEXT, Carrier.BYTES, 
Carrier.STREAM, Carrier.FILE },
                                   description = "The document to print, read 
as a stream: text, bytes, a stream or a file,"
                                                 + " in the format of the 
mimeType and flavor options. A Map, a POJO, a number"
-                                                + " or no body is not 
converted: the print job gets no data"),
+                                                + " or no body fails with an 
InvalidPayloadException"),
            producerOut = @BodyType(Carrier.UNCHANGED))
 public class PrinterEndpoint extends DefaultEndpoint implements 
EndpointServiceLocation {
 
diff --git 
a/components/camel-printer/src/main/java/org/apache/camel/component/printer/PrinterProducer.java
 
b/components/camel-printer/src/main/java/org/apache/camel/component/printer/PrinterProducer.java
index 1adc6fd4807b..f35ef3439f0d 100644
--- 
a/components/camel-printer/src/main/java/org/apache/camel/component/printer/PrinterProducer.java
+++ 
b/components/camel-printer/src/main/java/org/apache/camel/component/printer/PrinterProducer.java
@@ -52,8 +52,8 @@ public class PrinterProducer extends DefaultProducer {
 
     @Override
     public void process(Exchange exchange) throws Exception {
-        Object body = exchange.getIn().getBody();
-        InputStream is = 
exchange.getContext().getTypeConverter().convertTo(InputStream.class, body);
+        // a body that is not text, bytes, a stream or a file fails, rather 
than printing a job with no data
+        InputStream is = exchange.getIn().getMandatoryBody(InputStream.class);
         String jobName = exchange.getIn().getHeader(PrinterEndpoint.JOB_NAME, 
"Camel: lpr", String.class);
         print(is, jobName);
     }
diff --git 
a/components/camel-printer/src/test/java/org/apache/camel/component/printer/PrinterProducerBodyTest.java
 
b/components/camel-printer/src/test/java/org/apache/camel/component/printer/PrinterProducerBodyTest.java
new file mode 100644
index 000000000000..d63f40df0c78
--- /dev/null
+++ 
b/components/camel-printer/src/test/java/org/apache/camel/component/printer/PrinterProducerBodyTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.printer;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import javax.print.Doc;
+import javax.print.DocFlavor;
+import javax.print.DocPrintJob;
+import javax.print.PrintService;
+import javax.print.attribute.PrintRequestAttributeSet;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.InvalidPayloadException;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.DefaultExchange;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.ArgumentCaptor;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * The body of the lpr producer, printed on a mocked PrintService (no print 
service lookup, so nothing reaches a real
+ * printer).
+ */
+public class PrinterProducerBodyTest {
+
+    private CamelContext context;
+    private PrintService printService;
+    private DocPrintJob printJob;
+    private PrinterProducer producer;
+
+    @BeforeEach
+    public void setUp() throws Exception {
+        context = new DefaultCamelContext();
+        context.start();
+
+        printService = mock(PrintService.class);
+        when(printService.getName()).thenReturn("MockedPrinter");
+        
when(printService.isDocFlavorSupported(any(DocFlavor.class))).thenReturn(Boolean.TRUE);
+        printJob = mock(DocPrintJob.class);
+        when(printService.createPrintJob()).thenReturn(printJob);
+
+        PrinterEndpoint endpoint = 
context.getEndpoint("lpr://localhost/MockedPrinter", PrinterEndpoint.class);
+        producer = new PrinterProducer(endpoint, endpoint.getConfig());
+        producer.setPrintService(printService);
+        producer.start();
+    }
+
+    @AfterEach
+    public void tearDown() {
+        producer.stop();
+        context.stop();
+    }
+
+    static Stream<Arguments> unsupportedBodies() {
+        return Stream.of(
+                Arguments.of(Map.of("name", "Camel")),
+                Arguments.of(new Object()),
+                Arguments.of(42),
+                Arguments.of((Object) null));
+    }
+
+    @ParameterizedTest
+    @MethodSource("unsupportedBodies")
+    public void unsupportedBodyFails(Object body) {
+        Exchange exchange = new DefaultExchange(context);
+        exchange.getIn().setBody(body);
+
+        assertThrows(InvalidPayloadException.class, () -> 
producer.process(exchange));
+        verify(printService, never()).createPrintJob();
+    }
+
+    @Test
+    public void textBodyIsPrinted() throws Exception {
+        Exchange exchange = new DefaultExchange(context);
+        exchange.getIn().setBody("Hello Printer");
+
+        producer.process(exchange);
+
+        assertArrayEquals("Hello Printer".getBytes(StandardCharsets.UTF_8), 
printedBytes());
+    }
+
+    @Test
+    public void bytesBodyIsPrinted() throws Exception {
+        byte[] data = { 1, 2, 3, 4 };
+        Exchange exchange = new DefaultExchange(context);
+        exchange.getIn().setBody(data);
+
+        producer.process(exchange);
+
+        assertArrayEquals(data, printedBytes());
+    }
+
+    @Test
+    public void streamBodyIsPrinted() throws Exception {
+        byte[] data = { 5, 6, 7 };
+        Exchange exchange = new DefaultExchange(context);
+        exchange.getIn().setBody(new ByteArrayInputStream(data));
+
+        producer.process(exchange);
+
+        assertArrayEquals(data, printedBytes());
+    }
+
+    @Test
+    public void textBodyUsesTheCharsetOfTheExchange() throws Exception {
+        Exchange exchange = new DefaultExchange(context);
+        exchange.setProperty(Exchange.CHARSET_NAME, "ISO-8859-1");
+        exchange.getIn().setBody("Grüße");
+
+        producer.process(exchange);
+
+        assertArrayEquals("Grüße".getBytes(StandardCharsets.ISO_8859_1), 
printedBytes());
+    }
+
+    private byte[] printedBytes() throws Exception {
+        ArgumentCaptor<Doc> doc = ArgumentCaptor.forClass(Doc.class);
+        verify(printJob).print(doc.capture(), 
any(PrintRequestAttributeSet.class));
+        try (InputStream is = doc.getValue().getStreamForBytes()) {
+            return is.readAllBytes();
+        }
+    }
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 10f5a47c1b89..6df34ffeb504 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -1087,6 +1087,12 @@ the same JVM.
 An application that relied on the providers being de-registered on shutdown 
must now remove them itself with
 `Security.removeProvider("BC")` and `Security.removeProvider("BCPQC")`.
 
+=== camel-printer - a body that does not convert to a stream fails the exchange
+
+The `lpr` producer submitted a print job with no data, without an error, for a 
message body that has no conversion to
+`InputStream`, such as a `Map`, a POJO or a number, and for a message without 
a body. It now fails the exchange with an
+`InvalidPayloadException`. Send the document as text, bytes, a stream or a 
file.
+
 === camel-debezium - the row flattened by ExtractNewRecordState is the body
 
 With the Debezium `ExtractNewRecordState` single message transformation 
(`ExtractNewDocumentState` for MongoDB), the

Reply via email to