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 f8b8f4f2f81c CAMEL-24346: camel-google-sheets - number every range and 
tolerate a range without values
f8b8f4f2f81c is described below

commit f8b8f4f2f81c025124fe64f8fc1c19e18d91652a
Author: Andrea Cosentino <[email protected]>
AuthorDate: Fri Aug 7 09:26:35 2026 +0200

    CAMEL-24346: camel-google-sheets - number every range and tolerate a range 
without values
    
    Fix two defects in GoogleSheetsStreamConsumer: the splitResults range 
counter was
    re-created inside the loop so every exchange reported 
CamelGoogleSheetsRangeIndex=1,
    and a range without values threw NPE because the Sheets API omits the field 
for
    empty ranges. Also adds forceConsumerAsReady() to the whole-spreadsheet 
code path.
    
    Closes #25400
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
 .../camel-google/camel-google-sheets/pom.xml       |   5 +
 .../sheets/stream/GoogleSheetsStreamConsumer.java  |  83 +++++++++-------
 .../GoogleSheetsStreamConsumerRangeIndexTest.java  | 108 +++++++++++++++++++++
 3 files changed, 163 insertions(+), 33 deletions(-)

diff --git a/components/camel-google/camel-google-sheets/pom.xml 
b/components/camel-google/camel-google-sheets/pom.xml
index 78bfc57f7fa7..e236158b82cb 100644
--- a/components/camel-google/camel-google-sheets/pom.xml
+++ b/components/camel-google/camel-google-sheets/pom.xml
@@ -148,6 +148,11 @@
             <artifactId>camel-test-junit6</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-mock</artifactId>
diff --git 
a/components/camel-google/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/stream/GoogleSheetsStreamConsumer.java
 
b/components/camel-google/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/stream/GoogleSheetsStreamConsumer.java
index f39cb20a4b3d..7201f9cd340a 100644
--- 
a/components/camel-google/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/stream/GoogleSheetsStreamConsumer.java
+++ 
b/components/camel-google/camel-google-sheets/src/main/java/org/apache/camel/component/google/sheets/stream/GoogleSheetsStreamConsumer.java
@@ -23,6 +23,7 @@ import java.util.List;
 import java.util.Queue;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import com.google.api.services.sheets.v4.Sheets;
 import com.google.api.services.sheets.v4.model.BatchGetValuesResponse;
@@ -84,39 +85,7 @@ public class GoogleSheetsStreamConsumer extends 
ScheduledBatchPollingConsumer {
             forceConsumerAsReady();
 
             if (response.getValueRanges() != null) {
-                if (getConfiguration().isSplitResults()) {
-                    for (ValueRange valueRange : response.getValueRanges()) {
-                        AtomicInteger rangeIndex = new AtomicInteger(1);
-                        AtomicInteger valueIndex = new AtomicInteger();
-                        if (getConfiguration().getMaxResults() > 0) {
-                            valueRange.getValues().stream()
-                                    .limit(getConfiguration().getMaxResults())
-                                    .map(values -> 
createExchange(rangeIndex.get(), valueIndex.incrementAndGet(),
-                                            valueRange.getRange(), 
valueRange.getMajorDimension(), values))
-                                    .forEach(answer::add);
-                        } else {
-                            valueRange.getValues().stream()
-                                    .map(values -> 
createExchange(rangeIndex.get(), valueIndex.incrementAndGet(),
-                                            valueRange.getRange(), 
valueRange.getMajorDimension(), values))
-                                    .forEach(answer::add);
-                        }
-                        rangeIndex.incrementAndGet();
-                    }
-                } else {
-                    AtomicInteger rangeIndex = new AtomicInteger();
-                    response.getValueRanges()
-                            .stream()
-                            .peek(valueRange -> {
-                                if (getConfiguration().getMaxResults() > 0) {
-                                    valueRange.setValues(valueRange.getValues()
-                                            .stream()
-                                            
.limit(getConfiguration().getMaxResults())
-                                            .collect(Collectors.toList()));
-                                }
-                            })
-                            .map(valueRange -> 
createExchange(rangeIndex.incrementAndGet(), valueRange))
-                            .forEach(answer::add);
-                }
+                answer.addAll(createExchanges(response.getValueRanges()));
             }
         } else {
             Sheets.Spreadsheets.Get request = 
getClient().spreadsheets().get(getConfiguration().getSpreadsheetId());
@@ -124,12 +93,60 @@ public class GoogleSheetsStreamConsumer extends 
ScheduledBatchPollingConsumer {
             request.setIncludeGridData(getConfiguration().isIncludeGridData());
 
             Spreadsheet spreadsheet = request.execute();
+
+            // okay we have some response from Google so lets mark the 
consumer as ready
+            forceConsumerAsReady();
+
             answer.add(createExchange(spreadsheet));
         }
 
         return processBatch(CastUtils.cast(answer));
     }
 
