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

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