You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RpcDataProviderExtension.java 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. /*
  2. * Copyright 2000-2021 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.v7.server.communication.data;
  17. import java.util.ArrayList;
  18. import java.util.Collection;
  19. import java.util.HashMap;
  20. import java.util.HashSet;
  21. import java.util.LinkedHashSet;
  22. import java.util.List;
  23. import java.util.Map;
  24. import java.util.Set;
  25. import com.vaadin.server.AbstractExtension;
  26. import com.vaadin.server.ClientConnector;
  27. import com.vaadin.server.KeyMapper;
  28. import com.vaadin.shared.Range;
  29. import com.vaadin.shared.data.DataProviderRpc;
  30. import com.vaadin.shared.data.DataRequestRpc;
  31. import com.vaadin.v7.data.Container.Indexed;
  32. import com.vaadin.v7.data.Container.Indexed.ItemAddEvent;
  33. import com.vaadin.v7.data.Container.Indexed.ItemRemoveEvent;
  34. import com.vaadin.v7.data.Container.ItemSetChangeEvent;
  35. import com.vaadin.v7.data.Container.ItemSetChangeListener;
  36. import com.vaadin.v7.data.Container.ItemSetChangeNotifier;
  37. import com.vaadin.v7.data.Item;
  38. import com.vaadin.v7.data.Property;
  39. import com.vaadin.v7.data.Property.ValueChangeEvent;
  40. import com.vaadin.v7.data.Property.ValueChangeListener;
  41. import com.vaadin.v7.data.Property.ValueChangeNotifier;
  42. import com.vaadin.v7.shared.ui.grid.GridState;
  43. import com.vaadin.v7.ui.Grid;
  44. import com.vaadin.v7.ui.Grid.Column;
  45. import elemental.json.Json;
  46. import elemental.json.JsonArray;
  47. import elemental.json.JsonObject;
  48. /**
  49. * Provides Vaadin server-side container data source to a
  50. * {@link com.vaadin.v7.client.connectors.GridConnector GridConnector}. This is
  51. * currently implemented as an Extension hardcoded to support a specific
  52. * connector type. This will be changed once framework support for something
  53. * more flexible has been implemented.
  54. *
  55. * @since 7.4
  56. * @author Vaadin Ltd
  57. *
  58. * @deprecated As of 8.0, no replacement available.
  59. */
  60. @Deprecated
  61. public class RpcDataProviderExtension extends AbstractExtension {
  62. /**
  63. * Class for keeping track of current items and ValueChangeListeners.
  64. *
  65. * @since 7.6
  66. */
  67. private class ActiveItemHandler implements DataGenerator {
  68. private final Map<Object, GridValueChangeListener> activeItemMap = new HashMap<Object, GridValueChangeListener>();
  69. private final KeyMapper<Object> keyMapper = new KeyMapper<Object>();
  70. private final Set<Object> droppedItems = new HashSet<Object>();
  71. /**
  72. * Registers ValueChangeListeners for given item ids.
  73. * <p>
  74. * Note: This method will clean up any unneeded listeners and key
  75. * mappings
  76. *
  77. * @param itemIds
  78. * collection of new active item ids
  79. */
  80. public void addActiveItems(Collection<?> itemIds) {
  81. for (Object itemId : itemIds) {
  82. if (!activeItemMap.containsKey(itemId)) {
  83. activeItemMap.put(itemId, new GridValueChangeListener(
  84. itemId, container.getItem(itemId)));
  85. }
  86. }
  87. // Remove still active rows that were "dropped"
  88. droppedItems.removeAll(itemIds);
  89. internalDropItems(droppedItems);
  90. droppedItems.clear();
  91. }
  92. /**
  93. * Marks given item id as dropped. Dropped items are cleared when adding
  94. * new active items.
  95. *
  96. * @param itemId
  97. * dropped item id
  98. */
  99. public void dropActiveItem(Object itemId) {
  100. if (activeItemMap.containsKey(itemId)) {
  101. droppedItems.add(itemId);
  102. }
  103. }
  104. /**
  105. * Gets a collection copy of currently active item ids.
  106. *
  107. * @return collection of item ids
  108. */
  109. public Collection<Object> getActiveItemIds() {
  110. return new HashSet<Object>(activeItemMap.keySet());
  111. }
  112. /**
  113. * Gets a collection copy of currently active ValueChangeListeners.
  114. *
  115. * @return collection of value change listeners
  116. */
  117. public Collection<GridValueChangeListener> getValueChangeListeners() {
  118. return new HashSet<GridValueChangeListener>(activeItemMap.values());
  119. }
  120. @Override
  121. public void generateData(Object itemId, Item item, JsonObject rowData) {
  122. rowData.put(GridState.JSONKEY_ROWKEY, keyMapper.key(itemId));
  123. }
  124. @Override
  125. public void destroyData(Object itemId) {
  126. keyMapper.remove(itemId);
  127. removeListener(itemId);
  128. }
  129. private void removeListener(Object itemId) {
  130. GridValueChangeListener removed = activeItemMap.remove(itemId);
  131. if (removed != null) {
  132. removed.removeListener();
  133. }
  134. }
  135. }
  136. /**
  137. * A class to listen to changes in property values in the Container added
  138. * with
  139. * {@link Grid#setContainerDatasource(com.vaadin.v7.data.Container.Indexed)
  140. * Grid#setContainerDatasource(Container.Indexed)}, and notifies the data
  141. * source to update the client-side representation of the modified item.
  142. * <p>
  143. * One instance of this class can (and should) be reused for all the
  144. * properties in an item, since this class will inform that the entire row
  145. * needs to be re-evaluated (in contrast to a property-based change
  146. * management)
  147. * <p>
  148. * Since there's no Container-wide possibility to listen to any kind of
  149. * value changes, an instance of this class needs to be attached to each and
  150. * every Item's Property in the container.
  151. *
  152. * @see Grid#addValueChangeListener(com.vaadin.v7.data.Container, Object,
  153. * Object) Grid#addValueChangeListener(Container, Object, Object)
  154. * @see Grid#valueChangeListeners
  155. */
  156. private class GridValueChangeListener implements ValueChangeListener {
  157. private final Object itemId;
  158. private final Item item;
  159. public GridValueChangeListener(Object itemId, Item item) {
  160. /*
  161. * Using an assert instead of an exception throw, just to optimize
  162. * prematurely
  163. */
  164. assert itemId != null : "null itemId not accepted";
  165. this.itemId = itemId;
  166. this.item = item;
  167. internalAddColumns(getGrid().getColumns());
  168. }
  169. @Override
  170. public void valueChange(ValueChangeEvent event) {
  171. updateRowData(itemId);
  172. }
  173. public void removeListener() {
  174. removeColumns(getGrid().getColumns());
  175. }
  176. public void addColumns(Collection<Column> addedColumns) {
  177. internalAddColumns(addedColumns);
  178. updateRowData(itemId);
  179. }
  180. private void internalAddColumns(Collection<Column> addedColumns) {
  181. for (final Column column : addedColumns) {
  182. final Property<?> property = item
  183. .getItemProperty(column.getPropertyId());
  184. if (property instanceof ValueChangeNotifier) {
  185. ((ValueChangeNotifier) property)
  186. .addValueChangeListener(this);
  187. }
  188. }
  189. }
  190. public void removeColumns(Collection<Column> removedColumns) {
  191. for (final Column column : removedColumns) {
  192. final Property<?> property = item
  193. .getItemProperty(column.getPropertyId());
  194. if (property instanceof ValueChangeNotifier) {
  195. ((ValueChangeNotifier) property)
  196. .removeValueChangeListener(this);
  197. }
  198. }
  199. }
  200. }
  201. private final Indexed container;
  202. private DataProviderRpc rpc;
  203. private final ItemSetChangeListener itemListener = new ItemSetChangeListener() {
  204. @Override
  205. public void containerItemSetChange(ItemSetChangeEvent event) {
  206. if (event instanceof ItemAddEvent) {
  207. ItemAddEvent addEvent = (ItemAddEvent) event;
  208. int firstIndex = addEvent.getFirstIndex();
  209. int count = addEvent.getAddedItemsCount();
  210. insertRowData(firstIndex, count);
  211. } else if (event instanceof ItemRemoveEvent) {
  212. ItemRemoveEvent removeEvent = (ItemRemoveEvent) event;
  213. int firstIndex = removeEvent.getFirstIndex();
  214. int count = removeEvent.getRemovedItemsCount();
  215. removeRowData(firstIndex, count);
  216. } else {
  217. // Remove obsolete value change listeners.
  218. Set<Object> keySet = new HashSet<Object>(
  219. activeItemHandler.activeItemMap.keySet());
  220. for (Object itemId : keySet) {
  221. activeItemHandler.removeListener(itemId);
  222. }
  223. /* Mark as dirty to push changes in beforeClientResponse */
  224. bareItemSetTriggeredSizeChange = true;
  225. markAsDirty();
  226. }
  227. }
  228. };
  229. /** RpcDataProvider should send the current cache again. */
  230. private boolean refreshCache = false;
  231. /** Set of updated item ids */
  232. private transient Set<Object> updatedItemIds;
  233. /**
  234. * Queued RPC calls for adding and removing rows. Queue will be handled in
  235. * {@link beforeClientResponse}
  236. */
  237. private transient List<Runnable> rowChanges;
  238. /** Size possibly changed with a bare ItemSetChangeEvent */
  239. private boolean bareItemSetTriggeredSizeChange = false;
  240. private final Set<DataGenerator> dataGenerators = new LinkedHashSet<DataGenerator>();
  241. private final ActiveItemHandler activeItemHandler = new ActiveItemHandler();
  242. /**
  243. * Creates a new data provider using the given container.
  244. *
  245. * @param container
  246. * the container to make available
  247. */
  248. @Deprecated
  249. public RpcDataProviderExtension(Indexed container) {
  250. this.container = container;
  251. rpc = getRpcProxy(DataProviderRpc.class);
  252. registerRpc(new DataRequestRpc() {
  253. @Override
  254. public void requestRows(int firstRow, int numberOfRows,
  255. int firstCachedRowIndex, int cacheSize) {
  256. pushRowData(firstRow, numberOfRows, firstCachedRowIndex,
  257. cacheSize);
  258. }
  259. @Override
  260. public void dropRows(JsonArray rowKeys) {
  261. for (int i = 0; i < rowKeys.length(); ++i) {
  262. activeItemHandler.dropActiveItem(
  263. getKeyMapper().get(rowKeys.getString(i)));
  264. }
  265. }
  266. });
  267. if (container instanceof ItemSetChangeNotifier) {
  268. ((ItemSetChangeNotifier) container)
  269. .addItemSetChangeListener(itemListener);
  270. }
  271. addDataGenerator(activeItemHandler);
  272. }
  273. /**
  274. * {@inheritDoc}
  275. * <p>
  276. * RpcDataProviderExtension makes all actual RPC calls from this function
  277. * based on changes in the container.
  278. */
  279. @Override
  280. public void beforeClientResponse(boolean initial) {
  281. if (initial || bareItemSetTriggeredSizeChange) {
  282. /*
  283. * Push initial set of rows, assuming Grid will initially be
  284. * rendered scrolled to the top and with a decent amount of rows
  285. * visible. If this guess is right, initial data can be shown
  286. * without a round-trip and if it's wrong, the data will simply be
  287. * discarded.
  288. */
  289. int size = container.size();
  290. rpc.resetDataAndSize(size);
  291. int numberOfRows = Math.min(40, size);
  292. pushRowData(0, numberOfRows, 0, 0);
  293. } else {
  294. // Only do row changes if not initial response.
  295. if (rowChanges != null) {
  296. for (Runnable r : rowChanges) {
  297. r.run();
  298. }
  299. }
  300. // Send current rows again if needed.
  301. if (refreshCache) {
  302. for (Object itemId : activeItemHandler.getActiveItemIds()) {
  303. updateRowData(itemId);
  304. }
  305. }
  306. }
  307. internalUpdateRows(updatedItemIds);
  308. // Clear all changes.
  309. if (rowChanges != null) {
  310. rowChanges.clear();
  311. }
  312. if (updatedItemIds != null) {
  313. updatedItemIds.clear();
  314. }
  315. refreshCache = false;
  316. bareItemSetTriggeredSizeChange = false;
  317. super.beforeClientResponse(initial);
  318. }
  319. private void pushRowData(int firstRowToPush, int numberOfRows,
  320. int firstCachedRowIndex, int cacheSize) {
  321. Range newRange = Range.withLength(firstRowToPush, numberOfRows);
  322. Range cached = Range.withLength(firstCachedRowIndex, cacheSize);
  323. Range fullRange = newRange;
  324. if (!cached.isEmpty()) {
  325. fullRange = newRange.combineWith(cached);
  326. }
  327. List<?> itemIds = container.getItemIds(fullRange.getStart(),
  328. fullRange.length());
  329. JsonArray rows = Json.createArray();
  330. // Offset the index to match the wanted range.
  331. int diff = 0;
  332. if (!cached.isEmpty() && newRange.getStart() > cached.getStart()) {
  333. diff = cached.length();
  334. }
  335. for (int i = 0; i < newRange.length()
  336. && i + diff < itemIds.size(); ++i) {
  337. Object itemId = itemIds.get(i + diff);
  338. Item item = container.getItem(itemId);
  339. rows.set(i, getRowData(itemId, item));
  340. }
  341. rpc.setRowData(firstRowToPush, rows);
  342. activeItemHandler.addActiveItems(itemIds);
  343. }
  344. private JsonObject getRowData(Object itemId, Item item) {
  345. final JsonObject rowObject = Json.createObject();
  346. for (DataGenerator dg : dataGenerators) {
  347. dg.generateData(itemId, item, rowObject);
  348. }
  349. return rowObject;
  350. }
  351. /**
  352. * Makes the data source available to the given {@link Grid} component.
  353. *
  354. * @param component
  355. * the remote data grid component to extend
  356. */
  357. public void extend(Grid component) {
  358. super.extend(component);
  359. }
  360. /**
  361. * Adds a {@link DataGenerator} for this {@code RpcDataProviderExtension}.
  362. * DataGenerators are called when sending row data to client. If given
  363. * DataGenerator is already added, this method does nothing.
  364. *
  365. * @since 7.6
  366. * @param generator
  367. * generator to add
  368. */
  369. public void addDataGenerator(DataGenerator generator) {
  370. dataGenerators.add(generator);
  371. }
  372. /**
  373. * Removes a {@link DataGenerator} from this
  374. * {@code RpcDataProviderExtension}. If given DataGenerator is not added to
  375. * this data provider, this method does nothing.
  376. *
  377. * @since 7.6
  378. * @param generator
  379. * generator to remove
  380. */
  381. public void removeDataGenerator(DataGenerator generator) {
  382. dataGenerators.remove(generator);
  383. }
  384. /**
  385. * Informs the client side that new rows have been inserted into the data
  386. * source.
  387. *
  388. * @param index
  389. * the index at which new rows have been inserted
  390. * @param count
  391. * the number of rows inserted at <code>index</code>
  392. */
  393. private void insertRowData(final int index, final int count) {
  394. if (rowChanges == null) {
  395. rowChanges = new ArrayList<Runnable>();
  396. }
  397. if (rowChanges.isEmpty()) {
  398. markAsDirty();
  399. }
  400. /*
  401. * Since all changes should be processed in a consistent order, we don't
  402. * send the RPC call immediately. beforeClientResponse will decide
  403. * whether to send these or not. Valid situation to not send these is
  404. * initial response or bare ItemSetChange event.
  405. */
  406. rowChanges.add(new Runnable() {
  407. @Override
  408. public void run() {
  409. rpc.insertRowData(index, count);
  410. }
  411. });
  412. }
  413. /**
  414. * Informs the client side that rows have been removed from the data source.
  415. *
  416. * @param index
  417. * the index of the first row removed
  418. * @param count
  419. * the number of rows removed
  420. * @param firstItemId
  421. * the item id of the first removed item
  422. */
  423. private void removeRowData(final int index, final int count) {
  424. if (rowChanges == null) {
  425. rowChanges = new ArrayList<Runnable>();
  426. }
  427. if (rowChanges.isEmpty()) {
  428. markAsDirty();
  429. }
  430. /* See comment in insertRowData */
  431. rowChanges.add(new Runnable() {
  432. @Override
  433. public void run() {
  434. rpc.removeRowData(index, count);
  435. }
  436. });
  437. }
  438. /**
  439. * Informs the client side that data of a row has been modified in the data
  440. * source.
  441. *
  442. * @param itemId
  443. * the item Id the row that was updated
  444. */
  445. public void updateRowData(Object itemId) {
  446. if (updatedItemIds == null) {
  447. updatedItemIds = new LinkedHashSet<Object>();
  448. }
  449. if (updatedItemIds.isEmpty()) {
  450. // At least one new item will be updated. Mark as dirty to actually
  451. // update before response to client.
  452. markAsDirty();
  453. }
  454. updatedItemIds.add(itemId);
  455. }
  456. private void internalUpdateRows(Set<Object> itemIds) {
  457. if (itemIds == null || itemIds.isEmpty()) {
  458. return;
  459. }
  460. Collection<Object> activeItemIds = activeItemHandler.getActiveItemIds();
  461. JsonArray rowData = Json.createArray();
  462. int i = 0;
  463. for (Object itemId : itemIds) {
  464. if (activeItemIds.contains(itemId)) {
  465. Item item = container.getItem(itemId);
  466. if (item != null) {
  467. JsonObject row = getRowData(itemId, item);
  468. rowData.set(i++, row);
  469. }
  470. }
  471. }
  472. rpc.updateRowData(rowData);
  473. }
  474. /**
  475. * Pushes a new version of all the rows in the active cache range.
  476. */
  477. public void refreshCache() {
  478. if (!refreshCache) {
  479. refreshCache = true;
  480. markAsDirty();
  481. }
  482. }
  483. @Override
  484. public void setParent(ClientConnector parent) {
  485. if (parent == null) {
  486. // We're being detached, release various listeners
  487. internalDropItems(activeItemHandler.getActiveItemIds());
  488. if (container instanceof ItemSetChangeNotifier) {
  489. ((ItemSetChangeNotifier) container)
  490. .removeItemSetChangeListener(itemListener);
  491. }
  492. } else if (!(parent instanceof Grid)) {
  493. throw new IllegalStateException(
  494. "Grid is the only accepted parent type");
  495. }
  496. super.setParent(parent);
  497. }
  498. /**
  499. * Informs all DataGenerators than an item id has been dropped.
  500. *
  501. * @param droppedItemIds
  502. * collection of dropped item ids
  503. */
  504. private void internalDropItems(Collection<Object> droppedItemIds) {
  505. for (Object itemId : droppedItemIds) {
  506. for (DataGenerator generator : dataGenerators) {
  507. generator.destroyData(itemId);
  508. }
  509. }
  510. }
  511. /**
  512. * Informs this data provider that given columns have been removed from
  513. * grid.
  514. *
  515. * @param removedColumns
  516. * a list of removed columns
  517. */
  518. public void columnsRemoved(List<Column> removedColumns) {
  519. for (GridValueChangeListener l : activeItemHandler
  520. .getValueChangeListeners()) {
  521. l.removeColumns(removedColumns);
  522. }
  523. // No need to resend unchanged data. Client will remember the old
  524. // columns until next set of rows is sent.
  525. }
  526. /**
  527. * Informs this data provider that given columns have been added to grid.
  528. *
  529. * @param addedColumns
  530. * a list of added columns
  531. */
  532. public void columnsAdded(List<Column> addedColumns) {
  533. for (GridValueChangeListener l : activeItemHandler
  534. .getValueChangeListeners()) {
  535. l.addColumns(addedColumns);
  536. }
  537. // Resend all rows to contain new data.
  538. refreshCache();
  539. }
  540. public KeyMapper<Object> getKeyMapper() {
  541. return activeItemHandler.keyMapper;
  542. }
  543. protected Grid getGrid() {
  544. return (Grid) getParent();
  545. }
  546. }