This is an automated email from the ASF dual-hosted git repository. gnodet pushed a commit to branch backport/29161-to-camel-4.18.x in repository https://gitbox.apache.org/repos/asf/camel.git
commit f29d84db9dfd550a9edf127c141d8913cfcbed10 Author: Guillaume Nodet <[email protected]> AuthorDate: Wed Nov 4 15:42:55 2026 +0000 [backport camel-4.18.x] CAMEL-26581: camel-google-calendar - follow pages when polling by last update --- .../catalog/components/google-calendar-stream.json | 4 +- .../calendar/stream/google-calendar-stream.json | 4 +- .../docs/google-calendar-stream-component.adoc | 7 + .../stream/GoogleCalendarStreamConfiguration.java | 3 +- .../stream/GoogleCalendarStreamConsumer.java | 14 +- ...GoogleCalendarStreamConsumerPaginationTest.java | 188 +++++++++++++++++++++ ...oogleCalendarStreamComponentBuilderFactory.java | 4 +- ...GoogleCalendarStreamEndpointBuilderFactory.java | 8 +- 8 files changed, 223 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..b7d4feec81ac 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 @@ -65,5 +65,12 @@ include::partial$component-endpoint-headers.adoc[] // component options: END +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: 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..0dd751894991 --- /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.getFirst().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.removeFirst(); + 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. *
