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 82f7c6871c29 CAMEL-24347: camel-google-firestore - listCollections 
honours the configured documentId
82f7c6871c29 is described below

commit 82f7c6871c29c2f2a03b03fa57020fd957f89a39
Author: Andrea Cosentino <[email protected]>
AuthorDate: Tue Aug 11 14:22:37 2026 +0200

    CAMEL-24347: camel-google-firestore - listCollections honours the 
configured documentId
    
    listCollections was the only document operation reading the document id 
straight
    from the header: with documentId set on the endpoint it silently listed the 
root
    collections instead of the sub-collections of that document. It now falls 
back to
    the configured id like the other operations, while still treating a missing 
id as
    "list the root collections".
    
    The query and list operations also wrote their _id/_path entries into the 
map the
    Firestore SDK returned; they now build their own copy.
    
    Closes #25430
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
    Signed-off-by: Andrea Cosentino <[email protected]>
---
 .../camel-google/camel-google-firestore/pom.xml    |  5 ++
 .../google/firestore/GoogleFirestoreProducer.java  | 36 +++++++---
 .../GoogleFirestoreProducerDocumentIdTest.java     | 77 ++++++++++++++++++++++
 3 files changed, 109 insertions(+), 9 deletions(-)

diff --git a/components/camel-google/camel-google-firestore/pom.xml 
b/components/camel-google/camel-google-firestore/pom.xml
index a8e2fa53873b..3c506040fe03 100644
--- a/components/camel-google/camel-google-firestore/pom.xml
+++ b/components/camel-google/camel-google-firestore/pom.xml
@@ -71,5 +71,10 @@
             <artifactId>camel-test-junit6</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git 
a/components/camel-google/camel-google-firestore/src/main/java/org/apache/camel/component/google/firestore/GoogleFirestoreProducer.java
 
