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 22806dff2f7b CAMEL-26127: camel-fop - accept a DOM or Source body and 
fail clearly on no body (#28903)
22806dff2f7b is described below

commit 22806dff2f7bb371b23a3e5cede624f6350e986d
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 2 20:40:08 2026 +0100

    CAMEL-26127: camel-fop - accept a DOM or Source body and fail clearly on no 
body (#28903)
    
    … on no body
    
    FopProducer read the body with getBody(StreamSource.class), which is null 
for
    no body, a Map or a DOM Document, so the transform failed with a
    NullPointerException. A Source body is now used as is, a DOM Node is 
wrapped in
    a DOMSource, and any other body goes through 
getMandatoryBody(StreamSource.class),
    which fails with an InvalidPayloadException. The catalog body types are 
updated.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../org/apache/camel/catalog/components/fop.json   |  2 +-
 .../org/apache/camel/component/fop/fop.json        |  2 +-
 .../apache/camel/component/fop/FopEndpoint.java    |  7 +-
 .../apache/camel/component/fop/FopProducer.java    | 18 ++++-
 .../apache/camel/component/fop/FopBodyTest.java    | 93 ++++++++++++++++++++++
 5 files changed, 116 insertions(+), 6 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/fop.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/fop.json
index 38523237b1fc..03daa466e7ec 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/fop.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/fop.json
@@ -31,7 +31,7 @@
     "CamelFop.Output.Format": { "index": 0, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "String", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The output format", "constantName": 
"org.apache.camel.component.fop.FopConstants#CAMEL_FOP_OUTPUT_FORMAT" }
   },
   "bodyTypes": {
-    "producerIn": { "carriers": [ "text", "bytes", "stream", "file" ], 
"format": "xml", "description": "The XSL-FO document to render, as text, bytes, 
a stream or a file. A DOM Document, a Map or no body fails" },
+    "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "pojo" ], 
"format": "xml", "description": "The XSL-FO document to render, as text, bytes, 
a stream, a file, a DOM Document or a javax.xml.transform.Source. A Map or no 
body fails" },
     "producerOut": { "carriers": [ "bytes" ], "javaType": 
"java.io.ByteArrayOutputStream", "description": "The rendered document as 
bytes, in a ByteArrayOutputStream: PDF, or the outputType, or the format of the 
CamelFop.Output.Format header" }
   },
   "properties": {
diff --git 
a/components/camel-fop/src/generated/resources/META-INF/org/apache/camel/component/fop/fop.json
 
b/components/camel-fop/src/generated/resources/META-INF/org/apache/camel/component/fop/fop.json
index 38523237b1fc..03daa466e7ec 100644
--- 
a/components/camel-fop/src/generated/resources/META-INF/org/apache/camel/component/fop/fop.json
+++ 
b/components/camel-fop/src/generated/resources/META-INF/org/apache/camel/component/fop/fop.json
@@ -31,7 +31,7 @@
     "CamelFop.Output.Format": { "index": 0, "kind": "header", "displayName": 
"", "group": "producer", "label": "", "required": false, "javaType": "String", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The output format", "constantName": 
"org.apache.camel.component.fop.FopConstants#CAMEL_FOP_OUTPUT_FORMAT" }
   },
   "bodyTypes": {
-    "producerIn": { "carriers": [ "text", "bytes", "stream", "file" ], 
"format": "xml", "description": "The XSL-FO document to render, as text, bytes, 
a stream or a file. A DOM Document, a Map or no body fails" },
+    "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "pojo" ], 
"format": "xml", "description": "The XSL-FO document to render, as text, bytes, 
a stream, a file, a DOM Document or a javax.xml.transform.Source. A Map or no 
body fails" },
     "producerOut": { "carriers": [ "bytes" ], "javaType": 
"java.io.ByteArrayOutputStream", "description": "The rendered document as 
bytes, in a ByteArrayOutputStream: PDF, or the outputType, or the format of the 
CamelFop.Output.Format header" }
   },
   "properties": {
diff --git 
a/components/camel-fop/src/main/java/org/apache/camel/component/fop/FopEndpoint.java
 
b/components/camel-fop/src/main/java/org/apache/camel/component/fop/FopEndpoint.java
index bc7904ecc644..73ada96b4de1 100644
--- 
a/components/camel-fop/src/main/java/org/apache/camel/component/fop/FopEndpoint.java
+++ 
b/components/camel-fop/src/main/java/org/apache/camel/component/fop/FopEndpoint.java
@@ -39,9 +39,10 @@ import org.apache.fop.apps.FopFactory;
  */
 @UriEndpoint(firstVersion = "2.10.0", scheme = "fop", title = "FOP", syntax = 
"fop:outputType", producerOnly = true,
              remote = false, category = { Category.FILE, 
Category.TRANSFORMATION }, headersClass = FopConstants.class)
-@BodyTypes(producerIn = @BodyType(value = { Carrier.TEXT, Carrier.BYTES, 
Carrier.STREAM, Carrier.FILE }, format = "xml",
-                                  description = "The XSL-FO document to 
render, as text, bytes, a stream or a file. A DOM"
-                                                + " Document, a Map or no body 
fails"),
+@BodyTypes(producerIn = @BodyType(value = { Carrier.TEXT, Carrier.BYTES, 
Carrier.STREAM, Carrier.FILE, Carrier.POJO },
+                                  format = "xml",
+                                  description = "The XSL-FO document to 
render, as text, bytes, a stream, a file, a DOM"
+                                                + " Document or a 
javax.xml.transform.Source. A Map or no body fails"),
            producerOut = @BodyType(value = Carrier.BYTES, javaType = 
"java.io.ByteArrayOutputStream",
                                    description = "The rendered document as 
bytes, in a ByteArrayOutputStream: PDF, or the"
                                                  + " outputType, or the format 
of the CamelFop.Output.Format header"))
diff --git 
a/components/camel-fop/src/main/java/org/apache/camel/component/fop/FopProducer.java
 
b/components/camel-fop/src/main/java/org/apache/camel/component/fop/FopProducer.java
index 4e340648c773..e63491163d4c 100644
--- 
a/components/camel-fop/src/main/java/org/apache/camel/component/fop/FopProducer.java
+++ 
b/components/camel-fop/src/main/java/org/apache/camel/component/fop/FopProducer.java
@@ -26,10 +26,14 @@ import javax.xml.transform.Source;
 import javax.xml.transform.Transformer;
 import javax.xml.transform.TransformerException;
 import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
 import javax.xml.transform.sax.SAXResult;
 import javax.xml.transform.stream.StreamSource;
 
+import org.w3c.dom.Node;
+
 import org.apache.camel.Exchange;
+import org.apache.camel.InvalidPayloadException;
 import org.apache.camel.support.DefaultProducer;
 import org.apache.camel.support.PropertyBindingSupport;
 import org.apache.camel.util.PropertiesHelper;
@@ -61,7 +65,7 @@ public class FopProducer extends DefaultProducer {
         setUserAgentRendererOptions(userAgent, headers);
 
         String outputFormat = getOutputFormat(exchange);
-        Source src = exchange.getIn().getBody(StreamSource.class);
+        Source src = getSource(exchange);
 
         OutputStream out = transform(userAgent, outputFormat, src);
         exchange.getOut().setBody(out);
@@ -70,6 +74,18 @@ public class FopProducer extends DefaultProducer {
         exchange.getOut().setHeaders(headers);
     }
 
+    private static Source getSource(Exchange exchange) throws 
InvalidPayloadException {
+        Object body = exchange.getIn().getBody();
+        if (body instanceof Source source) {
+            return source;
+        } else if (body instanceof Node node) {
+            // an already parsed XSL-FO document, such as a DOM Document
+            return new DOMSource(node);
+        }
+        // fails with a clear error for no body or a body that is not XML
+        return exchange.getIn().getMandatoryBody(StreamSource.class);
+    }
+
     private String getOutputFormat(Exchange exchange) {
         String headerOutputFormat = 
exchange.getIn().getHeader(FopConstants.CAMEL_FOP_OUTPUT_FORMAT, String.class);
         if (headerOutputFormat != null) {
diff --git 
a/components/camel-fop/src/test/java/org/apache/camel/component/fop/FopBodyTest.java
 
b/components/camel-fop/src/test/java/org/apache/camel/component/fop/FopBodyTest.java
new file mode 100644
index 000000000000..f4536d3a2ab2
--- /dev/null
+++ 
b/components/camel-fop/src/test/java/org/apache/camel/component/fop/FopBodyTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.fop;
+
+import java.io.StringReader;
+import java.util.Map;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.dom.DOMSource;
+
+import org.w3c.dom.Document;
+
+import org.xml.sax.InputSource;
+
+import org.apache.camel.CamelExecutionException;
+import org.apache.camel.InvalidPayloadException;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * The bodies the FOP producer accepts, and a clear error for the ones it 
cannot render.
+ */
+public class FopBodyTest extends CamelTestSupport {
+
+    @Test
+    public void renderDomDocument() throws Exception {
+        Document document = parse(FopHelper.decorateTextWithXSLFO("Test 
Content"));
+
+        String text = template.requestBody("direct:start", document, 
String.class);
+        assertEquals("Test Content", text.trim());
+    }
+
+    @Test
+    public void renderDomSource() throws Exception {
+        DOMSource source = new 
DOMSource(parse(FopHelper.decorateTextWithXSLFO("Test Content")));
+
+        String text = template.requestBody("direct:start", source, 
String.class);
+        assertEquals("Test Content", text.trim());
+    }
+
+    @Test
+    public void nullBodyFailsWithInvalidPayload() {
+        CamelExecutionException e
+                = assertThrows(CamelExecutionException.class, () -> 
template.requestBody("direct:start", (Object) null));
+        assertInstanceOf(InvalidPayloadException.class, e.getCause());
+    }
+
+    @Test
+    public void mapBodyFailsWithInvalidPayload() {
+        CamelExecutionException e
+                = assertThrows(CamelExecutionException.class,
+                        () -> template.requestBody("direct:start", 
Map.of("text", "Test Content")));
+        assertInstanceOf(InvalidPayloadException.class, e.getCause());
+    }
+
+    private static Document parse(String xml) throws Exception {
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        factory.setNamespaceAware(true);
+        factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+        
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl";, 
true);
+        return factory.newDocumentBuilder().parse(new InputSource(new 
StringReader(xml)));
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            public void configure() {
+                from("direct:start")
+                        .to("fop:txt");
+            }
+        };
+    }
+}

Reply via email to