Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Spring Data API for Grid #7011

Merged
merged 21 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
<cvdlName/>
<jakarta.servlet.api.version>5.0.0</jakarta.servlet.api.version>
<jakarta.web.api.version>10.0.0</jakarta.web.api.version>
<spring-data-commons.version>3.4.1</spring-data-commons.version>

<!-- spreadsheet -->
<poi.version>5.2.5</poi.version>
Expand Down
7 changes: 7 additions & 0 deletions vaadin-crud-flow-parent/vaadin-crud-flow/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@
<artifactId>slf4j-simple</artifactId>
<scope>test</scope>
</dependency>
<!-- Optional Spring dependencies to be able to provide Spring Data API-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>${spring-data-commons.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
14 changes: 14 additions & 0 deletions vaadin-grid-flow-parent/vaadin-grid-flow/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
<artifactId>flow-html-components</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>com.vaadin</groupId>
<artifactId>flow-test-generic</artifactId>
Expand All @@ -60,6 +61,12 @@
<artifactId>vaadin-renderer-flow</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-spring</artifactId>
<version>${flow.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-text-field-flow</artifactId>
Expand Down Expand Up @@ -89,6 +96,13 @@
<artifactId>slf4j-simple</artifactId>
<scope>test</scope>
</dependency>
<!-- Optional Spring dependencies to be able to provide Spring Data API-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>${spring-data-commons.version}</version>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.util.stream.Stream;

import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Pageable;

import com.vaadin.flow.component.AttachEvent;
import com.vaadin.flow.component.ClientCallable;
Expand Down Expand Up @@ -131,6 +132,7 @@
import com.vaadin.flow.internal.JsonUtils;
import com.vaadin.flow.internal.ReflectTools;
import com.vaadin.flow.shared.Registration;
import com.vaadin.flow.spring.data.VaadinSpringDataHelpers;

import elemental.json.Json;
import elemental.json.JsonArray;
Expand Down Expand Up @@ -2916,6 +2918,130 @@ public GridLazyDataView<T> setItems(
return getLazyDataView();
}

public interface SpringData extends Serializable {
/**
* Callback interface for fetching a list of items from a backend based
* on a Spring Data Pageable.
*
* @param <T>
* the type of the items to fetch
*/
@FunctionalInterface
public interface FetchCallback<PAGEABLE, T> extends Serializable {
sissbruecker marked this conversation as resolved.
Show resolved Hide resolved

/**
* Fetches a list of items based on a pageable. The pageable defines
* the paging of the items to fetch and the sorting.
*
* @param pageable
* the pageable that defines which items to fetch and the
* sort order
* @return a list of items
*/
List<T> fetch(PAGEABLE pageable);
}

/**
* Callback interface for counting the number of items in a backend
* based on a Spring Data Pageable.
*/
@FunctionalInterface
public interface CountCallback<PAGEABLE> extends Serializable {
/**
* Counts the number of available items based on a pageable. The
* pageable defines the paging of the items to fetch and the sorting
* and is provided although it is generally not needed for
* determining the number of items.
*
* @param pageable
* the pageable that defines which items to fetch and the
* sort order
* @return the number of available items
*/
long count(PAGEABLE pageable);
}
}

/**
* Supply items lazily with a callback from a backend based on a Spring Data
* Pageable. The component will automatically fetch more items and adjust
* its size until the backend runs out of items. Usage example:
* <p>
* {@code component.setItemsPageable(pageable -> orderService.getOrders(pageable));}
* <p>
* The returned data view object can be used for further configuration, or
* later on fetched with {@link #getLazyDataView()}. For using in-memory
* data, like {@link java.util.Collection}, use
* {@link HasListDataView#setItems(Collection)} instead.
*
* @param fetchCallback
* a function that returns a sorted list of items from the
* backend based on the given pageable
* @return a data view for further configuration
*/
public GridLazyDataView<T> setItemsPageable(
SpringData.FetchCallback<Pageable, T> fetchCallback) {
return setItems(
query -> handleSpringFetchCallback(query, fetchCallback));
}

/**
* Supply items lazily with callbacks: the first one fetches a list of items
* from a backend based on a Spring Data Pageable, the second provides the
* exact count of items in the backend. Use this in case getting the count
* is cheap and the user benefits from the component showing immediately the
* exact size. Usage example:
* <p>
* {@code component.setItemsPageable(
* pageable -> orderService.getOrders(pageable),
* pageable -> orderService.countOrders());}
* <p>
* The returned data view object can be used for further configuration, or
* later on fetched with {@link #getLazyDataView()}. For using in-memory
* data, like {@link java.util.Collection}, use
* {@link HasListDataView#setItems(Collection)} instead.
*
* @param fetchCallback
* a function that returns a sorted list of items from the
* backend based on the given pageable
* @param countCallback
* a function that returns the number of items in the back end
* @return LazyDataView instance for further configuration
*/
public GridLazyDataView<T> setItemsPageable(
SpringData.FetchCallback<Pageable, T> fetchCallback,
SpringData.CountCallback<Pageable> countCallback) {
return setItems(
query -> handleSpringFetchCallback(query, fetchCallback),
query -> handleSpringCountCallback(query, countCallback));
}

@SuppressWarnings("unchecked")
private static <PAGEABLE, T> Stream<T> handleSpringFetchCallback(
Query<T, Void> query,
SpringData.FetchCallback<PAGEABLE, T> fetchCallback) {
PAGEABLE pageable = (PAGEABLE) VaadinSpringDataHelpers
.toSpringPageRequest(query);
List<T> itemList = fetchCallback.fetch(pageable);
return itemList.stream();
}

@SuppressWarnings("unchecked")
private static <PAGEABLE> int handleSpringCountCallback(
Query<?, Void> query,
SpringData.CountCallback<PAGEABLE> countCallback) {
PAGEABLE pageable = (PAGEABLE) VaadinSpringDataHelpers
.toSpringPageRequest(query);
long count = countCallback.count(pageable);
if (count > Integer.MAX_VALUE) {
LoggerFactory.getLogger(Grid.class).warn(
"The count of items in the backend ({}) exceeds the maximum supported by the Grid.",
count);
return Integer.MAX_VALUE;
}
return (int) count;
}

/**
* Gets the lazy data view for the grid. This data view should only be used
* when the items are provided lazily from the backend with:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ protected Stream<String> getExcludedPatterns() {
"com\\.vaadin\\.flow\\.component\\.contextmenu\\.osgi\\..*",
"com\\.vaadin\\.flow\\.component\\.treegrid\\.it\\..*",
"com\\.vaadin\\.flow\\.component\\.datepicker\\..*",
"com\\.vaadin\\.flow\\.component\\.grid\\.GridColumnOrderHelper.*"));
"com\\.vaadin\\.flow\\.component\\.grid\\.GridColumnOrderHelper.*",
"com\\.vaadin\\.flow\\.spring\\..*"));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2000-2025 Vaadin Ltd.
*
* Licensed 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 com.vaadin.flow.component.grid;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.junit.Assert;
import org.junit.Test;

public class GridSpringDataTest {

@Test
public void setItemsPageableNoCount() {
AtomicInteger pageSize = new AtomicInteger(-1);
AtomicInteger pageNumber = new AtomicInteger(-1);
Grid<Person> grid = new Grid<>(Person.class);
grid.setItemsPageable(pageable -> {
if (pageSize.get() != -1) {
throw new IllegalStateException(
"There should be only one call to the data provider");
}
pageSize.set(pageable.getPageSize());
pageNumber.set(pageable.getPageNumber());

return List.of(new Person("John", 1293));
});

Person item = grid.getLazyDataView().getItem(0);

Assert.assertEquals(0, pageNumber.get());
Assert.assertTrue(pageSize.get() > 0);
Assert.assertEquals("John", item.getName());
}

@Test
public void setItemsPageableWithCount() {
AtomicInteger pageSize = new AtomicInteger(-1);
AtomicInteger pageNumber = new AtomicInteger(-1);
Grid<Person> grid = new Grid<>(Person.class);
grid.setItemsPageable(pageable -> {
if (pageSize.get() != -1) {
throw new IllegalStateException(
"There should be only one call to the data provider");
}
pageSize.set(pageable.getPageSize());
pageNumber.set(pageable.getPageNumber());

return List.of(new Person("John", 1293), new Person("Jane", 1923),
new Person("Homer", 1956));
}, pageable -> 3L);

Person item = grid.getLazyDataView().getItems().toList().get(1);

Assert.assertEquals(0, pageNumber.get());
Assert.assertEquals(3, pageSize.get());
Assert.assertEquals("Jane", item.getName());
}
}
7 changes: 7 additions & 0 deletions vaadin-grid-pro-flow-parent/vaadin-grid-pro-flow/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@
<artifactId>slf4j-simple</artifactId>
<scope>test</scope>
</dependency>
<!-- Optional Spring dependencies to be able to provide Spring Data API-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>${spring-data-commons.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down