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 28KB

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