aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTeemu Suo-Anttila <teemusa@vaadin.com>2016-06-15 15:14:09 +0300
committerTeemu Suo-Anttila <teemusa@vaadin.com>2016-06-15 17:42:11 +0300
commitfc96cea89e7d5a374603eae14f329a36e92e89b7 (patch)
treea9ee989b04a3541e07882212637443149431d51e
parentfa1ae150f0a0de39e31a424eeb426abca6f46fa6 (diff)
downloadvaadin-framework-fc96cea89e7d5a374603eae14f329a36e92e89b7.tar.gz
vaadin-framework-fc96cea89e7d5a374603eae14f329a36e92e89b7.zip
Add a DataProvider implementation
Change-Id: I8fba190a905a4dac6bfef5693064218672e23ba4
-rw-r--r--client/src/main/java/com/vaadin/client/connectors/data/DataSourceConnector.java192
-rw-r--r--client/src/main/java/com/vaadin/client/connectors/data/HasDataSource.java35
-rw-r--r--server/src/main/java/com/vaadin/server/communication/data/typed/DataKeyMapper.java62
-rw-r--r--server/src/main/java/com/vaadin/server/communication/data/typed/DataProvider.java353
-rw-r--r--server/src/main/java/com/vaadin/server/communication/data/typed/KeyMapper.java29
-rw-r--r--server/src/main/java/com/vaadin/server/communication/data/typed/SimpleDataProvider.java189
-rw-r--r--server/src/main/java/com/vaadin/server/communication/data/typed/TypedDataGenerator.java49
-rw-r--r--shared/src/main/java/com/vaadin/shared/data/typed/DataProviderClientRpc.java75
-rw-r--r--shared/src/main/java/com/vaadin/shared/data/typed/DataProviderConstants.java28
9 files changed, 1012 insertions, 0 deletions
diff --git a/client/src/main/java/com/vaadin/client/connectors/data/DataSourceConnector.java b/client/src/main/java/com/vaadin/client/connectors/data/DataSourceConnector.java
new file mode 100644
index 0000000000..5a559dfafa
--- /dev/null
+++ b/client/src/main/java/com/vaadin/client/connectors/data/DataSourceConnector.java
@@ -0,0 +1,192 @@
+/*
+ * 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.client.connectors.data;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.extensions.AbstractExtensionConnector;
+import com.vaadin.client.widget.grid.datasources.ListDataSource;
+import com.vaadin.server.communication.data.typed.SimpleDataProvider;
+import com.vaadin.shared.data.DataRequestRpc;
+import com.vaadin.shared.data.typed.DataProviderClientRpc;
+import com.vaadin.shared.data.typed.DataProviderConstants;
+import com.vaadin.shared.ui.Connect;
+
+import elemental.json.Json;
+import elemental.json.JsonArray;
+import elemental.json.JsonObject;
+
+/**
+ * A simple connector for DataProvider class. Based on {@link ListDataSource}
+ * and does not support lazy loading or paging.
+ *
+ * @since
+ */
+@Connect(SimpleDataProvider.class)
+public class DataSourceConnector extends AbstractExtensionConnector {
+
+ private Map<String, JsonObject> keyToJson = new HashMap<String, JsonObject>();
+ private Set<String> droppedKeys = new HashSet<String>();
+ private ListDataSource<JsonObject> ds = new ListDataSource<JsonObject>();
+ private boolean pendingDrop = false;
+
+ @Override
+ protected void extend(ServerConnector target) {
+ registerRpc(DataProviderClientRpc.class, new DataProviderClientRpc() {
+
+ @Override
+ public void reset() {
+ ds.asList().clear();
+ // Inform the server-side that all keys are now dropped.
+ Set<String> keySet = new HashSet<String>(keyToJson.keySet());
+ for (String key : keySet) {
+ dropKey(key);
+ }
+ sendDroppedKeys();
+ }
+
+ @Override
+ public void setData(long firstIndex, JsonArray data) {
+ List<JsonObject> l = ds.asList();
+ assert firstIndex <= l.size() : "Gap in data. First Index: "
+ + firstIndex + ", Size: " + l.size();
+ for (long i = 0; i < data.length(); ++i) {
+ JsonObject object = data.getObject((int) i);
+ if (i + firstIndex == l.size()) {
+ l.add(object);
+ } else if (i + firstIndex < l.size()) {
+ int index = (int) (i + firstIndex);
+ dropKey(getKey(l.get(index)));
+ l.set(index, object);
+ }
+ keyToJson.put(getKey(object), object);
+ }
+ sendDroppedKeys();
+ }
+
+ @Override
+ public void add(JsonObject dataObject) {
+ ds.asList().add(dataObject);
+ keyToJson.put(getKey(dataObject), dataObject);
+ }
+
+ @Override
+ public void drop(String key) {
+ if (keyToJson.containsKey(key)) {
+ ds.asList().remove(keyToJson.get(key));
+ dropKey(key);
+ sendDroppedKeys();
+ }
+ }
+
+ @Override
+ public void updateData(JsonArray data) {
+ List<JsonObject> list = ds.asList();
+ for (int i = 0; i < data.length(); ++i) {
+ JsonObject json = data.getObject(i);
+ String key = getKey(json);
+
+ if (keyToJson.containsKey(key)) {
+ int index = list.indexOf(keyToJson.get(key));
+ list.set(index, json);
+ keyToJson.put(key, json);
+ } else {
+ dropKey(key);
+ }
+ }
+ sendDroppedKeys();
+ }
+ });
+
+ ServerConnector parent = getParent();
+ if (parent instanceof HasDataSource) {
+ ((HasDataSource) parent).setDataSource(ds);
+ } else {
+ assert false : "Parent not implementing HasDataSource";
+ }
+ }
+
+ /**
+ * Marks a key as dropped. Call to
+ * {@link DataSourceConnector#sendDroppedKeys()} should be called to make
+ * sure the information is sent to the server-side.
+ *
+ * @param key
+ * dropped key
+ */
+ private void dropKey(String key) {
+ droppedKeys.add(key);
+ if (keyToJson.containsKey(key)) {
+ keyToJson.remove(key);
+ }
+ }
+
+ /**
+ * Sends dropped keys to the server-side with a deferred scheduled command.
+ * Multiple calls to this method will only result to one command being
+ * executed.
+ */
+ private void sendDroppedKeys() {
+ if (pendingDrop) {
+ return;
+ }
+
+ pendingDrop = true;
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+
+ @Override
+ public void execute() {
+ pendingDrop = false;
+ if (droppedKeys.isEmpty()) {
+ return;
+ }
+
+ JsonArray keyArray = Json.createArray();
+ int i = 0;
+ for (String key : droppedKeys) {
+ keyArray.set(i++, key);
+ }
+
+ getRpcProxy(DataRequestRpc.class).dropRows(keyArray);
+
+ // Force RPC since it's delayed.
+ getConnection().getServerRpcQueue().flush();
+
+ droppedKeys.clear();
+ }
+ });
+ }
+
+ /**
+ * Gets the mapping key from given {@link JsonObject}.
+ *
+ * @param jsonObject
+ * json object to get the key from
+ */
+ protected String getKey(JsonObject jsonObject) {
+ if (jsonObject.hasKey(DataProviderConstants.KEY)) {
+ return jsonObject.getString(DataProviderConstants.KEY);
+ }
+ return null;
+ }
+} \ No newline at end of file
diff --git a/client/src/main/java/com/vaadin/client/connectors/data/HasDataSource.java b/client/src/main/java/com/vaadin/client/connectors/data/HasDataSource.java
new file mode 100644
index 0000000000..36a206115c
--- /dev/null
+++ b/client/src/main/java/com/vaadin/client/connectors/data/HasDataSource.java
@@ -0,0 +1,35 @@
+/*
+ * 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.client.connectors.data;
+
+import com.vaadin.client.data.DataSource;
+
+import elemental.json.JsonObject;
+
+/**
+ * Marker interface for Connectors that have a {@link DataSource}.
+ */
+public interface HasDataSource {
+
+ /**
+ * Sets the data source for this Conenctor.
+ *
+ * @param dataSource
+ * new data source
+ */
+ public void setDataSource(DataSource<JsonObject> dataSource);
+
+}
diff --git a/server/src/main/java/com/vaadin/server/communication/data/typed/DataKeyMapper.java b/server/src/main/java/com/vaadin/server/communication/data/typed/DataKeyMapper.java
new file mode 100644
index 0000000000..ff56a38639
--- /dev/null
+++ b/server/src/main/java/com/vaadin/server/communication/data/typed/DataKeyMapper.java
@@ -0,0 +1,62 @@
+/*
+ * 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;
+
+/**
+ * DataKeyMapper to map data objects to key strings.
+ *
+ * @since
+ * @param <T>
+ * data type
+ */
+public interface DataKeyMapper<T> extends Serializable {
+
+ /**
+ * Gets the key for data object. If no key exists beforehand, a new key is
+ * created.
+ *
+ * @param dataObject
+ * data object for key mapping
+ * @return key for given data object
+ */
+ String key(T dataObject);
+
+ /**
+ * Gets the data object identified by given key.
+ *
+ * @param key
+ * key of a data object
+ * @return identified data object; <code>null</code> if invalid key
+ */
+ T get(String key);
+
+ /**
+ * Removes a data object from the key mapping. The key is also dropped.
+ * Dropped keys are not reused.
+ *
+ * @param dataObject
+ * dropped data object
+ */
+ void remove(T dataObject);
+
+ /**
+ * Removes all data objects from the key mapping. The keys are also dropped.
+ * Dropped keys are not reused.
+ */
+ void removeAll();
+} \ No newline at end of file
diff --git a/server/src/main/java/com/vaadin/server/communication/data/typed/DataProvider.java b/server/src/main/java/com/vaadin/server/communication/data/typed/DataProvider.java
new file mode 100644
index 0000000000..309d38d558
--- /dev/null
+++ b/server/src/main/java/com/vaadin/server/communication/data/typed/DataProvider.java
@@ -0,0 +1,353 @@
+/*
+ * 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;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import com.vaadin.server.AbstractExtension;
+import com.vaadin.server.ClientConnector;
+import com.vaadin.shared.data.DataRequestRpc;
+import com.vaadin.shared.data.typed.DataProviderClientRpc;
+import com.vaadin.shared.data.typed.DataProviderConstants;
+import com.vaadin.ui.AbstractComponent;
+
+import elemental.json.Json;
+import elemental.json.JsonArray;
+import elemental.json.JsonObject;
+
+/**
+ * DataProvider base class. This class is the base for all DataProvider
+ * communication implementations. It uses {@link TypedDataGenerator}s to write
+ * {@link JsonObject}s representing each data object to be sent to the
+ * client-side.
+ *
+ * @since
+ */
+public abstract class DataProvider<T> extends AbstractExtension {
+
+ /**
+ * Creates the appropriate type of DataProvider based on the type of
+ * Collection provided to the method.
+ * <p>
+ * <strong>Note:</strong> this method will also extend the given component
+ * with the newly created DataProvider. The user should <strong>not</strong>
+ * call the {@link #extend(com.vaadin.server.AbstractClientConnector)}
+ * method explicitly.
+ * <p>
+ * TODO: Actually use different DataProviders and provide an API for the
+ * back end to inform changes back.
+ *
+ * @param data
+ * collection of data objects
+ * @param component
+ * component to extend with the data provider
+ * @return created data provider
+ */
+ public static <V> SimpleDataProvider<V> create(DataSource<V> data,
+ AbstractComponent component) {
+ SimpleDataProvider<V> dataProvider = new SimpleDataProvider<V>(data);
+ dataProvider.extend(component);
+ return dataProvider;
+ }
+
+ /**
+ * A class for handling currently active data and dropping data that is no
+ * longer needed. Data tracking is based on key string provided by
+ * {@link DataKeyMapper}.
+ * <p>
+ * When the {@link DataProvider} is pushing new data to the client-side via
+ * {@link DataProvider#pushData(long, Collection)},
+ * {@link #addActiveData(Collection)} and {@link #cleanUp(Collection)} are
+ * called with the same parameter. In the clean up method any dropped data
+ * objects that are not in the given collection will be cleaned up and
+ * {@link TypedDataGenerator#destroyData(Object)} will be called for them.
+ */
+ protected class ActiveDataHandler implements Serializable,
+ TypedDataGenerator<T> {
+
+ /**
+ * Set of key strings for currently active data objects
+ */
+ private final Set<String> activeData = new HashSet<String>();
+
+ /**
+ * Set of key strings for data objects dropped on the client. This set
+ * is used to clean up old data when it's no longer needed.
+ */
+ private final Set<String> droppedData = new HashSet<String>();
+
+ /**
+ * Adds given objects as currently active objects.
+ *
+ * @param dataObjects
+ * collection of new active data objects
+ */
+ public void addActiveData(Iterable<T> dataObjects) {
+ for (T data : dataObjects) {
+ if (!activeData.contains(getKeyMapper().key(data))) {
+ activeData.add(getKeyMapper().key(data));
+ }
+ }
+ }
+
+ /**
+ * Executes the data destruction for dropped data that is not sent to
+ * the client. This method takes most recently sent data objects in a
+ * collection. Doing the clean up like this prevents the
+ * {@link ActiveDataHandler} from creating new keys for rows that were
+ * dropped but got re-requested by the client-side. In the case of
+ * having all data at the client, the collection should be all the data
+ * in the back end.
+ *
+ * @see DataProvider#pushData(long, Collection)
+ * @param dataObjects
+ * collection of most recently sent data to the client
+ */
+ public void cleanUp(Iterable<T> dataObjects) {
+ Collection<String> keys = new HashSet<String>();
+ for (T data : dataObjects) {
+ keys.add(getKeyMapper().key(data));
+ }
+
+ // Remove still active rows that were dropped by the client
+ droppedData.removeAll(keys);
+ // Do data clean up for object no longer needed.
+ dropData(droppedData);
+ droppedData.clear();
+ }
+
+ /**
+ * Marks a data object identified by given key string to be dropped.
+ *
+ * @param key
+ * key string
+ */
+ public void dropActiveData(String key) {
+ if (activeData.contains(key)) {
+ droppedData.add(key);
+ }
+ }
+
+ /**
+ * Returns the collection of all currently active data.
+ *
+ * @return collection of active data objects
+ */
+ public Collection<T> getActiveData() {
+ HashSet<T> hashSet = new HashSet<T>();
+ for (String key : activeData) {
+ hashSet.add(getKeyMapper().get(key));
+ }
+ return hashSet;
+ }
+
+ @Override
+ public void generateData(T data, JsonObject jsonObject) {
+ // Write the key string for given data object
+ jsonObject.put(DataProviderConstants.KEY, getKeyMapper().key(data));
+ }
+
+ @Override
+ public void destroyData(T data) {
+ // Remove from active data set
+ activeData.remove(getKeyMapper().key(data));
+ // Drop the registered key
+ getKeyMapper().remove(data);
+ }
+ }
+
+ private Collection<TypedDataGenerator<T>> generators = new LinkedHashSet<TypedDataGenerator<T>>();
+ protected ActiveDataHandler handler = new ActiveDataHandler();
+ protected DataProviderClientRpc rpc;
+
+ protected DataSource<T> dataSource;
+ private DataChangeHandler<T> dataChangeHandler;
+ private DetachListener detachListener;
+ private DataKeyMapper<T> keyMapper;
+
+ protected DataProvider(DataSource<T> dataSource) {
+ addDataGenerator(handler);
+ this.dataSource = dataSource;
+ rpc = getRpcProxy(DataProviderClientRpc.class);
+ registerRpc(createRpc());
+ dataChangeHandler = createDataChangeHandler();
+ this.dataSource.addDataChangeHandler(dataChangeHandler);
+ keyMapper = createKeyMapper();
+ }
+
+ @Override
+ public void attach() {
+ super.attach();
+
+ if (detachListener == null) {
+ detachListener = new DetachListener() {
+
+ @Override
+ public void detach(DetachEvent event) {
+ cleanUp();
+ }
+ };
+ getUI().addDetachListener(detachListener);
+ }
+ }
+
+ @Override
+ public void setParent(ClientConnector parent) {
+ if (getParent() != null && parent == null) {
+ // Removing from parent, clean up.
+ cleanUp();
+ }
+
+ super.setParent(parent);
+ }
+
+ /**
+ * Adds a {@link TypedDataGenerator} to this {@link DataProvider}.
+ *
+ * @param generator
+ * typed data generator
+ */
+ public void addDataGenerator(TypedDataGenerator<T> generator) {
+ generators.add(generator);
+ }
+
+ /**
+ * Removes a {@link TypedDataGenerator} from this {@link DataProvider}.
+ *
+ * @param generator
+ * typed data generator
+ */
+ public void removeDataGenerator(TypedDataGenerator<T> generator) {
+ generators.remove(generator);
+ }
+
+ /**
+ * Gets the {@link DataKeyMapper} used by this {@link DataProvider}. Key
+ * mapper can be used to map keys sent to the client-side back to their
+ * respective data objects.
+ *
+ * @return key mapper
+ */
+ public DataKeyMapper<T> getKeyMapper() {
+ return keyMapper;
+ }
+
+ /**
+ * Sends given collection of data objects to the client-side.
+ *
+ * @param firstIndex
+ * first index of pushed data
+ * @param data
+ * data objects to send as an iterable
+ */
+ protected void pushData(long firstIndex, Iterable<T> data) {
+ JsonArray dataArray = Json.createArray();
+
+ int i = 0;
+ for (T item : data) {
+ dataArray.set(i++, getDataObject(item));
+ }
+
+ rpc.setData(firstIndex, dataArray);
+ handler.addActiveData(data);
+ handler.cleanUp(data);
+ }
+
+ /**
+ * Creates the JsonObject for given data object. This method calls all data
+ * generators for it.
+ *
+ * @param data
+ * data object to be made into a json object
+ * @return json object representing the data object
+ */
+ protected JsonObject getDataObject(T data) {
+ JsonObject dataObject = Json.createObject();
+
+ for (TypedDataGenerator<T> generator : generators) {
+ generator.generateData(data, dataObject);
+ }
+
+ return dataObject;
+ }
+
+ /**
+ * Drops data objects identified by given keys from memory. This will invoke
+ * {@link TypedDataGenerator#destroyData} for each of those objects.
+ *
+ * @param droppedKeys
+ * collection of dropped keys
+ */
+ private void dropData(Collection<String> droppedKeys) {
+ for (String key : droppedKeys) {
+ assert key != null : "Bookkeepping failure. Dropping a null key";
+
+ T data = getKeyMapper().get(key);
+ assert data != null : "Bookkeepping failure. No data object to match key";
+
+ for (TypedDataGenerator<T> g : generators) {
+ g.destroyData(data);
+ }
+ }
+ }
+
+ /**
+ * Clean up method for removing all listeners attached by the
+ * {@link DataProvider}. This method is called from {@link #remove()} or
+ * when the UI gets detached.
+ */
+ protected void cleanUp() {
+ if (dataSource != null) {
+ dataSource.removeDataChangeHandler(dataChangeHandler);
+ dataChangeHandler = null;
+ }
+ if (detachListener != null) {
+ getUI().removeDetachListener(detachListener);
+ detachListener = null;
+ }
+ }
+
+ /**
+ * Creates a {@link DataKeyMapper} to use with this {@link DataProvider}.
+ * <p>
+ * This method is called from the constructor.
+ *
+ * @return key mapper
+ */
+ protected abstract DataKeyMapper<T> createKeyMapper();
+
+ /**
+ * Creates a {@link DataRequestRpc} used with this {@link DataProvider}.
+ * <p>
+ * This method is called from the constructor.
+ *
+ * @return data request rpc implementation
+ */
+ protected abstract DataRequestRpc createRpc();
+
+ /**
+ * Creates a {@link DataChangeHandler} to use with the {@link DataSource}.
+ * <p>
+ * This method is called from the constructor.
+ *
+ * @return data change handler
+ */
+ protected abstract DataChangeHandler<T> createDataChangeHandler();
+} \ No newline at end of file
diff --git a/server/src/main/java/com/vaadin/server/communication/data/typed/KeyMapper.java b/server/src/main/java/com/vaadin/server/communication/data/typed/KeyMapper.java
new file mode 100644
index 0000000000..9a26882cd9
--- /dev/null
+++ b/server/src/main/java/com/vaadin/server/communication/data/typed/KeyMapper.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+/**
+ * Generic {@link DataKeyMapper} implementation based on
+ * {@link com.vaadin.server.KeyMapper}. Provides the interface on top of super
+ * class.
+ *
+ * @since
+ * @param <T>
+ * data type
+ */
+public class KeyMapper<T> extends com.vaadin.server.KeyMapper<T> implements
+ DataKeyMapper<T> {
+} \ No newline at end of file
diff --git a/server/src/main/java/com/vaadin/server/communication/data/typed/SimpleDataProvider.java b/server/src/main/java/com/vaadin/server/communication/data/typed/SimpleDataProvider.java
new file mode 100644
index 0000000000..8588e0315f
--- /dev/null
+++ b/server/src/main/java/com/vaadin/server/communication/data/typed/SimpleDataProvider.java
@@ -0,0 +1,189 @@
+/*
+ * 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.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import com.vaadin.shared.data.DataRequestRpc;
+import com.vaadin.shared.data.typed.DataProviderClientRpc;
+
+import elemental.json.Json;
+import elemental.json.JsonArray;
+
+/**
+ * DataProvider for Collections. This class takes care of sending data objects
+ * stored in a Collection from the server-side to the client-side.
+ * <p>
+ * This is an implementation that does not provide any kind of lazy loading. All
+ * data is sent to the client-side on the initial client response.
+ *
+ * @since
+ */
+public class SimpleDataProvider<T> extends DataProvider<T> {
+
+ /**
+ * Simple implementation of collection data provider communication. All data
+ * is sent by server automatically and no data is requested by client.
+ */
+ protected class SimpleDataRequestRpc implements DataRequestRpc {
+
+ @Override
+ public void requestRows(int firstRowIndex, int numberOfRows,
+ int firstCachedRowIndex, int cacheSize) {
+ throw new UnsupportedOperationException(
+ "Collection data provider sends all data from server."
+ + " It does not expect client to request anything.");
+ }
+
+ @Override
+ public void dropRows(JsonArray keys) {
+ for (int i = 0; i < keys.length(); ++i) {
+ handler.dropActiveData(keys.getString(i));
+ }
+
+ // Use the whole data as the ones sent to the client.
+ handler.cleanUp(dataSource);
+ }
+ }
+
+ private boolean reset = false;
+ private final Set<T> updatedData = new HashSet<T>();
+
+ /**
+ * Creates a new DataProvider with the given Collection.
+ *
+ * @param data
+ * collection of data to use
+ */
+ protected SimpleDataProvider(DataSource<T> data) {
+ super(data);
+ }
+
+ /**
+ * Initially and in the case of a reset all data should be pushed to the
+ * client.
+ */
+ @Override
+ public void beforeClientResponse(boolean initial) {
+ super.beforeClientResponse(initial);
+
+ if (reset) {
+ getRpcProxy(DataProviderClientRpc.class).reset();
+ }
+
+ if (initial || reset) {
+ pushData(0, dataSource);
+ } else if (!updatedData.isEmpty()) {
+ JsonArray dataArray = Json.createArray();
+ int i = 0;
+ for (T data : updatedData) {
+ dataArray.set(i++, getDataObject(data));
+ }
+ rpc.updateData(dataArray);
+ }
+
+ reset = false;
+ updatedData.clear();
+ }
+
+ /**
+ * Informs the DataProvider that a data object has been added. It is assumed
+ * to be the last object in the collection.
+ *
+ * @param data
+ * data object added to collection
+ */
+ protected void add(T data) {
+ rpc.add(getDataObject(data));
+ handler.addActiveData(Collections.singleton(data));
+ }
+
+ /**
+ * Informs the DataProvider that a data object has been removed.
+ *
+ * @param data
+ * data object removed from collection
+ */
+ protected void remove(T data) {
+ if (handler.getActiveData().contains(data)) {
+ rpc.drop(getKeyMapper().key(data));
+ }
+ }
+
+ /**
+ * Informs the DataProvider that the collection has changed.
+ */
+ protected void reset() {
+ if (reset) {
+ return;
+ }
+
+ reset = true;
+ markAsDirty();
+ }
+
+ /**
+ * Informs the DataProvider that a data object has been updated.
+ *
+ * @param data
+ * updated data object
+ */
+ protected void refresh(T data) {
+ if (updatedData.isEmpty()) {
+ markAsDirty();
+ }
+
+ updatedData.add(data);
+ }
+
+ @Override
+ protected DataKeyMapper<T> createKeyMapper() {
+ return new KeyMapper<T>();
+ }
+
+ @Override
+ protected DataRequestRpc createRpc() {
+ return new SimpleDataRequestRpc();
+ }
+
+ @Override
+ protected DataChangeHandler<T> createDataChangeHandler() {
+ return new DataChangeHandler<T>() {
+
+ @Override
+ public void onDataChange() {
+ reset();
+ }
+
+ @Override
+ public void onDataAppend(T data) {
+ add(data);
+ }
+
+ @Override
+ public void onDataRemove(T data) {
+ remove(data);
+ }
+
+ @Override
+ public void onDataUpdate(T data) {
+ refresh(data);
+ }
+ };
+ }
+} \ No newline at end of file
diff --git a/server/src/main/java/com/vaadin/server/communication/data/typed/TypedDataGenerator.java b/server/src/main/java/com/vaadin/server/communication/data/typed/TypedDataGenerator.java
new file mode 100644
index 0000000000..b21fe0699e
--- /dev/null
+++ b/server/src/main/java/com/vaadin/server/communication/data/typed/TypedDataGenerator.java
@@ -0,0 +1,49 @@
+/*
+ * 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;
+
+import elemental.json.JsonObject;
+
+/**
+ * Simple typed data generator for {@link DataProvider}.
+ *
+ * @since
+ */
+public interface TypedDataGenerator<T> extends Serializable {
+
+ /**
+ * Adds data for given object to {@link JsonObject}. This JsonObject will be
+ * sent to client-side DataSource.
+ *
+ * @param data
+ * data object
+ * @param jsonObject
+ * json object being sent to the client
+ */
+ void generateData(T data, JsonObject jsonObject);
+
+ /**
+ * Informs the {@link TypedDataGenerator} that given data has been dropped
+ * and is no longer needed. This method should clean up any unneeded
+ * information stored for this data.
+ *
+ * @param data
+ * dropped data
+ */
+ public void destroyData(T data);
+} \ No newline at end of file
diff --git a/shared/src/main/java/com/vaadin/shared/data/typed/DataProviderClientRpc.java b/shared/src/main/java/com/vaadin/shared/data/typed/DataProviderClientRpc.java
new file mode 100644
index 0000000000..4b5824bda7
--- /dev/null
+++ b/shared/src/main/java/com/vaadin/shared/data/typed/DataProviderClientRpc.java
@@ -0,0 +1,75 @@
+/*
+ * 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.shared.data.typed;
+
+import java.util.List;
+
+import com.vaadin.shared.communication.ClientRpc;
+
+import elemental.json.JsonArray;
+import elemental.json.JsonObject;
+
+/**
+ * RPC interface used by DataProvider to send data to the client-side.
+ *
+ * @since
+ */
+public interface DataProviderClientRpc extends ClientRpc {
+
+ /**
+ * Informs the client-side DataSource that all data has been invalidated.
+ */
+ void reset();
+
+ /**
+ * Sets the data of the client-side DataSource to match the given data
+ * starting from given index.
+ * <p>
+ * <strong>Note:</strong> This method will override any existing data in the
+ * range starting from first index with the length of the data array.
+ *
+ * @param firstIndex
+ * first index to update
+ * @param data
+ * array of new data
+ */
+ void setData(long firstIndex, JsonArray data);
+
+ /**
+ * Adds a new data object to the client-side DataSource. The new data object
+ * is added at the end of the data source.
+ *
+ * @param dataObject
+ * single added data object
+ */
+ void add(JsonObject dataObject);
+
+ /**
+ * Removes data identified by given key from the client-side DataSource.
+ *
+ * @param key
+ * key identifying the object to be removed
+ */
+ void drop(String key);
+
+ /**
+ * Updates an array of objects based on their identifying key.
+ *
+ * @param data
+ * array of updated data
+ */
+ void updateData(JsonArray data);
+} \ No newline at end of file
diff --git a/shared/src/main/java/com/vaadin/shared/data/typed/DataProviderConstants.java b/shared/src/main/java/com/vaadin/shared/data/typed/DataProviderConstants.java
new file mode 100644
index 0000000000..52fc225546
--- /dev/null
+++ b/shared/src/main/java/com/vaadin/shared/data/typed/DataProviderConstants.java
@@ -0,0 +1,28 @@
+/*
+ * 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.shared.data.typed;
+
+import java.io.Serializable;
+
+/**
+ * Set of contants used by DataProvider. These are commonly used JsonObject keys
+ * which are considered to be reserved for internal use.
+ *
+ * @since
+ */
+public final class DataProviderConstants implements Serializable {
+ public static final String KEY = "k";
+} \ No newline at end of file