Skip to content

Commit 68a52c5

Browse files
committed
feat: allow to pass count value via response hints
This commit allows to pass the count information via response hint, which is a hugh performance benefit, because it is no longer necessary to send all entities back to the ODataEndpoint.
1 parent dc3c578 commit 68a52c5

4 files changed

Lines changed: 75 additions & 7 deletions

File tree

src/main/java/io/neonbee/endpoint/odatav4/internal/olingo/processor/CountEntityCollectionProcessor.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import static io.neonbee.endpoint.odatav4.internal.olingo.processor.EntityProcessor.findEntityByKeyPredicates;
55
import static io.neonbee.endpoint.odatav4.internal.olingo.processor.NavigationPropertyHelper.chooseEntitySet;
66
import static io.neonbee.endpoint.odatav4.internal.olingo.processor.NavigationPropertyHelper.fetchNavigationTargetEntities;
7+
import static io.neonbee.endpoint.odatav4.internal.olingo.processor.ProcessorHelper.ODATA_COUNT_SIZE_KEY;
78
import static io.neonbee.endpoint.odatav4.internal.olingo.processor.ProcessorHelper.ODATA_EXPAND_KEY;
89
import static io.neonbee.endpoint.odatav4.internal.olingo.processor.ProcessorHelper.ODATA_FILTER_KEY;
910
import static io.neonbee.endpoint.odatav4.internal.olingo.processor.ProcessorHelper.ODATA_ORDER_BY_KEY;
@@ -120,7 +121,7 @@ public void readEntityCollection(ODataRequest request, ODataResponse response, U
120121
.orElse(Boolean.FALSE);
121122
List<Entity> resultEntityList = filterExecuted ? ew.getEntities()
122123
: applyFilterQueryOption(uriInfo.getFilterOption(), ew.getEntities());
123-
applyCountOption(uriInfo.getCountOption(), resultEntityList, entityCollection);
124+
applyCountOption(uriInfo.getCountOption(), resultEntityList, entityCollection, routingContext);
124125
if (!resultEntityList.isEmpty()) {
125126
boolean orderByExecuted =
126127
ofNullable(routingContext.<Boolean>get(RESPONSE_HEADER_PREFIX + ODATA_ORDER_BY_KEY))
@@ -144,7 +145,7 @@ public void readEntityCollection(ODataRequest request, ODataResponse response, U
144145
} else {
145146
responsePromise.complete(resultEntityList);
146147
}
147-
} catch (ODataException e) {
148+
} catch (ODataException | ClassCastException e) {
148149
processPromise.fail(e);
149150
}
150151
} else {
@@ -183,14 +184,16 @@ public void readEntityCollection(ODataRequest request, ODataResponse response, U
183184
}
184185

185186
private void applyCountOption(CountOption countOption, List<Entity> filteredEntities,
186-
EntityCollection entityCollection) {
187+
EntityCollection entityCollection, RoutingContext routingContext) {
187188
// Apply $count system query option. The $count system query option with a value of true
188189
// specifies that the total count of items within a collection matching the request be returned
189190
// along with the result. The $count system query option ignores any $top, $skip, or $expand query
190191
// options, and returns the total count of results across all pages including only those results
191192
// matching any specified $filter and $search.
192193
if ((countOption != null) && countOption.getValue()) {
193-
entityCollection.setCount(filteredEntities.size());
194+
int countSize = ofNullable(routingContext.<Integer>get(RESPONSE_HEADER_PREFIX + ODATA_COUNT_SIZE_KEY))
195+
.orElse(filteredEntities.size());
196+
entityCollection.setCount(countSize);
194197
}
195198
}
196199

@@ -321,15 +324,19 @@ public void countEntityCollection(ODataRequest request, ODataResponse response,
321324
* Content negotiation using the Accept request header or the $format system query option is not allowed
322325
* with the path segment /$count.
323326
*/
324-
List<Entity> resultEntityList = applyFilterQueryOption(uriInfo.getFilterOption(), ew.getEntities());
327+
Integer countSize =
328+
routingContext.<Integer>get(RESPONSE_HEADER_PREFIX + ODATA_COUNT_SIZE_KEY);
329+
if (countSize == null) {
330+
countSize = applyFilterQueryOption(uriInfo.getFilterOption(), ew.getEntities()).size();
331+
}
325332

326333
ByteArrayInputStream serializerContent = new ByteArrayInputStream(
327-
String.valueOf(resultEntityList.size()).getBytes(StandardCharsets.UTF_8));
334+
String.valueOf(countSize).getBytes(StandardCharsets.UTF_8));
328335
response.setContent(serializerContent);
329336
response.setHeader(HttpHeader.CONTENT_TYPE, ContentType.TEXT_PLAIN.toContentTypeString());
330337
response.setStatusCode(HttpStatusCode.OK.getStatusCode());
331338
processPromise.complete();
332-
} catch (ODataException e) {
339+
} catch (ODataException | ClassCastException e) {
333340
processPromise.fail(e);
334341
}
335342
});

