This is an automated email from the ASF dual-hosted git repository.
oscerd 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 e37450999b02 CAMEL-23456: camel-docling - extract document title and
type metadata in EXTRACT_METADATA (#24245)
e37450999b02 is described below
commit e37450999b02c897ab7f32c8076e050d34c51926
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Jun 25 14:09:33 2026 +0200
CAMEL-23456: camel-docling - extract document title and type metadata in
EXTRACT_METADATA (#24245)
The EXTRACT_METADATA operation now populates the title and documentType
fields on DocumentMetadata (and the CamelDoclingMetadataTitle /
CamelDoclingMetadataDocumentType headers) when the information can be
determined from the document. The title is taken from the first text item
that Docling labels as a document title; the documentType is derived from
the source file extension, falling back to the MIME subtype.
Previously both fields did not exist on DocumentMetadata and the related
assertions in MetadataExtractionIT were disabled with TODO comments. The
title/documentType assertions are now enabled.
Signed-off-by: Andrea Cosentino <[email protected]>
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
---
.../apache/camel/catalog/components/docling.json | 19 +++----
.../apache/camel/component/docling/docling.json | 19 +++----
.../src/main/docs/docling-component.adoc | 10 ++--
.../camel/component/docling/DoclingHeaders.java | 3 ++
.../camel/component/docling/DoclingProducer.java | 61 ++++++++++++++++++++++
.../camel/component/docling/DocumentMetadata.java | 30 +++++++++++
.../docling/integration/MetadataExtractionIT.java | 4 +-
.../ROOT/pages/camel-4x-upgrade-guide-4_21.adoc | 8 +++
.../dsl/DoclingEndpointBuilderFactory.java | 12 +++++
9 files changed, 141 insertions(+), 25 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/docling.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/docling.json
index 7d7c78c7d667..46afe44b8955 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/docling.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/docling.json
@@ -101,15 +101,16 @@
"CamelDoclingBatchProcessingTime": { "index": 19, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Total processing time for the batch in
milliseconds", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#BATCH_PROCESSING_TIME" },
"CamelDoclingBatchSplitResults": { "index": 20, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Split batch results into individual
exchanges instead of single BatchProcessingResults", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#BATCH_SPLIT_RESULTS" },
"CamelDoclingMetadataPageCount": { "index": 21, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Number of pages in the document",
"constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_PAGE_COUNT" },
- "CamelDoclingMetadataLanguage": { "index": 22, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Document language code",
"constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_LANGUAGE" },
- "CamelDoclingMetadataDocumentType": { "index": 23, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Document type\/format", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_DOCUMENT_TYPE" },
- "CamelDoclingMetadataFormat": { "index": 24, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Document format (MIME type)",
"constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_FORMAT" },
- "CamelDoclingMetadataFileSize": { "index": 25, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "File size in bytes", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_FILE_SIZE" },
- "CamelDoclingMetadataFileName": { "index": 26, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "File name", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_FILE_NAME" },
- "CamelDoclingMetadataRaw": { "index": 27, "kind": "header", "displayName":
"", "group": "producer", "label": "", "required": false, "javaType":
"Map<String, Object>", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Raw metadata fields as a Map",
"constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_RAW" },
- "CamelDoclingChunkingTokenizer": { "index": 28, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Tokenizer for hybrid chunking (e.g.
sentence-transformers\/all-MiniLM-L6-v2)", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#CHUNKING_TOKENIZER" },
- "CamelDoclingChunkingMaxTokens": { "index": 29, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Maximum tokens per chunk for hybrid
chunking", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#CHUNKING_MAX_TOKENS" },
- "CamelDoclingChunkingMergePeers": { "index": 30, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Whether to merge peer chunks in hybrid
chunking", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#CHUNKING_MERGE_PEERS" }
+ "CamelDoclingMetadataTitle": { "index": 22, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Document title", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_TITLE" },
+ "CamelDoclingMetadataLanguage": { "index": 23, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Document language code",
"constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_LANGUAGE" },
+ "CamelDoclingMetadataDocumentType": { "index": 24, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Document type\/format", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_DOCUMENT_TYPE" },
+ "CamelDoclingMetadataFormat": { "index": 25, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Document format (MIME type)",
"constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_FORMAT" },
+ "CamelDoclingMetadataFileSize": { "index": 26, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "File size in bytes", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_FILE_SIZE" },
+ "CamelDoclingMetadataFileName": { "index": 27, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "File name", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_FILE_NAME" },
+ "CamelDoclingMetadataRaw": { "index": 28, "kind": "header", "displayName":
"", "group": "producer", "label": "", "required": false, "javaType":
"Map<String, Object>", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Raw metadata fields as a Map",
"constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_RAW" },
+ "CamelDoclingChunkingTokenizer": { "index": 29, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Tokenizer for hybrid chunking (e.g.
sentence-transformers\/all-MiniLM-L6-v2)", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#CHUNKING_TOKENIZER" },
+ "CamelDoclingChunkingMaxTokens": { "index": 30, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Maximum tokens per chunk for hybrid
chunking", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#CHUNKING_MAX_TOKENS" },
+ "CamelDoclingChunkingMergePeers": { "index": 31, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Whether to merge peer chunks in hybrid
chunking", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#CHUNKING_MERGE_PEERS" }
},
"properties": {
"operationId": { "index": 0, "kind": "path", "displayName": "Operation
Id", "group": "producer", "label": "", "required": true, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "The operation identifier"
},
diff --git
a/components/camel-ai/camel-docling/src/generated/resources/META-INF/org/apache/camel/component/docling/docling.json
b/components/camel-ai/camel-docling/src/generated/resources/META-INF/org/apache/camel/component/docling/docling.json
index 7d7c78c7d667..46afe44b8955 100644
---
a/components/camel-ai/camel-docling/src/generated/resources/META-INF/org/apache/camel/component/docling/docling.json
+++
b/components/camel-ai/camel-docling/src/generated/resources/META-INF/org/apache/camel/component/docling/docling.json
@@ -101,15 +101,16 @@
"CamelDoclingBatchProcessingTime": { "index": 19, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Total processing time for the batch in
milliseconds", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#BATCH_PROCESSING_TIME" },
"CamelDoclingBatchSplitResults": { "index": 20, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Split batch results into individual
exchanges instead of single BatchProcessingResults", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#BATCH_SPLIT_RESULTS" },
"CamelDoclingMetadataPageCount": { "index": 21, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Number of pages in the document",
"constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_PAGE_COUNT" },
- "CamelDoclingMetadataLanguage": { "index": 22, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Document language code",
"constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_LANGUAGE" },
- "CamelDoclingMetadataDocumentType": { "index": 23, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Document type\/format", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_DOCUMENT_TYPE" },
- "CamelDoclingMetadataFormat": { "index": 24, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Document format (MIME type)",
"constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_FORMAT" },
- "CamelDoclingMetadataFileSize": { "index": 25, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "File size in bytes", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_FILE_SIZE" },
- "CamelDoclingMetadataFileName": { "index": 26, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "File name", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_FILE_NAME" },
- "CamelDoclingMetadataRaw": { "index": 27, "kind": "header", "displayName":
"", "group": "producer", "label": "", "required": false, "javaType":
"Map<String, Object>", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Raw metadata fields as a Map",
"constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_RAW" },
- "CamelDoclingChunkingTokenizer": { "index": 28, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Tokenizer for hybrid chunking (e.g.
sentence-transformers\/all-MiniLM-L6-v2)", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#CHUNKING_TOKENIZER" },
- "CamelDoclingChunkingMaxTokens": { "index": 29, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Maximum tokens per chunk for hybrid
chunking", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#CHUNKING_MAX_TOKENS" },
- "CamelDoclingChunkingMergePeers": { "index": 30, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Whether to merge peer chunks in hybrid
chunking", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#CHUNKING_MERGE_PEERS" }
+ "CamelDoclingMetadataTitle": { "index": 22, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Document title", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_TITLE" },
+ "CamelDoclingMetadataLanguage": { "index": 23, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Document language code",
"constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_LANGUAGE" },
+ "CamelDoclingMetadataDocumentType": { "index": 24, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Document type\/format", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_DOCUMENT_TYPE" },
+ "CamelDoclingMetadataFormat": { "index": 25, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Document format (MIME type)",
"constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_FORMAT" },
+ "CamelDoclingMetadataFileSize": { "index": 26, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "Long", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "File size in bytes", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_FILE_SIZE" },
+ "CamelDoclingMetadataFileName": { "index": 27, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "File name", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_FILE_NAME" },
+ "CamelDoclingMetadataRaw": { "index": 28, "kind": "header", "displayName":
"", "group": "producer", "label": "", "required": false, "javaType":
"Map<String, Object>", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Raw metadata fields as a Map",
"constantName":
"org.apache.camel.component.docling.DoclingHeaders#METADATA_RAW" },
+ "CamelDoclingChunkingTokenizer": { "index": 29, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Tokenizer for hybrid chunking (e.g.
sentence-transformers\/all-MiniLM-L6-v2)", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#CHUNKING_TOKENIZER" },
+ "CamelDoclingChunkingMaxTokens": { "index": 30, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Maximum tokens per chunk for hybrid
chunking", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#CHUNKING_MAX_TOKENS" },
+ "CamelDoclingChunkingMergePeers": { "index": 31, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Whether to merge peer chunks in hybrid
chunking", "constantName":
"org.apache.camel.component.docling.DoclingHeaders#CHUNKING_MERGE_PEERS" }
},
"properties": {
"operationId": { "index": 0, "kind": "path", "displayName": "Operation
Id", "group": "producer", "label": "", "required": true, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "The operation identifier"
},
diff --git
a/components/camel-ai/camel-docling/src/main/docs/docling-component.adoc
b/components/camel-ai/camel-docling/src/main/docs/docling-component.adoc
index 57f77bddea62..ccdf462717a1 100644
--- a/components/camel-ai/camel-docling/src/main/docs/docling-component.adoc
+++ b/components/camel-ai/camel-docling/src/main/docs/docling-component.adoc
@@ -448,12 +448,12 @@ from("file:///data/documents?include=.*\\.pdf")
// Access metadata fields
String title = metadata.getTitle();
- String author = metadata.getAuthor();
+ String documentType = metadata.getDocumentType();
Integer pageCount = metadata.getPageCount();
- Instant creationDate = metadata.getCreationDate();
+ String format = metadata.getFormat();
- log.info("Document: {} by {}, Pages: {}, Created: {}",
- title, author, pageCount, creationDate);
+ log.info("Document: {} ({}), Pages: {}, Format: {}",
+ title, documentType, pageCount, format);
// Metadata is also available in headers
String titleFromHeader =
exchange.getIn().getHeader("CamelDoclingMetadataTitle", String.class);
@@ -473,7 +473,7 @@ YAML::
- to:
uri: docling:EXTRACT_METADATA
- log:
- message: "Document: ${header.CamelDoclingMetadataTitle} by
${header.CamelDoclingMetadataAuthor}"
+ message: "Document: ${header.CamelDoclingMetadataTitle}
(${header.CamelDoclingMetadataDocumentType})"
- log:
message: "Pages: ${header.CamelDoclingMetadataPageCount}"
- process:
diff --git
a/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingHeaders.java
b/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingHeaders.java
index 5f19834daa06..8c4aac66e4cd 100644
---
a/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingHeaders.java
+++
b/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingHeaders.java
@@ -87,6 +87,9 @@ public final class DoclingHeaders {
@Metadata(description = "Number of pages in the document", javaType =
"Integer")
public static final String METADATA_PAGE_COUNT =
"CamelDoclingMetadataPageCount";
+ @Metadata(description = "Document title", javaType = "String")
+ public static final String METADATA_TITLE = "CamelDoclingMetadataTitle";
+
@Metadata(description = "Document language code", javaType = "String")
public static final String METADATA_LANGUAGE =
"CamelDoclingMetadataLanguage";
diff --git
a/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingProducer.java
b/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingProducer.java
index 4129db3858a0..cd970853e290 100644
---
a/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingProducer.java
+++
b/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingProducer.java
@@ -34,6 +34,7 @@ import java.util.Base64;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@@ -50,6 +51,8 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import ai.docling.core.DoclingDocument;
+import ai.docling.core.DoclingDocument.BaseTextItem;
+import ai.docling.core.DoclingDocument.DocItemLabel;
import ai.docling.core.DoclingDocument.DocumentOrigin;
import ai.docling.serve.api.DoclingServeApi;
import ai.docling.serve.api.chunk.request.ChunkDocumentRequest;
@@ -681,6 +684,9 @@ public class DoclingProducer extends DefaultProducer {
metadata.setFormat(origin.getMimetype());
}
+ metadata.setDocumentType(deriveDocumentType(origin));
+ metadata.setTitle(extractTitle(doclingDocument));
+
// Store raw metadata if requested
if (configuration.isIncludeRawMetadata()) {
@SuppressWarnings("unchecked")
@@ -729,6 +735,9 @@ public class DoclingProducer extends DefaultProducer {
metadata.setFormat(origin.getMimetype());
}
+ metadata.setDocumentType(deriveDocumentType(origin));
+ metadata.setTitle(extractTitle(doclingDocument));
+
// Store raw metadata if configured
if (configuration.isIncludeRawMetadata()) {
JsonNode rootNode = objectMapper.readTree(jsonOutput);
@@ -745,6 +754,12 @@ public class DoclingProducer extends DefaultProducer {
}
private void setMetadataHeaders(Exchange exchange, DocumentMetadata
metadata) {
+ if (metadata.getTitle() != null) {
+ exchange.getIn().setHeader(DoclingHeaders.METADATA_TITLE,
metadata.getTitle());
+ }
+ if (metadata.getDocumentType() != null) {
+ exchange.getIn().setHeader(DoclingHeaders.METADATA_DOCUMENT_TYPE,
metadata.getDocumentType());
+ }
if (metadata.getPageCount() != null) {
exchange.getIn().setHeader(DoclingHeaders.METADATA_PAGE_COUNT,
metadata.getPageCount());
}
@@ -763,6 +778,52 @@ public class DoclingProducer extends DefaultProducer {
}
}
+ /**
+ * Extracts the document title from the Docling document, defined as the
first text item labelled as
+ * {@link DocItemLabel#TITLE}. Returns {@code null} when the document does
not expose a title.
+ */
+ private String extractTitle(DoclingDocument doclingDocument) {
+ List<BaseTextItem> texts = doclingDocument.getTexts();
+ if (texts == null) {
+ return null;
+ }
+ for (BaseTextItem item : texts) {
+ if (item != null && item.getLabel() == DocItemLabel.TITLE) {
+ String text = item.getText();
+ if (text != null && !text.isBlank()) {
+ return text.trim();
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Derives a short, human-readable document type (e.g., {@code PDF},
{@code DOCX}) from the document origin,
+ * preferring the source file extension and falling back to the MIME
subtype. Returns {@code null} when the type
+ * cannot be determined.
+ */
+ private String deriveDocumentType(DocumentOrigin origin) {
+ if (origin == null) {
+ return null;
+ }
+ String filename = origin.getFilename();
+ if (filename != null) {
+ int dot = filename.lastIndexOf('.');
+ if (dot > 0 && dot < filename.length() - 1) {
+ return filename.substring(dot + 1).toUpperCase(Locale.ROOT);
+ }
+ }
+ String mimetype = origin.getMimetype();
+ if (mimetype != null && !mimetype.isBlank()) {
+ int slash = mimetype.lastIndexOf('/');
+ String subtype = slash >= 0 && slash < mimetype.length() - 1
+ ? mimetype.substring(slash + 1) : mimetype;
+ return subtype.toUpperCase(Locale.ROOT);
+ }
+ return null;
+ }
+
private void processBatchConversion(Exchange exchange, String
outputFormat) throws Exception {
LOG.debug("DoclingProducer processing batch conversion with format:
{}", outputFormat);
diff --git
a/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DocumentMetadata.java
b/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DocumentMetadata.java
index 240a3cb24f1f..b903b4cd82d4 100644
---
a/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DocumentMetadata.java
+++
b/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DocumentMetadata.java
@@ -24,6 +24,8 @@ import java.util.Map;
*/
public class DocumentMetadata {
+ private String title;
+ private String documentType;
private Integer pageCount;
private String format;
private Long fileSizeBytes;
@@ -35,6 +37,32 @@ public class DocumentMetadata {
this.rawMetadata = new HashMap<>();
}
+ /**
+ * Gets the document title as detected by Docling (the text labelled as
the document title), when present.
+ *
+ * @return the document title, or {@code null} if the source document does
not expose one
+ */
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ /**
+ * Gets the document type derived from the source (e.g., "PDF", "DOCX"),
when it can be determined.
+ *
+ * @return the document type, or {@code null} if it cannot be determined
+ */
+ public String getDocumentType() {
+ return documentType;
+ }
+
+ public void setDocumentType(String documentType) {
+ this.documentType = documentType;
+ }
+
/**
* Gets the number of pages in the document.
*
@@ -138,6 +166,8 @@ public class DocumentMetadata {
@Override
public String toString() {
return "DocumentMetadata{"
+ + "title='" + title + '\''
+ + ", documentType='" + documentType + '\''
+ ", pageCount=" + pageCount
+ ", format='" + format + '\''
+ ", fileSizeBytes=" + fileSizeBytes + ", fileName='" +
fileName + '\'' + ", filePath='" + filePath + '\''
diff --git
a/components/camel-ai/camel-docling/src/test/java/org/apache/camel/component/docling/integration/MetadataExtractionIT.java
b/components/camel-ai/camel-docling/src/test/java/org/apache/camel/component/docling/integration/MetadataExtractionIT.java
index 9886e2e63a81..45303a0368c7 100644
---
a/components/camel-ai/camel-docling/src/test/java/org/apache/camel/component/docling/integration/MetadataExtractionIT.java
+++
b/components/camel-ai/camel-docling/src/test/java/org/apache/camel/component/docling/integration/MetadataExtractionIT.java
@@ -71,8 +71,8 @@ class MetadataExtractionIT extends DoclingITestSupport {
assertNotNull(metadata.getFilePath(), "File path should be set");
assertThat(metadata.getPageCount()).isEqualTo(5);
assertThat(metadata.getFormat()).isEqualTo("application/pdf");
- // TODO: assertThat(metadata.getTitle()).isEqualTo("The Evolution of
the Word Processor");
- // TODO: assertThat(metadata.getDocumentType()).isEqualTo("PDF");
+ assertThat(metadata.getTitle()).isEqualTo("The Evolution of the Word
Processor");
+ assertThat(metadata.getDocumentType()).isEqualTo("PDF");
LOG.info("Successfully extracted metadata: {}", metadata);
LOG.info("File name: {}", metadata.getFileName());
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
index 5dec2e266e8b..4e8e7d3121d3 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
@@ -13,6 +13,14 @@ See the xref:camel-upgrade-recipes-tool.adoc[documentation]
page for details.
== Upgrading Camel 4.20 to 4.21
+=== camel-docling
+
+The `EXTRACT_METADATA` operation now populates the `title` and `documentType`
fields on the returned
+`DocumentMetadata` (and the corresponding `CamelDoclingMetadataTitle` and
`CamelDoclingMetadataDocumentType`
+headers) when the information can be determined from the document. The title
is taken from the document title
+detected by Docling, and the document type is derived from the source file
extension or MIME type. Previously
+these two fields were always empty.
+
=== camel-grok - potential breaking change
The library used for Camel Grok component has been migrated from no more
maintained `io.krakens:java-grok` to its fork `io.github.whatap:java-grok`.
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DoclingEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DoclingEndpointBuilderFactory.java
index 22e2b26bb47b..dcb0f400e56f 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DoclingEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/DoclingEndpointBuilderFactory.java
@@ -1741,6 +1741,18 @@ public interface DoclingEndpointBuilderFactory {
public String doclingMetadataPageCount() {
return "CamelDoclingMetadataPageCount";
}
+ /**
+ * Document title.
+ *
+ * The option is a: {@code String} type.
+ *
+ * Group: producer
+ *
+ * @return the name of the header {@code DoclingMetadataTitle}.
+ */
+ public String doclingMetadataTitle() {
+ return "CamelDoclingMetadataTitle";
+ }
/**
* Document language code.
*