b/components/camel-google/camel-google-firestore/src/main/java/org/apache/camel/component/google/firestore/GoogleFirestoreProducer.java
index 3032de741d78..022fd115c55c 100644
--- 
a/components/camel-google/camel-google-firestore/src/main/java/org/apache/camel/component/google/firestore/GoogleFirestoreProducer.java
+++ 
b/components/camel-google/camel-google-firestore/src/main/java/org/apache/camel/component/google/firestore/GoogleFirestoreProducer.java
@@ -243,10 +243,7 @@ public class GoogleFirestoreProducer extends 
DefaultProducer {
         List<Map<String, Object>> results = new ArrayList<>();
 
         for (QueryDocumentSnapshot document : querySnapshot.getDocuments()) {
-            Map<String, Object> docData = document.getData();
-            docData.put("_id", document.getId());
-            docData.put("_path", document.getReference().getPath());
-            results.add(docData);
+            results.add(withDocumentMetadata(document));
         }
 
         LOG.debug("Query returned {} documents from collection: {}", 
results.size(), collectionName);
@@ -255,6 +252,17 @@ public class GoogleFirestoreProducer extends 
DefaultProducer {
         message.setBody(results);
     }
 
+    /**
+     * The document data plus its id and path. The snapshot data is copied 
first: the returned map is handed to the
+     * route, and the id/path entries must not be written into the object the 
SDK gave us.
+     */
+    private static Map<String, Object> 
withDocumentMetadata(QueryDocumentSnapshot document) {
+        Map<String, Object> docData = new HashMap<>(document.getData());
+        docData.put("_id", document.getId());
+        docData.put("_path", document.getReference().getPath());
+        return docData;
+    }
+
     private Query applyWhereClause(Query query, String field, String operator, 
Object value) {
         return switch (operator.toLowerCase()) {
             case "==", "eq", "equals" -> query.whereEqualTo(field, value);
@@ -291,10 +299,7 @@ public class GoogleFirestoreProducer extends 
DefaultProducer {
         List<Map<String, Object>> results = new ArrayList<>();
 
         for (QueryDocumentSnapshot document : querySnapshot.getDocuments()) {
-            Map<String, Object> docData = document.getData();
-            docData.put("_id", document.getId());
-            docData.put("_path", document.getReference().getPath());
-            results.add(docData);
+            results.add(withDocumentMetadata(document));
         }
 
         LOG.debug("Listed {} documents from collection: {}", results.size(), 
collectionName);
@@ -304,7 +309,7 @@ public class GoogleFirestoreProducer extends 
DefaultProducer {
     }
 
     private void listCollections(Firestore firestore, Exchange exchange) 
throws Exception {
-        String documentId = 
exchange.getIn().getHeader(GoogleFirestoreConstants.DOCUMENT_ID, String.class);
+        String documentId = determineListedDocumentId(exchange);
         List<String> collectionIds = new ArrayList<>();
 
         Iterable<CollectionReference> collections;
@@ -384,6 +389,19 @@ public class GoogleFirestoreProducer extends 
DefaultProducer {
         return collectionName;
     }
 
+    /**
+     * The document id for listCollections, where it is optional: without one 
the root collections are listed. Unlike
+     * {@link #determineDocumentId(Exchange)} this does not fail when none is 
set, but it does fall back to the
+     * configured id the same way.
+     */
+    String determineListedDocumentId(Exchange exchange) {
+        String documentId = 
exchange.getIn().getHeader(GoogleFirestoreConstants.DOCUMENT_ID, String.class);
+        if (ObjectHelper.isEmpty(documentId)) {
+            documentId = getConfiguration().getDocumentId();
+        }
+        return documentId;
+    }
+
     private String determineDocumentId(Exchange exchange) {
         String documentId = 
exchange.getIn().getHeader(GoogleFirestoreConstants.DOCUMENT_ID, String.class);
         if (ObjectHelper.isEmpty(documentId)) {
diff --git 
a/components/camel-google/camel-google-firestore/src/test/java/org/apache/camel/component/google/firestore/GoogleFirestoreProducerDocumentIdTest.java
 
b/components/camel-google/camel-google-firestore/src/test/java/org/apache/camel/component/google/firestore/GoogleFirestoreProducerDocumentIdTest.java
new file mode 100644
index 000000000000..0a09c0679931
--- /dev/null
+++ 
b/components/camel-google/camel-google-firestore/src/test/java/org/apache/camel/component/google/firestore/GoogleFirestoreProducerDocumentIdTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.google.firestore;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.DefaultExchange;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Verifies that the operations resolve the document id the same way: the 
header first, then the endpoint option.
+ */
+class GoogleFirestoreProducerDocumentIdTest {
+
+    private DefaultCamelContext context;
+
+    @AfterEach
+    void tearDown() {
+        if (context != null) {
+            context.stop();
+        }
+    }
+
+    private GoogleFirestoreProducer producer(String query) throws Exception {
+        if (context != null) {
+            context.stop();
+        }
+        context = new DefaultCamelContext();
+        // the endpoint is deliberately not started, so no firestore client is 
built
+        GoogleFirestoreComponent component = 
context.getComponent("google-firestore", GoogleFirestoreComponent.class);
+        GoogleFirestoreEndpoint endpoint
+                = (GoogleFirestoreEndpoint) 
component.createEndpoint("google-firestore://users" + query);
+        return new GoogleFirestoreProducer(endpoint);
+    }
+
+    @Test
+    void theConfiguredDocumentIdIsUsedWhenNoHeaderIsSet() throws Exception {
+        GoogleFirestoreProducer producer = producer("?documentId=configured");
+
+        assertThat(producer.determineListedDocumentId(new 
DefaultExchange(context))).isEqualTo("configured");
+    }
+
+    @Test
+    void theHeaderWinsOverTheConfiguredDocumentId() throws Exception {
+        GoogleFirestoreProducer producer = producer("?documentId=configured");
+
+        Exchange exchange = new DefaultExchange(context);
+        exchange.getIn().setHeader(GoogleFirestoreConstants.DOCUMENT_ID, 
"from-header");
+
+        
assertThat(producer.determineListedDocumentId(exchange)).isEqualTo("from-header");
+    }
+
+    @Test
+    void withoutAnyDocumentIdTheRootCollectionsAreListed() throws Exception {
+        // listCollections is the one operation where the document id is 
optional: no id means the root
+        GoogleFirestoreProducer producer = producer("");
+
+        assertThat(producer.determineListedDocumentId(new 
DefaultExchange(context))).isNull();
+    }
+}

Reply via email to