diff options
8 files changed, 340 insertions, 58 deletions
diff --git a/documentation/datamodel/datamodel-datasources.asciidoc b/documentation/datamodel/datamodel-datasources.asciidoc index ddc78bc6cf..997ad20207 100644 --- a/documentation/datamodel/datamodel-datasources.asciidoc +++ b/documentation/datamodel/datamodel-datasources.asciidoc @@ -152,7 +152,7 @@ Information about which items to fetch as well as some additional details are ma [source, java] ---- -DataSource<Person> dataSource = new DataSource<>( +DataSource<Person> dataSource = new BackendDataSource<>( // First callback fetches items based on a query query -> { // The index of the first item to load @@ -197,7 +197,7 @@ public interface PersonService { int getPersonCount(); - static PersonSort createSort( + PersonSort createSort( String propertyName, boolean descending); } @@ -208,11 +208,11 @@ The sorting options set through the component will be available through [interfa [source, java] ---- -DataSource<Person> dataSource = new DataSource<>( +DataSource<Person> dataSource = new BackEndDataSource<>( query -> { List<PersonSort> sortOrders = new ArrayList<>(); for(SortOrder<String> queryOrder : query.getSortOrders()) { - PersonSort sort = PersonService.createSort( + PersonSort sort = getPersonService().createSort( // The name of the sorted property queryOrder.getSorted(), // The sort direction for this property @@ -220,7 +220,7 @@ DataSource<Person> dataSource = new DataSource<>( sortOrders.add(sort); } - return service.fetchPersons( + return getPersonService().fetchPersons( query.getOffset(), query.getLimit(), sortOrders @@ -329,7 +329,7 @@ We can create a helper method for handling the filter since the same logic is ne [source, java] ---- -DataSource<Person> dataSource = new DataSource<>( +DataSource<Person> dataSource = new BackEndDataSource<>( query -> { BackendFilter filter = query.getFilter(); diff --git a/server/src/main/java/com/vaadin/server/data/BackEndDataSource.java b/server/src/main/java/com/vaadin/server/data/BackEndDataSource.java new file mode 100644 index 0000000000..556d9cb25c --- /dev/null +++ b/server/src/main/java/com/vaadin/server/data/BackEndDataSource.java @@ -0,0 +1,85 @@ +/* + * Copyright 2000-2014 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.server.data; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * A {@link DataSource} for any back end. + * + * @param <T> + * data source data type + */ +public class BackEndDataSource<T> implements + DataSource<T> { + + private Function<Query, Stream<T>> request; + private Function<Query, Integer> sizeCallback; + + /** + * Constructs a new DataSource to request data from an arbitrary back end + * request function. + * + * @param request + * function that requests data from back end based on query + * @param sizeCallback + * function that return the amount of data in back end for query + */ + public BackEndDataSource(Function<Query, Stream<T>> request, + Function<Query, Integer> sizeCallback) { + Objects.requireNonNull(request, "Request function can't be null"); + Objects.requireNonNull(sizeCallback, "Size callback can't be null"); + this.request = request; + this.sizeCallback = sizeCallback; + } + + @Override + public Stream<T> apply(Query t) { + return request.apply(t); + } + + @Override + public int size(Query t) { + return sizeCallback.apply(t); + } + + /** + * Sets a default sorting order to the data source. + * + * @param sortOrders + * a list of sorting information containing field ids and directions + * @return new data source with modified sorting + */ + public BackEndDataSource<T> sortingBy(List<SortOrder<String>> sortOrders) { + return new BackEndDataSource<>(query -> { + List<SortOrder<String>> queryOrder = new ArrayList<>( + query.getSortOrders()); + queryOrder.addAll(sortOrders); + return request.apply(new Query(query.getLimit(), query.getOffset(), + queryOrder, query.getFilters())); + }, sizeCallback); + } + + @Override + public boolean isInMemory() { + return false; + } + +} diff --git a/server/src/main/java/com/vaadin/server/data/DataSource.java b/server/src/main/java/com/vaadin/server/data/DataSource.java index d4e5e17aac..4f6db29535 100644 --- a/server/src/main/java/com/vaadin/server/data/DataSource.java +++ b/server/src/main/java/com/vaadin/server/data/DataSource.java @@ -15,43 +15,42 @@ */ package com.vaadin.server.data; +import java.io.Serializable; import java.util.Arrays; import java.util.Collection; -import java.util.Objects; import java.util.function.Function; import java.util.stream.Stream; /** - * A generic data source for any back end and Listing UI components. - * + * Minimal DataSource API for communication between the DataProvider and a back + * end service. + * + * @since * @param <T> - * data source data type + * data type + * + * @see InMemoryDataSource + * @see BackEndDataSource */ -public class DataSource<T> implements - Function<Query, Stream<T>>, java.io.Serializable { - - protected Function<Query, Stream<T>> request; - protected Function<Query, Integer> sizeCallback; +public interface DataSource<T> extends Function<Query, Stream<T>>, + Serializable { - protected DataSource() { - } + /** + * Gets whether the DataSource content all available in memory or does it + * use some external backend. + * + * @return {@code true} if all data is in memory; {@code false} if not + */ + boolean isInMemory(); /** - * Constructs a new DataSource to request data from an arbitrary back end - * request function. - * - * @param request - * function that requests data from back end based on query - * @param sizeCallback - * function that return the amount of data in back end for query + * Gets the amount of data in this DataSource. + * + * @param t + * query with sorting and filtering + * @return the size of the data source */ - public DataSource(Function<Query, Stream<T>> request, - Function<Query, Integer> sizeCallback) { - Objects.requireNonNull(request, "Request function can't be null"); - Objects.requireNonNull(sizeCallback, "Size callback can't be null"); - this.request = request; - this.sizeCallback = sizeCallback; - } + int size(Query t); /** * This method creates a new {@link InMemoryDataSource} from a given @@ -79,18 +78,4 @@ public class DataSource<T> implements public static <T> InMemoryDataSource<T> create(T... data) { return new InMemoryDataSource<>(Arrays.asList(data)); } - - @Override - public Stream<T> apply(Query t) { - return request.apply(t); - } - - public int size(Query t) { - return sizeCallback.apply(t); - } - - public boolean isInMemory() { - return false; - } - -} +}
\ No newline at end of file diff --git a/server/src/main/java/com/vaadin/server/data/InMemoryDataSource.java b/server/src/main/java/com/vaadin/server/data/InMemoryDataSource.java index bbc5d738c6..bb75f45ffb 100644 --- a/server/src/main/java/com/vaadin/server/data/InMemoryDataSource.java +++ b/server/src/main/java/com/vaadin/server/data/InMemoryDataSource.java @@ -17,6 +17,7 @@ package com.vaadin.server.data; import java.util.ArrayList; import java.util.Collection; +import java.util.Comparator; import java.util.List; import java.util.function.Function; import java.util.stream.Stream; @@ -28,14 +29,15 @@ import java.util.stream.Stream; * @param <T> * data type */ -public class InMemoryDataSource<T> extends DataSource<T> { +public class InMemoryDataSource<T> implements DataSource<T> { + private Function<Query, Stream<T>> request; private int size; /** * Constructs a new ListDataSource. This method makes a protective copy of * the contents of the Collection. - * + * * @param collection * initial data */ @@ -49,12 +51,44 @@ public class InMemoryDataSource<T> extends DataSource<T> { * Chaining constructor for making modified {@link InMemoryDataSource}s. * This Constructor is used internally for making sorted and filtered * variants of a base data source with actual data. - * + * * @param request * request for the new data source */ protected InMemoryDataSource(Function<Query, Stream<T>> request) { - super(request,null); + this.request = request; + } + + @Override + public Stream<T> apply(Query query) { + return request.apply(query); + } + + /** + * Sets a default sorting order to the data source. + * + * @param sortOrder + * a {@link Comparator} providing the needed sorting order + * @return new data source with modified sorting + */ + public InMemoryDataSource<T> sortingBy(Comparator<T> sortOrder) { + return new InMemoryDataSource<>(q -> request.apply(q) + .sorted(sortOrder)); + } + + /** + * Sets a default sorting order to the data source. This method is a + * short-hand for {@code sortingBy(Comparator.comparing(sortOrder))}. + * + * @param sortOrder + * function to sort by + * @param <U> + * the type of the Comparable sort key + * @return new data source with modified sorting + */ + public <U extends Comparable<? super U>> InMemoryDataSource<T> sortingBy( + Function<T, U> sortOrder) { + return sortingBy(Comparator.comparing(sortOrder)); } @Override diff --git a/server/src/main/java/com/vaadin/server/data/SortOrder.java b/server/src/main/java/com/vaadin/server/data/SortOrder.java index 3b8fb65932..cf2cd7e208 100644 --- a/server/src/main/java/com/vaadin/server/data/SortOrder.java +++ b/server/src/main/java/com/vaadin/server/data/SortOrder.java @@ -19,20 +19,43 @@ import java.io.Serializable; import com.vaadin.shared.data.sort.SortDirection; +/** + * Sorting information for one field. + * + * @see Query + */ public class SortOrder<T> implements Serializable { - private T sorted; - private SortDirection direction; + private final T sorted; + private final SortDirection direction; + /** + * Constructs a field sorting information + * + * @param sorted + * sorting information, usually field id or {@link java.util.Comparator} + * @param direction + * sorting direction + */ public SortOrder(T sorted, SortDirection direction) { this.sorted = sorted; this.direction = direction; } + /** + * Sorting information + * + * @return sorting entity, usually field id or {@link java.util.Comparator} + */ public T getSorted() { return sorted; } + /** + * Sorting direction + * + * @return sorting direction + */ public SortDirection getDirection() { return direction; } diff --git a/server/src/test/java/com/vaadin/server/data/datasource/InMemoryDataSourceTest.java b/server/src/test/java/com/vaadin/server/data/datasource/InMemoryDataSourceTest.java index 9ab9731beb..5c71b1e48f 100644 --- a/server/src/test/java/com/vaadin/server/data/datasource/InMemoryDataSourceTest.java +++ b/server/src/test/java/com/vaadin/server/data/datasource/InMemoryDataSourceTest.java @@ -2,14 +2,17 @@ package com.vaadin.server.data.datasource; import static org.junit.Assert.assertTrue; +import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; import com.vaadin.server.data.DataSource; +import com.vaadin.server.data.InMemoryDataSource; +import com.vaadin.server.data.Query; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import com.vaadin.server.data.InMemoryDataSource; -import com.vaadin.server.data.Query; public class InMemoryDataSourceTest { @@ -32,4 +35,70 @@ public class InMemoryDataSourceTest { data.isEmpty()); } + @Test + public void testSortByComparatorListsDiffer() { + Comparator<StrBean> comp = Comparator.comparing(StrBean::getValue) + .thenComparing(StrBean::getRandomNumber) + .thenComparing(StrBean::getId); + List<StrBean> list = dataSource.sortingBy(comp).apply(new Query()) + .collect(Collectors.toList()); + + // First value in data is { Xyz, 10, 100 } which should be last in list + Assert.assertNotEquals("First value should not match", data.get(0), + list.get(0)); + + Assert.assertEquals("Sorted data and original data sizes don't match", + data.size(), list.size()); + + data.sort(comp); + for (int i = 0; i < data.size(); ++i) { + Assert.assertEquals("Sorting result differed", data.get(i), + list.get(i)); + } + } + + @Test + public void testDefatulSortWithSpecifiedPostSort() { + Comparator<StrBean> comp = Comparator.comparing(StrBean::getValue) + .thenComparing(Comparator.comparing(StrBean::getId).reversed()); + List<StrBean> list = dataSource.sortingBy(comp).apply(new Query()) + // The sort here should come e.g from a Component + .sorted(Comparator.comparing(StrBean::getRandomNumber)) + .collect(Collectors.toList()); + + Assert.assertEquals("Sorted data and original data sizes don't match", + data.size(), list.size()); + + for (int i = 1; i < list.size(); ++i) { + StrBean prev = list.get(i - 1); + StrBean cur = list.get(i); + // Test specific sort + Assert.assertTrue(prev.getRandomNumber() <= cur.getRandomNumber()); + + if (prev.getRandomNumber() == cur.getRandomNumber()) { + // Test default sort + Assert.assertTrue(prev.getValue().compareTo(cur.getValue()) <= 0); + if (prev.getValue().equals(cur.getValue())) { + Assert.assertTrue(prev.getId() > cur.getId()); + } + } + } + } + + @Test + public void testDefatulSortWithFunction() { + List<StrBean> list = dataSource.sortingBy(StrBean::getValue) + .apply(new Query()).collect(Collectors.toList()); + + Assert.assertEquals("Sorted data and original data sizes don't match", + data.size(), list.size()); + + for (int i = 1; i < list.size(); ++i) { + StrBean prev = list.get(i - 1); + StrBean cur = list.get(i); + + // Test default sort + Assert.assertTrue(prev.getValue().compareTo(cur.getValue()) <= 0); + } + } } diff --git a/server/src/test/java/com/vaadin/server/data/datasource/bov/DataSourceBoVTest.java b/server/src/test/java/com/vaadin/server/data/datasource/bov/DataSourceBoVTest.java index a42657813a..3b0c12deb1 100644 --- a/server/src/test/java/com/vaadin/server/data/datasource/bov/DataSourceBoVTest.java +++ b/server/src/test/java/com/vaadin/server/data/datasource/bov/DataSourceBoVTest.java @@ -15,13 +15,19 @@ */ package com.vaadin.server.data.datasource.bov; +import com.vaadin.server.data.BackEndDataSource; import com.vaadin.server.data.DataSource; +import com.vaadin.server.data.SortOrder; +import com.vaadin.shared.data.sort.SortDirection; import org.junit.Before; import org.junit.Test; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * Vaadin 8 Example from Book of Vaadin @@ -32,7 +38,7 @@ public class DataSourceBoVTest { private PersonServiceImpl personService; - public static class PersonServiceImpl implements PersonService{ + public static class PersonServiceImpl implements PersonService { final Person[] persons; public PersonServiceImpl(Person... persons) { @@ -48,15 +54,48 @@ public class DataSourceBoVTest { } @Override + public List<Person> fetchPersons(int offset, int limit, Collection<PersonSort> personSorts) { + Stream<Person> personStream = Arrays.stream(persons) + .skip(offset) + .limit(limit); + if (personSorts != null) + for (PersonSort personSort : personSorts) { + personStream = personStream.sorted(personSort); + } + return personStream.collect(Collectors.toList()); + } + + @Override public int getPersonCount() { return persons.length; } + + @Override + public PersonSort createSort(String propertyName, boolean descending) { + PersonSort result; + switch (propertyName) { + case "name": + result = (person1, person2) -> String.CASE_INSENSITIVE_ORDER.compare(person1.getName(), person2.getName()); + break; + case "born": + result = (person1, person2) -> person2.getBorn() - person1.getBorn(); + break; + default: + throw new IllegalArgumentException("wrong field name " + propertyName); + } + if (descending) return (person1, person2) -> result.compare(person2, person1); + else return result; + } } @Test public void testPersons() { + DataSource<Person> dataSource = createUnsortedDatasource(); + //TODO test if the datasource contains all defined Persons in correct(unchanged) order + } - DataSource<Person> dataSource = new DataSource<Person>( + private DataSource<Person> createUnsortedDatasource() { + DataSource<Person> dataSource = new BackEndDataSource<>( // First callback fetches items based on a query query -> { // The index of the first item to load @@ -72,6 +111,41 @@ public class DataSourceBoVTest { // Second callback fetches the number of items for a query query -> getPersonService().getPersonCount() ); + return dataSource; + } + + @Test + public void testSortedPersons() { + + DataSource<Person> dataSource = createSortedDataSource(); + //TODO test if datasource contains all defined Persons in correct order + //TODO test Query.sortOrders correctness + } + + private DataSource<Person> createSortedDataSource() { + DataSource<Person> dataSource = new BackEndDataSource<>( + // First callback fetches items based on a query + query -> { + List<PersonService.PersonSort> sortOrders = new ArrayList<>(); + for (SortOrder<String> queryOrder : query.getSortOrders()) { + PersonService.PersonSort sort = personService.createSort( + // The name of the sorted property + queryOrder.getSorted(), + // The sort direction for this property + queryOrder.getDirection() == SortDirection.DESCENDING); + sortOrders.add(sort); + } + return getPersonService().fetchPersons( + query.getOffset(), + query.getLimit(), + sortOrders + ).stream(); + } + , + // Second callback fetches the number of items for a query + query -> getPersonService().getPersonCount() + ); + return dataSource; } public PersonServiceImpl getPersonService() { diff --git a/server/src/test/java/com/vaadin/server/data/datasource/bov/PersonService.java b/server/src/test/java/com/vaadin/server/data/datasource/bov/PersonService.java index 752cda4032..75419e4f00 100644 --- a/server/src/test/java/com/vaadin/server/data/datasource/bov/PersonService.java +++ b/server/src/test/java/com/vaadin/server/data/datasource/bov/PersonService.java @@ -16,15 +16,27 @@ package com.vaadin.server.data.datasource.bov; import java.io.Serializable; +import java.util.Collection; +import java.util.Comparator; import java.util.List; /** - * TODO class description + * Data access service example. * * @author Vaadin Ltd + * @see Person */ -public interface PersonService extends Serializable{ +public interface PersonService extends Serializable { List<Person> fetchPersons(int offset, int limit); + List<Person> fetchPersons(int offset, int limit, Collection<PersonSort> personSorts); + int getPersonCount(); + + public interface PersonSort extends Comparator<Person>, Serializable { + } + + PersonSort createSort( + String propertyName, + boolean descending); } |