src/main/java/io/neonbee/endpoint/odatav4/internal/olingo/processor/ProcessorHelper.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ public final class ProcessorHelper {
4949
/** OData key predicate key. */
5050
public static final String ODATA_KEY_PREDICATE_KEY = "OData.key";
5151

52+
/** OData count size key. */
53+
public static final String ODATA_COUNT_SIZE_KEY = "OData.count.size";
54+
5255
private ProcessorHelper() {}
5356

5457
private static DataQuery odataRequestToQuery(ODataRequest request, DataAction action, Buffer body) {

src/test/java/io/neonbee/endpoint/odatav4/internal/olingo/processor/ProcessorHelperTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.neonbee.endpoint.odatav4.internal.olingo.processor;
22

33
import static com.google.common.truth.Truth.assertThat;
4+
import static io.neonbee.endpoint.odatav4.internal.olingo.processor.ProcessorHelper.ODATA_COUNT_SIZE_KEY;
45
import static io.neonbee.endpoint.odatav4.internal.olingo.processor.ProcessorHelper.ODATA_EXPAND_KEY;
56
import static io.neonbee.endpoint.odatav4.internal.olingo.processor.ProcessorHelper.ODATA_FILTER_KEY;
67
import static io.neonbee.endpoint.odatav4.internal.olingo.processor.ProcessorHelper.ODATA_SKIP_KEY;
@@ -34,10 +35,12 @@ void transferResponseHint() {
3435
DataContext dataContext = new DataContextImpl();
3536
dataContext.responseData().put(ODATA_FILTER_KEY, Boolean.TRUE);
3637
dataContext.responseData().put(ODATA_EXPAND_KEY, Boolean.FALSE);
38+
dataContext.responseData().put(ODATA_COUNT_SIZE_KEY, 42);
3739
ProcessorHelper.transferResponseHint(dataContext, routingContext);
3840
assertThat(routingContext.<Boolean>get(RESPONSE_HEADER_PREFIX + ODATA_FILTER_KEY)).isTrue();
3941
assertThat(routingContext.<Boolean>get(RESPONSE_HEADER_PREFIX + ODATA_EXPAND_KEY)).isFalse();
4042
assertThat(routingContext.<Boolean>get(RESPONSE_HEADER_PREFIX + ODATA_SKIP_KEY)).isNull();
4143
assertThat(routingContext.<Boolean>get(RESPONSE_HEADER_PREFIX + ODATA_TOP_KEY)).isNull();
44+
assertThat(routingContext.<Integer>get(RESPONSE_HEADER_PREFIX + ODATA_COUNT_SIZE_KEY)).isEqualTo(42);
4245
}
4346
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package io.neonbee.test.endpoint.odata.verticle;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
import static io.neonbee.endpoint.odatav4.internal.olingo.processor.ProcessorHelper.ODATA_COUNT_SIZE_KEY;
5+
import static io.neonbee.test.endpoint.odata.verticle.TestService1EntityVerticle.TEST_ENTITY_SET_FQN;
6+
import static io.neonbee.test.endpoint.odata.verticle.TestService1EntityVerticle.getDeclaredEntityModel;
7+
8+
import java.nio.file.Path;
9+
import java.util.List;
10+
import java.util.Map;
11+
12+
import org.junit.jupiter.api.DisplayName;
13+
import org.junit.jupiter.api.Test;
14+
15+
import io.neonbee.entity.EntityWrapper;
16+
import io.neonbee.test.base.ODataEndpointTestBase;
17+
import io.neonbee.test.base.ODataRequest;
18+
import io.vertx.core.Future;
19+
import io.vertx.junit5.VertxTestContext;
20+
21+
class ODataCountTest extends ODataEndpointTestBase {
22+
23+
@Override
24+
protected List<Path> provideEntityModels() {
25+
return List.of(getDeclaredEntityModel());
26+
}
27+
28+
private Future<Void> deployVerticleWithResponseHintCount(int count) {
29+
return deployVerticle(createDummyEntityVerticle(TEST_ENTITY_SET_FQN).withDynamicResponse((query, context) -> {
30+
context.responseData().put(ODATA_COUNT_SIZE_KEY, count);
31+
return new EntityWrapper(TEST_ENTITY_SET_FQN, List.of());
32+
})).mapEmpty();
33+
}
34+
35+
@Test
36+
@DisplayName("Respond with 200 and includes inline count read from the response hint")
37+
void entitiesWithInlineCountFromHintTest(VertxTestContext testContext) {
38+
deployVerticleWithResponseHintCount(42).onSuccess(v -> {
39+
Map<String, String> countQuery = Map.of("$count", "true");
40+
assertOData(requestOData(new ODataRequest(TEST_ENTITY_SET_FQN).setQuery(countQuery)),
41+
body -> assertThat(body.toJsonObject().getMap()).containsAtLeast("@odata.count",
42+
42),
43+
testContext).onComplete(testContext.succeedingThenComplete());
44+
}).onFailure(testContext::failNow);
45+
}
46+
47+
@Test
48+
@DisplayName("Test /$count returns the value passed via response hint")
49+
void countEntitiesViaCountFromHintTest(VertxTestContext testContext) {
50+
deployVerticleWithResponseHintCount(1337)
51+
.onSuccess(v -> assertOData(requestOData(new ODataRequest(TEST_ENTITY_SET_FQN).setCount()), "1337",
52+
testContext).onComplete(testContext.succeedingThenComplete()))
53+
.onFailure(testContext::failNow);
54+
}
55+
}

0 commit comments

Comments
 (0)