--- /dev/null
+/*
+ * 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.tokka.connectors.data;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.data.AbstractRemoteDataSource;
+import com.vaadin.client.data.DataSource;
+import com.vaadin.client.extensions.AbstractExtensionConnector;
+import com.vaadin.shared.data.DataRequestRpc;
+import com.vaadin.shared.data.typed.DataCommunicatorClientRpc;
+import com.vaadin.shared.data.typed.DataProviderConstants;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.tokka.server.communication.data.DataCommunicator;
+
+import elemental.json.Json;
+import elemental.json.JsonArray;
+import elemental.json.JsonObject;
+
+/**
+ * A connector for DataCommunicator class.
+ *
+ * @since
+ */
+@Connect(DataCommunicator.class)
+public class DataCommunicatorConnector extends AbstractExtensionConnector {
+
+ public class VaadinDataSource extends AbstractRemoteDataSource<JsonObject> {
+
+ private Set<String> droppedKeys = new HashSet<String>();
+
+ protected VaadinDataSource() {
+ registerRpc(DataCommunicatorClientRpc.class,
+ new DataCommunicatorClientRpc() {
+
+ @Override
+ public void reset(int size) {
+ resetDataAndSize(size);
+ }
+
+ @Override
+ public void setData(int firstIndex, JsonArray data) {
+ ArrayList<JsonObject> rows = new ArrayList<JsonObject>(
+ data.length());
+ for (int i = 0; i < data.length(); i++) {
+ JsonObject rowObject = data.getObject(i);
+ rows.add(rowObject);
+ }
+
+ setRowData(firstIndex, rows);
+ }
+
+ @Override
+ public void add(int index) {
+ VaadinDataSource.this.insertRowData(index, 1);
+ }
+
+ @Override
+ public void drop(int index) {
+ VaadinDataSource.this.removeRowData(index, 1);
+ }
+
+ @Override
+ public void updateData(JsonArray data) {
+ for (int i = 0; i < data.length(); ++i) {
+ updateRowData(data.getObject(i));
+ }
+ }
+ });
+ }
+
+ public RowHandle<JsonObject> getHandleByKey(String key) {
+ JsonObject row = Json.createObject();
+ row.put(DataProviderConstants.KEY, key);
+ return new RowHandleImpl(row, key);
+ }
+
+ @Override
+ protected void requestRows(int firstRowIndex, int numberOfRows,
+ RequestRowsCallback<JsonObject> callback) {
+ getRpcProxy(DataRequestRpc.class).requestRows(firstRowIndex,
+ numberOfRows, 0, 0);
+
+ JsonArray dropped = Json.createArray();
+ int i = 0;
+ for (String key : droppedKeys) {
+ dropped.set(i++, key);
+ }
+
+ getRpcProxy(DataRequestRpc.class).dropRows(dropped);
+ }
+
+ @Override
+ public String getRowKey(JsonObject row) {
+ return row.getString(DataProviderConstants.KEY);
+ }
+
+ @Override
+ protected void onDropFromCache(int rowIndex, JsonObject removed) {
+ droppedKeys.add(getRowKey(removed));
+
+ super.onDropFromCache(rowIndex, removed);
+ }
+
+ /**
+ * Updates row data based on row key.
+ *
+ * @param row
+ * new row object
+ */
+ protected void updateRowData(JsonObject row) {
+ int index = indexOfKey(getRowKey(row));
+ if (index >= 0) {
+ setRowData(index, Collections.singletonList(row));
+ }
+ }
+ }
+
+ private DataSource<JsonObject> ds = new VaadinDataSource();
+
+ @Override
+ protected void extend(ServerConnector target) {
+ ServerConnector parent = getParent();
+ if (parent instanceof HasDataSource) {
+ ((HasDataSource) parent).setDataSource(ds);
+ } else {
+ assert false : "Parent not implementing HasDataSource";
+ }
+ }
+}
\ No newline at end of file
+++ /dev/null
-/*
- * 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.tokka.connectors.data;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-import com.vaadin.client.ServerConnector;
-import com.vaadin.client.data.AbstractRemoteDataSource;
-import com.vaadin.client.data.DataSource;
-import com.vaadin.client.extensions.AbstractExtensionConnector;
-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 com.vaadin.tokka.server.communication.data.DataProvider;
-
-import elemental.json.Json;
-import elemental.json.JsonArray;
-import elemental.json.JsonObject;
-
-/**
- * A connector for DataProvider class.
- *
- * @since
- */
-@Connect(DataProvider.class)
-public class DataSourceConnector extends AbstractExtensionConnector {
-
- public class VaadinDataSource extends AbstractRemoteDataSource<JsonObject> {
-
- private Set<String> droppedKeys = new HashSet<String>();
-
- protected VaadinDataSource() {
- registerRpc(DataProviderClientRpc.class,
- new DataProviderClientRpc() {
-
- @Override
- public void reset(int size) {
- resetDataAndSize(size);
- }
-
- @Override
- public void setData(int firstIndex, JsonArray data) {
- ArrayList<JsonObject> rows = new ArrayList<JsonObject>(
- data.length());
- for (int i = 0; i < data.length(); i++) {
- JsonObject rowObject = data.getObject(i);
- rows.add(rowObject);
- }
-
- setRowData(firstIndex, rows);
- }
-
- @Override
- public void add(int index) {
- VaadinDataSource.this.insertRowData(index, 1);
- }
-
- @Override
- public void drop(int index) {
- VaadinDataSource.this.removeRowData(index, 1);
- }
-
- @Override
- public void updateData(JsonArray data) {
- for (int i = 0; i < data.length(); ++i) {
- updateRowData(data.getObject(i));
- }
- }
- });
- }
-
- public RowHandle<JsonObject> getHandleByKey(String key) {
- JsonObject row = Json.createObject();
- row.put(DataProviderConstants.KEY, key);
- return new RowHandleImpl(row, key);
- }
-
- @Override
- protected void requestRows(int firstRowIndex, int numberOfRows,
- RequestRowsCallback<JsonObject> callback) {
- getRpcProxy(DataRequestRpc.class).requestRows(firstRowIndex,
- numberOfRows, 0, 0);
-
- JsonArray dropped = Json.createArray();
- int i = 0;
- for (String key : droppedKeys) {
- dropped.set(i++, key);
- }
-
- getRpcProxy(DataRequestRpc.class).dropRows(dropped);
- }
-
- @Override
- public String getRowKey(JsonObject row) {
- return row.getString(DataProviderConstants.KEY);
- }
-
- @Override
- protected void onDropFromCache(int rowIndex, JsonObject removed) {
- droppedKeys.add(getRowKey(removed));
-
- super.onDropFromCache(rowIndex, removed);
- }
-
- /**
- * Updates row data based on row key.
- *
- * @param row
- * new row object
- */
- protected void updateRowData(JsonObject row) {
- int index = indexOfKey(getRowKey(row));
- if (index >= 0) {
- setRowData(index, Collections.singletonList(row));
- }
- }
- }
-
- private DataSource<JsonObject> ds = new VaadinDataSource();
-
- @Override
- protected void extend(ServerConnector target) {
- ServerConnector parent = getParent();
- if (parent instanceof HasDataSource) {
- ((HasDataSource) parent).setDataSource(ds);
- } else {
- assert false : "Parent not implementing HasDataSource";
- }
- }
-}
\ No newline at end of file
--- /dev/null
+/*
+ * 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.tokka.server.communication.data;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import com.vaadin.server.AbstractExtension;
+import com.vaadin.server.ClientConnector;
+import com.vaadin.shared.data.DataRequestRpc;
+import com.vaadin.shared.data.typed.DataCommunicatorClientRpc;
+import com.vaadin.shared.data.typed.DataProviderConstants;
+import com.vaadin.shared.ui.grid.Range;
+import com.vaadin.tokka.event.Registration;
+
+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 class DataCommunicator<T> extends AbstractExtension {
+
+ /**
+ * 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) {
+ pushRows = Range.withLength(firstRowIndex, numberOfRows);
+ markAsDirty();
+ }
+
+ @Override
+ public void dropRows(JsonArray keys) {
+ for (int i = 0; i < keys.length(); ++i) {
+ handler.dropActiveData(keys.getString(i));
+ }
+ }
+ }
+
+ /**
+ * 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 DataCommunicator} is pushing new data to the client-side via
+ * {@link DataCommunicator#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(Stream<T> dataObjects) {
+ dataObjects.map(getKeyMapper()::key)
+ .filter(key -> !activeData.contains(key))
+ .forEach(activeData::add);
+ }
+
+ /**
+ * 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.
+ *
+ * @param dataObjects
+ * collection of most recently sent data to the client
+ */
+ public void cleanUp(Stream<T> dataObjects) {
+ Collection<String> keys = dataObjects.map(getKeyMapper()::key)
+ .collect(Collectors.toSet());
+
+ // 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 DataCommunicatorClientRpc rpc;
+
+ protected DataSource<T> dataSource;
+ private Registration dataChangeHandler;
+ private DetachListener detachListener;
+ private DataKeyMapper<T> keyMapper;
+
+ private boolean reset = false;
+ private final Set<T> updatedData = new HashSet<T>();
+ private Range pushRows = Range.withLength(0, 40);
+
+ public DataCommunicator(DataSource<T> dataSource) {
+ addDataGenerator(handler);
+ this.dataSource = dataSource;
+ rpc = getRpcProxy(DataCommunicatorClientRpc.class);
+ registerRpc(createRpc());
+ dataChangeHandler = this.dataSource
+ .addDataChangeHandler(createDataChangeHandler());
+ keyMapper = createKeyMapper();
+ }
+
+ /**
+ * 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 (initial || reset) {
+ rpc.reset(dataSource.size());
+ }
+
+ if (!pushRows.isEmpty()) {
+ Stream<T> rowsToPush = dataSource.request()
+ .skip(pushRows.getStart()).limit(pushRows.length());
+ pushData(pushRows.getStart(), rowsToPush);
+ }
+
+ if (!updatedData.isEmpty()) {
+ JsonArray dataArray = Json.createArray();
+ int i = 0;
+ for (T data : updatedData) {
+ dataArray.set(i++, getDataObject(data));
+ }
+ rpc.updateData(dataArray);
+ }
+
+ pushRows = Range.withLength(0, 0);
+ reset = false;
+ updatedData.clear();
+ }
+
+ @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 DataCommunicator}.
+ *
+ * @param generator
+ * typed data generator
+ */
+ public void addDataGenerator(TypedDataGenerator<T> generator) {
+ generators.add(generator);
+ }
+
+ /**
+ * Removes a {@link TypedDataGenerator} from this {@link DataCommunicator}.
+ *
+ * @param generator
+ * typed data generator
+ */
+ public void removeDataGenerator(TypedDataGenerator<T> generator) {
+ generators.remove(generator);
+ }
+
+ /**
+ * Gets the {@link DataKeyMapper} used by this {@link DataCommunicator}. 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(int firstIndex, Stream<T> data) {
+ JsonArray dataArray = Json.createArray();
+
+ int i = 0;
+ List<T> collected = data.collect(Collectors.toList());
+ for (T item : collected) {
+ dataArray.set(i++, getDataObject(item));
+ }
+
+ rpc.setData(firstIndex, dataArray);
+ handler.addActiveData(collected.stream());
+ handler.cleanUp(collected.stream());
+ }
+
+ /**
+ * 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 DataCommunicator}. This method is called from {@link #remove()} or
+ * when the UI gets detached.
+ */
+ protected void cleanUp() {
+ if (dataSource != null) {
+ dataChangeHandler.removeHandler();
+ dataChangeHandler = null;
+ }
+ if (detachListener != null) {
+ getUI().removeDetachListener(detachListener);
+ detachListener = null;
+ }
+ }
+
+ /**
+ * 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(0);
+ }
+
+ /**
+ * 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(0);
+ }
+ }
+
+ /**
+ * 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
+ */
+ public void refresh(T data) {
+ if (updatedData.isEmpty()) {
+ markAsDirty();
+ }
+
+ updatedData.add(data);
+ }
+
+ /**
+ * Creates a {@link DataKeyMapper} to use with this {@link DataCommunicator}.
+ * <p>
+ * This method is called from the constructor.
+ *
+ * @return key mapper
+ */
+ protected DataKeyMapper<T> createKeyMapper() {
+ return new KeyMapper<T>();
+ }
+
+ /**
+ * Creates a {@link DataRequestRpc} used with this {@link DataCommunicator}.
+ * <p>
+ * This method is called from the constructor.
+ *
+ * @return data request rpc implementation
+ */
+ protected DataRequestRpc createRpc() {
+ return new SimpleDataRequestRpc();
+ }
+
+ /**
+ * Creates a {@link DataChangeHandler} to use with the {@link DataSource}.
+ * <p>
+ * This method is called from the constructor.
+ *
+ * @return data change handler
+ */
+ 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
+++ /dev/null
-/*
- * 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.tokka.server.communication.data;
-
-import java.io.Serializable;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-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.shared.ui.grid.Range;
-import com.vaadin.tokka.event.Registration;
-
-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 class DataProvider<T> extends AbstractExtension {
-
- /**
- * 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) {
- pushRows = Range.withLength(firstRowIndex, numberOfRows);
- markAsDirty();
- }
-
- @Override
- public void dropRows(JsonArray keys) {
- for (int i = 0; i < keys.length(); ++i) {
- handler.dropActiveData(keys.getString(i));
- }
- }
- }
-
- /**
- * 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(Stream<T> dataObjects) {
- dataObjects.map(getKeyMapper()::key)
- .filter(key -> !activeData.contains(key))
- .forEach(activeData::add);
- }
-
- /**
- * 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.
- *
- * @param dataObjects
- * collection of most recently sent data to the client
- */
- public void cleanUp(Stream<T> dataObjects) {
- Collection<String> keys = dataObjects.map(getKeyMapper()::key)
- .collect(Collectors.toSet());
-
- // 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 Registration dataChangeHandler;
- private DetachListener detachListener;
- private DataKeyMapper<T> keyMapper;
-
- private boolean reset = false;
- private final Set<T> updatedData = new HashSet<T>();
- private Range pushRows = Range.withLength(0, 40);
-
- public DataProvider(DataSource<T> dataSource) {
- addDataGenerator(handler);
- this.dataSource = dataSource;
- rpc = getRpcProxy(DataProviderClientRpc.class);
- registerRpc(createRpc());
- dataChangeHandler = this.dataSource
- .addDataChangeHandler(createDataChangeHandler());
- keyMapper = createKeyMapper();
- }
-
- /**
- * 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 (initial || reset) {
- rpc.reset(dataSource.size());
- }
-
- if (!pushRows.isEmpty()) {
- Stream<T> rowsToPush = dataSource.request()
- .skip(pushRows.getStart()).limit(pushRows.length());
- pushData(pushRows.getStart(), rowsToPush);
- }
-
- if (!updatedData.isEmpty()) {
- JsonArray dataArray = Json.createArray();
- int i = 0;
- for (T data : updatedData) {
- dataArray.set(i++, getDataObject(data));
- }
- rpc.updateData(dataArray);
- }
-
- pushRows = Range.withLength(0, 0);
- reset = false;
- updatedData.clear();
- }
-
- @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(int firstIndex, Stream<T> data) {
- JsonArray dataArray = Json.createArray();
-
- int i = 0;
- List<T> collected = data.collect(Collectors.toList());
- for (T item : collected) {
- dataArray.set(i++, getDataObject(item));
- }
-
- rpc.setData(firstIndex, dataArray);
- handler.addActiveData(collected.stream());
- handler.cleanUp(collected.stream());
- }
-
- /**
- * 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) {
- dataChangeHandler.removeHandler();
- dataChangeHandler = null;
- }
- if (detachListener != null) {
- getUI().removeDetachListener(detachListener);
- detachListener = null;
- }
- }
-
- /**
- * 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(0);
- }
-
- /**
- * 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(0);
- }
- }
-
- /**
- * 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
- */
- public void refresh(T data) {
- if (updatedData.isEmpty()) {
- markAsDirty();
- }
-
- updatedData.add(data);
- }
-
- /**
- * Creates a {@link DataKeyMapper} to use with this {@link DataProvider}.
- * <p>
- * This method is called from the constructor.
- *
- * @return key mapper
- */
- protected DataKeyMapper<T> createKeyMapper() {
- return new KeyMapper<T>();
- }
-
- /**
- * Creates a {@link DataRequestRpc} used with this {@link DataProvider}.
- * <p>
- * This method is called from the constructor.
- *
- * @return data request rpc implementation
- */
- protected DataRequestRpc createRpc() {
- return new SimpleDataRequestRpc();
- }
-
- /**
- * Creates a {@link DataChangeHandler} to use with the {@link DataSource}.
- * <p>
- * This method is called from the constructor.
- *
- * @return data change handler
- */
- 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
import elemental.json.JsonObject;
/**
- * Simple typed data generator for {@link DataProvider}.
+ * Simple typed data generator for {@link DataCommunicator}.
*
* @since
*/
import com.vaadin.server.AbstractExtension;
import com.vaadin.tokka.server.ListingExtension;
-import com.vaadin.tokka.server.communication.data.DataProvider;
+import com.vaadin.tokka.server.communication.data.DataCommunicator;
import com.vaadin.tokka.server.communication.data.SelectionModel;
import com.vaadin.tokka.server.communication.data.TypedDataGenerator;
import com.vaadin.ui.AbstractComponent;
/**
* Base class for Listing components. Provides common handling for
- * {@link DataProvider}, {@link SelectionModel} and {@link TypedDataGenerator}s.
+ * {@link DataCommunicator}, {@link SelectionModel} and {@link TypedDataGenerator}s.
*
* @param <T>
* listing data type
* @return the data object
*/
protected T getData(String key) {
- DataProvider<T> dataProvider = getParent().getDataProvider();
+ DataCommunicator<T> dataProvider = getParent().getDataCommunicator();
if (dataProvider != null) {
return dataProvider.getKeyMapper().get(key);
}
* data object to refresh
*/
protected void refresh(T data) {
- DataProvider<T> dataProvider = getParent().getDataProvider();
+ DataCommunicator<T> dataProvider = getParent().getDataCommunicator();
if (dataProvider != null) {
dataProvider.refresh(data);
}
}
/* DataProvider for this Listing component */
- private DataProvider<T> dataProvider;
+ private DataCommunicator<T> dataCommunicator;
/* TypedDataGenerators used by this Listing */
private Set<TypedDataGenerator<T>> generators = new LinkedHashSet<>();
/* SelectionModel for this Listing */
private SelectionModel<T> selectionModel;
/**
- * Adds a {@link TypedDataGenerator} for the {@link DataProvider} of this
+ * Adds a {@link TypedDataGenerator} for the {@link DataCommunicator} of this
* Listing component.
*
* @param generator
protected void addDataGenerator(TypedDataGenerator<T> generator) {
generators.add(generator);
- if (dataProvider != null) {
- dataProvider.addDataGenerator(generator);
+ if (dataCommunicator != null) {
+ dataCommunicator.addDataGenerator(generator);
}
}
/**
- * Removed a {@link TypedDataGenerator} from the {@link DataProvider} of
+ * Removed a {@link TypedDataGenerator} from the {@link DataCommunicator} of
* this Listing component.
*
* @param generator
protected void removeDataGenerator(TypedDataGenerator<T> generator) {
generators.remove(generator);
- if (dataProvider != null) {
- dataProvider.removeDataGenerator(generator);
+ if (dataCommunicator != null) {
+ dataCommunicator.removeDataGenerator(generator);
}
}
* @param dataProvider
* new data provider
*/
- protected void setDataProvider(DataProvider<T> dataProvider) {
- if (this.dataProvider == dataProvider) {
+ protected void setDataCommunicator(DataCommunicator<T> dataProvider) {
+ if (this.dataCommunicator == dataProvider) {
return;
}
- if (this.dataProvider != null) {
- this.dataProvider.remove();
+ if (this.dataCommunicator != null) {
+ this.dataCommunicator.remove();
}
- this.dataProvider = dataProvider;
+ this.dataCommunicator = dataProvider;
if (dataProvider != null) {
addExtension(dataProvider);
}
/**
- * Get the {@link DataProvider} of this Listing component.
+ * Get the {@link DataCommunicator} of this Listing component.
*
* @return data provider
*/
- protected DataProvider<T> getDataProvider() {
- return dataProvider;
+ protected DataCommunicator<T> getDataCommunicator() {
+ return dataCommunicator;
}
@SuppressWarnings("unchecked")
import java.util.Map;
import java.util.function.Function;
-import com.vaadin.tokka.server.communication.data.DataProvider;
+import com.vaadin.tokka.server.communication.data.DataCommunicator;
import com.vaadin.tokka.server.communication.data.DataSource;
import com.vaadin.tokka.server.communication.data.SingleSelection;
import com.vaadin.tokka.ui.components.AbstractListing;
@Override
public void setDataSource(DataSource<T> data) {
this.dataSource = data;
- setDataProvider(new DataProvider<>(data));
+ setDataCommunicator(new DataCommunicator<>(data));
}
@Override
import java.util.function.Function;
import com.vaadin.shared.data.typed.DataProviderConstants;
-import com.vaadin.tokka.server.communication.data.DataProvider;
+import com.vaadin.tokka.server.communication.data.DataCommunicator;
import com.vaadin.tokka.server.communication.data.DataSource;
import com.vaadin.tokka.server.communication.data.SingleSelection;
import com.vaadin.tokka.server.communication.data.TypedDataGenerator;
public void setDataSource(DataSource<T> data) {
dataSource = data;
if (dataSource != null) {
- setDataProvider(new DataProvider<>(dataSource));
+ setDataCommunicator(new DataCommunicator<>(dataSource));
} else {
- setDataProvider(null);
+ setDataCommunicator(null);
}
}
import org.junit.Test;
import com.vaadin.tokka.server.communication.data.AbstractDataSource;
-import com.vaadin.tokka.server.communication.data.DataProvider;
+import com.vaadin.tokka.server.communication.data.DataCommunicator;
import com.vaadin.tokka.server.communication.data.DataSource;
import com.vaadin.tokka.ui.components.AbstractListing;
import com.vaadin.tokka.ui.components.AbstractListing.AbstractListingExtension;
@Override
public void setDataSource(DataSource<String> data) {
this.data = data;
- setDataProvider(new DataProvider<>(data));
+ setDataCommunicator(new DataCommunicator<>(data));
}
@Override
CountGenerator countGenerator = new CountGenerator();
countGenerator.extend(testComponent);
- testComponent.getDataProvider().beforeClientResponse(true);
+ testComponent.getDataCommunicator().beforeClientResponse(true);
assertEquals("Generator was not called.", 1, countGenerator.callCount);
}
countGenerator.extend(testComponent);
countGenerator.remove();
- testComponent.getDataProvider().beforeClientResponse(true);
+ testComponent.getDataCommunicator().beforeClientResponse(true);
assertEquals("Generator was called.", 0, countGenerator.callCount);
}
--- /dev/null
+/*
+ * 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 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 DataCommunicatorClientRpc extends ClientRpc {
+
+ /**
+ * Informs the client-side DataSource that all data has been invalidated.
+ *
+ * @param size
+ * size of the data source
+ */
+ void reset(int size);
+
+ /**
+ * 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(int firstIndex, JsonArray data);
+
+ /**
+ * Adds a new data object to the client-side DataSource. Client-side will
+ * request the new data if needed.
+ *
+ * @param index
+ * added row index
+ */
+ void add(int index);
+
+ /**
+ * Removes data with given index from the client-side DataSource.
+ *
+ * @param index
+ * removed row index
+ */
+ void drop(int index);
+
+ /**
+ * 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
+++ /dev/null
-/*
- * 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 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.
- *
- * @param size
- * size of the data source
- */
- void reset(int size);
-
- /**
- * 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(int firstIndex, JsonArray data);
-
- /**
- * Adds a new data object to the client-side DataSource. Client-side will
- * request the new data if needed.
- *
- * @param index
- * added row index
- */
- void add(int index);
-
- /**
- * Removes data with given index from the client-side DataSource.
- *
- * @param index
- * removed row index
- */
- void drop(int index);
-
- /**
- * 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