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");
+ }
+ };
+ }
+}