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
