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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. /*
  2. * Copyright 2000-2014 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.tokka.server.communication.data;
  17. import java.io.Serializable;
  18. import java.util.Collection;
  19. import java.util.HashSet;
  20. import java.util.LinkedHashSet;
  21. import java.util.List;
  22. import java.util.Set;
  23. import java.util.stream.Collectors;
  24. import java.util.stream.Stream;
  25. import com.vaadin.server.AbstractExtension;
  26. import com.vaadin.shared.data.DataRequestRpc;
  27. import com.vaadin.shared.data.typed.DataCommunicatorClientRpc;
  28. import com.vaadin.shared.data.typed.DataProviderConstants;
  29. import com.vaadin.shared.ui.grid.Range;
  30. import com.vaadin.tokka.event.Registration;
  31. import elemental.json.Json;
  32. import elemental.json.JsonArray;
  33. import elemental.json.JsonObject;
  34. /**
  35. * DataProvider base class. This class is the base for all DataProvider
  36. * communication implementations. It uses {@link TypedDataGenerator}s to write
  37. * {@link JsonObject}s representing each data object to be sent to the
  38. * client-side.
  39. *
  40. * @since
  41. */
  42. public class DataCommunicator<T> extends AbstractExtension {
  43. /**
  44. * Simple implementation of collection data provider communication. All data
  45. * is sent by server automatically and no data is requested by client.
  46. */
  47. protected class SimpleDataRequestRpc implements DataRequestRpc {
  48. @Override
  49. public void requestRows(int firstRowIndex, int numberOfRows,
  50. int firstCachedRowIndex, int cacheSize) {
  51. pushRows = Range.withLength(firstRowIndex, numberOfRows);
  52. markAsDirty();
  53. }
  54. @Override
  55. public void dropRows(JsonArray keys) {
  56. for (int i = 0; i < keys.length(); ++i) {
  57. handler.dropActiveData(keys.getString(i));
  58. }
  59. }
  60. }
  61. /**
  62. * A class for handling currently active data and dropping data that is no
  63. * longer needed. Data tracking is based on key string provided by
  64. * {@link DataKeyMapper}.
  65. * <p>
  66. * When the {@link DataCommunicator} is pushing new data to the client-side
  67. * via {@link DataCommunicator#pushData(long, Collection)},
  68. * {@link #addActiveData(Collection)} and {@link #cleanUp(Collection)} are
  69. * called with the same parameter. In the clean up method any dropped data
  70. * objects that are not in the given collection will be cleaned up and
  71. * {@link TypedDataGenerator#destroyData(Object)} will be called for them.
  72. */
  73. protected class ActiveDataHandler implements Serializable,
  74. TypedDataGenerator<T> {
  75. /**
  76. * Set of key strings for currently active data objects
  77. */
  78. private final Set<String> activeData = new HashSet<String>();
  79. /**
  80. * Set of key strings for data objects dropped on the client. This set
  81. * is used to clean up old data when it's no longer needed.
  82. */
  83. private final Set<String> droppedData = new HashSet<String>();
  84. /**
  85. * Adds given objects as currently active objects.
  86. *
  87. * @param dataObjects
  88. * collection of new active data objects
  89. */
  90. public void addActiveData(Stream<T> dataObjects) {
  91. dataObjects.map(getKeyMapper()::key)
  92. .filter(key -> !activeData.contains(key))
  93. .forEach(activeData::add);
  94. }
  95. /**
  96. * Executes the data destruction for dropped data that is not sent to
  97. * the client. This method takes most recently sent data objects in a
  98. * collection. Doing the clean up like this prevents the
  99. * {@link ActiveDataHandler} from creating new keys for rows that were
  100. * dropped but got re-requested by the client-side. In the case of
  101. * having all data at the client, the collection should be all the data
  102. * in the back end.
  103. *
  104. * @param dataObjects
  105. * collection of most recently sent data to the client
  106. */
  107. public void cleanUp(Stream<T> dataObjects) {
  108. Collection<String> keys = dataObjects.map(getKeyMapper()::key)
  109. .collect(Collectors.toSet());
  110. // Remove still active rows that were dropped by the client
  111. droppedData.removeAll(keys);
  112. // Do data clean up for object no longer needed.
  113. dropData(droppedData);
  114. droppedData.clear();
  115. }
  116. /**
  117. * Marks a data object identified by given key string to be dropped.
  118. *
  119. * @param key
  120. * key string
  121. */
  122. public void dropActiveData(String key) {
  123. if (activeData.contains(key)) {
  124. droppedData.add(key);
  125. }
  126. }
  127. /**
  128. * Returns the collection of all currently active data.
  129. *
  130. * @return collection of active data objects
  131. */
  132. public Collection<T> getActiveData() {
  133. HashSet<T> hashSet = new HashSet<T>();
  134. for (String key : activeData) {
  135. hashSet.add(getKeyMapper().get(key));
  136. }
  137. return hashSet;
  138. }
  139. @Override
  140. public void generateData(T data, JsonObject jsonObject) {
  141. // Write the key string for given data object
  142. jsonObject.put(DataProviderConstants.KEY, getKeyMapper().key(data));
  143. }
  144. @Override
  145. public void destroyData(T data) {
  146. // Remove from active data set
  147. activeData.remove(getKeyMapper().key(data));
  148. // Drop the registered key
  149. getKeyMapper().remove(data);
  150. }
  151. }
  152. private Collection<TypedDataGenerator<T>> generators = new LinkedHashSet<TypedDataGenerator<T>>();
  153. protected ActiveDataHandler handler = new ActiveDataHandler();
  154. protected DataCommunicatorClientRpc rpc;
  155. protected DataSource<T> dataSource;
  156. private DataKeyMapper<T> keyMapper;
  157. private boolean reset = false;
  158. private final Set<T> updatedData = new HashSet<T>();
  159. private Range pushRows = Range.withLength(0, 40);
  160. public DataCommunicator(DataSource<T> dataSource) {
  161. addDataGenerator(handler);
  162. this.dataSource = dataSource;
  163. rpc = getRpcProxy(DataCommunicatorClientRpc.class);
  164. registerRpc(createRpc());
  165. keyMapper = createKeyMapper();
  166. }
  167. /**
  168. * Initially and in the case of a reset all data should be pushed to the
  169. * client.
  170. */
  171. @Override
  172. public void beforeClientResponse(boolean initial) {
  173. super.beforeClientResponse(initial);
  174. if (initial || reset) {
  175. // FIXME: Rethink the size question.
  176. rpc.reset((int) dataSource.apply(null).count());
  177. }
  178. if (!pushRows.isEmpty()) {
  179. // FIXME: Query object
  180. Stream<T> rowsToPush = dataSource.apply(null)
  181. .skip(pushRows.getStart()).limit(pushRows.length());
  182. pushData(pushRows.getStart(), rowsToPush);
  183. }
  184. if (!updatedData.isEmpty()) {
  185. JsonArray dataArray = Json.createArray();
  186. int i = 0;
  187. for (T data : updatedData) {
  188. dataArray.set(i++, getDataObject(data));
  189. }
  190. rpc.updateData(dataArray);
  191. }
  192. pushRows = Range.withLength(0, 0);
  193. reset = false;
  194. updatedData.clear();
  195. }
  196. /**
  197. * Adds a {@link TypedDataGenerator} to this {@link DataCommunicator}.
  198. *
  199. * @param generator
  200. * typed data generator
  201. */
  202. public void addDataGenerator(TypedDataGenerator<T> generator) {
  203. generators.add(generator);
  204. }
  205. /**
  206. * Removes a {@link TypedDataGenerator} from this {@link DataCommunicator}.
  207. *
  208. * @param generator
  209. * typed data generator
  210. */
  211. public void removeDataGenerator(TypedDataGenerator<T> generator) {
  212. generators.remove(generator);
  213. }
  214. /**
  215. * Gets the {@link DataKeyMapper} used by this {@link DataCommunicator}. Key
  216. * mapper can be used to map keys sent to the client-side back to their
  217. * respective data objects.
  218. *
  219. * @return key mapper
  220. */
  221. public DataKeyMapper<T> getKeyMapper() {
  222. return keyMapper;
  223. }
  224. /**
  225. * Sends given collection of data objects to the client-side.
  226. *
  227. * @param firstIndex
  228. * first index of pushed data
  229. * @param data
  230. * data objects to send as an iterable
  231. */
  232. protected void pushData(int firstIndex, Stream<T> data) {
  233. JsonArray dataArray = Json.createArray();
  234. int i = 0;
  235. List<T> collected = data.collect(Collectors.toList());
  236. for (T item : collected) {
  237. dataArray.set(i++, getDataObject(item));
  238. }
  239. rpc.setData(firstIndex, dataArray);
  240. handler.addActiveData(collected.stream());
  241. handler.cleanUp(collected.stream());
  242. }
  243. /**
  244. * Creates the JsonObject for given data object. This method calls all data
  245. * generators for it.
  246. *
  247. * @param data
  248. * data object to be made into a json object
  249. * @return json object representing the data object
  250. */
  251. protected JsonObject getDataObject(T data) {
  252. JsonObject dataObject = Json.createObject();
  253. for (TypedDataGenerator<T> generator : generators) {
  254. generator.generateData(data, dataObject);
  255. }
  256. return dataObject;
  257. }
  258. /**
  259. * Drops data objects identified by given keys from memory. This will invoke
  260. * {@link TypedDataGenerator#destroyData} for each of those objects.
  261. *
  262. * @param droppedKeys
  263. * collection of dropped keys
  264. */
  265. private void dropData(Collection<String> droppedKeys) {
  266. for (String key : droppedKeys) {
  267. assert key != null : "Bookkeepping failure. Dropping a null key";
  268. T data = getKeyMapper().get(key);
  269. assert data != null : "Bookkeepping failure. No data object to match key";
  270. for (TypedDataGenerator<T> g : generators) {
  271. g.destroyData(data);
  272. }
  273. }
  274. }
  275. /**
  276. * Informs the DataProvider that a data object has been added. It is assumed
  277. * to be the last object in the collection.
  278. *
  279. * @param data
  280. * data object added to collection
  281. */
  282. protected void add(T data) {
  283. rpc.add(0);
  284. }
  285. /**
  286. * Informs the DataProvider that a data object has been removed.
  287. *
  288. * @param data
  289. * data object removed from collection
  290. */
  291. protected void remove(T data) {
  292. if (handler.getActiveData().contains(data)) {
  293. rpc.drop(0);
  294. }
  295. }
  296. /**
  297. * Informs the DataProvider that the collection has changed.
  298. */
  299. protected void reset() {
  300. if (reset) {
  301. return;
  302. }
  303. reset = true;
  304. markAsDirty();
  305. }
  306. /**
  307. * Informs the DataProvider that a data object has been updated.
  308. *
  309. * @param data
  310. * updated data object
  311. */
  312. public void refresh(T data) {
  313. if (updatedData.isEmpty()) {
  314. markAsDirty();
  315. }
  316. updatedData.add(data);
  317. }
  318. /**
  319. * Creates a {@link DataKeyMapper} to use with this DataCommunicator.
  320. * <p>
  321. * This method is called from the constructor.
  322. *
  323. * @return key mapper
  324. */
  325. protected DataKeyMapper<T> createKeyMapper() {
  326. return new KeyMapper<T>();
  327. }
  328. /**
  329. * Creates a {@link DataRequestRpc} used with this {@link DataCommunicator}.
  330. * <p>
  331. * This method is called from the constructor.
  332. *
  333. * @return data request rpc implementation
  334. */
  335. protected DataRequestRpc createRpc() {
  336. return new SimpleDataRequestRpc();
  337. }
  338. }