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