123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628 |
- /*
- * Copyright 2000-2021 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.v7.server.communication.data;
-
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.LinkedHashSet;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
-
- import com.vaadin.server.AbstractExtension;
- import com.vaadin.server.ClientConnector;
- import com.vaadin.server.KeyMapper;
- import com.vaadin.shared.Range;
- import com.vaadin.shared.data.DataProviderRpc;
- import com.vaadin.shared.data.DataRequestRpc;
- import com.vaadin.v7.data.Container.Indexed;
- import com.vaadin.v7.data.Container.Indexed.ItemAddEvent;
- import com.vaadin.v7.data.Container.Indexed.ItemRemoveEvent;
- import com.vaadin.v7.data.Container.ItemSetChangeEvent;
- import com.vaadin.v7.data.Container.ItemSetChangeListener;
- import com.vaadin.v7.data.Container.ItemSetChangeNotifier;
- import com.vaadin.v7.data.Item;
- import com.vaadin.v7.data.Property;
- import com.vaadin.v7.data.Property.ValueChangeEvent;
- import com.vaadin.v7.data.Property.ValueChangeListener;
- import com.vaadin.v7.data.Property.ValueChangeNotifier;
- import com.vaadin.v7.shared.ui.grid.GridState;
- import com.vaadin.v7.ui.Grid;
- import com.vaadin.v7.ui.Grid.Column;
-
- import elemental.json.Json;
- import elemental.json.JsonArray;
- import elemental.json.JsonObject;
-
- /**
- * Provides Vaadin server-side container data source to a
- * {@link com.vaadin.v7.client.connectors.GridConnector GridConnector}. This is
- * currently implemented as an Extension hardcoded to support a specific
- * connector type. This will be changed once framework support for something
- * more flexible has been implemented.
- *
- * @since 7.4
- * @author Vaadin Ltd
- *
- * @deprecated As of 8.0, no replacement available.
- */
- @Deprecated
- public class RpcDataProviderExtension extends AbstractExtension {
-
- /**
- * Class for keeping track of current items and ValueChangeListeners.
- *
- * @since 7.6
- */
- private class ActiveItemHandler implements DataGenerator {
-
- private final Map<Object, GridValueChangeListener> activeItemMap = new HashMap<Object, GridValueChangeListener>();
- private final KeyMapper<Object> keyMapper = new KeyMapper<Object>();
- private final Set<Object> droppedItems = new HashSet<Object>();
-
- /**
- * Registers ValueChangeListeners for given item ids.
- * <p>
- * Note: This method will clean up any unneeded listeners and key
- * mappings
- *
- * @param itemIds
- * collection of new active item ids
- */
- public void addActiveItems(Collection<?> itemIds) {
- for (Object itemId : itemIds) {
- if (!activeItemMap.containsKey(itemId)) {
- activeItemMap.put(itemId, new GridValueChangeListener(
- itemId, container.getItem(itemId)));
- }
- }
-
- // Remove still active rows that were "dropped"
- droppedItems.removeAll(itemIds);
- internalDropItems(droppedItems);
- droppedItems.clear();
- }
-
- /**
- * Marks given item id as dropped. Dropped items are cleared when adding
- * new active items.
- *
- * @param itemId
- * dropped item id
- */
- public void dropActiveItem(Object itemId) {
- if (activeItemMap.containsKey(itemId)) {
- droppedItems.add(itemId);
- }
- }
-
- /**
- * Gets a collection copy of currently active item ids.
- *
- * @return collection of item ids
- */
- public Collection<Object> getActiveItemIds() {
- return new HashSet<Object>(activeItemMap.keySet());
- }
-
- /**
- * Gets a collection copy of currently active ValueChangeListeners.
- *
- * @return collection of value change listeners
- */
- public Collection<GridValueChangeListener> getValueChangeListeners() {
- return new HashSet<GridValueChangeListener>(activeItemMap.values());
- }
-
- @Override
- public void generateData(Object itemId, Item item, JsonObject rowData) {
- rowData.put(GridState.JSONKEY_ROWKEY, keyMapper.key(itemId));
- }
-
- @Override
- public void destroyData(Object itemId) {
- keyMapper.remove(itemId);
- removeListener(itemId);
- }
-
- private void removeListener(Object itemId) {
- GridValueChangeListener removed = activeItemMap.remove(itemId);
-
- if (removed != null) {
- removed.removeListener();
- }
- }
- }
-
- /**
- * A class to listen to changes in property values in the Container added
- * with
- * {@link Grid#setContainerDatasource(com.vaadin.v7.data.Container.Indexed)
- * Grid#setContainerDatasource(Container.Indexed)}, and notifies the data
- * source to update the client-side representation of the modified item.
- * <p>
- * One instance of this class can (and should) be reused for all the
- * properties in an item, since this class will inform that the entire row
- * needs to be re-evaluated (in contrast to a property-based change
- * management)
- * <p>
- * Since there's no Container-wide possibility to listen to any kind of
- * value changes, an instance of this class needs to be attached to each and
- * every Item's Property in the container.
- *
- * @see Grid#addValueChangeListener(com.vaadin.v7.data.Container, Object,
- * Object) Grid#addValueChangeListener(Container, Object, Object)
- * @see Grid#valueChangeListeners
- */
- private class GridValueChangeListener implements ValueChangeListener {
- private final Object itemId;
- private final Item item;
-
- public GridValueChangeListener(Object itemId, Item item) {
- /*
- * Using an assert instead of an exception throw, just to optimize
- * prematurely
- */
- assert itemId != null : "null itemId not accepted";
- this.itemId = itemId;
- this.item = item;
-
- internalAddColumns(getGrid().getColumns());
- }
-
- @Override
- public void valueChange(ValueChangeEvent event) {
- updateRowData(itemId);
- }
-
- public void removeListener() {
- removeColumns(getGrid().getColumns());
- }
-
- public void addColumns(Collection<Column> addedColumns) {
- internalAddColumns(addedColumns);
- updateRowData(itemId);
- }
-
- private void internalAddColumns(Collection<Column> addedColumns) {
- for (final Column column : addedColumns) {
- final Property<?> property = item
- .getItemProperty(column.getPropertyId());
- if (property instanceof ValueChangeNotifier) {
- ((ValueChangeNotifier) property)
- .addValueChangeListener(this);
- }
- }
- }
-
- public void removeColumns(Collection<Column> removedColumns) {
- for (final Column column : removedColumns) {
- final Property<?> property = item
- .getItemProperty(column.getPropertyId());
- if (property instanceof ValueChangeNotifier) {
- ((ValueChangeNotifier) property)
- .removeValueChangeListener(this);
- }
- }
- }
- }
-
- private final Indexed container;
-
- private DataProviderRpc rpc;
-
- private final ItemSetChangeListener itemListener = new ItemSetChangeListener() {
- @Override
- public void containerItemSetChange(ItemSetChangeEvent event) {
-
- if (event instanceof ItemAddEvent) {
- ItemAddEvent addEvent = (ItemAddEvent) event;
- int firstIndex = addEvent.getFirstIndex();
- int count = addEvent.getAddedItemsCount();
- insertRowData(firstIndex, count);
- } else if (event instanceof ItemRemoveEvent) {
- ItemRemoveEvent removeEvent = (ItemRemoveEvent) event;
- int firstIndex = removeEvent.getFirstIndex();
- int count = removeEvent.getRemovedItemsCount();
- removeRowData(firstIndex, count);
- } else {
- // Remove obsolete value change listeners.
- Set<Object> keySet = new HashSet<Object>(
- activeItemHandler.activeItemMap.keySet());
- for (Object itemId : keySet) {
- activeItemHandler.removeListener(itemId);
- }
-
- /* Mark as dirty to push changes in beforeClientResponse */
- bareItemSetTriggeredSizeChange = true;
- markAsDirty();
- }
- }
- };
-
- /** RpcDataProvider should send the current cache again. */
- private boolean refreshCache = false;
-
- /** Set of updated item ids */
- private transient Set<Object> updatedItemIds;
-
- /**
- * Queued RPC calls for adding and removing rows. Queue will be handled in
- * {@link beforeClientResponse}
- */
- private transient List<Runnable> rowChanges;
-
- /** Size possibly changed with a bare ItemSetChangeEvent */
- private boolean bareItemSetTriggeredSizeChange = false;
-
- private final Set<DataGenerator> dataGenerators = new LinkedHashSet<DataGenerator>();
-
- private final ActiveItemHandler activeItemHandler = new ActiveItemHandler();
-
- /**
- * Creates a new data provider using the given container.
- *
- * @param container
- * the container to make available
- */
- @Deprecated
- public RpcDataProviderExtension(Indexed container) {
- this.container = container;
- rpc = getRpcProxy(DataProviderRpc.class);
-
- registerRpc(new DataRequestRpc() {
- @Override
- public void requestRows(int firstRow, int numberOfRows,
- int firstCachedRowIndex, int cacheSize) {
- pushRowData(firstRow, numberOfRows, firstCachedRowIndex,
- cacheSize);
- }
-
- @Override
- public void dropRows(JsonArray rowKeys) {
- for (int i = 0; i < rowKeys.length(); ++i) {
- activeItemHandler.dropActiveItem(
- getKeyMapper().get(rowKeys.getString(i)));
- }
- }
- });
-
- if (container instanceof ItemSetChangeNotifier) {
- ((ItemSetChangeNotifier) container)
- .addItemSetChangeListener(itemListener);
- }
-
- addDataGenerator(activeItemHandler);
- }
-
- /**
- * {@inheritDoc}
- * <p>
- * RpcDataProviderExtension makes all actual RPC calls from this function
- * based on changes in the container.
- */
- @Override
- public void beforeClientResponse(boolean initial) {
- if (initial || bareItemSetTriggeredSizeChange) {
- /*
- * Push initial set of rows, assuming Grid will initially be
- * rendered scrolled to the top and with a decent amount of rows
- * visible. If this guess is right, initial data can be shown
- * without a round-trip and if it's wrong, the data will simply be
- * discarded.
- */
- int size = container.size();
- rpc.resetDataAndSize(size);
-
- int numberOfRows = Math.min(40, size);
- pushRowData(0, numberOfRows, 0, 0);
- } else {
- // Only do row changes if not initial response.
- if (rowChanges != null) {
- for (Runnable r : rowChanges) {
- r.run();
- }
- }
-
- // Send current rows again if needed.
- if (refreshCache) {
- for (Object itemId : activeItemHandler.getActiveItemIds()) {
- updateRowData(itemId);
- }
- }
- }
-
- internalUpdateRows(updatedItemIds);
-
- // Clear all changes.
- if (rowChanges != null) {
- rowChanges.clear();
- }
- if (updatedItemIds != null) {
- updatedItemIds.clear();
- }
- refreshCache = false;
- bareItemSetTriggeredSizeChange = false;
-
- super.beforeClientResponse(initial);
- }
-
- private void pushRowData(int firstRowToPush, int numberOfRows,
- int firstCachedRowIndex, int cacheSize) {
- Range newRange = Range.withLength(firstRowToPush, numberOfRows);
- Range cached = Range.withLength(firstCachedRowIndex, cacheSize);
- Range fullRange = newRange;
- if (!cached.isEmpty()) {
- fullRange = newRange.combineWith(cached);
- }
-
- List<?> itemIds = container.getItemIds(fullRange.getStart(),
- fullRange.length());
-
- JsonArray rows = Json.createArray();
-
- // Offset the index to match the wanted range.
- int diff = 0;
- if (!cached.isEmpty() && newRange.getStart() > cached.getStart()) {
- diff = cached.length();
- }
-
- for (int i = 0; i < newRange.length()
- && i + diff < itemIds.size(); ++i) {
- Object itemId = itemIds.get(i + diff);
-
- Item item = container.getItem(itemId);
-
- rows.set(i, getRowData(itemId, item));
- }
- rpc.setRowData(firstRowToPush, rows);
-
- activeItemHandler.addActiveItems(itemIds);
- }
-
- private JsonObject getRowData(Object itemId, Item item) {
-
- final JsonObject rowObject = Json.createObject();
- for (DataGenerator dg : dataGenerators) {
- dg.generateData(itemId, item, rowObject);
- }
-
- return rowObject;
- }
-
- /**
- * Makes the data source available to the given {@link Grid} component.
- *
- * @param component
- * the remote data grid component to extend
- */
- public void extend(Grid component) {
- super.extend(component);
- }
-
- /**
- * Adds a {@link DataGenerator} for this {@code RpcDataProviderExtension}.
- * DataGenerators are called when sending row data to client. If given
- * DataGenerator is already added, this method does nothing.
- *
- * @since 7.6
- * @param generator
- * generator to add
- */
- public void addDataGenerator(DataGenerator generator) {
- dataGenerators.add(generator);
- }
-
- /**
- * Removes a {@link DataGenerator} from this
- * {@code RpcDataProviderExtension}. If given DataGenerator is not added to
- * this data provider, this method does nothing.
- *
- * @since 7.6
- * @param generator
- * generator to remove
- */
- public void removeDataGenerator(DataGenerator generator) {
- dataGenerators.remove(generator);
- }
-
- /**
- * Informs the client side that new rows have been inserted into the data
- * source.
- *
- * @param index
- * the index at which new rows have been inserted
- * @param count
- * the number of rows inserted at <code>index</code>
- */
- private void insertRowData(final int index, final int count) {
- if (rowChanges == null) {
- rowChanges = new ArrayList<Runnable>();
- }
-
- if (rowChanges.isEmpty()) {
- markAsDirty();
- }
-
- /*
- * Since all changes should be processed in a consistent order, we don't
- * send the RPC call immediately. beforeClientResponse will decide
- * whether to send these or not. Valid situation to not send these is
- * initial response or bare ItemSetChange event.
- */
- rowChanges.add(new Runnable() {
- @Override
- public void run() {
- rpc.insertRowData(index, count);
- }
- });
- }
-
- /**
- * Informs the client side that rows have been removed from the data source.
- *
- * @param index
- * the index of the first row removed
- * @param count
- * the number of rows removed
- * @param firstItemId
- * the item id of the first removed item
- */
- private void removeRowData(final int index, final int count) {
- if (rowChanges == null) {
- rowChanges = new ArrayList<Runnable>();
- }
-
- if (rowChanges.isEmpty()) {
- markAsDirty();
- }
-
- /* See comment in insertRowData */
- rowChanges.add(new Runnable() {
- @Override
- public void run() {
- rpc.removeRowData(index, count);
- }
- });
- }
-
- /**
- * Informs the client side that data of a row has been modified in the data
- * source.
- *
- * @param itemId
- * the item Id the row that was updated
- */
- public void updateRowData(Object itemId) {
- if (updatedItemIds == null) {
- updatedItemIds = new LinkedHashSet<Object>();
- }
-
- if (updatedItemIds.isEmpty()) {
- // At least one new item will be updated. Mark as dirty to actually
- // update before response to client.
- markAsDirty();
- }
-
- updatedItemIds.add(itemId);
- }
-
- private void internalUpdateRows(Set<Object> itemIds) {
- if (itemIds == null || itemIds.isEmpty()) {
- return;
- }
-
- Collection<Object> activeItemIds = activeItemHandler.getActiveItemIds();
- JsonArray rowData = Json.createArray();
- int i = 0;
- for (Object itemId : itemIds) {
- if (activeItemIds.contains(itemId)) {
- Item item = container.getItem(itemId);
- if (item != null) {
- JsonObject row = getRowData(itemId, item);
- rowData.set(i++, row);
- }
- }
- }
- rpc.updateRowData(rowData);
- }
-
- /**
- * Pushes a new version of all the rows in the active cache range.
- */
- public void refreshCache() {
- if (!refreshCache) {
- refreshCache = true;
- markAsDirty();
- }
- }
-
- @Override
- public void setParent(ClientConnector parent) {
- if (parent == null) {
- // We're being detached, release various listeners
- internalDropItems(activeItemHandler.getActiveItemIds());
-
- if (container instanceof ItemSetChangeNotifier) {
- ((ItemSetChangeNotifier) container)
- .removeItemSetChangeListener(itemListener);
- }
-
- } else if (!(parent instanceof Grid)) {
- throw new IllegalStateException(
- "Grid is the only accepted parent type");
- }
- super.setParent(parent);
- }
-
- /**
- * Informs all DataGenerators than an item id has been dropped.
- *
- * @param droppedItemIds
- * collection of dropped item ids
- */
- private void internalDropItems(Collection<Object> droppedItemIds) {
- for (Object itemId : droppedItemIds) {
- for (DataGenerator generator : dataGenerators) {
- generator.destroyData(itemId);
- }
- }
- }
-
- /**
- * Informs this data provider that given columns have been removed from
- * grid.
- *
- * @param removedColumns
- * a list of removed columns
- */
- public void columnsRemoved(List<Column> removedColumns) {
- for (GridValueChangeListener l : activeItemHandler
- .getValueChangeListeners()) {
- l.removeColumns(removedColumns);
- }
-
- // No need to resend unchanged data. Client will remember the old
- // columns until next set of rows is sent.
- }
-
- /**
- * Informs this data provider that given columns have been added to grid.
- *
- * @param addedColumns
- * a list of added columns
- */
- public void columnsAdded(List<Column> addedColumns) {
- for (GridValueChangeListener l : activeItemHandler
- .getValueChangeListeners()) {
- l.addColumns(addedColumns);
- }
-
- // Resend all rows to contain new data.
- refreshCache();
- }
-
- public KeyMapper<Object> getKeyMapper() {
- return activeItemHandler.keyMapper;
- }
-
- protected Grid getGrid() {
- return (Grid) getParent();
- }
- }
|