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.

DataProvider.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  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.server.communication.data.typed;
  17. import java.io.Serializable;
  18. import java.util.Collection;
  19. import java.util.HashSet;
  20. import java.util.LinkedHashSet;
  21. import java.util.Set;
  22. import com.vaadin.server.AbstractExtension;
  23. import com.vaadin.server.ClientConnector;
  24. import com.vaadin.shared.data.DataRequestRpc;
  25. import com.vaadin.shared.data.typed.DataProviderClientRpc;
  26. import com.vaadin.shared.data.typed.DataProviderConstants;
  27. import com.vaadin.ui.AbstractComponent;
  28. import elemental.json.Json;
  29. import elemental.json.JsonArray;
  30. import elemental.json.JsonObject;
  31. /**
  32. * DataProvider base class. This class is the base for all DataProvider
  33. * communication implementations. It uses {@link TypedDataGenerator}s to write
  34. * {@link JsonObject}s representing each data object to be sent to the
  35. * client-side.
  36. *
  37. * @since
  38. */
  39. public abstract class DataProvider<T> extends AbstractExtension {
  40. /**
  41. * Creates the appropriate type of DataProvider based on the type of
  42. * Collection provided to the method.
  43. * <p>
  44. * TODO: Actually use different DataProviders and provide an API for the
  45. * back end to inform changes back.
  46. *
  47. * @param data
  48. * collection of data objects
  49. * @param component
  50. * component to extend with the data provider
  51. * @return created data provider
  52. */
  53. public static <V> SimpleDataProvider<V> create(DataSource<V> data,
  54. AbstractComponent component) {
  55. SimpleDataProvider<V> dataProvider = new SimpleDataProvider<V>(data);
  56. return dataProvider;
  57. }
  58. /**
  59. * A class for handling currently active data and dropping data that is no
  60. * longer needed. Data tracking is based on key string provided by
  61. * {@link DataKeyMapper}.
  62. * <p>
  63. * When the {@link DataProvider} is pushing new data to the client-side via
  64. * {@link DataProvider#pushData(long, Collection)},
  65. * {@link #addActiveData(Collection)} and {@link #cleanUp(Collection)} are
  66. * called with the same parameter. In the clean up method any dropped data
  67. * objects that are not in the given collection will be cleaned up and
  68. * {@link TypedDataGenerator#destroyData(Object)} will be called for them.
  69. */
  70. protected class ActiveDataHandler implements Serializable,
  71. TypedDataGenerator<T> {
  72. /**
  73. * Set of key strings for currently active data objects
  74. */
  75. private final Set<String> activeData = new HashSet<String>();
  76. /**
  77. * Set of key strings for data objects dropped on the client. This set
  78. * is used to clean up old data when it's no longer needed.
  79. */
  80. private final Set<String> droppedData = new HashSet<String>();
  81. /**
  82. * Adds given objects as currently active objects.
  83. *
  84. * @param dataObjects
  85. * collection of new active data objects
  86. */
  87. public void addActiveData(Iterable<T> dataObjects) {
  88. for (T data : dataObjects) {
  89. if (!activeData.contains(getKeyMapper().key(data))) {
  90. activeData.add(getKeyMapper().key(data));
  91. }
  92. }
  93. }
  94. /**
  95. * Executes the data destruction for dropped data that is not sent to
  96. * the client. This method takes most recently sent data objects in a
  97. * collection. Doing the clean up like this prevents the
  98. * {@link ActiveDataHandler} from creating new keys for rows that were
  99. * dropped but got re-requested by the client-side. In the case of
  100. * having all data at the client, the collection should be all the data
  101. * in the back end.
  102. *
  103. * @see DataProvider#pushData(long, Collection)
  104. * @param dataObjects
  105. * collection of most recently sent data to the client
  106. */
  107. public void cleanUp(Iterable<T> dataObjects) {
  108. Collection<String> keys = new HashSet<String>();
  109. for (T data : dataObjects) {
  110. keys.add(getKeyMapper().key(data));
  111. }
  112. // Remove still active rows that were dropped by the client
  113. droppedData.removeAll(keys);
  114. // Do data clean up for object no longer needed.
  115. dropData(droppedData);
  116. droppedData.clear();
  117. }
  118. /**
  119. * Marks a data object identified by given key string to be dropped.
  120. *
  121. * @param key
  122. * key string
  123. */
  124. public void dropActiveData(String key) {
  125. if (activeData.contains(key)) {
  126. droppedData.add(key);
  127. }
  128. }
  129. /**
  130. * Returns the collection of all currently active data.
  131. *
  132. * @return collection of active data objects
  133. */
  134. public Collection<T> getActiveData() {
  135. HashSet<T> hashSet = new HashSet<T>();
  136. for (String key : activeData) {
  137. hashSet.add(getKeyMapper().get(key));
  138. }
  139. return hashSet;
  140. }
  141. @Override
  142. public void generateData(T data, JsonObject jsonObject) {
  143. // Write the key string for given data object
  144. jsonObject.put(DataProviderConstants.KEY, getKeyMapper().key(data));
  145. }
  146. @Override
  147. public void destroyData(T data) {
  148. // Remove from active data set
  149. activeData.remove(getKeyMapper().key(data));
  150. // Drop the registered key
  151. getKeyMapper().remove(data);
  152. }
  153. }
  154. private Collection<TypedDataGenerator<T>> generators = new LinkedHashSet<TypedDataGenerator<T>>();
  155. protected ActiveDataHandler handler = new ActiveDataHandler();
  156. protected DataProviderClientRpc rpc;
  157. protected DataSource<T> dataSource;
  158. private DataChangeHandler<T> dataChangeHandler;
  159. private DetachListener detachListener;
  160. private DataKeyMapper<T> keyMapper;
  161. protected DataProvider(DataSource<T> dataSource) {
  162. addDataGenerator(handler);
  163. this.dataSource = dataSource;
  164. rpc = getRpcProxy(DataProviderClientRpc.class);
  165. registerRpc(createRpc());
  166. dataChangeHandler = createDataChangeHandler();
  167. this.dataSource.addDataChangeHandler(dataChangeHandler);
  168. keyMapper = createKeyMapper();
  169. }
  170. @Override
  171. public void attach() {
  172. super.attach();
  173. if (detachListener == null) {
  174. detachListener = new DetachListener() {
  175. @Override
  176. public void detach(DetachEvent event) {
  177. cleanUp();
  178. }
  179. };
  180. getUI().addDetachListener(detachListener);
  181. }
  182. }
  183. @Override
  184. public void setParent(ClientConnector parent) {
  185. if (getParent() != null && parent == null) {
  186. // Removing from parent, clean up.
  187. cleanUp();
  188. }
  189. super.setParent(parent);
  190. }
  191. /**
  192. * Adds a {@link TypedDataGenerator} to this {@link DataProvider}.
  193. *
  194. * @param generator
  195. * typed data generator
  196. */
  197. public void addDataGenerator(TypedDataGenerator<T> generator) {
  198. generators.add(generator);
  199. }
  200. /**
  201. * Removes a {@link TypedDataGenerator} from this {@link DataProvider}.
  202. *
  203. * @param generator
  204. * typed data generator
  205. */
  206. public void removeDataGenerator(TypedDataGenerator<T> generator) {
  207. generators.remove(generator);
  208. }
  209. /**
  210. * Gets the {@link DataKeyMapper} used by this {@link DataProvider}. Key
  211. * mapper can be used to map keys sent to the client-side back to their
  212. * respective data objects.
  213. *
  214. * @return key mapper
  215. */
  216. public DataKeyMapper<T> getKeyMapper() {
  217. return keyMapper;
  218. }
  219. public abstract void refresh(T data);
  220. /**
  221. * Sends given collection of data objects to the client-side.
  222. *
  223. * @param firstIndex
  224. * first index of pushed data
  225. * @param data
  226. * data objects to send as an iterable
  227. */
  228. protected void pushData(long firstIndex, Iterable<T> data) {
  229. JsonArray dataArray = Json.createArray();
  230. int i = 0;
  231. for (T item : data) {
  232. dataArray.set(i++, getDataObject(item));
  233. }
  234. rpc.setData(firstIndex, dataArray);
  235. handler.addActiveData(data);
  236. handler.cleanUp(data);
  237. }
  238. /**
  239. * Creates the JsonObject for given data object. This method calls all data
  240. * generators for it.
  241. *
  242. * @param data
  243. * data object to be made into a json object
  244. * @return json object representing the data object
  245. */
  246. protected JsonObject getDataObject(T data) {
  247. JsonObject dataObject = Json.createObject();
  248. for (TypedDataGenerator<T> generator : generators) {
  249. generator.generateData(data, dataObject);
  250. }
  251. return dataObject;
  252. }
  253. /**
  254. * Drops data objects identified by given keys from memory. This will invoke
  255. * {@link TypedDataGenerator#destroyData} for each of those objects.
  256. *
  257. * @param droppedKeys
  258. * collection of dropped keys
  259. */
  260. private void dropData(Collection<String> droppedKeys) {
  261. for (String key : droppedKeys) {
  262. assert key != null : "Bookkeepping failure. Dropping a null key";
  263. T data = getKeyMapper().get(key);
  264. assert data != null : "Bookkeepping failure. No data object to match key";
  265. for (TypedDataGenerator<T> g : generators) {
  266. g.destroyData(data);
  267. }
  268. }
  269. }
  270. /**
  271. * Clean up method for removing all listeners attached by the
  272. * {@link DataProvider}. This method is called from {@link #remove()} or
  273. * when the UI gets detached.
  274. */
  275. protected void cleanUp() {
  276. if (dataSource != null) {
  277. dataSource.removeDataChangeHandler(dataChangeHandler);
  278. dataChangeHandler = null;
  279. }
  280. if (detachListener != null) {
  281. getUI().removeDetachListener(detachListener);
  282. detachListener = null;
  283. }
  284. }
  285. /**
  286. * Creates a {@link DataKeyMapper} to use with this {@link DataProvider}.
  287. * <p>
  288. * This method is called from the constructor.
  289. *
  290. * @return key mapper
  291. */
  292. protected abstract DataKeyMapper<T> createKeyMapper();
  293. /**
  294. * Creates a {@link DataRequestRpc} used with this {@link DataProvider}.
  295. * <p>
  296. * This method is called from the constructor.
  297. *
  298. * @return data request rpc implementation
  299. */
  300. protected abstract DataRequestRpc createRpc();
  301. /**
  302. * Creates a {@link DataChangeHandler} to use with the {@link DataSource}.
  303. * <p>
  304. * This method is called from the constructor.
  305. *
  306. * @return data change handler
  307. */
  308. protected abstract DataChangeHandler<T> createDataChangeHandler();
  309. }