From cb264dacfe74cba1266b54df8ce8c6ded1510004 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Leif=20=C3=85strand?= Date: Fri, 20 Jan 2017 10:34:33 +0200 Subject: [PATCH] Add a data provider wrapper with a configurable filter (#8280) * Add a data provider wrapper with a configurable filter This is one of many steps towards #8245 --- .../data/provider/BackEndDataProvider.java | 42 ++++++++ .../ConfigurableFilterDataProvider.java | 43 +++++++++ ...ConfigurableFilterDataProviderWrapper.java | 79 +++++++++++++++ .../data/provider/DataProviderWrapper.java | 6 +- .../data/provider/ListDataProvider.java | 4 +- .../vaadin/server/SerializableBiFunction.java | 39 ++++++++ .../provider/BackendDataProviderTest.java | 42 ++++---- ...igurableFilterDataProviderWrapperTest.java | 96 +++++++++++++++++++ 8 files changed, 331 insertions(+), 20 deletions(-) create mode 100644 server/src/main/java/com/vaadin/data/provider/ConfigurableFilterDataProvider.java create mode 100644 server/src/main/java/com/vaadin/data/provider/ConfigurableFilterDataProviderWrapper.java create mode 100644 server/src/main/java/com/vaadin/server/SerializableBiFunction.java create mode 100644 server/src/test/java/com/vaadin/data/provider/ConfigurableFilterDataProviderWrapperTest.java diff --git a/server/src/main/java/com/vaadin/data/provider/BackEndDataProvider.java b/server/src/main/java/com/vaadin/data/provider/BackEndDataProvider.java index 96d5e54394..199d0b64fe 100644 --- a/server/src/main/java/com/vaadin/data/provider/BackEndDataProvider.java +++ b/server/src/main/java/com/vaadin/data/provider/BackEndDataProvider.java @@ -18,6 +18,8 @@ package com.vaadin.data.provider; import java.util.Collections; import java.util.List; +import com.vaadin.server.SerializableBiFunction; + /** * A data provider that lazy loads items from a back end. * @@ -71,4 +73,44 @@ public interface BackEndDataProvider extends DataProvider { default boolean isInMemory() { return false; } + + /** + * Wraps this data provider to create a data provider that supports + * programmatically setting a filter that will be combined with a filter + * provided through the query. + * + * @see #withConfigurableFilter() + * + * @param filterCombiner + * a callback for combining and the configured filter with the + * filter from the query to get a filter to pass to the wrapped + * provider. Will only be called if the query contains a filter. + * + * @return a data provider with a configurable filter, not null + */ + public default ConfigurableFilterDataProvider withConfigurableFilter( + SerializableBiFunction filterCombiner) { + return new ConfigurableFilterDataProviderWrapper(this) { + @Override + protected F combineFilters(F configuredFilter, C queryFilter) { + return filterCombiner.apply(configuredFilter, queryFilter); + } + }; + } + + /** + * Wraps this data provider to create a data provider that supports + * programmatically setting a filter but no filtering through the query. + * + * @see #withConfigurableFilter(SerializableBiFunction) + * + * @return a data provider with a configurable filter, not null + */ + public default ConfigurableFilterDataProvider withConfigurableFilter() { + return withConfigurableFilter((configuredFilter, queryFilter) -> { + assert queryFilter == null : "Filter from Void query must be null"; + + return configuredFilter; + }); + } } diff --git a/server/src/main/java/com/vaadin/data/provider/ConfigurableFilterDataProvider.java b/server/src/main/java/com/vaadin/data/provider/ConfigurableFilterDataProvider.java new file mode 100644 index 0000000000..8521e5498a --- /dev/null +++ b/server/src/main/java/com/vaadin/data/provider/ConfigurableFilterDataProvider.java @@ -0,0 +1,43 @@ +/* + * Copyright 2000-2016 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.data.provider; + +/** + * A data provider that supports programmatically setting a filter that will be + * applied to all queries. + * + * @author Vaadin Ltd + * + * @param + * the data provider item type + * @param + * the query filter type + * @param + * the configurable filter type + */ +public interface ConfigurableFilterDataProvider + extends DataProvider { + + /** + * Sets the filter to use for all queries handled by this data provider. + * + * @param filter + * the filter to set, or null to clear any + * previously set filter + */ + public void setFilter(C filter); + +} diff --git a/server/src/main/java/com/vaadin/data/provider/ConfigurableFilterDataProviderWrapper.java b/server/src/main/java/com/vaadin/data/provider/ConfigurableFilterDataProviderWrapper.java new file mode 100644 index 0000000000..c6256787ce --- /dev/null +++ b/server/src/main/java/com/vaadin/data/provider/ConfigurableFilterDataProviderWrapper.java @@ -0,0 +1,79 @@ +/* + * Copyright 2000-2016 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.data.provider; + +/** + * A configurable data provider that wraps another data provider by combining + * any filter from the component with the configured filter and passing that to + * the wrapped provider through the query. + * + * @author Vaadin Ltd + * + * @param + * the data provider item type + * @param + * the query filter type + * @param + * the configurable filter type + */ +public abstract class ConfigurableFilterDataProviderWrapper + extends DataProviderWrapper + implements ConfigurableFilterDataProvider { + + private C configuredFilter; + + /** + * Creates a new configurable filter data provider by wrapping an existing + * data provider. + * + * @param dataProvider + * the data provider to wrap, not null + */ + public ConfigurableFilterDataProviderWrapper( + DataProvider dataProvider) { + super(dataProvider); + } + + @Override + protected C getFilter(Query query) { + return query.getFilter().map( + queryFilter -> combineFilters(configuredFilter, queryFilter)) + .orElse(configuredFilter); + } + + /** + * Combines the configured filter and the filter from the query into one + * filter instance that can be passed to the wrapped data provider. This + * method is called only if there is a query filter, otherwise the + * configured filter will be directly passed to the query. + * + * @param configuredFilter + * the filter that this data provider is configured to use, or + * null if no filter has been configured + * @param queryFilter + * the filter received through the query, not null + * @return a filter that combines the two provided queries, or + * null to not pass any filter to the wrapped data + * provider + */ + protected abstract C combineFilters(C configuredFilter, Q queryFilter); + + @Override + public void setFilter(C filter) { + this.configuredFilter = filter; + refreshAll(); + } +} diff --git a/server/src/main/java/com/vaadin/data/provider/DataProviderWrapper.java b/server/src/main/java/com/vaadin/data/provider/DataProviderWrapper.java index f792c8c255..b6f835ac42 100644 --- a/server/src/main/java/com/vaadin/data/provider/DataProviderWrapper.java +++ b/server/src/main/java/com/vaadin/data/provider/DataProviderWrapper.java @@ -15,6 +15,7 @@ */ package com.vaadin.data.provider; +import java.util.Objects; import java.util.stream.Stream; import com.vaadin.server.SerializableFunction; @@ -78,10 +79,11 @@ public abstract class DataProviderWrapper * Constructs a filtering wrapper for a data provider. * * @param dataProvider - * the wrapped data provider + * the wrapped data provider, not null */ protected DataProviderWrapper(DataProvider dataProvider) { - this.dataProvider = dataProvider; + this.dataProvider = Objects.requireNonNull(dataProvider, + "The wrapped data provider cannot be null."); } @Override diff --git a/server/src/main/java/com/vaadin/data/provider/ListDataProvider.java b/server/src/main/java/com/vaadin/data/provider/ListDataProvider.java index 3d021dc0e6..470339bc2a 100644 --- a/server/src/main/java/com/vaadin/data/provider/ListDataProvider.java +++ b/server/src/main/java/com/vaadin/data/provider/ListDataProvider.java @@ -35,7 +35,8 @@ import com.vaadin.shared.data.sort.SortDirection; */ public class ListDataProvider extends AbstractDataProvider> - implements AppendableFilterDataProvider> { + implements AppendableFilterDataProvider>, + ConfigurableFilterDataProvider, SerializablePredicate> { private SerializableComparator sortOrder = null; @@ -236,6 +237,7 @@ public class ListDataProvider * the filter to set, or null to remove any set * filters */ + @Override public void setFilter(SerializablePredicate filter) { this.filter = filter; refreshAll(); diff --git a/server/src/main/java/com/vaadin/server/SerializableBiFunction.java b/server/src/main/java/com/vaadin/server/SerializableBiFunction.java new file mode 100644 index 0000000000..04d082c283 --- /dev/null +++ b/server/src/main/java/com/vaadin/server/SerializableBiFunction.java @@ -0,0 +1,39 @@ +/* + * Copyright 2000-2016 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; + +import java.io.Serializable; +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * A {@link BiFunction} that is also {@link Serializable}. + * + * @see Function + * @author Vaadin Ltd + * @since 8.0 + * @param + * the type of the the first function parameter + * @param + * the type of the the second function parameter + * @param + * the type of the result of the function + */ +@FunctionalInterface +public interface SerializableBiFunction + extends BiFunction, Serializable { + // Only method inherited from BiFunction +} diff --git a/server/src/test/java/com/vaadin/data/provider/BackendDataProviderTest.java b/server/src/test/java/com/vaadin/data/provider/BackendDataProviderTest.java index 797f14957c..e2f5211d00 100644 --- a/server/src/test/java/com/vaadin/data/provider/BackendDataProviderTest.java +++ b/server/src/test/java/com/vaadin/data/provider/BackendDataProviderTest.java @@ -23,7 +23,7 @@ public class BackendDataProviderTest extends Comparator.comparing(StrBean::getRandomNumber)); } - private Comparator getComparator(SortOrder so) { + private static Comparator getComparator(SortOrder so) { Comparator comparator = propertyToComparatorMap .get(so.getSorted()); if (so.getDirection() == SortDirection.DESCENDING) { @@ -32,24 +32,32 @@ public class BackendDataProviderTest extends return comparator; } + public static class StrBeanBackEndDataProvider extends + CallbackDataProvider> { + + public StrBeanBackEndDataProvider(List data) { + super(query -> { + Stream stream = data.stream().filter( + t -> query.getFilter().orElse(s -> true).test(t)); + if (!query.getSortOrders().isEmpty()) { + Comparator sorting = query.getSortOrders().stream() + .map(BackendDataProviderTest::getComparator) + .reduce((c1, c2) -> c1.thenComparing(c2)).get(); + stream = stream.sorted(sorting); + } + List list = stream.skip(query.getOffset()) + .limit(query.getLimit()).collect(Collectors.toList()); + list.forEach(s -> System.err.println(s.toString())); + return list.stream(); + }, query -> (int) data.stream() + .filter(t -> query.getFilter().orElse(s -> true).test(t)) + .count()); + } + } + @Override protected BackEndDataProvider> createDataProvider() { - return dataProvider = new CallbackDataProvider<>(query -> { - Stream stream = data.stream() - .filter(t -> query.getFilter().orElse(s -> true).test(t)); - if (!query.getSortOrders().isEmpty()) { - Comparator sorting = query.getSortOrders().stream() - .map(this::getComparator) - .reduce((c1, c2) -> c1.thenComparing(c2)).get(); - stream = stream.sorted(sorting); - } - List list = stream.skip(query.getOffset()) - .limit(query.getLimit()).collect(Collectors.toList()); - list.forEach(s -> System.err.println(s.toString())); - return list.stream(); - }, query -> (int) data.stream() - .filter(t -> query.getFilter().orElse(s -> true).test(t)) - .count()); + return dataProvider = new StrBeanBackEndDataProvider(data); } @Override diff --git a/server/src/test/java/com/vaadin/data/provider/ConfigurableFilterDataProviderWrapperTest.java b/server/src/test/java/com/vaadin/data/provider/ConfigurableFilterDataProviderWrapperTest.java new file mode 100644 index 0000000000..115e4fd6bd --- /dev/null +++ b/server/src/test/java/com/vaadin/data/provider/ConfigurableFilterDataProviderWrapperTest.java @@ -0,0 +1,96 @@ +/* + * Copyright 2000-2016 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.data.provider; + +import org.junit.Assert; +import org.junit.Test; + +import com.vaadin.data.provider.BackendDataProviderTest.StrBeanBackEndDataProvider; +import com.vaadin.server.SerializablePredicate; + +public class ConfigurableFilterDataProviderWrapperTest { + private static SerializablePredicate xyzFilter = item -> item + .getValue().equals("Xyz"); + + private StrBeanBackEndDataProvider backEndProvider = new StrBeanBackEndDataProvider( + StrBean.generateRandomBeans(100)); + private ConfigurableFilterDataProvider> configurableVoid = backEndProvider + .withConfigurableFilter(); + private ConfigurableFilterDataProvider, SerializablePredicate> configurablePredicate = backEndProvider + .withConfigurableFilter((configuredFilter, queryFilter) -> item -> { + if (configuredFilter != null && !configuredFilter.test(item)) { + return false; + } + + return queryFilter.test(item); + }); + + @Test + public void void_setFilter() { + configurableVoid.setFilter(xyzFilter); + + Assert.assertEquals("Set filter should be used", 1, + configurableVoid.size(new Query<>())); + + configurableVoid.setFilter(null); + + Assert.assertEquals("null filter should return all items", 100, + configurableVoid.size(new Query<>())); + } + + @Test(expected = Exception.class) + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void void_nonNullQueryFilter_throws() { + configurableVoid + .size((Query) new Query("invalid filter")); + } + + @Test + public void predicate_setFilter() { + configurablePredicate.setFilter(xyzFilter); + + Assert.assertEquals("Set filter should be used", 1, + configurablePredicate.size(new Query<>())); + + configurablePredicate.setFilter(null); + + Assert.assertEquals("null filter should return all items", 100, + configurablePredicate.size(new Query<>())); + } + + @Test + public void predicate_queryFilter() { + Assert.assertEquals("Query filter should be used", 1, + configurablePredicate.size(new Query<>(xyzFilter))); + + Assert.assertEquals("null query filter should return all items", 100, + configurablePredicate.size(new Query<>())); + } + + @Test + public void predicate_combinedFilters() { + configurablePredicate.setFilter(item -> item.getValue().equals("Foo")); + + Assert.assertEquals("Both filters should be used", 0, + configurablePredicate.size(new Query<>(xyzFilter))); + + configurablePredicate.setFilter(null); + + Assert.assertEquals("Only zyz filter should be used", 1, + configurablePredicate.size(new Query<>(xyzFilter))); + } + +} -- 2.39.5