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

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new e46a3119b912 CAMEL-26176: camel-olingo4 - filterAlreadySeen keeps the 
type of a complex or collection value (#29005)
e46a3119b912 is described below

commit e46a3119b91238e2fb3ee97bc82aee2002de3f02
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Nov 3 11:40:08 2026 +0100

    CAMEL-26176: camel-olingo4 - filterAlreadySeen keeps the type of a complex 
or collection value (#29005)
    
    …mplex or collection value
    
    Olingo4Index treated every Iterable as a list of results. A 
ClientComplexValue is an
    Iterable of its properties, so with filterAlreadySeen a complex property 
came back as a
    List of ClientProperty, and as an empty List the next time. A 
ClientCollectionValue came
    back as a plain List, and the consumer then sent a message with an empty 
List on every
    poll. Now a complex value is indexed as one result, a collection value is 
filtered into
    a new ClientCollectionValue of the same type, and the consumer treats an 
empty filtered
    collection as an empty poll. The body types of the endpoint are updated.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../apache/camel/catalog/components/olingo4.json   |   4 +-
 .../apache/camel/component/olingo4/olingo4.json    |   4 +-
 .../camel/component/olingo4/Olingo4Consumer.java   |   7 +-
 .../camel/component/olingo4/Olingo4Endpoint.java   |  15 +--
 .../camel/component/olingo4/Olingo4Index.java      |  24 +++-
 .../olingo4/Olingo4ComponentConsumerTest.java      |  27 +++++
 .../olingo4/Olingo4ComponentProducerTest.java      |  28 +++++
 .../camel/component/olingo4/Olingo4IndexTest.java  | 124 +++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |   9 ++
 9 files changed, 225 insertions(+), 17 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/olingo4.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/olingo4.json
index 3ed2715acbf3..fc228840dcd2 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/olingo4.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/olingo4.json
@@ -46,9 +46,9 @@
     "CamelOlingo4.responseHttpHeaders": { "index": 0, "kind": "header", 
"displayName": "", "group": "producer", "label": "producer", "required": false, 
"javaType": "Map<String, String>", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "The response Http 
headers", "constantName": 
"org.apache.camel.component.olingo4.internal.Olingo4Constants#FULL_RESPONSE_HTTP_HEADERS"
 }
   },
   "bodyTypes": {
-    "consumerOut": { "carriers": [ "pojo", "value" ], "byOption": [ { 
"option": "methodName", "value": "uread", "carriers": [ "stream" ] }, { 
"option": "filterAlreadySeen", "value": "true", "carriers": [ "pojo", "value", 
"list" ] } ], "description": "The result of read for the resourcePath, as in 
the producer: an entity set is split into one message per ClientEntity and a 
collection property into one message per ClientValue; with splitResult=false 
the ClientEntitySet or the ClientCollect [...]
+    "consumerOut": { "carriers": [ "pojo", "value" ], "byOption": [ { 
"option": "methodName", "value": "uread", "carriers": [ "stream" ] } ], 
"description": "The result of read for the resourcePath, as in the producer: an 
entity set is split into one message per ClientEntity and a collection property 
into one message per ClientValue; with splitResult=false the ClientEntitySet or 
the ClientCollectionValue comes in one message. With filterAlreadySeen only the 
entities or values not read be [...]
     "producerIn": { "carriers": [ "pojo", "text" ], "dependsOn": [ "inBody" ], 
"byOption": [ { "option": "methodName", "value": "read", "carriers": [ "any" ] 
}, { "option": "methodName", "value": "uread", "carriers": [ "any" ] }, { 
"option": "methodName", "value": "delete", "carriers": [ "any" ] }, { "option": 
"methodName", "value": "action", "carriers": [ "pojo", "text", "null" ] }, { 
"option": "methodName", "value": "batch", "carriers": [ "list" ] } ], 
"description": "create, update, p [...]
-    "producerOut": { "carriers": [ "pojo", "value" ], "byOption": [ { 
"option": "methodName", "value": "uread", "carriers": [ "stream" ] }, { 
"option": "methodName", "value": "delete", "carriers": [ "value" ] }, { 
"option": "methodName", "value": "batch", "carriers": [ "list" ] }, { "option": 
"filterAlreadySeen", "value": "true", "carriers": [ "pojo", "value", "list", 
"null" ] } ], "description": "read gives the Olingo client object of the 
resource: a ClientEntitySet, a ClientEntity, the [...]
+    "producerOut": { "carriers": [ "pojo", "value" ], "byOption": [ { 
"option": "methodName", "value": "uread", "carriers": [ "stream" ] }, { 
"option": "methodName", "value": "delete", "carriers": [ "value" ] }, { 
"option": "methodName", "value": "batch", "carriers": [ "list" ] }, { "option": 
"filterAlreadySeen", "value": "true", "carriers": [ "pojo", "value", "null" ] } 
], "description": "read gives the Olingo client object of the resource: a 
ClientEntitySet, a ClientEntity, the Edm of  [...]
   },
   "properties": {
     "apiName": { "index": 0, "kind": "path", "displayName": "Api Name", 
"group": "common", "label": "", "required": true, "type": "enum", "javaType": 
"org.apache.camel.component.olingo4.internal.Olingo4ApiName", "enum": [ 
"DEFAULT" ], "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.olingo4.Olingo4Configuration", 
"configurationField": "configuration", "description": "What kind of operation 
to perform" },
diff --git 
a/components/camel-olingo4/camel-olingo4-component/src/generated/resources/META-INF/org/apache/camel/component/olingo4/olingo4.json
 
b/components/camel-olingo4/camel-olingo4-component/src/generated/resources/META-INF/org/apache/camel/component/olingo4/olingo4.json
index 3ed2715acbf3..fc228840dcd2 100644
--- 
a/components/camel-olingo4/camel-olingo4-component/src/generated/resources/META-INF/org/apache/camel/component/olingo4/olingo4.json
+++ 
b/components/camel-olingo4/camel-olingo4-component/src/generated/resources/META-INF/org/apache/camel/component/olingo4/olingo4.json
@@ -46,9 +46,9 @@
     "CamelOlingo4.responseHttpHeaders": { "index": 0, "kind": "header", 
"displayName": "", "group": "producer", "label": "producer", "required": false, 
"javaType": "Map<String, String>", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "The response Http 
headers", "constantName": 
"org.apache.camel.component.olingo4.internal.Olingo4Constants#FULL_RESPONSE_HTTP_HEADERS"
 }
   },
   "bodyTypes": {
-    "consumerOut": { "carriers": [ "pojo", "value" ], "byOption": [ { 
"option": "methodName", "value": "uread", "carriers": [ "stream" ] }, { 
"option": "filterAlreadySeen", "value": "true", "carriers": [ "pojo", "value", 
"list" ] } ], "description": "The result of read for the resourcePath, as in 
the producer: an entity set is split into one message per ClientEntity and a 
collection property into one message per ClientValue; with splitResult=false 
the ClientEntitySet or the ClientCollect [...]
+    "consumerOut": { "carriers": [ "pojo", "value" ], "byOption": [ { 
"option": "methodName", "value": "uread", "carriers": [ "stream" ] } ], 
"description": "The result of read for the resourcePath, as in the producer: an 
entity set is split into one message per ClientEntity and a collection property 
into one message per ClientValue; with splitResult=false the ClientEntitySet or 
the ClientCollectionValue comes in one message. With filterAlreadySeen only the 
entities or values not read be [...]
     "producerIn": { "carriers": [ "pojo", "text" ], "dependsOn": [ "inBody" ], 
"byOption": [ { "option": "methodName", "value": "read", "carriers": [ "any" ] 
}, { "option": "methodName", "value": "uread", "carriers": [ "any" ] }, { 
"option": "methodName", "value": "delete", "carriers": [ "any" ] }, { "option": 
"methodName", "value": "action", "carriers": [ "pojo", "text", "null" ] }, { 
"option": "methodName", "value": "batch", "carriers": [ "list" ] } ], 
"description": "create, update, p [...]
-    "producerOut": { "carriers": [ "pojo", "value" ], "byOption": [ { 
"option": "methodName", "value": "uread", "carriers": [ "stream" ] }, { 
"option": "methodName", "value": "delete", "carriers": [ "value" ] }, { 
"option": "methodName", "value": "batch", "carriers": [ "list" ] }, { "option": 
"filterAlreadySeen", "value": "true", "carriers": [ "pojo", "value", "list", 
"null" ] } ], "description": "read gives the Olingo client object of the 
resource: a ClientEntitySet, a ClientEntity, the [...]
+    "producerOut": { "carriers": [ "pojo", "value" ], "byOption": [ { 
"option": "methodName", "value": "uread", "carriers": [ "stream" ] }, { 
"option": "methodName", "value": "delete", "carriers": [ "value" ] }, { 
"option": "methodName", "value": "batch", "carriers": [ "list" ] }, { "option": 
"filterAlreadySeen", "value": "true", "carriers": [ "pojo", "value", "null" ] } 
], "description": "read gives the Olingo client object of the resource: a 
ClientEntitySet, a ClientEntity, the Edm of  [...]
   },
   "properties": {
     "apiName": { "index": 0, "kind": "path", "displayName": "Api Name", 
"group": "common", "label": "", "required": true, "type": "enum", "javaType": 
"org.apache.camel.component.olingo4.internal.Olingo4ApiName", "enum": [ 
"DEFAULT" ], "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.olingo4.Olingo4Configuration", 
"configurationField": "configuration", "description": "What kind of operation 
to perform" },
diff --git 
a/components/camel-olingo4/camel-olingo4-component/src/main/java/org/apache/camel/component/olingo4/Olingo4Consumer.java
 
b/components/camel-olingo4/camel-olingo4-component/src/main/java/org/apache/camel/component/olingo4/Olingo4Consumer.java
index ee1c1d796ca2..e861af728b35 100644
--- 
a/components/camel-olingo4/camel-olingo4-component/src/main/java/org/apache/camel/component/olingo4/Olingo4Consumer.java
+++ 
b/components/camel-olingo4/camel-olingo4-component/src/main/java/org/apache/camel/component/olingo4/Olingo4Consumer.java
@@ -99,10 +99,13 @@ public class Olingo4Consumer extends 
AbstractApiConsumer<Olingo4ApiName, Olingo4
 
             //
             // Allow consumer idle properties to properly handle an empty
-            // polling response
+            // polling response, also when filterAlreadySeen left no new
+            // value in a collection value
             //
             if (result[0] == null
-                    || result[0] instanceof ClientEntitySet && 
(((ClientEntitySet) result[0]).getEntities().isEmpty())) {
+                    || result[0] instanceof ClientEntitySet && 
(((ClientEntitySet) result[0]).getEntities().isEmpty())
+                    || resultIndex != null && result[0] instanceof 
ClientCollectionValue
+                            && ((ClientCollectionValue<?>) 
result[0]).isEmpty()) {
                 return 0;
             } else {
                 return ApiConsumerHelper.getResultsProcessed(this, result[0], 
isSplitResult());
diff --git 
a/components/camel-olingo4/camel-olingo4-component/src/main/java/org/apache/camel/component/olingo4/Olingo4Endpoint.java
 
b/components/camel-olingo4/camel-olingo4-component/src/main/java/org/apache/camel/component/olingo4/Olingo4Endpoint.java
index 9ae65bdd7f07..726980487bc7 100644
--- 
a/components/camel-olingo4/camel-olingo4-component/src/main/java/org/apache/camel/component/olingo4/Olingo4Endpoint.java
+++ 
b/components/camel-olingo4/camel-olingo4-component/src/main/java/org/apache/camel/component/olingo4/Olingo4Endpoint.java
@@ -54,13 +54,12 @@ import org.apache.camel.util.CaseInsensitiveMap;
 @BodyTypes(consumerOut = @BodyType(value = { Carrier.POJO, Carrier.VALUE },
                                    byOption = {
                                            @BodyType.When(option = 
"methodName", value = "uread",
-                                                          carriers = 
Carrier.STREAM),
-                                           @BodyType.When(option = 
"filterAlreadySeen", value = "true",
-                                                          carriers = { 
Carrier.POJO, Carrier.VALUE, Carrier.LIST }) },
+                                                          carriers = 
Carrier.STREAM) },
                                    description = "The result of read for the 
resourcePath, as in the producer: an entity"
                                                  + " set is split into one 
message per ClientEntity and a collection"
                                                  + " property into one message 
per ClientValue; with splitResult=false the"
-                                                 + " ClientEntitySet or the 
ClientCollectionValue comes in one message. uread"
+                                                 + " ClientEntitySet or the 
ClientCollectionValue comes in one message. With"
+                                                 + " filterAlreadySeen only 
the entities or values not read before come. uread"
                                                  + " gives the response 
unparsed as a stream"),
            producerIn = @BodyType(value = { Carrier.POJO, Carrier.TEXT }, 
dependsOn = "inBody",
                                   byOption = {
@@ -86,9 +85,7 @@ import org.apache.camel.util.CaseInsensitiveMap;
                                            @BodyType.When(option = 
"methodName", value = "batch",
                                                           carriers = 
Carrier.LIST),
                                            @BodyType.When(option = 
"filterAlreadySeen", value = "true",
-                                                          carriers = {
-                                                                  
Carrier.POJO, Carrier.VALUE, Carrier.LIST,
-                                                                  Carrier.NULL 
}) },
+                                                          carriers = { 
Carrier.POJO, Carrier.VALUE, Carrier.NULL }) },
                                    description = "read gives the Olingo client 
object of the resource: a ClientEntitySet,"
                                                  + " a ClientEntity, the Edm 
of $metadata or the ClientServiceDocument; a"
                                                  + " ClientPrimitiveValue, 
ClientComplexValue or ClientCollectionValue for a"
@@ -96,8 +93,8 @@ import org.apache.camel.util.CaseInsensitiveMap;
                                                  + " a stream. create, update, 
patch, merge and action give the ClientEntity"
                                                  + " of the response, or the 
HttpStatusCode when it has no content, as delete"
                                                  + " does; batch gives a List 
of Olingo4BatchResponse. With"
-                                                 + " filterAlreadySeen, a 
result read before is null, and a complex or"
-                                                 + " collection value becomes 
a List"))
+                                                 + " filterAlreadySeen, a 
result read before is null, and an entity set or"
+                                                 + " a collection value keeps 
only the entities or values not read before"))
 @Deprecated(since = "4.18")
 public class Olingo4Endpoint extends AbstractApiEndpoint<Olingo4ApiName, 
Olingo4Configuration>
         implements EndpointServiceLocation {
diff --git 
a/components/camel-olingo4/camel-olingo4-component/src/main/java/org/apache/camel/component/olingo4/Olingo4Index.java
 
b/components/camel-olingo4/camel-olingo4-component/src/main/java/org/apache/camel/component/olingo4/Olingo4Index.java
index a6781ec9e301..ca2be27571d8 100644
--- 
a/components/camel-olingo4/camel-olingo4-component/src/main/java/org/apache/camel/component/olingo4/Olingo4Index.java
+++ 
b/components/camel-olingo4/camel-olingo4-component/src/main/java/org/apache/camel/component/olingo4/Olingo4Index.java
@@ -18,12 +18,16 @@ package org.apache.camel.component.olingo4;
 
 import java.lang.reflect.Array;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
+import org.apache.olingo.client.api.domain.ClientCollectionValue;
 import org.apache.olingo.client.api.domain.ClientEntity;
 import org.apache.olingo.client.api.domain.ClientEntitySet;
+import org.apache.olingo.client.api.domain.ClientValue;
+import org.apache.olingo.client.core.domain.ClientCollectionValueImpl;
 import org.apache.olingo.client.core.domain.ClientEntitySetImpl;
 
 @Deprecated(since = "4.18")
@@ -94,6 +98,18 @@ public class Olingo4Index {
         return entitySet;
     }
 
+    private ClientCollectionValue<ClientValue> 
filter(ClientCollectionValue<ClientValue> collection) {
+        // keep the collection value (and its type name) with the values not 
seen before
+        ClientCollectionValue<ClientValue> filtered = new 
ClientCollectionValueImpl<>(collection.getTypeName());
+        for (ClientValue value : collection) {
+            if (!resultIndex.contains(value.hashCode())) {
+                filtered.add(value);
+            }
+        }
+
+        return filtered;
+    }
+
     private void index(ClientEntitySet entitySet) {
         if (entitySet == null) {
             return;
@@ -110,7 +126,8 @@ public class Olingo4Index {
     public void index(Object result) {
         if (result instanceof ClientEntitySet) {
             index((ClientEntitySet) result);
-        } else if (result instanceof Iterable) {
+        } else if (result instanceof ClientCollectionValue || result 
instanceof Collection) {
+            // a ClientComplexValue is an Iterable of its properties, but it 
is one result
             index((Iterable<?>) result);
         } else {
             indexDefault(result);
@@ -121,7 +138,10 @@ public class Olingo4Index {
     public Object filterResponse(Object response) {
         if (response instanceof ClientEntitySet) {
             response = filter((ClientEntitySet) response);
-        } else if (response instanceof Iterable) {
+        } else if (response instanceof ClientCollectionValue) {
+            response = filter((ClientCollectionValue<ClientValue>) response);
+        } else if (response instanceof Collection) {
+            // a ClientComplexValue is an Iterable of its properties, but it 
is one result
             response = filter((Iterable<Object>) response);
         } else if (response.getClass().isArray()) {
             List<Object> result = new ArrayList<>();
diff --git 
a/components/camel-olingo4/camel-olingo4-component/src/test/java/org/apache/camel/component/olingo4/Olingo4ComponentConsumerTest.java
 
b/components/camel-olingo4/camel-olingo4-component/src/test/java/org/apache/camel/component/olingo4/Olingo4ComponentConsumerTest.java
index 61e601acc000..5a7d8d65f641 100644
--- 
a/components/camel-olingo4/camel-olingo4-component/src/test/java/org/apache/camel/component/olingo4/Olingo4ComponentConsumerTest.java
+++ 
b/components/camel-olingo4/camel-olingo4-component/src/test/java/org/apache/camel/component/olingo4/Olingo4ComponentConsumerTest.java
@@ -423,6 +423,33 @@ public class Olingo4ComponentConsumerTest extends 
AbstractOlingo4WireMockTestSup
         assertEquals("Boise", 
propValue.get("City").getComplexValue().get("Name").getValue().toString());
     }
 
+    /**
+     * Read the People object's AddressInfo collection value with 
filterAlreadySeen: one message per address, and no
+     * message when a later poll has no address not seen before
+     */
+    @Test
+    public void testConsumerReadClientCollectionValuesFilterAlreadySeen() 
throws Exception {
+        MockEndpoint mockEndpoint = 
getMockEndpoint("mock:consumer-alreadyseen-collection-value");
+        mockEndpoint.expectedMessageCount(1);
+        // the consumer polls many times in this period
+        mockEndpoint.setAssertPeriod(1000L);
+
+        RouteBuilder builder = new RouteBuilder() {
+            public void configure() {
+                from("olingo4://read/" + TEST_PEOPLE + 
"/AddressInfo?delay=2&splitResult=true&filterAlreadySeen=true")
+                        .to("mock:consumer-alreadyseen-collection-value");
+            }
+        };
+        addRouteAndStartContext(builder);
+
+        mockEndpoint.assertIsSatisfied();
+
+        Object body = mockEndpoint.getExchanges().get(0).getIn().getBody();
+        assertIsInstanceOf(ClientComplexValue.class, body);
+        ClientComplexValue value = (ClientComplexValue) body;
+        assertEquals("Boise", 
value.get("City").getComplexValue().get("Name").getValue().toString());
+    }
+
     @Override
     public String getClassIdentifier() {
         return "Olingo4ComponentConsumerTest";
diff --git 
a/components/camel-olingo4/camel-olingo4-component/src/test/java/org/apache/camel/component/olingo4/Olingo4ComponentProducerTest.java
 
b/components/camel-olingo4/camel-olingo4-component/src/test/java/org/apache/camel/component/olingo4/Olingo4ComponentProducerTest.java
index 1d6808255fc2..0d1263ca7e5f 100644
--- 
a/components/camel-olingo4/camel-olingo4-component/src/test/java/org/apache/camel/component/olingo4/Olingo4ComponentProducerTest.java
+++ 
b/components/camel-olingo4/camel-olingo4-component/src/test/java/org/apache/camel/component/olingo4/Olingo4ComponentProducerTest.java
@@ -46,6 +46,7 @@ import static 
org.apache.camel.test.junit6.TestSupport.assertIsInstanceOf;
 import static org.apache.camel.test.junit6.TestSupport.assertStringContains;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
@@ -341,6 +342,27 @@ public class Olingo4ComponentProducerTest extends 
AbstractOlingo4WireMockTestSup
         }
     }
 
+    /**
+     * Read a complex and a collection property twice with filterAlreadySeen: 
the values keep their Olingo type
+     */
+    @Order(10)
+    @Test
+    public void testProducerReadPropertiesFilterAlreadySeen() {
+        // a complex value is one result
+        Object body = requestBody("direct:readcomplexprop-filterseen", null);
+        ClientComplexValue complexProperty = 
assertIsInstanceOf(ClientComplexValue.class, body);
+        assertEquals("San Francisco", 
complexProperty.get("City").getComplexValue().get("Name").getValue().toString());
+        assertNull(requestBody("direct:readcomplexprop-filterseen", null));
+
+        // a collection value keeps the values not read before
+        body = requestBody("direct:readcollectionprop-filterseen", null);
+        ClientCollectionValue<?> collectionProperty = 
assertIsInstanceOf(ClientCollectionValue.class, body);
+        assertEquals(1, collectionProperty.size());
+        body = requestBody("direct:readcollectionprop-filterseen", null);
+        collectionProperty = assertIsInstanceOf(ClientCollectionValue.class, 
body);
+        assertTrue(collectionProperty.isEmpty());
+    }
+
     @Override
     protected RouteBuilder createRouteBuilder() {
         return new RouteBuilder() {
@@ -362,6 +384,12 @@ public class Olingo4ComponentProducerTest extends 
AbstractOlingo4WireMockTestSup
 
                 from("direct:readcollectionprop").to("olingo4://read/" + 
TEST_PEOPLE + "/AddressInfo");
 
+                from("direct:readcomplexprop-filterseen")
+                        
.to("olingo4://read/Airports('KSFO')/Location?filterAlreadySeen=true");
+
+                from("direct:readcollectionprop-filterseen")
+                        .to("olingo4://read/" + TEST_PEOPLE + 
"/AddressInfo?filterAlreadySeen=true");
+
                 from("direct:readentitybyid").to("olingo4://read/" + 
TEST_PEOPLE + "");
 
                 from("direct:readwithfilter")
diff --git 
a/components/camel-olingo4/camel-olingo4-component/src/test/java/org/apache/camel/component/olingo4/Olingo4IndexTest.java
 
b/components/camel-olingo4/camel-olingo4-component/src/test/java/org/apache/camel/component/olingo4/Olingo4IndexTest.java
new file mode 100644
index 000000000000..f94f7ec01015
--- /dev/null
+++ 
b/components/camel-olingo4/camel-olingo4-component/src/test/java/org/apache/camel/component/olingo4/Olingo4IndexTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.olingo4;
+
+import java.util.List;
+
+import org.apache.olingo.client.api.domain.ClientCollectionValue;
+import org.apache.olingo.client.api.domain.ClientComplexValue;
+import org.apache.olingo.client.api.domain.ClientEntitySet;
+import org.apache.olingo.client.api.domain.ClientObjectFactory;
+import org.apache.olingo.client.api.domain.ClientValue;
+import org.apache.olingo.client.core.domain.ClientObjectFactoryImpl;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class Olingo4IndexTest {
+
+    private final ClientObjectFactory factory = new ClientObjectFactoryImpl();
+
+    private ClientComplexValue location(String city) {
+        ClientComplexValue value = 
factory.newComplexValue("Microsoft.OData.SampleService.Models.TripPin.AirportLocation");
+        value.add(factory.newPrimitiveProperty("Address", 
factory.newPrimitiveValueBuilder().buildString("Main Street")));
+        value.add(factory.newPrimitiveProperty("City", 
factory.newPrimitiveValueBuilder().buildString(city)));
+        return value;
+    }
+
+    private ClientCollectionValue<ClientValue> emails(String... emails) {
+        ClientCollectionValue<ClientValue> value = 
factory.newCollectionValue("Edm.String");
+        for (String email : emails) {
+            value.add(factory.newPrimitiveValueBuilder().buildString(email));
+        }
+        return value;
+    }
+
+    @Test
+    public void testComplexValueIsOneResult() {
+        Olingo4Index index = new Olingo4Index();
+
+        Object first = index.filterResponse(location("Boise"));
+        ClientComplexValue value = assertInstanceOf(ClientComplexValue.class, 
first);
+        assertEquals("Boise", 
value.get("City").getPrimitiveValue().toString());
+        index.index(first);
+
+        // the same value read again was seen before
+        assertNull(index.filterResponse(location("Boise")));
+
+        // a changed value is a new result
+        Object changed = index.filterResponse(location("Seattle"));
+        value = assertInstanceOf(ClientComplexValue.class, changed);
+        assertEquals("Seattle", 
value.get("City").getPrimitiveValue().toString());
+    }
+
+    @Test
+    public void testCollectionValueKeepsItsType() {
+        Olingo4Index index = new Olingo4Index();
+
+        Object first = index.filterResponse(emails("[email protected]", 
"[email protected]"));
+        ClientCollectionValue<?> value = 
assertInstanceOf(ClientCollectionValue.class, first);
+        assertEquals("Collection(Edm.String)", value.getTypeName());
+        assertEquals(2, value.size());
+        index.index(first);
+
+        // nothing new: an empty collection value, not a List
+        Object second = index.filterResponse(emails("[email protected]", 
"[email protected]"));
+        value = assertInstanceOf(ClientCollectionValue.class, second);
+        assertTrue(value.isEmpty());
+
+        // only the value not read before
+        Object third = index.filterResponse(emails("[email protected]", 
"[email protected]", "[email protected]"));
+        value = assertInstanceOf(ClientCollectionValue.class, third);
+        assertEquals("Collection(Edm.String)", value.getTypeName());
+        assertEquals(1, value.size());
+        assertEquals("[email protected]", 
value.iterator().next().asPrimitive().toString());
+    }
+
+    @Test
+    public void testEntitySet() {
+        Olingo4Index index = new Olingo4Index();
+
+        ClientEntitySet entitySet = factory.newEntitySet();
+        entitySet.getEntities().add(factory.newEntity(null));
+        Object first = index.filterResponse(entitySet);
+        assertSame(entitySet, first);
+        assertEquals(1, entitySet.getEntities().size());
+        index.index(first);
+
+        ClientEntitySet again = factory.newEntitySet();
+        again.getEntities().add(factory.newEntity(null));
+        Object second = index.filterResponse(again);
+        assertSame(again, second);
+        assertTrue(again.getEntities().isEmpty());
+    }
+
+    @Test
+    public void testListIsFilteredPerElement() {
+        Olingo4Index index = new Olingo4Index();
+
+        Object first = index.filterResponse(List.of("a", "b"));
+        assertEquals(List.of("a", "b"), first);
+        index.index(first);
+
+        assertEquals(List.of("c"), index.filterResponse(List.of("a", "b", 
"c")));
+        assertEquals(List.of("c"), index.filterResponse(new String[] { "a", 
"c" }));
+    }
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 6155b4596898..78a71e410aaa 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -1111,6 +1111,15 @@ still names the local work file. So a later step that 
reads the body again, such
 or before handing it on, for example with `convertBodyTo(byte[].class)` (which 
reads the file into memory). Routes that
 cleaned the local work directory themselves can stop doing so.
 
+=== camel-olingo4 - filterAlreadySeen keeps the type of a complex or 
collection value
+
+With `filterAlreadySeen=true`, reading a complex property gave a 
`java.util.List` of its `ClientProperty` instead of the
+`ClientComplexValue`, and an empty `List` when it was read again. A collection 
property gave a `java.util.List` of the
+values not read before instead of a `ClientCollectionValue`. Now a complex 
value is one result: it is the
+`ClientComplexValue`, or `null` when the same value was read before. A 
collection value stays a `ClientCollectionValue`
+with only the values not read before. The consumer sends no message for a poll 
where nothing new is left in the
+collection, as it already did for an entity set. Routes that read the `List` 
must read the Olingo value instead.
+
 === camel-openstack - potential breaking change
 
 The four remaining Exchange header constants of `SwiftConstants` have been 
renamed to follow the Camel naming

Reply via email to