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: <code>int</code> 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.
*