diff options
author | Teemu Suo-Anttila <teemusa@vaadin.com> | 2016-02-04 15:30:31 +0200 |
---|---|---|
committer | Teemu Suo-Anttila <teemusa@vaadin.com> | 2016-02-04 15:30:31 +0200 |
commit | ed3fd8a20855d4bdbaa3f0e54534c93e649a8e50 (patch) | |
tree | 3f36cc325a3304a884dab22678d366438ab27d9c | |
parent | 607e17afe508e6da32086e513c863b192d4e2baa (diff) | |
download | vaadin-framework-ed3fd8a20855d4bdbaa3f0e54534c93e649a8e50.tar.gz vaadin-framework-ed3fd8a20855d4bdbaa3f0e54534c93e649a8e50.zip |
Introduce DataSource API and DataChangeHandler for it
Change-Id: I3b24bca46ffc136884e163c94f3f4c304c1e12b2
6 files changed, 304 insertions, 42 deletions
diff --git a/server/src/com/vaadin/server/communication/data/typed/AbstractDataSource.java b/server/src/com/vaadin/server/communication/data/typed/AbstractDataSource.java new file mode 100644 index 0000000000..bf16b47aa5 --- /dev/null +++ b/server/src/com/vaadin/server/communication/data/typed/AbstractDataSource.java @@ -0,0 +1,64 @@ +/* + * 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.communication.data.typed; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Base class for AbstractDataSource. Provides tracking for + * {@link DataChangeHandler}s and helper methods to call them. + * + * @since + */ +public abstract class AbstractDataSource<T> implements DataSource<T> { + + protected Set<DataChangeHandler<T>> handlers = new LinkedHashSet<DataChangeHandler<T>>(); + + @Override + public void addDataChangeHandler(DataChangeHandler<T> handler) { + handlers.add(handler); + } + + @Override + public void removeDataChangeHandler(DataChangeHandler<T> handler) { + handlers.remove(handler); + } + + protected void fireDataChange() { + for (DataChangeHandler<T> handler : handlers) { + handler.onDataChange(); + } + } + + protected void fireDataAdd(T data) { + for (DataChangeHandler<T> handler : handlers) { + handler.onDataAdd(data); + } + } + + protected void fireDataRemove(T data) { + for (DataChangeHandler<T> handler : handlers) { + handler.onDataRemove(data); + } + } + + protected void fireDataUpdate(T data) { + for (DataChangeHandler<T> handler : handlers) { + handler.onDataUpdate(data); + } + } +} diff --git a/server/src/com/vaadin/server/communication/data/typed/CollectionDataSource.java b/server/src/com/vaadin/server/communication/data/typed/CollectionDataSource.java new file mode 100644 index 0000000000..3a29befccf --- /dev/null +++ b/server/src/com/vaadin/server/communication/data/typed/CollectionDataSource.java @@ -0,0 +1,69 @@ +/* + * 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.communication.data.typed; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +/** + * DataSource that stores its data in a List. This DataSource does not support + * paging. + * + * @since + */ +public class CollectionDataSource<T> extends AbstractDataSource<T> { + + protected List<T> backend; + + public CollectionDataSource() { + backend = new ArrayList<T>(); + } + + public CollectionDataSource(Collection<T> data) { + this(); + backend.addAll(data); + } + + @Override + public void save(T data) { + if (backend.contains(data)) { + fireDataUpdate(data); + } else { + backend.add(data); + fireDataAdd(data); + } + } + + @Override + public void remove(T data) { + if (backend.contains(data)) { + backend.remove(data); + fireDataRemove(data); + } + } + + @Override + public Iterator<T> iterator() { + return backend.iterator(); + } + + @Override + public long size() { + return backend.size(); + } +} diff --git a/server/src/com/vaadin/server/communication/data/typed/DataProvider.java b/server/src/com/vaadin/server/communication/data/typed/DataProvider.java index b47af1c033..d2fa859aba 100644 --- a/server/src/com/vaadin/server/communication/data/typed/DataProvider.java +++ b/server/src/com/vaadin/server/communication/data/typed/DataProvider.java @@ -59,7 +59,7 @@ public abstract class DataProvider<T> extends AbstractExtension { * component to extend with the data provider * @return created data provider */ - public static <V> SimpleDataProvider<V> create(Collection<V> data, + public static <V> SimpleDataProvider<V> create(DataSource<V> data, AbstractComponent component) { SimpleDataProvider<V> dataProvider = new SimpleDataProvider<V>(data); dataProvider.extend(component); @@ -98,7 +98,7 @@ public abstract class DataProvider<T> extends AbstractExtension { * @param dataObjects * collection of new active data objects */ - public void addActiveData(Collection<T> dataObjects) { + public void addActiveData(Iterable<T> dataObjects) { for (T data : dataObjects) { if (!activeData.contains(getKeyMapper().key(data))) { activeData.add(getKeyMapper().key(data)); @@ -119,7 +119,7 @@ public abstract class DataProvider<T> extends AbstractExtension { * @param dataObjects * collection of most recently sent data to the client */ - public void cleanUp(Collection<T> dataObjects) { + public void cleanUp(Iterable<T> dataObjects) { Collection<String> keys = new HashSet<String>(); for (T data : dataObjects) { keys.add(getKeyMapper().key(data)); @@ -213,7 +213,7 @@ public abstract class DataProvider<T> extends AbstractExtension { * @param data * data objects to send as an iterable */ - protected void pushData(long firstIndex, Collection<T> data) { + protected void pushData(long firstIndex, Iterable<T> data) { JsonArray dataArray = Json.createArray(); int i = 0; diff --git a/server/src/com/vaadin/server/communication/data/typed/DataSource.java b/server/src/com/vaadin/server/communication/data/typed/DataSource.java new file mode 100644 index 0000000000..d39286a028 --- /dev/null +++ b/server/src/com/vaadin/server/communication/data/typed/DataSource.java @@ -0,0 +1,117 @@ +/* + * 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.communication.data.typed; + +import java.io.Serializable; + +/** + * Minimal DataSource API for communication between the DataProvider and a back + * end service. + * + * @since + * @param <T> + * data type + */ +public interface DataSource<T> extends Iterable<T>, Serializable { + + /** + * Gets the data object count from the back end. + * + * @return back end size + */ + long size(); + + /** + * Saves a data object to the back end. If it's a new object, it should be + * created in the back end. Existing objects with changes should be stored. + * + * @param data + * data object to save + */ + void save(T data); + + /** + * Removes the given data object from the back end. + * + * @param data + * data object to remove + */ + void remove(T data); + + /** + * Adds a new DataChangeHandler to this DataSource. DataChangeHandler is + * called when changes occur in DataSource. + * + * @param handler + * data change handler + */ + void addDataChangeHandler(DataChangeHandler<T> handler); + + /** + * Removed a DataChangeHandler from this DataSource. + * + * @param handler + * data change handler + */ + void removeDataChangeHandler(DataChangeHandler<T> handler); + + /** + * Interface for DataSources to inform of various changes in the back end to + * anyone interested. + * + * @param <T> + * data type + */ + interface DataChangeHandler<T> extends Serializable { + + /** + * This method is called when a generic change in the DataSource. All + * cached data should be considered invalid. + * <p> + * <strong>Note: </strong> This method usually does an expensive full + * refresh of everything. Even though it makes everything up to date, + * you should only use this when really needed. + */ + void onDataChange(); + + /** + * This method is called when a data object has been added as the last + * object in the back end. + * + * @param data + * new data object + */ + void onDataAdd(T data); + + /** + * This method is called when a data object has been removed from the + * back end. + * + * @param data + * removed data object + */ + void onDataRemove(T data); + + /** + * This method is called when a data object has been updated in the back + * end. + * + * @param data + * updated data object + */ + void onDataUpdate(T data); + } +} diff --git a/server/src/com/vaadin/server/communication/data/typed/SimpleDataProvider.java b/server/src/com/vaadin/server/communication/data/typed/SimpleDataProvider.java index ebf3734f51..9fba28e59a 100644 --- a/server/src/com/vaadin/server/communication/data/typed/SimpleDataProvider.java +++ b/server/src/com/vaadin/server/communication/data/typed/SimpleDataProvider.java @@ -15,10 +15,10 @@ */ package com.vaadin.server.communication.data.typed; -import java.util.Collection; import java.util.HashSet; import java.util.Set; +import com.vaadin.server.communication.data.typed.DataSource.DataChangeHandler; import com.vaadin.shared.data.DataProviderClientRpc; import com.vaadin.shared.data.DataRequestRpc; @@ -64,7 +64,7 @@ public class SimpleDataProvider<T> extends DataProvider<T> { private boolean reset = false; private final Set<T> updatedData = new HashSet<T>(); - private Collection<T> data; + private DataSource<T> data; // TODO: Allow customizing the used key mapper private DataKeyMapper<T> keyMapper = new KeyMapper<T>(); @@ -74,8 +74,30 @@ public class SimpleDataProvider<T> extends DataProvider<T> { * @param data * collection of data to use */ - protected SimpleDataProvider(Collection<T> data) { + protected SimpleDataProvider(DataSource<T> data) { this.data = data; + this.data.addDataChangeHandler(new DataChangeHandler<T>() { + + @Override + public void onDataChange() { + reset(); + } + + @Override + public void onDataAdd(T data) { + add(data); + } + + @Override + public void onDataRemove(T data) { + remove(data); + } + + @Override + public void onDataUpdate(T data) { + refresh(data); + } + }); } /** @@ -114,7 +136,7 @@ public class SimpleDataProvider<T> extends DataProvider<T> { * @param data * data object added to collection */ - public void add(T data) { + protected void add(T data) { rpc.add(getDataObject(data)); } @@ -124,7 +146,7 @@ public class SimpleDataProvider<T> extends DataProvider<T> { * @param data * data object removed from collection */ - public void remove(T data) { + protected void remove(T data) { if (handler.getActiveData().contains(data)) { rpc.drop(getKeyMapper().key(data)); } @@ -133,7 +155,7 @@ public class SimpleDataProvider<T> extends DataProvider<T> { /** * Informs the DataProvider that the collection has changed. */ - public void reset() { + protected void reset() { if (reset) { return; } @@ -148,7 +170,7 @@ public class SimpleDataProvider<T> extends DataProvider<T> { * @param data * updated data object */ - public void refresh(T data) { + protected void refresh(T data) { if (updatedData.isEmpty()) { markAsDirty(); } diff --git a/uitest/src/com/vaadin/tests/dataprovider/DummyDataProviderUI.java b/uitest/src/com/vaadin/tests/dataprovider/DummyDataProviderUI.java index c5e2dfedb2..0cd45f1994 100644 --- a/uitest/src/com/vaadin/tests/dataprovider/DummyDataProviderUI.java +++ b/uitest/src/com/vaadin/tests/dataprovider/DummyDataProviderUI.java @@ -24,7 +24,9 @@ import java.util.Random; import com.vaadin.annotations.Widgetset; import com.vaadin.server.VaadinRequest; +import com.vaadin.server.communication.data.typed.CollectionDataSource; import com.vaadin.server.communication.data.typed.DataProvider; +import com.vaadin.server.communication.data.typed.DataSource; import com.vaadin.server.communication.data.typed.SimpleDataProvider; import com.vaadin.server.communication.data.typed.TypedDataGenerator; import com.vaadin.tests.components.AbstractTestUI; @@ -44,14 +46,8 @@ public class DummyDataProviderUI extends AbstractTestUI { public static class DummyDataComponent extends AbstractComponent { private SimpleDataProvider<ComplexPerson> dataProvider; - private List<ComplexPerson> data; - public DummyDataComponent(Collection<ComplexPerson> data) { - if (data instanceof List) { - this.data = (List<ComplexPerson>) data; - } else { - this.data = new ArrayList<ComplexPerson>(data); - } + public DummyDataComponent(DataSource<ComplexPerson> data) { dataProvider = DataProvider.create(data, this); dataProvider .addDataGenerator(new TypedDataGenerator<ComplexPerson>() { @@ -69,29 +65,22 @@ public class DummyDataProviderUI extends AbstractTestUI { } }); } + } - void addItem(ComplexPerson person) { - // TODO: This should be in the back end implementation - if (data.add(person)) { - dataProvider.add(person); - } - } + private static class MyDataSource extends + CollectionDataSource<ComplexPerson> { - void removeItem(ComplexPerson person) { - // TODO: This should be in the back end implementation - if (data.remove(person)) { - dataProvider.remove(person); - } + public MyDataSource(Collection<ComplexPerson> data) { + super(data); } - public void sort(Comparator<ComplexPerson> comparator) { - // TODO: This should be in the back end implementation - Collections.sort(data, comparator); - dataProvider.reset(); + public void sort(Comparator<ComplexPerson> c) { + Collections.sort(backend, c); + fireDataChange(); } - public void update(ComplexPerson p) { - dataProvider.refresh(p); + public List<ComplexPerson> getData() { + return Collections.unmodifiableList(backend); } } @@ -108,41 +97,42 @@ public class DummyDataProviderUI extends AbstractTestUI { }; private Random r = new Random(RANDOM_SEED); - private List<ComplexPerson> persons = createPersons(PERSON_COUNT, r); + private MyDataSource dataSource; private DummyDataComponent dummy; @Override protected void setup(VaadinRequest request) { - dummy = new DummyDataComponent(persons); + dataSource = new MyDataSource(createPersons(PERSON_COUNT, r)); + dummy = new DummyDataComponent(dataSource); Button remove = new Button("Remove third", new ClickListener() { @Override public void buttonClick(ClickEvent event) { - dummy.removeItem(persons.get(2)); + dataSource.remove(dataSource.getData().get(2)); } }); Button add = new Button("Add new", new ClickListener() { @Override public void buttonClick(ClickEvent event) { - dummy.addItem(ComplexPerson.create(r)); + dataSource.save(ComplexPerson.create(r)); } }); Button sort = new Button("Sort content", new ClickListener() { @Override public void buttonClick(ClickEvent event) { - dummy.sort(nameComparator); + dataSource.sort(nameComparator); } }); Button edit = new Button("Edit first", new ClickListener() { @Override public void buttonClick(ClickEvent event) { - ComplexPerson p = persons.get(0); + ComplexPerson p = dataSource.iterator().next(); p.setFirstName("Foo"); - dummy.update(p); + dataSource.save(p); } }); |