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.

DataCommunicator.java 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  1. /*
  2. * Copyright 2000-2016 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.data.provider;
  17. import java.io.Serializable;
  18. import java.util.ArrayList;
  19. import java.util.Collection;
  20. import java.util.Comparator;
  21. import java.util.HashSet;
  22. import java.util.LinkedHashSet;
  23. import java.util.List;
  24. import java.util.Objects;
  25. import java.util.Set;
  26. import java.util.stream.Collectors;
  27. import java.util.stream.Stream;
  28. import com.vaadin.data.provider.DataChangeEvent.DataRefreshEvent;
  29. import com.vaadin.server.AbstractExtension;
  30. import com.vaadin.server.KeyMapper;
  31. import com.vaadin.server.SerializableConsumer;
  32. import com.vaadin.shared.Range;
  33. import com.vaadin.shared.Registration;
  34. import com.vaadin.shared.data.DataCommunicatorClientRpc;
  35. import com.vaadin.shared.data.DataCommunicatorConstants;
  36. import com.vaadin.shared.data.DataRequestRpc;
  37. import com.vaadin.shared.extension.datacommunicator.DataCommunicatorState;
  38. import elemental.json.Json;
  39. import elemental.json.JsonArray;
  40. import elemental.json.JsonObject;
  41. /**
  42. * DataProvider base class. This class is the base for all DataProvider
  43. * communication implementations. It uses {@link DataGenerator}s to write
  44. * {@link JsonObject}s representing each data object to be sent to the
  45. * client-side.
  46. *
  47. * @param <T>
  48. * the bean type
  49. *
  50. * @since 8.0
  51. */
  52. public class DataCommunicator<T> extends AbstractExtension {
  53. private Registration dataProviderUpdateRegistration;
  54. /**
  55. * Simple implementation of collection data provider communication. All data
  56. * is sent by server automatically and no data is requested by client.
  57. */
  58. protected class SimpleDataRequestRpc implements DataRequestRpc {
  59. @Override
  60. public void requestRows(int firstRowIndex, int numberOfRows,
  61. int firstCachedRowIndex, int cacheSize) {
  62. onRequestRows(firstRowIndex, numberOfRows, firstCachedRowIndex,
  63. cacheSize);
  64. }
  65. @Override
  66. public void dropRows(JsonArray keys) {
  67. onDropRows(keys);
  68. }
  69. }
  70. /**
  71. * A class for handling currently active data and dropping data that is no
  72. * longer needed. Data tracking is based on key string provided by
  73. * {@link DataKeyMapper}.
  74. * <p>
  75. * When the {@link DataCommunicator} is pushing new data to the client-side
  76. * via {@link DataCommunicator#pushData(int, Stream)},
  77. * {@link #addActiveData(Stream)} and {@link #cleanUp(Stream)} are called
  78. * with the same parameter. In the clean up method any dropped data objects
  79. * that are not in the given collection will be cleaned up and
  80. * {@link DataGenerator#destroyData(Object)} will be called for them.
  81. */
  82. protected class ActiveDataHandler
  83. implements Serializable, DataGenerator<T> {
  84. /**
  85. * Set of key strings for currently active data objects
  86. */
  87. private final Set<String> activeData = new HashSet<>();
  88. /**
  89. * Set of key strings for data objects dropped on the client. This set
  90. * is used to clean up old data when it's no longer needed.
  91. */
  92. private final Set<String> droppedData = new HashSet<>();
  93. /**
  94. * Adds given objects as currently active objects.
  95. *
  96. * @param dataObjects
  97. * collection of new active data objects
  98. */
  99. public void addActiveData(Stream<T> dataObjects) {
  100. dataObjects.map(getKeyMapper()::key)
  101. .filter(key -> !activeData.contains(key))
  102. .forEach(activeData::add);
  103. }
  104. /**
  105. * Executes the data destruction for dropped data that is not sent to
  106. * the client. This method takes most recently sent data objects in a
  107. * collection. Doing the clean up like this prevents the
  108. * {@link ActiveDataHandler} from creating new keys for rows that were
  109. * dropped but got re-requested by the client-side. In the case of
  110. * having all data at the client, the collection should be all the data
  111. * in the back end.
  112. *
  113. * @param dataObjects
  114. * collection of most recently sent data to the client
  115. */
  116. public void cleanUp(Stream<T> dataObjects) {
  117. Collection<String> keys = dataObjects.map(getKeyMapper()::key)
  118. .collect(Collectors.toSet());
  119. // Remove still active rows that were dropped by the client
  120. droppedData.removeAll(keys);
  121. // Do data clean up for object no longer needed.
  122. dropData(droppedData);
  123. droppedData.clear();
  124. }
  125. /**
  126. * Marks a data object identified by given key string to be dropped.
  127. *
  128. * @param key
  129. * key string
  130. */
  131. public void dropActiveData(String key) {
  132. if (activeData.contains(key)) {
  133. droppedData.add(key);
  134. }
  135. }
  136. /**
  137. * Returns the collection of all currently active data.
  138. *
  139. * @return collection of active data objects
  140. */
  141. public Collection<T> getActiveData() {
  142. HashSet<T> hashSet = new HashSet<>();
  143. for (String key : activeData) {
  144. hashSet.add(getKeyMapper().get(key));
  145. }
  146. return hashSet;
  147. }
  148. @Override
  149. public void generateData(T data, JsonObject jsonObject) {
  150. // Write the key string for given data object
  151. jsonObject.put(DataCommunicatorConstants.KEY,
  152. getKeyMapper().key(data));
  153. }
  154. @Override
  155. public void destroyData(T data) {
  156. // Remove from active data set
  157. activeData.remove(getKeyMapper().key(data));
  158. // Drop the registered key
  159. getKeyMapper().remove(data);
  160. }
  161. @Override
  162. public void destroyAllData() {
  163. activeData.clear();
  164. getKeyMapper().removeAll();
  165. }
  166. }
  167. private final Collection<DataGenerator<T>> generators = new LinkedHashSet<>();
  168. private final ActiveDataHandler handler = new ActiveDataHandler();
  169. /** Empty default data provider */
  170. protected DataProvider<T, ?> dataProvider = new CallbackDataProvider<>(
  171. q -> Stream.empty(), q -> 0);
  172. private final DataKeyMapper<T> keyMapper;
  173. protected boolean reset = false;
  174. private final Set<T> updatedData = new HashSet<>();
  175. private int minPushSize = 40;
  176. private Range pushRows = Range.withLength(0, minPushSize);
  177. private Object filter;
  178. private Comparator<T> inMemorySorting;
  179. private final List<QuerySortOrder> backEndSorting = new ArrayList<>();
  180. private final DataCommunicatorClientRpc rpc;
  181. public DataCommunicator() {
  182. addDataGenerator(handler);
  183. rpc = getRpcProxy(DataCommunicatorClientRpc.class);
  184. registerRpc(createRpc());
  185. keyMapper = createKeyMapper();
  186. }
  187. @Override
  188. public void attach() {
  189. super.attach();
  190. attachDataProviderListener();
  191. }
  192. @Override
  193. public void detach() {
  194. super.detach();
  195. detachDataProviderListener();
  196. }
  197. /**
  198. * Set the range of rows to push for next response.
  199. *
  200. * @param pushRows
  201. * @since 8.0.6
  202. */
  203. protected void setPushRows(Range pushRows) {
  204. this.pushRows = pushRows;
  205. }
  206. /**
  207. * Get the current range of rows to push in the next response.
  208. *
  209. * @return the range of rows to push
  210. * @since 8.0.6
  211. */
  212. protected Range getPushRows() {
  213. return pushRows;
  214. }
  215. /**
  216. * Get the object used for filtering in this data communicator.
  217. *
  218. * @return the filter object of this data communicator
  219. * @since 8.0.6
  220. */
  221. protected Object getFilter() {
  222. return filter;
  223. }
  224. /**
  225. * Get the client rpc interface for this data communicator.
  226. *
  227. * @return the client rpc interface for this data communicator
  228. * @since 8.0.6
  229. */
  230. protected DataCommunicatorClientRpc getClientRpc() {
  231. return rpc;
  232. }
  233. /**
  234. * Request the given rows to be available on the client side.
  235. *
  236. * @param firstRowIndex
  237. * the index of the first requested row
  238. * @param numberOfRows
  239. * the number of requested rows
  240. * @param firstCachedRowIndex
  241. * the index of the first cached row
  242. * @param cacheSize
  243. * the number of cached rows
  244. * @since 8.0.6
  245. */
  246. protected void onRequestRows(int firstRowIndex, int numberOfRows,
  247. int firstCachedRowIndex, int cacheSize) {
  248. setPushRows(Range.withLength(firstRowIndex, numberOfRows));
  249. markAsDirty();
  250. }
  251. /**
  252. * Triggered when rows have been dropped from the client side cache.
  253. *
  254. * @param keys
  255. * the keys of the rows that have been dropped
  256. * @since 8.0.6
  257. */
  258. protected void onDropRows(JsonArray keys) {
  259. for (int i = 0; i < keys.length(); ++i) {
  260. handler.dropActiveData(keys.getString(i));
  261. }
  262. }
  263. /**
  264. * Initially and in the case of a reset all data should be pushed to the
  265. * client.
  266. */
  267. @Override
  268. public void beforeClientResponse(boolean initial) {
  269. super.beforeClientResponse(initial);
  270. sendDataToClient(initial);
  271. }
  272. /**
  273. * Send the needed data and updates to the client side.
  274. *
  275. * @param initial
  276. * {@code true} if initial data load, {@code false} if not
  277. * @since 8.0.6
  278. */
  279. protected void sendDataToClient(boolean initial) {
  280. if (getDataProvider() == null) {
  281. return;
  282. }
  283. if (initial || reset) {
  284. @SuppressWarnings({ "rawtypes", "unchecked" })
  285. int dataProviderSize = getDataProvider().size(new Query(filter));
  286. rpc.reset(dataProviderSize);
  287. }
  288. Range requestedRows = getPushRows();
  289. boolean triggerReset = false;
  290. if (!requestedRows.isEmpty()) {
  291. int offset = requestedRows.getStart();
  292. int limit = requestedRows.length();
  293. @SuppressWarnings({ "rawtypes", "unchecked" })
  294. List<T> rowsToPush = (List<T>) getDataProvider()
  295. .fetch(new Query(offset, limit, backEndSorting,
  296. inMemorySorting, filter))
  297. .collect(Collectors.toList());
  298. if (!initial && !reset && rowsToPush.size() == 0) {
  299. triggerReset = true;
  300. }
  301. pushData(offset, rowsToPush);
  302. }
  303. if (!updatedData.isEmpty()) {
  304. JsonArray dataArray = Json.createArray();
  305. int i = 0;
  306. for (T data : updatedData) {
  307. dataArray.set(i++, getDataObject(data));
  308. }
  309. rpc.updateData(dataArray);
  310. }
  311. setPushRows(Range.withLength(0, 0));
  312. reset = triggerReset;
  313. updatedData.clear();
  314. }
  315. /**
  316. * Adds a data generator to this data communicator. Data generators can be
  317. * used to insert custom data to the rows sent to the client. If the data
  318. * generator is already added, does nothing.
  319. *
  320. * @param generator
  321. * the data generator to add, not null
  322. */
  323. public void addDataGenerator(DataGenerator<T> generator) {
  324. Objects.requireNonNull(generator, "generator cannot be null");
  325. generators.add(generator);
  326. // Make sure data gets generated when adding data generators.
  327. reset();
  328. }
  329. /**
  330. * Removes a data generator from this data communicator. If there is no such
  331. * data generator, does nothing.
  332. *
  333. * @param generator
  334. * the data generator to remove, not null
  335. */
  336. public void removeDataGenerator(DataGenerator<T> generator) {
  337. Objects.requireNonNull(generator, "generator cannot be null");
  338. generators.remove(generator);
  339. }
  340. /**
  341. * Gets the {@link DataKeyMapper} used by this {@link DataCommunicator}. Key
  342. * mapper can be used to map keys sent to the client-side back to their
  343. * respective data objects.
  344. *
  345. * @return key mapper
  346. */
  347. public DataKeyMapper<T> getKeyMapper() {
  348. return keyMapper;
  349. }
  350. /**
  351. * Sends given collection of data objects to the client-side.
  352. *
  353. * @param firstIndex
  354. * first index of pushed data
  355. * @param data
  356. * data objects to send as an iterable
  357. */
  358. protected void pushData(int firstIndex, List<T> data) {
  359. JsonArray dataArray = Json.createArray();
  360. int i = 0;
  361. for (T item : data) {
  362. dataArray.set(i++, getDataObject(item));
  363. }
  364. rpc.setData(firstIndex, dataArray);
  365. handler.addActiveData(data.stream());
  366. handler.cleanUp(data.stream());
  367. }
  368. /**
  369. * Creates the JsonObject for given data object. This method calls all data
  370. * generators for it.
  371. *
  372. * @param data
  373. * data object to be made into a json object
  374. * @return json object representing the data object
  375. */
  376. protected JsonObject getDataObject(T data) {
  377. JsonObject dataObject = Json.createObject();
  378. for (DataGenerator<T> generator : generators) {
  379. generator.generateData(data, dataObject);
  380. }
  381. return dataObject;
  382. }
  383. /**
  384. * Returns the active data handler.
  385. *
  386. * @return the active data handler
  387. * @since 8.0.6
  388. */
  389. protected ActiveDataHandler getActiveDataHandler() {
  390. return handler;
  391. }
  392. /**
  393. * Drops data objects identified by given keys from memory. This will invoke
  394. * {@link DataGenerator#destroyData} for each of those objects.
  395. *
  396. * @param droppedKeys
  397. * collection of dropped keys
  398. */
  399. private void dropData(Collection<String> droppedKeys) {
  400. for (String key : droppedKeys) {
  401. assert key != null : "Bookkeepping failure. Dropping a null key";
  402. T data = getKeyMapper().get(key);
  403. assert data != null : "Bookkeepping failure. No data object to match key";
  404. for (DataGenerator<T> g : generators) {
  405. g.destroyData(data);
  406. }
  407. }
  408. }
  409. /**
  410. * Drops all data associated with this data communicator.
  411. */
  412. protected void dropAllData() {
  413. for (DataGenerator<T> g : generators) {
  414. g.destroyAllData();
  415. }
  416. handler.destroyAllData();
  417. }
  418. /**
  419. * Informs the DataProvider that the collection has changed.
  420. */
  421. public void reset() {
  422. if (reset) {
  423. return;
  424. }
  425. reset = true;
  426. markAsDirty();
  427. }
  428. /**
  429. * Informs the DataProvider that a data object has been updated.
  430. *
  431. * @param data
  432. * updated data object
  433. */
  434. public void refresh(T data) {
  435. if (!handler.getActiveData().contains(data)) {
  436. // Item is not currently available at the client-side
  437. return;
  438. }
  439. if (updatedData.isEmpty()) {
  440. markAsDirty();
  441. }
  442. updatedData.add(data);
  443. }
  444. /**
  445. * Returns the currently set updated data.
  446. *
  447. * @return the set of data that should be updated on the next response
  448. * @since 8.0.6
  449. */
  450. protected Set<T> getUpdatedData() {
  451. return updatedData;
  452. }
  453. /**
  454. * Sets the {@link Comparator} to use with in-memory sorting.
  455. *
  456. * @param comparator
  457. * comparator used to sort data
  458. */
  459. public void setInMemorySorting(Comparator<T> comparator) {
  460. inMemorySorting = comparator;
  461. reset();
  462. }
  463. /**
  464. * Returns the {@link Comparator} to use with in-memory sorting.
  465. *
  466. * @return comparator used to sort data
  467. * @since 8.0.6
  468. */
  469. public Comparator<T> getInMemorySorting() {
  470. return inMemorySorting;
  471. }
  472. /**
  473. * Sets the {@link QuerySortOrder}s to use with backend sorting.
  474. *
  475. * @param sortOrder
  476. * list of sort order information to pass to a query
  477. */
  478. public void setBackEndSorting(List<QuerySortOrder> sortOrder) {
  479. backEndSorting.clear();
  480. backEndSorting.addAll(sortOrder);
  481. reset();
  482. }
  483. /**
  484. * Returns the {@link QuerySortOrder} to use with backend sorting.
  485. *
  486. * @return list of sort order information to pass to a query
  487. * @since 8.0.6
  488. */
  489. public List<QuerySortOrder> getBackEndSorting() {
  490. return backEndSorting;
  491. }
  492. /**
  493. * Creates a {@link DataKeyMapper} to use with this DataCommunicator.
  494. * <p>
  495. * This method is called from the constructor.
  496. *
  497. * @return key mapper
  498. */
  499. protected DataKeyMapper<T> createKeyMapper() {
  500. return new KeyMapper<>();
  501. }
  502. /**
  503. * Creates a {@link DataRequestRpc} used with this {@link DataCommunicator}.
  504. * <p>
  505. * This method is called from the constructor.
  506. *
  507. * @return data request rpc implementation
  508. */
  509. protected DataRequestRpc createRpc() {
  510. return new SimpleDataRequestRpc();
  511. }
  512. /**
  513. * Gets the current data provider from this DataCommunicator.
  514. *
  515. * @return the data provider
  516. */
  517. public DataProvider<T, ?> getDataProvider() {
  518. return dataProvider;
  519. }
  520. /**
  521. * Sets the current data provider for this DataCommunicator.
  522. * <p>
  523. * The returned consumer can be used to set some other filter value that
  524. * should be included in queries sent to the data provider. It is only valid
  525. * until another data provider is set.
  526. *
  527. * @param dataProvider
  528. * the data provider to set, not <code>null</code>
  529. * @param initialFilter
  530. * the initial filter value to use, or <code>null</code> to not
  531. * use any initial filter value
  532. *
  533. * @param <F>
  534. * the filter type
  535. *
  536. * @return a consumer that accepts a new filter value to use
  537. */
  538. public <F> SerializableConsumer<F> setDataProvider(
  539. DataProvider<T, F> dataProvider, F initialFilter) {
  540. Objects.requireNonNull(dataProvider, "data provider cannot be null");
  541. filter = initialFilter;
  542. detachDataProviderListener();
  543. dropAllData();
  544. this.dataProvider = dataProvider;
  545. /*
  546. * This introduces behavior which influence on the client-server
  547. * communication: now the very first response to the client will always
  548. * contain some data. If data provider has been set already then {@code
  549. * pushRows} is empty at this point. So without the next line the very
  550. * first response will be without data. And the client will request more
  551. * data in the next request after the response. The next line allows to
  552. * send some data (in the {@code pushRows} range) to the client even in
  553. * the very first response. This is necessary for disabled component
  554. * (and theoretically allows to the client doesn't request more data in
  555. * a happy path).
  556. */
  557. setPushRows(Range.between(0, getMinPushSize()));
  558. if (isAttached()) {
  559. attachDataProviderListener();
  560. }
  561. reset();
  562. return filter -> {
  563. if (this.dataProvider != dataProvider) {
  564. throw new IllegalStateException(
  565. "Filter slot is no longer valid after data provider has been changed");
  566. }
  567. if (!Objects.equals(this.filter, filter)) {
  568. this.filter = filter;
  569. reset();
  570. }
  571. };
  572. }
  573. /**
  574. * Set minimum size of data which will be sent to the client when data
  575. * source is set.
  576. * <p>
  577. * Server doesn't send all data from data source to the client. It sends
  578. * some initial chunk of data (whose size is determined as minimum between
  579. * {@code size} parameter of this method and data size). Client decides
  580. * whether it is able to show more data and request server to send more data
  581. * (next chunk).
  582. * <p>
  583. * When component is disabled then client cannot communicate to the server
  584. * side (by design, because of security reasons). It means that client will
  585. * get <b>only</b> initial chunk of data whose size is set here.
  586. *
  587. * @param size
  588. * the size of initial data to send to the client
  589. */
  590. public void setMinPushSize(int size) {
  591. if (size < 0) {
  592. throw new IllegalArgumentException("Value cannot be negative");
  593. }
  594. minPushSize = size;
  595. }
  596. /**
  597. * Get minimum size of data which will be sent to the client when data
  598. * source is set.
  599. *
  600. * @see #setMinPushSize(int)
  601. *
  602. * @return current minimum push size of initial data chunk which is sent to
  603. * the client when data source is set
  604. */
  605. public int getMinPushSize() {
  606. return minPushSize;
  607. }
  608. @Override
  609. protected DataCommunicatorState getState(boolean markAsDirty) {
  610. return (DataCommunicatorState) super.getState(markAsDirty);
  611. }
  612. @Override
  613. protected DataCommunicatorState getState() {
  614. return (DataCommunicatorState) super.getState();
  615. }
  616. private void attachDataProviderListener() {
  617. dataProviderUpdateRegistration = getDataProvider()
  618. .addDataProviderListener(event -> {
  619. getUI().access(() -> {
  620. if (event instanceof DataRefreshEvent) {
  621. T item = ((DataRefreshEvent<T>) event).getItem();
  622. generators.forEach(g -> g.refreshData(item));
  623. keyMapper.refresh(item, dataProvider::getId);
  624. refresh(item);
  625. } else {
  626. reset();
  627. }
  628. });
  629. });
  630. }
  631. private void detachDataProviderListener() {
  632. if (dataProviderUpdateRegistration != null) {
  633. dataProviderUpdateRegistration.remove();
  634. dataProviderUpdateRegistration = null;
  635. }
  636. }
  637. }