+    /**
+     * Builds one exchange per value when the results are split, one per range 
otherwise. The range index counts the
+     * ranges of the whole response, not of a single range.
+     */
+    Queue<Exchange> createExchanges(List<ValueRange> valueRanges) {
+        Queue<Exchange> answer = new ArrayDeque<>();
+        AtomicInteger rangeIndex = new AtomicInteger();
+
+        if (getConfiguration().isSplitResults()) {
+            for (ValueRange valueRange : valueRanges) {
+                int currentRange = rangeIndex.incrementAndGet();
+                AtomicInteger valueIndex = new AtomicInteger();
+                Stream<List<Object>> values = valuesOf(valueRange).stream();
+                if (getConfiguration().getMaxResults() > 0) {
+                    values = values.limit(getConfiguration().getMaxResults());
+                }
+                values.map(value -> createExchange(currentRange, 
valueIndex.incrementAndGet(),
+                        valueRange.getRange(), valueRange.getMajorDimension(), 
value))
+                        .forEach(answer::add);
+            }
+        } else {
+            valueRanges.stream()
+                    .peek(valueRange -> {
+                        if (getConfiguration().getMaxResults() > 0) {
+                            valueRange.setValues(valuesOf(valueRange)
+                                    .stream()
+                                    .limit(getConfiguration().getMaxResults())
+                                    .collect(Collectors.toList()));
+                        }
+                    })
+                    .map(valueRange -> 
createExchange(rangeIndex.incrementAndGet(), valueRange))
+                    .forEach(answer::add);
+        }
+
+        return answer;
+    }
+
+    /**
+     * The values of a range, never null: the sheets API omits the field for a 
range that holds no value at all.
+     */
+    private static List<List<Object>> valuesOf(ValueRange valueRange) {
+        return valueRange.getValues() != null ? valueRange.getValues() : 
List.of();
+    }
+
     @Override
     public int processBatch(Queue<Object> exchanges) throws Exception {
         int total = exchanges.size();
diff --git 
a/components/camel-google/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/stream/GoogleSheetsStreamConsumerRangeIndexTest.java
 
b/components/camel-google/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/stream/GoogleSheetsStreamConsumerRangeIndexTest.java
new file mode 100644
index 000000000000..3bd796625dee
--- /dev/null
+++ 
b/components/camel-google/camel-google-sheets/src/test/java/org/apache/camel/component/google/sheets/stream/GoogleSheetsStreamConsumerRangeIndexTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.sheets.stream;
+
+import java.util.List;
+import java.util.Queue;
+
+import com.google.api.services.sheets.v4.model.ValueRange;
+import org.apache.camel.Exchange;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+/**
+ * Verifies the exchanges the stream consumer builds out of a batch-get 
response: one per value when the results are
+ * split, one per range otherwise, and the range index counting the ranges of 
the response.
+ */
+class GoogleSheetsStreamConsumerRangeIndexTest {
+
+    private DefaultCamelContext context;
+
+    @AfterEach
+    void tearDown() {
+        if (context != null) {
+            context.stop();
+        }
+    }
+
+    private GoogleSheetsStreamConsumer consumer(String query) throws Exception 
{
+        if (context != null) {
+            context.stop();
+        }
+        context = new DefaultCamelContext();
+        // the endpoint is deliberately not started, so no google client is 
built
+        GoogleSheetsStreamComponent component
+                = context.getComponent("google-sheets-stream", 
GoogleSheetsStreamComponent.class);
+        GoogleSheetsStreamEndpoint endpoint = (GoogleSheetsStreamEndpoint) 
component.createEndpoint(
+                
"google-sheets-stream://sheet1?clientId=id&clientSecret=secret&range=A1:B2,C1:D2"
 + query);
+        return new GoogleSheetsStreamConsumer(endpoint, exchange -> {
+        });
+    }
+
+    private static ValueRange range(String name, List<List<Object>> values) {
+        return new 
ValueRange().setRange(name).setMajorDimension("ROWS").setValues(values);
+    }
+
+    private static List<Object> rangeIndexes(Queue<Exchange> exchanges) {
+        return exchanges.stream()
+                .map(e -> 
e.getIn().getHeader(GoogleSheetsStreamConstants.RANGE_INDEX))
+                .toList();
+    }
+
+    @Test
+    void splitResultsNumbersEveryRange() throws Exception {
+        Queue<Exchange> exchanges = 
consumer("&splitResults=true").createExchanges(List.of(
+                range("A1:B2", List.of(List.of("a1", "b1"), List.of("a2", 
"b2"))),
+                range("C1:D2", List.of(List.of("c1", "d1")))));
+
+        // one exchange per value, and the range index identifies which range 
the value came from
+        assertThat(rangeIndexes(exchanges)).containsExactly(1, 1, 2);
+        assertThat(exchanges.stream().map(e -> 
e.getIn().getHeader(GoogleSheetsStreamConstants.VALUE_INDEX)).toList())
+                .containsExactly(1, 2, 1);
+    }
+
+    @Test
+    void withoutSplitResultsEveryRangeIsOneExchange() throws Exception {
+        Queue<Exchange> exchanges = consumer("").createExchanges(List.of(
+                range("A1:B2", List.of(List.of("a1", "b1"))),
+                range("C1:D2", List.of(List.of("c1", "d1")))));
+
+        assertThat(rangeIndexes(exchanges)).containsExactly(1, 2);
+    }
+
+    @Test
+    void aRangeWithoutValuesIsNotAFailure() throws Exception {
+        // the sheets API omits the values field for a range that holds nothing
+        GoogleSheetsStreamConsumer consumer = consumer("&splitResults=true");
+        ValueRange empty = new 
ValueRange().setRange("A1:B2").setMajorDimension("ROWS");
+
+        assertThatCode(() -> 
consumer.createExchanges(List.of(empty))).doesNotThrowAnyException();
+        assertThat(consumer.createExchanges(List.of(empty))).isEmpty();
+    }
+
+    @Test
+    void aRangeWithoutValuesIsNotAFailureWhenResultsAreNotSplit() throws 
Exception {
+        GoogleSheetsStreamConsumer consumer = consumer("&maxResults=1");
+        ValueRange empty = new 
ValueRange().setRange("A1:B2").setMajorDimension("ROWS");
+
+        assertThatCode(() -> 
consumer.createExchanges(List.of(empty))).doesNotThrowAnyException();
+    }
+}

Reply via email to