This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch camel-4.18.x
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/camel-4.18.x by this push:
     new 893258a9cbc5 [backport camel-4.18.x] CAMEL-26581: 
camel-google-calendar - follow pages when polling by last update (#29184)
893258a9cbc5 is described below

commit 893258a9cbc5175f61b8261149199b370e32cc26
Author: Guillaume Nodet <[email protected]>
AuthorDate: Thu Nov 5 10:31:28 2026 +0100

    [backport camel-4.18.x] CAMEL-26581: camel-google-calendar - follow pages 
when polling by last update (#29184)
    
    * [backport camel-4.18.x] CAMEL-26581: camel-google-calendar - follow pages 
when polling by last update
    
    * CAMEL-26581: fix Java 17 compat in test (replace 
List.getFirst/removeFirst with get(0)/remove(0))
    
    * docs: add YAML usage example for considerLastUpdate option
    
    * docs: add == Usage heading to google-calendar-stream doc to match main 
branch
    
    ---------
    
    Co-authored-by: Guillaume Nodet <[email protected]>
---
 .../catalog/components/google-calendar-stream.json |   4 +-
 .../calendar/stream/google-calendar-stream.json    |   4 +-
 .../docs/google-calendar-stream-component.adoc     |  25 +++
 .../stream/GoogleCalendarStreamConfiguration.java  |   3 +-
 .../stream/GoogleCalendarStreamConsumer.java       |  14 +-
 ...GoogleCalendarStreamConsumerPaginationTest.java | 188 +++++++++++++++++++++
 ...oogleCalendarStreamComponentBuilderFactory.java |   4 +-
 ...GoogleCalendarStreamEndpointBuilderFactory.java |   8 +-
 8 files changed, 241 insertions(+), 9 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/google-calendar-stream.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/google-calendar-stream.json
index aae7638cdae6..fb8ed22c1495 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/google-calendar-stream.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/google-calendar-stream.json
@@ -32,7 +32,7 @@
     "considerLastUpdate": { "index": 5, "kind": "property", "displayName": 
"Consider Last Update", "group": "consumer", "label": "", "required": false, 
"type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": 
false, "secret": false, "defaultValue": false, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "Take into account the 
lastUpdate of the last event po [...]
     "consumeFromNow": { "index": 6, "kind": "property", "displayName": 
"Consume From Now", "group": "consumer", "label": "", "required": false, 
"type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": 
false, "secret": false, "defaultValue": true, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "Consume events in the 
selected calendar from now on" },
     "delegate": { "index": 7, "kind": "property", "displayName": "Delegate", 
"group": "consumer", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "Delegate for 
wide-domain service account" },
-    "maxResults": { "index": 8, "kind": "property", "displayName": "Max 
Results", "group": "consumer", "label": "", "required": false, "type": 
"integer", "javaType": "int", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": 10, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "Max results to be 
returned" },
+    "maxResults": { "index": 8, "kind": "property", "displayName": "Max 
Results", "group": "consumer", "label": "", "required": false, "type": 
"integer", "javaType": "int", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": 10, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "Maximum number of 
events returned per Google Calendar API page. With syncFlow [...]
     "query": { "index": 9, "kind": "property", "displayName": "Query", 
"group": "consumer", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "The query to execute on 
calendar" },
     "scopes": { "index": 10, "kind": "property", "displayName": "Scopes", 
"group": "consumer", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "Specifies the level of 
permissions you want a calendar application to have to a user account. [...]
     "syncFlow": { "index": 11, "kind": "property", "displayName": "Sync Flow", 
"group": "consumer", "label": "", "required": false, "type": "boolean", 
"javaType": "boolean", "deprecated": false, "autowired": false, "secret": 
false, "defaultValue": false, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "Sync events, see 
https:\/\/developers.google.com\/calendar\/v3\/sync Note [...]
@@ -59,7 +59,7 @@
     "considerLastUpdate": { "index": 4, "kind": "parameter", "displayName": 
"Consider Last Update", "group": "consumer", "label": "", "required": false, 
"type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": 
false, "secret": false, "defaultValue": false, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "Take into account the 
lastUpdate of the last event p [...]
     "consumeFromNow": { "index": 5, "kind": "parameter", "displayName": 
"Consume From Now", "group": "consumer", "label": "", "required": false, 
"type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": 
false, "secret": false, "defaultValue": true, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "Consume events in the 
selected calendar from now on" },
     "delegate": { "index": 6, "kind": "parameter", "displayName": "Delegate", 
"group": "consumer", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "Delegate for 
wide-domain service account" },
-    "maxResults": { "index": 7, "kind": "parameter", "displayName": "Max 
Results", "group": "consumer", "label": "", "required": false, "type": 
"integer", "javaType": "int", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": 10, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "Max results to be 
returned" },
+    "maxResults": { "index": 7, "kind": "parameter", "displayName": "Max 
Results", "group": "consumer", "label": "", "required": false, "type": 
"integer", "javaType": "int", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": 10, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "Maximum number of 
events returned per Google Calendar API page. With syncFlo [...]
     "query": { "index": 8, "kind": "parameter", "displayName": "Query", 
"group": "consumer", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "The query to execute on 
calendar" },
     "scopes": { "index": 9, "kind": "parameter", "displayName": "Scopes", 
"group": "consumer", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "Specifies the level of 
permissions you want a calendar application to have to a user account. [...]
     "sendEmptyMessageWhenIdle": { "index": 10, "kind": "parameter", 
"displayName": "Send Empty Message When Idle", "group": "consumer", "label": 
"consumer", "required": false, "type": "boolean", "javaType": "boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": 
false, "description": "If the polling consumer did not poll any files, you can 
enable this option to send an empty message (no body) instead." },
diff --git 
a/components/camel-google/camel-google-calendar/src/generated/resources/META-INF/org/apache/camel/component/google/calendar/stream/google-calendar-stream.json
 
b/components/camel-google/camel-google-calendar/src/generated/resources/META-INF/org/apache/camel/component/google/calendar/stream/google-calendar-stream.json
index aae7638cdae6..fb8ed22c1495 100644
--- 
a/components/camel-google/camel-google-calendar/src/generated/resources/META-INF/org/apache/camel/component/google/calendar/stream/google-calendar-stream.json
+++ 
b/components/camel-google/camel-google-calendar/src/generated/resources/META-INF/org/apache/camel/component/google/calendar/stream/google-calendar-stream.json
@@ -32,7 +32,7 @@
     "considerLastUpdate": { "index": 5, "kind": "property", "displayName": 
"Consider Last Update", "group": "consumer", "label": "", "required": false, 
"type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": 
false, "secret": false, "defaultValue": false, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "Take into account the 
lastUpdate of the last event po [...]
     "consumeFromNow": { "index": 6, "kind": "property", "displayName": 
"Consume From Now", "group": "consumer", "label": "", "required": false, 
"type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": 
false, "secret": false, "defaultValue": true, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "Consume events in the 
selected calendar from now on" },
     "delegate": { "index": 7, "kind": "property", "displayName": "Delegate", 
"group": "consumer", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "Delegate for 
wide-domain service account" },
-    "maxResults": { "index": 8, "kind": "property", "displayName": "Max 
Results", "group": "consumer", "label": "", "required": false, "type": 
"integer", "javaType": "int", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": 10, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "Max results to be 
returned" },
+    "maxResults": { "index": 8, "kind": "property", "displayName": "Max 
Results", "group": "consumer", "label": "", "required": false, "type": 
"integer", "javaType": "int", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": 10, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "Maximum number of 
events returned per Google Calendar API page. With syncFlow [...]
     "query": { "index": 9, "kind": "property", "displayName": "Query", 
"group": "consumer", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "The query to execute on 
calendar" },
     "scopes": { "index": 10, "kind": "property", "displayName": "Scopes", 
"group": "consumer", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "Specifies the level of 
permissions you want a calendar application to have to a user account. [...]
     "syncFlow": { "index": 11, "kind": "property", "displayName": "Sync Flow", 
"group": "consumer", "label": "", "required": false, "type": "boolean", 
"javaType": "boolean", "deprecated": false, "autowired": false, "secret": 
false, "defaultValue": false, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "Sync events, see 
https:\/\/developers.google.com\/calendar\/v3\/sync Note [...]
@@ -59,7 +59,7 @@
     "considerLastUpdate": { "index": 4, "kind": "parameter", "displayName": 
"Consider Last Update", "group": "consumer", "label": "", "required": false, 
"type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": 
false, "secret": false, "defaultValue": false, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "Take into account the 
lastUpdate of the last event p [...]
     "consumeFromNow": { "index": 5, "kind": "parameter", "displayName": 
"Consume From Now", "group": "consumer", "label": "", "required": false, 
"type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": 
false, "secret": false, "defaultValue": true, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "Consume events in the 
selected calendar from now on" },
     "delegate": { "index": 6, "kind": "parameter", "displayName": "Delegate", 
"group": "consumer", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "Delegate for 
wide-domain service account" },
-    "maxResults": { "index": 7, "kind": "parameter", "displayName": "Max 
Results", "group": "consumer", "label": "", "required": false, "type": 
"integer", "javaType": "int", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": 10, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "Max results to be 
returned" },
+    "maxResults": { "index": 7, "kind": "parameter", "displayName": "Max 
Results", "group": "consumer", "label": "", "required": false, "type": 
"integer", "javaType": "int", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": 10, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "Maximum number of 
events returned per Google Calendar API page. With syncFlo [...]
     "query": { "index": 8, "kind": "parameter", "displayName": "Query", 
"group": "consumer", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "The query to execute on 
calendar" },
     "scopes": { "index": 9, "kind": "parameter", "displayName": "Scopes", 
"group": "consumer", "label": "", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.google.calendar.stream.GoogleCalendarStreamConfiguration",
 "configurationField": "configuration", "description": "Specifies the level of 
permissions you want a calendar application to have to a user account. [...]
     "sendEmptyMessageWhenIdle": { "index": 10, "kind": "parameter", 
"displayName": "Send Empty Message When Idle", "group": "consumer", "label": 
"consumer", "required": false, "type": "boolean", "javaType": "boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": 
false, "description": "If the polling consumer did not poll any files, you can 
enable this option to send an empty message (no body) instead." },
diff --git 
a/components/camel-google/camel-google-calendar/src/main/docs/google-calendar-stream-component.adoc
 
b/components/camel-google/camel-google-calendar/src/main/docs/google-calendar-stream-component.adoc
index a4ac1fab6c17..8e48b8836137 100644
--- 
a/components/camel-google/camel-google-calendar/src/main/docs/google-calendar-stream-component.adoc
+++ 
b/components/camel-google/camel-google-calendar/src/main/docs/google-calendar-stream-component.adoc
@@ -64,6 +64,31 @@ include::partial$component-endpoint-options.adoc[]
 include::partial$component-endpoint-headers.adoc[]
 // component options: END
 
+== Usage
 
+The consumer polls a calendar and sends each event as a message, with its id 
in the `CamelGoogleCalendarEventId` header.
+With `syncFlow=false` and `considerLastUpdate=true`, each poll reads all 
result pages. In this mode,
+`maxResults` controls the API page size rather than the total number of events 
delivered by a poll.
+This lets the consumer move past a page containing only events already seen at 
the last update timestamp.
+With `considerLastUpdate=false`, non-sync polling continues to read only one 
page per poll.
+
+With `considerLastUpdate`, each poll starts from the last update of the events 
it has already seen:
+
+[source,yaml]
+----
+- route:
+    from:
+      uri: google-calendar-stream:events
+      parameters:
+        calendarId: primary
+        considerLastUpdate: true
+        clientId: "{{google.clientId}}"
+        clientSecret: "{{google.clientSecret}}"
+        refreshToken: "{{google.refreshToken}}"
+        applicationName: camel-example
+      steps:
+        - log:
+            message: "Event ${header.CamelGoogleCalendarEventId}: 
${body.summary}"
+----
 
 include::spring-boot:partial$starter.adoc[]
diff --git 
a/components/camel-google/camel-google-calendar/src/main/java/org/apache/camel/component/google/calendar/stream/GoogleCalendarStreamConfiguration.java
 
b/components/camel-google/camel-google-calendar/src/main/java/org/apache/camel/component/google/calendar/stream/GoogleCalendarStreamConfiguration.java
index b546bd700c71..4d9906978cc8 100644
--- 
a/components/camel-google/camel-google-calendar/src/main/java/org/apache/camel/component/google/calendar/stream/GoogleCalendarStreamConfiguration.java
+++ 
b/components/camel-google/camel-google-calendar/src/main/java/org/apache/camel/component/google/calendar/stream/GoogleCalendarStreamConfiguration.java
@@ -211,7 +211,8 @@ public class GoogleCalendarStreamConfiguration implements 
Cloneable {
     }
 
     /**
-     * Max results to be returned
+     * Maximum number of events returned per Google Calendar API page. With 
syncFlow=false and considerLastUpdate=true,
+     * each poll reads all pages; otherwise each poll reads one page.
      */
     public void setMaxResults(int maxResults) {
         this.maxResults = maxResults;
diff --git 
a/components/camel-google/camel-google-calendar/src/main/java/org/apache/camel/component/google/calendar/stream/GoogleCalendarStreamConsumer.java
 
b/components/camel-google/camel-google-calendar/src/main/java/org/apache/camel/component/google/calendar/stream/GoogleCalendarStreamConsumer.java
index 77c58243bee8..fbe73fcd249e 100644
--- 
a/components/camel-google/camel-google-calendar/src/main/java/org/apache/camel/component/google/calendar/stream/GoogleCalendarStreamConsumer.java
+++ 
b/components/camel-google/camel-google-calendar/src/main/java/org/apache/camel/component/google/calendar/stream/GoogleCalendarStreamConsumer.java
@@ -140,7 +140,19 @@ public class GoogleCalendarStreamConsumer extends 
ScheduledBatchPollingConsumer
                 LOG.info("Sync complete.");
             }
         } else {
-            c = request.setOrderBy("updated").execute();
+            // Keep the same filters while paging. Moving updatedMin between 
pages can skip events,
+            // and requesting only the first page can leave it full of already 
delivered boundary events.
+            List<Event> events = new ArrayList<>();
+            request.setOrderBy("updated");
+            do {
+                c = request.execute();
+                if (c != null && c.getItems() != null) {
+                    events.addAll(c.getItems());
+                }
+                request.setPageToken(c != null ? c.getNextPageToken() : null);
+            } while (getConfiguration().isConsiderLastUpdate() && 
ObjectHelper.isNotEmpty(request.getPageToken()));
+            // Only move the cursor after every page succeeded, so a failed 
listing can be retried.
+            c = new Events().setItems(events);
         }
 
         if (c != null && c.getItems() != null) {
diff --git 
a/components/camel-google/camel-google-calendar/src/test/java/org/apache/camel/component/google/calendar/stream/GoogleCalendarStreamConsumerPaginationTest.java
 
b/components/camel-google/camel-google-calendar/src/test/java/org/apache/camel/component/google/calendar/stream/GoogleCalendarStreamConsumerPaginationTest.java
new file mode 100644
index 000000000000..0eebd20b7353
--- /dev/null
+++ 
b/components/camel-google/camel-google-calendar/src/test/java/org/apache/camel/component/google/calendar/stream/GoogleCalendarStreamConsumerPaginationTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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.calendar.stream;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+
+import com.google.api.client.http.GenericUrl;
+import com.google.api.client.http.LowLevelHttpRequest;
+import com.google.api.client.http.LowLevelHttpResponse;
+import com.google.api.client.json.gson.GsonFactory;
+import com.google.api.client.testing.http.MockHttpTransport;
+import com.google.api.client.testing.http.MockLowLevelHttpRequest;
+import com.google.api.client.testing.http.MockLowLevelHttpResponse;
+import com.google.api.services.calendar.Calendar;
+import org.apache.camel.Exchange;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class GoogleCalendarStreamConsumerPaginationTest {
+
+    private DefaultCamelContext context;
+    private final List<GenericUrl> requests = new ArrayList<>();
+    private final List<String> delivered = new ArrayList<>();
+    private final List<String> responses = new ArrayList<>();
+
+    @BeforeEach
+    void setUp() throws Exception {
+        context = new DefaultCamelContext();
+        context.start();
+    }
+
+    @AfterEach
+    void tearDown() {
+        context.stop();
+    }
+
+    @Test
+    void pollsPastAnAlreadyDeliveredBoundaryEvent() throws Exception {
+        responses.add(page("a", "2023-11-14T22:13:20Z", null));
+        responses.add(page("a", "2023-11-14T22:13:20Z", "second"));
+        responses.add(page("b", "2023-11-14T22:13:25Z", null));
+        GoogleCalendarStreamConsumer consumer = consumer(false);
+
+        assertEquals(1, consumer.poll());
+        assertEquals(1, consumer.poll());
+
+        assertEquals(List.of("a", "b"), delivered);
+        assertEquals(3, requests.size());
+        assertEquals("second", requests.get(2).getFirst("pageToken"));
+        assertEquals(requests.get(1).getFirst("updatedMin"), 
requests.get(2).getFirst("updatedMin"));
+        assertEquals("1", requests.get(2).getFirst("maxResults"));
+    }
+
+    @Test
+    void pollsAllEventsWithTheSameUpdateTime() throws Exception {
+        responses.add(page("a", "2023-11-14T22:13:20Z", "second"));
+        responses.add(page("b", "2023-11-14T22:13:20Z", null));
+        responses.add(page("a", "2023-11-14T22:13:20Z", "second"));
+        responses.add(page("b", "2023-11-14T22:13:20Z", null));
+        GoogleCalendarStreamConsumer consumer = consumer(false);
+
+        assertEquals(2, consumer.poll());
+        assertEquals(0, consumer.poll());
+        assertEquals(List.of("a", "b"), delivered);
+    }
+
+    @Test
+    void followsAPageTokenEvenWhenThePageHasNoItems() throws Exception {
+        responses.add("{\"nextPageToken\":\"second\"}");
+        responses.add(page("b", "2023-11-14T22:13:20Z", null));
+
+        assertEquals(1, consumer(false).poll());
+        assertEquals(List.of("b"), delivered);
+    }
+
+    @Test
+    void aFailedLaterPageDoesNotAdvanceTheCursor() throws Exception {
+        responses.add(page("a", "2023-11-14T22:13:20Z", "second"));
+        responses.add(null);
+        GoogleCalendarStreamConsumer consumer = consumer(false);
+
+        assertThrows(IOException.class, consumer::poll);
+        assertNull(consumer.getLastUpdate());
+        assertTrue(delivered.isEmpty());
+    }
+
+    @Test
+    void syncFlowKeepsOnePagePerPollAndUsesTheSyncToken() throws Exception {
+        responses.add(page("a", "2023-11-14T22:13:20Z", "second"));
+        responses.add("{\"nextSyncToken\":\"sync\",\"items\":[]}");
+        responses.add("{\"nextSyncToken\":\"sync2\",\"items\":[]}");
+        GoogleCalendarStreamConsumer consumer = consumer(true);
+
+        assertEquals(1, consumer.poll());
+        assertEquals(1, requests.size());
+        assertEquals(0, consumer.poll());
+        assertEquals("second", requests.get(1).getFirst("pageToken"));
+        assertEquals(0, consumer.poll());
+        assertEquals("sync", requests.get(2).getFirst("syncToken"));
+        assertNull(requests.get(2).getFirst("pageToken"));
+    }
+
+    @Test
+    void withoutLastUpdatePollingKeepsOnePagePerPoll() throws Exception {
+        responses.add(page("a", "2023-11-14T22:13:20Z", "second"));
+        responses.add(page("b", "2023-11-14T22:13:25Z", null));
+        GoogleCalendarStreamConsumer consumer = consumer(false, false);
+
+        assertEquals(1, consumer.poll());
+        assertEquals(1, requests.size());
+        assertEquals(List.of("a"), delivered);
+        assertNull(requests.get(0).getFirst("updatedMin"));
+    }
+
+    private GoogleCalendarStreamConsumer consumer(boolean syncFlow) {
+        return consumer(syncFlow, !syncFlow);
+    }
+
+    private GoogleCalendarStreamConsumer consumer(boolean syncFlow, boolean 
considerLastUpdate) {
+        GoogleCalendarStreamEndpoint endpoint = context.getEndpoint(
+                
"google-calendar-stream://events?consumeFromNow=false&considerLastUpdate=" + 
considerLastUpdate
+                                                                    + 
"&syncFlow=" + syncFlow
+                                                                    + 
"&maxResults=1&clientId=id&clientSecret=secret",
+                GoogleCalendarStreamEndpoint.class);
+        Calendar client = new Calendar.Builder(new MockHttpTransport() {
+            @Override
+            public LowLevelHttpRequest buildRequest(String method, String url) 
{
+                return new MockLowLevelHttpRequest(url) {
+                    @Override
+                    public LowLevelHttpResponse execute() throws IOException {
+                        requests.add(new GenericUrl(url));
+                        String response = responses.remove(0);
+                        if (response == null) {
+                            throw new IOException("Cannot read the next page");
+                        }
+                        return new 
MockLowLevelHttpResponse().setContentType("application/json").setContent(response);
+                    }
+                };
+            }
+        }, GsonFactory.getDefaultInstance(), 
null).setApplicationName("test").build();
+        return new GoogleCalendarStreamConsumer(endpoint, exchange -> {
+        }) {
+            @Override
+            protected Calendar getClient() {
+                return client;
+            }
+
+            @Override
+            public int processBatch(Queue<Object> exchanges) {
+                int count = exchanges.size();
+                for (Object value : exchanges) {
+                    Exchange exchange = (Exchange) value;
+                    
delivered.add(exchange.getIn().getHeader(GoogleCalendarStreamConstants.EVENT_ID,
 String.class));
+                }
+                return count;
+            }
+        };
+    }
+
+    private static String page(String id, String updated, String nextToken) {
+        return "{\"items\":[{\"id\":\"" + id + "\",\"updated\":\"" + updated + 
"\"}]"
+               + (nextToken == null ? "" : ",\"nextPageToken\":\"" + nextToken 
+ "\"") + "}";
+    }
+}
diff --git 
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/GoogleCalendarStreamComponentBuilderFactory.java
 
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/GoogleCalendarStreamComponentBuilderFactory.java
index 6868535747e9..f0479d5bbafc 100644
--- 
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/GoogleCalendarStreamComponentBuilderFactory.java
+++ 
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/GoogleCalendarStreamComponentBuilderFactory.java
@@ -194,7 +194,9 @@ public interface 
GoogleCalendarStreamComponentBuilderFactory {
     
         
         /**
-         * Max results to be returned.
+         * Maximum number of events returned per Google Calendar API page. With
+         * syncFlow=false and considerLastUpdate=true, each poll reads all
+         * pages; otherwise each poll reads one page.
          * 
          * The option is a: &lt;code&gt;int&lt;/code&gt; type.
          * 
diff --git 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/GoogleCalendarStreamEndpointBuilderFactory.java
 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/GoogleCalendarStreamEndpointBuilderFactory.java
index dc7df527ab45..3117276f8b70 100644
--- 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/GoogleCalendarStreamEndpointBuilderFactory.java
+++ 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/GoogleCalendarStreamEndpointBuilderFactory.java
@@ -165,7 +165,9 @@ public interface GoogleCalendarStreamEndpointBuilderFactory 
{
             return this;
         }
         /**
-         * Max results to be returned.
+         * Maximum number of events returned per Google Calendar API page. With
+         * syncFlow=false and considerLastUpdate=true, each poll reads all
+         * pages; otherwise each poll reads one page.
          * 
          * The option is a: <code>int</code> type.
          * 
@@ -180,7 +182,9 @@ public interface GoogleCalendarStreamEndpointBuilderFactory 
{
             return this;
         }
         /**
-         * Max results to be returned.
+         * Maximum number of events returned per Google Calendar API page. With
+         * syncFlow=false and considerLastUpdate=true, each poll reads all
+         * pages; otherwise each poll reads one page.
          * 
          * The option will be converted to a <code>int</code> type.
          * 

Reply via email to