summaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorTeemu Suo-Anttila <teemusa@vaadin.com>2016-11-22 13:07:32 +0200
committerVaadin Code Review <review@vaadin.com>2016-11-22 14:49:44 +0000
commit97062bad914ea27934f3928b07f74f60cb26fd8c (patch)
tree0e6497a45210476e1d1dc74c92f1f7e0c76e34d2 /server
parent5708366ebf49e1c5c800b4684a735d1cd1868772 (diff)
downloadvaadin-framework-97062bad914ea27934f3928b07f74f60cb26fd8c.tar.gz
vaadin-framework-97062bad914ea27934f3928b07f74f60cb26fd8c.zip
Implement chaining of filters for data providers
Change-Id: I7b7dced73e19b9b4e4358b95878eb31fe5c87346
Diffstat (limited to 'server')
-rw-r--r--server/src/main/java/com/vaadin/server/data/AppendableFilterDataProvider.java75
-rw-r--r--server/src/main/java/com/vaadin/server/data/DataProvider.java13
-rw-r--r--server/src/main/java/com/vaadin/server/data/FilteringDataProviderWrapper.java163
-rw-r--r--server/src/main/java/com/vaadin/server/data/ListDataProvider.java9
-rw-r--r--server/src/test/java/com/vaadin/server/data/provider/ListDataProviderTest.java74
5 files changed, 292 insertions, 42 deletions
diff --git a/server/src/main/java/com/vaadin/server/data/AppendableFilterDataProvider.java b/server/src/main/java/com/vaadin/server/data/AppendableFilterDataProvider.java
new file mode 100644
index 0000000000..b6c57f03da
--- /dev/null
+++ b/server/src/main/java/com/vaadin/server/data/AppendableFilterDataProvider.java
@@ -0,0 +1,75 @@
+/*
+ * 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.data;
+
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * Interface for DataProviders that support chaining filters.
+ *
+ * @author Vaadin Ltd
+ * @since
+ *
+ * @param <T>
+ * the data provider data type
+ * @param <F>
+ * the data provider filter type
+ */
+public interface AppendableFilterDataProvider<T, F> extends DataProvider<T, F> {
+
+ /**
+ * Applies a filter to the current chain of filters in this data provider.
+ *
+ * @param filter
+ * the applied filter; not {@code null}
+ * @return new data provider with the filter applied
+ */
+ public default AppendableFilterDataProvider<T, F> applyFilter(F filter) {
+ Objects.requireNonNull(filter, "The applied filter can't be null");
+ return FilteringDataProviderWrapper.chain(this, filter);
+ }
+
+ /**
+ * Combines two filters into one. Default implementation returns the base
+ * filter if the optional is not present.
+ * <p>
+ * Actual implementation of the combination is done in the method
+ * {@link #combineFilters(Object, Object)}.
+ *
+ * @param filter1
+ * the base filter; not {@code null}
+ * @param filter2
+ * an optional filter
+ * @return combined filter; not {@code null}
+ */
+ public default F combineFilters(F filter1, Optional<F> filter2) {
+ Objects.requireNonNull(filter1, "The base filter can't be null");
+ return filter2.map(f -> combineFilters(filter1, f)).orElse(filter1);
+ }
+
+ /**
+ * Combines two filters into one.
+ *
+ * @param filter1
+ * the base filter; not {@code null}
+ * @param filter2
+ * the filter to merge to the base filter; not {@code null}
+ * @return combined filter; not {@code null}
+ */
+ public F combineFilters(F filter1, F filter2);
+
+}
diff --git a/server/src/main/java/com/vaadin/server/data/DataProvider.java b/server/src/main/java/com/vaadin/server/data/DataProvider.java
index 129fe08713..0975a02c94 100644
--- a/server/src/main/java/com/vaadin/server/data/DataProvider.java
+++ b/server/src/main/java/com/vaadin/server/data/DataProvider.java
@@ -18,6 +18,7 @@ package com.vaadin.server.data;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Objects;
import java.util.stream.Stream;
import com.vaadin.server.SerializableFunction;
@@ -99,7 +100,8 @@ public interface DataProvider<T, F> extends Serializable {
* @return wrapped data provider with provided filter
*/
public default DataProvider<T, Void> setFilter(F filter) {
- return new FilteringDataProviderWrapper<>(this, filter);
+ Objects.requireNonNull(filter, "Filter can't be null");
+ return FilteringDataProviderWrapper.filter(this, filter);
}
/**
@@ -114,8 +116,8 @@ public interface DataProvider<T, F> extends Serializable {
* DataProvider&lt;Person, Predicate&lt;Person&gt;&gt; dataProvider;
* // ComboBox uses String as the filter type
* DataProvider&lt;Person, String&gt; wrappedProvider = dataProvider
- * .convertFilter(filterText -> {
- * Predicate&lt;Person&gt; predicate = person -> person.getName()
+ * .convertFilter(filterText -&gt; {
+ * Predicate&lt;Person&gt; predicate = person -&gt; person.getName()
* .startsWith(filterText);
* return predicate;
* });
@@ -123,7 +125,7 @@ public interface DataProvider<T, F> extends Serializable {
* </pre>
*
* @param mapper
- * the mapper from new filter type to old filter type
+ * the mapper from new filter type to old filter type; not null
*
* @param <M>
* the filter type to map from; typically provided by a Component
@@ -132,7 +134,8 @@ public interface DataProvider<T, F> extends Serializable {
*/
public default <M> DataProvider<T, M> convertFilter(
SerializableFunction<M, F> mapper) {
- return new FilteringDataProviderWrapper<>(this, mapper);
+ Objects.requireNonNull(mapper, "Filter mapper can't be null");
+ return FilteringDataProviderWrapper.convert(this, mapper);
}
/**
diff --git a/server/src/main/java/com/vaadin/server/data/FilteringDataProviderWrapper.java b/server/src/main/java/com/vaadin/server/data/FilteringDataProviderWrapper.java
index a6807cfb26..08a76d4904 100644
--- a/server/src/main/java/com/vaadin/server/data/FilteringDataProviderWrapper.java
+++ b/server/src/main/java/com/vaadin/server/data/FilteringDataProviderWrapper.java
@@ -21,10 +21,12 @@ import com.vaadin.server.SerializableFunction;
import com.vaadin.shared.Registration;
/**
- * Wrapper class for modifying filters in a query. Used to create a suitable
- * {@link Query} for the underlying data provider.
+ * Wrapper class for modifying, chaining and replacing filters in a query. Used
+ * to create a suitable {@link Query} for the underlying data provider with
+ * correct filters.
*
* @author Vaadin Ltd.
+ * @since
*
* @param <T>
* data provider data type
@@ -33,41 +35,53 @@ import com.vaadin.shared.Registration;
* @param <M>
* underlying data provider filter type
*/
-public class FilteringDataProviderWrapper<T, F, M>
+public abstract class FilteringDataProviderWrapper<T, F, M>
implements DataProvider<T, F> {
- private DataProvider<T, M> dataProvider;
- private SerializableFunction<F, M> mapper;
- private M staticFilter = null;
-
/**
- * Constructs a filtering wrapper for a data provider with always applied
- * static filter.
+ * Variant of data provider wrapper that supports chaining filters.
*
- * @param dataProvider
- * the wrapped data provider
- * @param filter
- * the static filter
+ * @param <T>
+ * the data provider data type
+ * @param <F>
+ * the data provider filter type
*/
- public FilteringDataProviderWrapper(DataProvider<T, M> dataProvider,
- M filter) {
- this.dataProvider = dataProvider;
- this.staticFilter = filter;
+ protected abstract static class AppendableFilterDataProviderWrapper<T, F>
+ extends FilteringDataProviderWrapper<T, F, F>
+ implements AppendableFilterDataProvider<T, F> {
+
+ /**
+ * Constructs a filtering wrapper for a data provider with filter
+ * chaining.
+ *
+ * @param dataProvider
+ * the wrapped data provider
+ */
+ protected AppendableFilterDataProviderWrapper(
+ AppendableFilterDataProvider<T, F> dataProvider) {
+ super(dataProvider);
+ }
+
+ @Override
+ public F combineFilters(F filter1, F filter2) {
+ return ((AppendableFilterDataProvider<T, F>) dataProvider)
+ .combineFilters(filter1, filter2);
+ }
}
/**
- * Constructs a filtering wrapper for a data provider with a mapping from
- * one filter type to another.
+ * The actual data provider behind this wrapper.
+ */
+ protected DataProvider<T, M> dataProvider;
+
+ /**
+ * Constructs a filtering wrapper for a data provider.
*
* @param dataProvider
* the wrapped data provider
- * @param mapper
- * the filter mapping function
*/
- public FilteringDataProviderWrapper(DataProvider<T, M> dataProvider,
- SerializableFunction<F, M> mapper) {
+ protected FilteringDataProviderWrapper(DataProvider<T, M> dataProvider) {
this.dataProvider = dataProvider;
- this.mapper = mapper;
}
@Override
@@ -97,10 +111,101 @@ public class FilteringDataProviderWrapper<T, F, M>
t.getSortOrders(), getFilter(t)));
}
- private M getFilter(Query<F> query) {
- if (staticFilter != null) {
- return staticFilter;
- }
- return query.getFilter().map(mapper).orElse(null);
+ /**
+ * Gets the filter that should be used in the modified Query.
+ *
+ * @param query
+ * the current query
+ * @return filter for the modified Query
+ */
+ protected abstract M getFilter(Query<F> query);
+
+ /**
+ * Creates a data provider wrapper with a static filter set to each Query.
+ * This {@code DataProvider} will deliberately ignore any possible filters
+ * from the Query.
+ *
+ * @see DataProvider#setFilter(Object)
+ *
+ * @param dataProvider
+ * the underlying data provider
+ * @param filter
+ * the static filter for each query
+ *
+ * @param <T>
+ * data provider data type
+ * @param <F>
+ * query filter type
+ *
+ * @return wrapped data provider with static filter
+ */
+ public static <T, F> DataProvider<T, Void> filter(
+ DataProvider<T, F> dataProvider, F filter) {
+ return new FilteringDataProviderWrapper<T, Void, F>(dataProvider) {
+
+ @Override
+ protected F getFilter(Query<Void> query) {
+ return filter;
+ }
+ };
+ }
+
+ /**
+ * Creates a data provider wrapper with filter type mapping. The mapper
+ * function will be applied to a query filter if it is present.
+ *
+ * @see DataProvider#convertFilter(SerializableFunction)
+ *
+ * @param dataProvider
+ * the underlying data provider
+ * @param mapper
+ * the function to map from one filter type to another
+ *
+ * @param <T>
+ * data provider data type
+ * @param <F>
+ * wrapper query filter type
+ * @param <M>
+ * underlying data provider filter type
+ *
+ * @return wrapped data provider with filter conversion
+ */
+ public static <T, F, M> DataProvider<T, F> convert(
+ DataProvider<T, M> dataProvider,
+ SerializableFunction<F, M> mapper) {
+ return new FilteringDataProviderWrapper<T, F, M>(dataProvider) {
+
+ @Override
+ protected M getFilter(Query<F> query) {
+ return query.getFilter().map(mapper).orElse(null);
+ }
+ };
+ }
+
+ /**
+ * Creates a data provider wrapper with a chained filter. The filter will be
+ * combined to existing filters using
+ * {@link AppendableFilterDataProvider#combineFilters(Object, java.util.Optional)}.
+ *
+ * @param dataProvider
+ * the underlying data provider
+ * @param filter
+ * the chained filter
+ *
+ * @param <T>
+ * data provider data type
+ * @param <F>
+ * query filter type
+ * @return wrapped data provider with chained filter
+ */
+ public static <T, F> AppendableFilterDataProvider<T, F> chain(
+ AppendableFilterDataProvider<T, F> dataProvider, F filter) {
+ return new AppendableFilterDataProviderWrapper<T, F>(dataProvider) {
+
+ @Override
+ protected F getFilter(Query<F> query) {
+ return combineFilters(filter, query.getFilter());
+ }
+ };
}
}
diff --git a/server/src/main/java/com/vaadin/server/data/ListDataProvider.java b/server/src/main/java/com/vaadin/server/data/ListDataProvider.java
index 0184a2e37b..7d2fa5c1f3 100644
--- a/server/src/main/java/com/vaadin/server/data/ListDataProvider.java
+++ b/server/src/main/java/com/vaadin/server/data/ListDataProvider.java
@@ -31,7 +31,8 @@ import com.vaadin.server.SerializablePredicate;
* data type
*/
public class ListDataProvider<T>
- extends AbstractDataProvider<T, SerializablePredicate<T>> {
+ extends AbstractDataProvider<T, SerializablePredicate<T>>
+ implements AppendableFilterDataProvider<T, SerializablePredicate<T>> {
private Comparator<T> sortOrder;
private final Collection<T> backend;
@@ -121,4 +122,10 @@ public class ListDataProvider<T>
.count();
}
+ @Override
+ public SerializablePredicate<T> combineFilters(
+ SerializablePredicate<T> filter1,
+ SerializablePredicate<T> filter2) {
+ return t -> filter1.test(t) && filter2.test(t);
+ }
}
diff --git a/server/src/test/java/com/vaadin/server/data/provider/ListDataProviderTest.java b/server/src/test/java/com/vaadin/server/data/provider/ListDataProviderTest.java
index 29a234061f..ec0f054da3 100644
--- a/server/src/test/java/com/vaadin/server/data/provider/ListDataProviderTest.java
+++ b/server/src/test/java/com/vaadin/server/data/provider/ListDataProviderTest.java
@@ -20,6 +20,10 @@ public class ListDataProviderTest {
private ListDataProvider<StrBean> dataProvider;
private List<StrBean> data;
+ private SerializablePredicate<StrBean> fooFilter = s -> s.getValue()
+ .equals("Foo");
+ private SerializablePredicate<StrBean> gt5Filter = s -> s
+ .getRandomNumber() > 5;
@Before
public void setUp() {
@@ -214,23 +218,79 @@ public class ListDataProviderTest {
Assert.assertEquals("No item should match 'Zyx'", 0, dataProvider.size(
new Query<>(strBean -> strBean.getValue().contains("Zyx"))));
Assert.assertEquals("Unexpected number of matches for 'Foo'", 36,
- dataProvider.size(new Query<>(
- strBean -> strBean.getValue().contains("Foo"))));
+ dataProvider.size(new Query<>(fooFilter)));
}
@Test
public void filteringListDataProvider_defaultFilter() {
- SerializablePredicate<StrBean> filter = s -> s.getRandomNumber() > 4;
// Intentionally lost filter type. Not actually filterable anymore.
- DataProvider<StrBean, ?> filtered = dataProvider.setFilter(filter);
+ DataProvider<StrBean, ?> filtered = dataProvider.setFilter(gt5Filter);
Assert.assertEquals("Filter not applied, unexpected item count",
- dataProvider.size(new Query<>(filter)),
+ dataProvider.size(new Query<>(gt5Filter)),
filtered.size(new Query<>()));
Assert.assertEquals("Further filtering succeeded",
filtered.size(new Query<>()),
- filtered.size((Query) new Query<SerializablePredicate<StrBean>>(
- s -> s.getValue().equals("Foo"))));
+ filtered.size((Query) new Query<>(fooFilter)));
+ }
+
+ public void filteringListDataProvider_appliedFilters() {
+ Assert.assertEquals("Filtering result differ",
+ data.stream().filter(fooFilter).count(),
+ dataProvider.applyFilter(fooFilter).size(new Query<>()));
+
+ Assert.assertEquals("Chained filtering result differ",
+ data.stream().filter(fooFilter.and(gt5Filter)).count(),
+ dataProvider.applyFilter(fooFilter)
+ .size(new Query<>(gt5Filter)));
+ }
+
+ @Test
+ public void filteringListDataProvider_chainedFilters() {
+ Assert.assertEquals("Chained filtering result differ",
+ data.stream().filter(fooFilter.and(gt5Filter)).count(),
+ dataProvider.applyFilter(fooFilter).applyFilter(gt5Filter)
+ .size(new Query<>()));
+ }
+
+ @Test
+ public void filteringListDataProvider_chainedFiltersWithOrInsteadOfAnd() {
+ ListDataProvider<StrBean> orFilteredDataProvider = new ListDataProvider<StrBean>(
+ data) {
+
+ @Override
+ public SerializablePredicate<StrBean> combineFilters(
+ SerializablePredicate<StrBean> filter1,
+ SerializablePredicate<StrBean> filter2) {
+ return t -> filter1.test(t) || filter2.test(t);
+ }
+ };
+
+ Assert.assertEquals("Chained filtering result differ",
+ data.stream().filter(fooFilter.or(gt5Filter)).count(),
+ orFilteredDataProvider.applyFilter(fooFilter)
+ .applyFilter(gt5Filter).size(new Query<>()));
}
+
+ @Test
+ public void filteringListDataProvider_appliedFilterAndConverter() {
+ Assert.assertEquals("Filtering result differ with 'Foo'",
+ data.stream().filter(gt5Filter.and(fooFilter)).count(),
+ dataProvider.applyFilter(gt5Filter).convertFilter(
+ text -> strBean -> strBean.getValue().equals(text))
+ .size(new Query<>("Foo")));
+
+ Assert.assertEquals("Filtering result differ with 'Xyz'", data.stream()
+ .filter(gt5Filter.and(s -> s.getValue().equals("Xyz"))).count(),
+ dataProvider.applyFilter(gt5Filter).convertFilter(
+ text -> strBean -> strBean.getValue().equals(text))
+ .size(new Query<>("Xyz")));
+
+ Assert.assertEquals("No results should've been found", 0,
+ dataProvider.applyFilter(gt5Filter).convertFilter(
+ text -> strBean -> strBean.getValue().equals(text))
+ .size(new Query<>("Zyx")));
+ }
+
}