From ed3fd8a20855d4bdbaa3f0e54534c93e649a8e50 Mon Sep 17 00:00:00 2001 From: Teemu Suo-Anttila Date: Thu, 4 Feb 2016 15:30:31 +0200 Subject: Introduce DataSource API and DataChangeHandler for it Change-Id: I3b24bca46ffc136884e163c94f3f4c304c1e12b2 --- .../data/typed/AbstractDataSource.java | 64 +++++++++++ .../data/typed/CollectionDataSource.java | 69 ++++++++++++ .../communication/data/typed/DataProvider.java | 8 +- .../communication/data/typed/DataSource.java | 117 +++++++++++++++++++++ .../data/typed/SimpleDataProvider.java | 36 +++++-- .../tests/dataprovider/DummyDataProviderUI.java | 52 ++++----- 6 files changed, 304 insertions(+), 42 deletions(-) create mode 100644 server/src/com/vaadin/server/communication/data/typed/AbstractDataSource.java create mode 100644 server/src/com/vaadin/server/communication/data/typed/CollectionDataSource.java create mode 100644 server/src/com/vaadin/server/communication/data/typed/DataSource.java 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 implements DataSource { + + protected Set> handlers = new LinkedHashSet>(); + + @Override + public void addDataChangeHandler(DataChangeHandler handler) { + handlers.add(handler); + } + + @Override + public void removeDataChangeHandler(DataChangeHandler handler) { + handlers.remove(handler); + } + + protected void fireDataChange() { + for (DataChangeHandler handler : handlers) { + handler.onDataChange(); + } + } + + protected void fireDataAdd(T data) { + for (DataChangeHandler handler : handlers) { + handler.onDataAdd(data); + } + } + + protected void fireDataRemove(T data) { + for (DataChangeHandler handler : handlers) { + handler.onDataRemove(data); + } + } + + protected void fireDataUpdate(T data) { + for (DataChangeHandler 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 extends AbstractDataSource { + + protected List backend; + + public CollectionDataSource() { + backend = new ArrayList(); + } + + public CollectionDataSource(Collection 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 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 extends AbstractExtension { * component to extend with the data provider * @return created data provider */ - public static SimpleDataProvider create(Collection data, + public static SimpleDataProvider create(DataSource data, AbstractComponent component) { SimpleDataProvider dataProvider = new SimpleDataProvider(data); dataProvider.extend(component); @@ -98,7 +98,7 @@ public abstract class DataProvider extends AbstractExtension { * @param dataObjects * collection of new active data objects */ - public void addActiveData(Collection dataObjects) { + public void addActiveData(Iterable dataObjects) { for (T data : dataObjects) { if (!activeData.contains(getKeyMapper().key(data))) { activeData.add(getKeyMapper().key(data)); @@ -119,7 +119,7 @@ public abstract class DataProvider extends AbstractExtension { * @param dataObjects * collection of most recently sent data to the client */ - public void cleanUp(Collection dataObjects) { + public void cleanUp(Iterable dataObjects) { Collection keys = new HashSet(); for (T data : dataObjects) { keys.add(getKeyMapper().key(data)); @@ -213,7 +213,7 @@ public abstract class DataProvider extends AbstractExtension { * @param data * data objects to send as an iterable */ - protected void pushData(long firstIndex, Collection data) { + protected void pushData(long firstIndex, Iterable 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 + * data type + */ +public interface DataSource extends Iterable, 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 handler); + + /** + * Removed a DataChangeHandler from this DataSource. + * + * @param handler + * data change handler + */ + void removeDataChangeHandler(DataChangeHandler handler); + + /** + * Interface for DataSources to inform of various changes in the back end to + * anyone interested. + * + * @param + * data type + */ + interface DataChangeHandler extends Serializable { + + /** + * This method is called when a generic change in the DataSource. All + * cached data should be considered invalid. + *

+ * Note: 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 extends DataProvider { private boolean reset = false; private final Set updatedData = new HashSet(); - private Collection data; + private DataSource data; // TODO: Allow customizing the used key mapper private DataKeyMapper keyMapper = new KeyMapper(); @@ -74,8 +74,30 @@ public class SimpleDataProvider extends DataProvider { * @param data * collection of data to use */ - protected SimpleDataProvider(Collection data) { + protected SimpleDataProvider(DataSource data) { this.data = data; + this.data.addDataChangeHandler(new DataChangeHandler() { + + @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 extends DataProvider { * @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 extends DataProvider { * @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 extends DataProvider { /** * Informs the DataProvider that the collection has changed. */ - public void reset() { + protected void reset() { if (reset) { return; } @@ -148,7 +170,7 @@ public class SimpleDataProvider extends DataProvider { * @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 dataProvider; - private List data; - public DummyDataComponent(Collection data) { - if (data instanceof List) { - this.data = (List) data; - } else { - this.data = new ArrayList(data); - } + public DummyDataComponent(DataSource data) { dataProvider = DataProvider.create(data, this); dataProvider .addDataGenerator(new TypedDataGenerator() { @@ -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 { - void removeItem(ComplexPerson person) { - // TODO: This should be in the back end implementation - if (data.remove(person)) { - dataProvider.remove(person); - } + public MyDataSource(Collection data) { + super(data); } - public void sort(Comparator comparator) { - // TODO: This should be in the back end implementation - Collections.sort(data, comparator); - dataProvider.reset(); + public void sort(Comparator c) { + Collections.sort(backend, c); + fireDataChange(); } - public void update(ComplexPerson p) { - dataProvider.refresh(p); + public List getData() { + return Collections.unmodifiableList(backend); } } @@ -108,41 +97,42 @@ public class DummyDataProviderUI extends AbstractTestUI { }; private Random r = new Random(RANDOM_SEED); - private List 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); } }); -- cgit v1.2.3