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

Reply via email to