This is an automated email from the ASF dual-hosted git repository.
davsclaus 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 838be9334e02 CAMEL-26513: camel-printer - a doc flavor the print
service does not support fails the exchange (#29110)
838be9334e02 is described below
commit 838be9334e026f5417a86693748ca17d4fc2f427
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Nov 4 19:59:59 2026 +0100
CAMEL-26513: camel-printer - a doc flavor the print service does not
support fails the exchange (#29110)
- CAMEL-26513: camel-printer - a doc flavor the print service does not
support fails the exchange
- CAMEL-26513: camel-printer - close the streams and keep the cause when
writing the sendToPrinter=false file fails
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../apache/camel/catalog/docs/lpr-component.adoc | 5 +
.../camel-printer/src/main/docs/lpr-component.adoc | 5 +
.../camel/component/printer/PrinterOperations.java | 10 +-
.../camel/component/printer/PrinterProducer.java | 12 +-
.../printer/PrinterOperationsWriteToFileTest.java | 91 +++++++++++++
.../printer/PrinterProducerDocFlavorTest.java | 142 +++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 7 +
7 files changed, 263 insertions(+), 9 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/lpr-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/lpr-component.adoc
index e814de09ca29..5143d1b8cfa2 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/lpr-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/lpr-component.adoc
@@ -63,6 +63,11 @@ Sending data to the printer is very straightforward and
involves
creating a producer endpoint that can be sent message exchanges on in
route.
+The document is printed in the doc flavor of the `flavor` and `mimeType`
options.
+When the print service does not support that doc flavor, the exchange fails
with a
+`javax.print.PrintException` that names the doc flavor and the print service,
and no
+print job is created. This also applies with `sendToPrinter=false`.
+
== Examples
Usage samples.
diff --git a/components/camel-printer/src/main/docs/lpr-component.adoc
b/components/camel-printer/src/main/docs/lpr-component.adoc
index e814de09ca29..5143d1b8cfa2 100644
--- a/components/camel-printer/src/main/docs/lpr-component.adoc
+++ b/components/camel-printer/src/main/docs/lpr-component.adoc
@@ -63,6 +63,11 @@ Sending data to the printer is very straightforward and
involves
creating a producer endpoint that can be sent message exchanges on in
route.
+The document is printed in the doc flavor of the `flavor` and `mimeType`
options.
+When the print service does not support that doc flavor, the exchange fails
with a
+`javax.print.PrintException` that names the doc flavor and the print service,
and no
+print job is created. This also applies with `sendToPrinter=false`.
+
== Examples
Usage samples.
diff --git
a/components/camel-printer/src/main/java/org/apache/camel/component/printer/PrinterOperations.java
b/components/camel-printer/src/main/java/org/apache/camel/component/printer/PrinterOperations.java
index 4985ccbcd2ae..d672f9c7bdc2 100644
---
a/components/camel-printer/src/main/java/org/apache/camel/component/printer/PrinterOperations.java
+++
b/components/camel-printer/src/main/java/org/apache/camel/component/printer/PrinterOperations.java
@@ -84,13 +84,11 @@ public class PrinterOperations implements
PrinterOperationsInterface {
}
LOG.debug("Writing print job to file: {}", file.getAbsolutePath());
- try {
- InputStream in = doc.getStreamForBytes();
- FileOutputStream fos = new FileOutputStream(file);
- IOHelper.copyAndCloseInput(in, fos);
- IOHelper.close(fos);
+ try (InputStream in = doc.getStreamForBytes();
+ FileOutputStream fos = new FileOutputStream(file)) {
+ IOHelper.copy(in, fos);
} catch (Exception e) {
- throw new PrintException("Error writing Document to the target
file " + file.getAbsolutePath());
+ throw new PrintException("Error writing Document to the target
file " + file.getAbsolutePath(), e);
}
} else {
if (LOG.isDebugEnabled()) {
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 f35ef3439f0d..ddf1f40aeb91 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
@@ -59,10 +59,16 @@ public class PrinterProducer extends DefaultProducer {
}
private void print(InputStream body, String jobName) throws PrintException
{
- if
(printerOperations.getPrintService().isDocFlavorSupported(printerOperations.getFlavor()))
{
- PrintDocument printDoc = new PrintDocument(body,
printerOperations.getFlavor());
- printerOperations.print(printDoc, config.isSendToPrinter(),
config.getMimeType(), jobName);
+ PrintService service = printerOperations.getPrintService();
+ DocFlavor flavor = printerOperations.getFlavor();
+ // fail, as DocPrintJob.print does, rather than skipping the job
without an error
+ if (!service.isDocFlavorSupported(flavor)) {
+ throw new PrintException(
+ "Print service " + service.getName() + " does not support
the doc flavor " + flavor
+ + ". Set the flavor and mimeType options
to a doc flavor this print service supports.");
}
+ PrintDocument printDoc = new PrintDocument(body, flavor);
+ printerOperations.print(printDoc, config.isSendToPrinter(),
config.getMimeType(), jobName);
}
private DocFlavor assignDocFlavor() {
diff --git
a/components/camel-printer/src/test/java/org/apache/camel/component/printer/PrinterOperationsWriteToFileTest.java
b/components/camel-printer/src/test/java/org/apache/camel/component/printer/PrinterOperationsWriteToFileTest.java
new file mode 100644
index 000000000000..5b12ebffbe79
--- /dev/null
+++
b/components/camel-printer/src/test/java/org/apache/camel/component/printer/PrinterOperationsWriteToFileTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.IOException;
+import java.io.InputStream;
+
+import javax.print.Doc;
+import javax.print.DocFlavor;
+import javax.print.PrintException;
+import javax.print.PrintService;
+import javax.print.attribute.HashPrintRequestAttributeSet;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * sendToPrinter=false writes the document to a file under ./target; a failing
copy must close the streams and keep the
+ * cause.
+ */
+public class PrinterOperationsWriteToFileTest {
+
+ private static final IOException READ_FAILURE = new IOException("Simulated
read failure");
+
+ private static final class FailingInputStream extends InputStream {
+ private boolean closed;
+
+ @Override
+ public int read() throws IOException {
+ throw READ_FAILURE;
+ }
+
+ @Override
+ public void close() {
+ closed = true;
+ }
+ }
+
+ private final FailingInputStream in = new FailingInputStream();
+ private PrintService printService;
+ private PrinterOperations operations;
+ private Doc doc;
+
+ @BeforeEach
+ public void setUp() throws Exception {
+ printService = mock(PrintService.class);
+ when(printService.getName()).thenReturn("MockedPrinter");
+ operations = new PrinterOperations(
+ printService, DocFlavor.INPUT_STREAM.AUTOSENSE, new
HashPrintRequestAttributeSet());
+ doc = mock(Doc.class);
+ when(doc.getStreamForBytes()).thenReturn(in);
+ }
+
+ @Test
+ public void failingCopyClosesTheDocumentStream() {
+ assertThrows(PrintException.class, () -> operations.print(doc, false,
"AUTOSENSE", "job"));
+
+ assertTrue(in.closed, "The document stream should be closed when the
copy fails");
+ verify(printService, never()).createPrintJob();
+ }
+
+ @Test
+ public void failingCopyKeepsTheCause() {
+ PrintException e = assertThrows(PrintException.class, () ->
operations.print(doc, false, "AUTOSENSE", "job"));
+
+ assertTrue(e.getMessage().startsWith("Error writing Document to the
target file"), e.getMessage());
+ assertSame(READ_FAILURE, e.getCause());
+ }
+}
diff --git
a/components/camel-printer/src/test/java/org/apache/camel/component/printer/PrinterProducerDocFlavorTest.java
b/components/camel-printer/src/test/java/org/apache/camel/component/printer/PrinterProducerDocFlavorTest.java
new file mode 100644
index 000000000000..6957bd76c091
--- /dev/null
+++
b/components/camel-printer/src/test/java/org/apache/camel/component/printer/PrinterProducerDocFlavorTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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 javax.print.Doc;
+import javax.print.DocFlavor;
+import javax.print.DocPrintJob;
+import javax.print.PrintException;
+import javax.print.PrintService;
+import javax.print.attribute.PrintRequestAttributeSet;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+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 doc flavor check of the lpr producer, on a mocked PrintService (no
print service lookup, so nothing reaches a
+ * real printer).
+ */
+public class PrinterProducerDocFlavorTest {
+
+ 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");
+ printJob = mock(DocPrintJob.class);
+ when(printService.createPrintJob()).thenReturn(printJob);
+ }
+
+ @AfterEach
+ public void tearDown() {
+ if (producer != null) {
+ producer.stop();
+ }
+ context.stop();
+ }
+
+ private void startProducer(String uri) {
+ PrinterEndpoint endpoint = context.getEndpoint(uri,
PrinterEndpoint.class);
+ producer = new PrinterProducer(endpoint, endpoint.getConfig());
+ producer.setPrintService(printService);
+ producer.start();
+ }
+
+ private Exchange exchange(String body) {
+ Exchange exchange = new DefaultExchange(context);
+ exchange.getIn().setBody(body);
+ return exchange;
+ }
+
+ @Test
+ public void unsupportedFlavorFails() throws Exception {
+
when(printService.isDocFlavorSupported(any(DocFlavor.class))).thenReturn(Boolean.FALSE);
+
startProducer("lpr://localhost/MockedPrinter?flavor=DocFlavor.INPUT_STREAM&mimeType=PDF");
+
+ PrintException e = assertThrows(PrintException.class, () ->
producer.process(exchange("Hello Printer")));
+
+ assertTrue(e.getMessage().contains("MockedPrinter"), e.getMessage());
+
assertTrue(e.getMessage().contains(DocFlavor.INPUT_STREAM.PDF.toString()),
e.getMessage());
+ verify(printService).isDocFlavorSupported(DocFlavor.INPUT_STREAM.PDF);
+ verify(printService, never()).createPrintJob();
+ }
+
+ @Test
+ public void unsupportedFlavorFailsWithoutSendToPrinter() throws Exception {
+
when(printService.isDocFlavorSupported(any(DocFlavor.class))).thenReturn(Boolean.FALSE);
+ startProducer("lpr://localhost/MockedPrinter?sendToPrinter=false");
+
+ PrintException e = assertThrows(PrintException.class, () ->
producer.process(exchange("Hello Printer")));
+
+
assertTrue(e.getMessage().contains(DocFlavor.BYTE_ARRAY.AUTOSENSE.toString()),
e.getMessage());
+ verify(printService, never()).createPrintJob();
+ }
+
+ @Test
+ public void unsupportedFlavorFailsTheRoute() throws Exception {
+
when(printService.isDocFlavorSupported(any(DocFlavor.class))).thenReturn(Boolean.FALSE);
+ startProducer("lpr://localhost/MockedPrinter");
+
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:start").process(producer).to("mock:printed");
+ }
+ });
+ MockEndpoint printed = context.getEndpoint("mock:printed",
MockEndpoint.class);
+ printed.expectedMessageCount(0);
+
+ Exchange exchange =
context.createProducerTemplate().send("direct:start", exchange("Hello
Printer"));
+
+ assertEquals(PrintException.class, exchange.getException().getClass());
+ printed.assertIsSatisfied();
+ verify(printService, never()).createPrintJob();
+ }
+
+ @Test
+ public void supportedFlavorIsPrinted() throws Exception {
+
when(printService.isDocFlavorSupported(DocFlavor.INPUT_STREAM.PDF)).thenReturn(Boolean.TRUE);
+
startProducer("lpr://localhost/MockedPrinter?flavor=DocFlavor.INPUT_STREAM&mimeType=PDF");
+
+ producer.process(exchange("Hello Printer"));
+
+ verify(printJob).print(any(Doc.class),
any(PrintRequestAttributeSet.class));
+ }
+}
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 22b29bdfcb8c..ba7d3a6fc0f5 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
@@ -1537,6 +1537,13 @@ The `lpr` producer submitted a print job with no data,
without an error, for a m
`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-printer - a doc flavor the print service does not support fails the
exchange
+
+The `lpr` producer skipped the print job, without an error, when the print
service did not support the doc flavor of
+the `flavor` and `mimeType` options, and the exchange completed as if the
document was printed. It now fails the
+exchange with a `javax.print.PrintException` that names the doc flavor and the
print service, also with
+`sendToPrinter=false`. Set the `flavor` and `mimeType` options to a doc flavor
that the printer supports.
+
=== camel-debezium - the row flattened by ExtractNewRecordState is the body
With the Debezium `ExtractNewRecordState` single message transformation
(`ExtractNewDocumentState` for MongoDB), the