PivotDataProvider extends AbstractDataProvider<PivotedItem, SerializablePredicate<PivotedItem>> { @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public Stream<PivotedItem> fetch(Query<PivotedItem, SerializablePredicate<PivotedItem>> query) { int requestedOffset = query.getOffset(); query.getLimit(); List<PivotedItem> result = new ArrayList<>(); while (result.size() < query.getLimit()) { Optional<SerializablePredicate<T>> sp = (Optional) query.getFilter(); if (requestedOffset < lastRequestedOffset && offsetMap.containsKey(requestedOffset)) { dataCache.clear(); lastPivotOffset = offsetMap.get(requestedOffset); } lastRequestedOffset = requestedOffset; if (dataCache.isEmpty()) { offsetMap.put(requestedOffset, lastPivotOffset); Query<T, SerializablePredicate<T>> newQuery = new Query<>(lastPivotOffset, PAGE_SIZE, query.getSortOrders(), null, sp.orElse(null)); provider.fetch(newQuery).forEach(t -> dataCache.add(t)); lastPivotOffset = lastPivotOffset + PAGE_SIZE; } if (dataCache.isEmpty()) { break; } else { T t = dataCache.poll(); Object rowPropertyValue = ClassUtils.getFieldValue(t, rowKeyProperty); if (lastRowPropertyValue == null || !Objects.equals(rowPropertyValue, lastRowPropertyValue)) { if (pivotedItem != null) { result.add(pivotedItem); } pivotedItem = new PivotedItem(rowPropertyValue); lastRowPropertyValue = rowPropertyValue; } for (int i = 0; i < fixedColumnKeys.size(); i++) { String fk = fixedColumnKeys.get(i); Object value = ClassUtils.getFieldValue(t, fk); pivotedItem.setFixedValue(fk, value); } Object colKeyValue = ClassUtils.getFieldValue(t, columnKeyProperty); for (int i = 0; i < pivotedProperties.size(); i++) { String key = pivotedProperties.get(i); Object value = ClassUtils.getFieldValue(t, key); pivotedItem.setValue(colKeyValue, key, value); } } } if (pivotedItem != null && result.size() < query.getLimit()) { result.add(pivotedItem); } return result.stream(); } PivotDataProvider(BaseDataProvider<ID, T> provider, String rowKeyProperty, String columnKeyProperty,
			List<String> fixedColumnKeys, List<String> pivotedProperties, Supplier<Integer> sizeSupplier); @Override @SuppressWarnings({ "unchecked", "rawtypes" }) Stream<PivotedItem> fetch(Query<PivotedItem, SerializablePredicate<PivotedItem>> query); Consumer<Integer> getAfterCountCompleted(); String getColumnKeyProperty(); List<String> getFixedColumnKeys(); List<String> getPivotedProperties(); String getRowKeyProperty(); int getSize(); @Override boolean isInMemory(); void setAfterCountCompleted(Consumer<Integer> afterCountCompleted); @Override @SuppressWarnings({ "rawtypes", "unchecked" }) int size(Query<PivotedItem, SerializablePredicate<PivotedItem>> query);  }
@Test @SuppressWarnings("unchecked") public void testFetch() { pivotProvider = new PivotDataProvider<>(provider, "name", "someEnum", List.of("name"), List.of("age"), () -> 1); TestEntity t1 = new TestEntity(); t1.setName("Bob"); t1.setAge(44L); t1.setSomeEnum(TestEnum.A); TestEntity t2 = new TestEntity(); t2.setName("Bob"); t2.setAge(45L); t2.setSomeEnum(TestEnum.B); when(query.getOffset()).thenReturn(0); when(query.getLimit()).thenReturn(1); when(provider.fetch(any(Query.class))).thenReturn(Stream.of(t1, t2), Stream.empty()); Stream<PivotedItem> fetch = pivotProvider.fetch(query); ArgumentCaptor<Query<TestEntity, SerializablePredicate<TestEntity>>> captor = ArgumentCaptor.forClass(Query.class); verify(provider, times(2)).fetch(captor.capture()); Query<TestEntity, SerializablePredicate<TestEntity>> value = captor.getAllValues().get(0); assertEquals(0, value.getOffset()); assertEquals(1000, value.getLimit()); value = captor.getAllValues().get(1); assertEquals(1000, value.getOffset()); assertEquals(1000, value.getLimit()); Optional<PivotedItem> findFirst = fetch.findFirst(); assertTrue(findFirst.isPresent()); PivotedItem pivotedItem = findFirst.get(); assertEquals("Bob", pivotedItem.getRowKeyValue()); assertEquals("Bob", pivotedItem.getFixedValue("name")); assertEquals(44L, pivotedItem.getValue(TestEnum.A, "age")); assertEquals(45L, pivotedItem.getValue(TestEnum.B, "age")); }