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.

AbstractRemoteDataSource.java 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  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.client.data;
  17. import java.util.Collection;
  18. import java.util.Collections;
  19. import java.util.HashMap;
  20. import java.util.List;
  21. import java.util.Map;
  22. import com.google.gwt.core.client.Duration;
  23. import com.google.gwt.core.client.Scheduler;
  24. import com.google.gwt.core.client.Scheduler.ScheduledCommand;
  25. import com.vaadin.client.Profiler;
  26. import com.vaadin.shared.ui.grid.Range;
  27. /**
  28. * Base implementation for data sources that fetch data from a remote system.
  29. * This class takes care of caching data and communicating with the data source
  30. * user. An implementation of this class should override
  31. * {@link #requestRows(int, int)} to trigger asynchronously loading of data.
  32. * When data is received from the server, new row data should be passed to
  33. * {@link #setRowData(int, List)}. {@link #setEstimatedSize(int)} should be used
  34. * based on estimations of how many rows are available.
  35. *
  36. * @since
  37. * @author Vaadin Ltd
  38. * @param <T>
  39. * the row type
  40. */
  41. public abstract class AbstractRemoteDataSource<T> implements DataSource<T> {
  42. protected class RowHandleImpl extends RowHandle<T> {
  43. private T row;
  44. private final Object key;
  45. public RowHandleImpl(final T row, final Object key) {
  46. this.row = row;
  47. this.key = key;
  48. }
  49. /**
  50. * A method for the data source to update the row data.
  51. *
  52. * @param row
  53. * the updated row object
  54. */
  55. public void setRow(final T row) {
  56. this.row = row;
  57. assert getRowKey(row).equals(key) : "The old key does not "
  58. + "equal the new key for the given row (old: " + key
  59. + ", new :" + getRowKey(row) + ")";
  60. }
  61. @Override
  62. public T getRow() throws IllegalStateException {
  63. if (isPinned()) {
  64. return row;
  65. } else {
  66. throw new IllegalStateException("The row handle for key " + key
  67. + " was not pinned");
  68. }
  69. }
  70. public boolean isPinned() {
  71. return pinnedRows.containsKey(key);
  72. }
  73. @Override
  74. public void pin() {
  75. pinHandle(this);
  76. }
  77. @Override
  78. public void unpin() throws IllegalStateException {
  79. unpinHandle(this);
  80. }
  81. @Override
  82. protected boolean equalsExplicit(final Object obj) {
  83. if (obj instanceof AbstractRemoteDataSource.RowHandleImpl) {
  84. /*
  85. * Java prefers AbstractRemoteDataSource<?>.RowHandleImpl. I
  86. * like the @SuppressWarnings more (keeps the line length in
  87. * check.)
  88. */
  89. @SuppressWarnings("unchecked")
  90. final RowHandleImpl rhi = (RowHandleImpl) obj;
  91. return key.equals(rhi.key);
  92. } else {
  93. return false;
  94. }
  95. }
  96. @Override
  97. protected int hashCodeExplicit() {
  98. return key.hashCode();
  99. }
  100. @Override
  101. public void updateRow() {
  102. int index = indexOf(row);
  103. if (index >= 0) {
  104. dataChangeHandler.dataUpdated(index, 1);
  105. }
  106. }
  107. }
  108. /**
  109. * Records the start of the previously requested range. This is used when
  110. * tracking request timings to distinguish between explicit responses and
  111. * arbitrary updates pushed from the server.
  112. */
  113. private int lastRequestStart = -1;
  114. private double pendingRequestTime;
  115. private boolean coverageCheckPending = false;
  116. private Range requestedAvailability = Range.between(0, 0);
  117. private Range cached = Range.between(0, 0);
  118. private final HashMap<Integer, T> indexToRowMap = new HashMap<Integer, T>();
  119. private final HashMap<Object, Integer> keyToIndexMap = new HashMap<Object, Integer>();
  120. private DataChangeHandler dataChangeHandler;
  121. private CacheStrategy cacheStrategy = new CacheStrategy.DefaultCacheStrategy();
  122. private final ScheduledCommand coverageChecker = new ScheduledCommand() {
  123. @Override
  124. public void execute() {
  125. coverageCheckPending = false;
  126. checkCacheCoverage();
  127. }
  128. };
  129. private Map<Object, Integer> pinnedCounts = new HashMap<Object, Integer>();
  130. private Map<Object, RowHandleImpl> pinnedRows = new HashMap<Object, RowHandleImpl>();
  131. protected Collection<T> temporarilyPinnedRows = Collections.emptySet();
  132. private void ensureCoverageCheck() {
  133. if (!coverageCheckPending) {
  134. coverageCheckPending = true;
  135. Scheduler.get().scheduleDeferred(coverageChecker);
  136. }
  137. }
  138. /**
  139. * Pins a row with given handle. This function can be overridden to do
  140. * specific logic related to pinning rows.
  141. *
  142. * @param handle
  143. * row handle to pin
  144. */
  145. protected void pinHandle(RowHandleImpl handle) {
  146. Object key = handle.key;
  147. Integer count = pinnedCounts.get(key);
  148. if (count == null) {
  149. count = Integer.valueOf(0);
  150. pinnedRows.put(key, handle);
  151. }
  152. pinnedCounts.put(key, Integer.valueOf(count.intValue() + 1));
  153. }
  154. /**
  155. * Unpins a previously pinned row with given handle. This function can be
  156. * overridden to do specific logic related to unpinning rows.
  157. *
  158. * @param handle
  159. * row handle to unpin
  160. *
  161. * @throws IllegalStateException
  162. * if given row handle has not been pinned before
  163. */
  164. protected void unpinHandle(RowHandleImpl handle)
  165. throws IllegalStateException {
  166. Object key = handle.key;
  167. final Integer count = pinnedCounts.get(key);
  168. if (count == null) {
  169. throw new IllegalStateException("Row " + handle.getRow()
  170. + " with key " + key + " was not pinned to begin with");
  171. } else if (count.equals(Integer.valueOf(1))) {
  172. pinnedRows.remove(key);
  173. pinnedCounts.remove(key);
  174. } else {
  175. pinnedCounts.put(key, Integer.valueOf(count.intValue() - 1));
  176. }
  177. }
  178. @Override
  179. public void ensureAvailability(int firstRowIndex, int numberOfRows) {
  180. requestedAvailability = Range.withLength(firstRowIndex, numberOfRows);
  181. /*
  182. * Don't request any data right away since the data might be included in
  183. * a message that has been received but not yet fully processed.
  184. */
  185. ensureCoverageCheck();
  186. }
  187. private void checkCacheCoverage() {
  188. if (lastRequestStart != -1) {
  189. // Anyone clearing lastRequestStart should run this method again
  190. return;
  191. }
  192. Profiler.enter("AbstractRemoteDataSource.checkCacheCoverage");
  193. Range minCacheRange = getMinCacheRange();
  194. if (!minCacheRange.intersects(cached) || cached.isEmpty()) {
  195. /*
  196. * Simple case: no overlap between cached data and needed data.
  197. * Clear the cache and request new data
  198. */
  199. indexToRowMap.clear();
  200. keyToIndexMap.clear();
  201. cached = Range.between(0, 0);
  202. handleMissingRows(getMaxCacheRange());
  203. } else {
  204. discardStaleCacheEntries();
  205. // Might need more rows -> request them
  206. if (!minCacheRange.isSubsetOf(cached)) {
  207. Range[] missingCachePartition = getMaxCacheRange()
  208. .partitionWith(cached);
  209. handleMissingRows(missingCachePartition[0]);
  210. handleMissingRows(missingCachePartition[2]);
  211. } else {
  212. dataChangeHandler.dataAvailable(cached.getStart(),
  213. cached.length());
  214. }
  215. }
  216. Profiler.leave("AbstractRemoteDataSource.checkCacheCoverage");
  217. }
  218. private void discardStaleCacheEntries() {
  219. Range[] cacheParition = cached.partitionWith(getMaxCacheRange());
  220. dropFromCache(cacheParition[0]);
  221. cached = cacheParition[1];
  222. dropFromCache(cacheParition[2]);
  223. }
  224. private void dropFromCache(Range range) {
  225. for (int i = range.getStart(); i < range.getEnd(); i++) {
  226. T removed = indexToRowMap.remove(Integer.valueOf(i));
  227. keyToIndexMap.remove(getRowKey(removed));
  228. }
  229. }
  230. private void handleMissingRows(Range range) {
  231. if (range.isEmpty()) {
  232. return;
  233. }
  234. lastRequestStart = range.getStart();
  235. pendingRequestTime = Duration.currentTimeMillis();
  236. requestRows(range.getStart(), range.length());
  237. }
  238. /**
  239. * Triggers fetching rows from the remote data source.
  240. * {@link #setRowData(int, List)} should be invoked with data for the
  241. * requested rows when they have been received.
  242. *
  243. * @param firstRowIndex
  244. * the index of the first row to fetch
  245. * @param numberOfRows
  246. * the number of rows to fetch
  247. */
  248. protected abstract void requestRows(int firstRowIndex, int numberOfRows);
  249. @Override
  250. public T getRow(int rowIndex) {
  251. return indexToRowMap.get(Integer.valueOf(rowIndex));
  252. }
  253. @Override
  254. public int indexOf(T row) {
  255. Object key = getRowKey(row);
  256. if (keyToIndexMap.containsKey(key)) {
  257. return keyToIndexMap.get(key);
  258. }
  259. return -1;
  260. }
  261. @Override
  262. public void setDataChangeHandler(DataChangeHandler dataChangeHandler) {
  263. this.dataChangeHandler = dataChangeHandler;
  264. if (dataChangeHandler != null && !cached.isEmpty()) {
  265. // Push currently cached data to the implementation
  266. dataChangeHandler.dataUpdated(cached.getStart(), cached.length());
  267. dataChangeHandler.dataAvailable(cached.getStart(), cached.length());
  268. }
  269. }
  270. /**
  271. * Informs this data source that updated data has been sent from the server.
  272. *
  273. * @param firstRowIndex
  274. * the index of the first received row
  275. * @param rowData
  276. * a list of rows, starting from <code>firstRowIndex</code>
  277. */
  278. protected void setRowData(int firstRowIndex, List<T> rowData) {
  279. Profiler.enter("AbstractRemoteDataSource.setRowData");
  280. Range received = Range.withLength(firstRowIndex, rowData.size());
  281. if (firstRowIndex == lastRequestStart) {
  282. // Provide timing information if we know when we asked for this data
  283. cacheStrategy.onDataArrive(Duration.currentTimeMillis()
  284. - pendingRequestTime, received.length());
  285. }
  286. lastRequestStart = -1;
  287. Range maxCacheRange = getMaxCacheRange();
  288. Range[] partition = received.partitionWith(maxCacheRange);
  289. Range newUsefulData = partition[1];
  290. if (!newUsefulData.isEmpty()) {
  291. // Update the parts that are actually inside
  292. for (int i = newUsefulData.getStart(); i < newUsefulData.getEnd(); i++) {
  293. final T row = rowData.get(i - firstRowIndex);
  294. indexToRowMap.put(Integer.valueOf(i), row);
  295. keyToIndexMap.put(getRowKey(row), Integer.valueOf(i));
  296. }
  297. if (dataChangeHandler != null) {
  298. Profiler.enter("AbstractRemoteDataSource.setRowData notify dataChangeHandler");
  299. dataChangeHandler.dataUpdated(newUsefulData.getStart(),
  300. newUsefulData.length());
  301. Profiler.leave("AbstractRemoteDataSource.setRowData notify dataChangeHandler");
  302. }
  303. // Potentially extend the range
  304. if (cached.isEmpty()) {
  305. cached = newUsefulData;
  306. } else {
  307. discardStaleCacheEntries();
  308. /*
  309. * everything might've become stale so we need to re-check for
  310. * emptiness.
  311. */
  312. if (!cached.isEmpty()) {
  313. cached = cached.combineWith(newUsefulData);
  314. } else {
  315. cached = newUsefulData;
  316. }
  317. }
  318. dataChangeHandler.dataAvailable(cached.getStart(), cached.length());
  319. updatePinnedRows(rowData);
  320. }
  321. if (!partition[0].isEmpty() || !partition[2].isEmpty()) {
  322. /*
  323. * FIXME
  324. *
  325. * Got data that we might need in a moment if the container is
  326. * updated before the widget settings. Support for this will be
  327. * implemented later on.
  328. */
  329. }
  330. // Eventually check whether all needed rows are now available
  331. ensureCoverageCheck();
  332. Profiler.leave("AbstractRemoteDataSource.setRowData");
  333. }
  334. private void updatePinnedRows(final List<T> rowData) {
  335. for (final T row : rowData) {
  336. final Object key = getRowKey(row);
  337. final RowHandleImpl handle = pinnedRows.get(key);
  338. if (handle != null) {
  339. handle.setRow(row);
  340. }
  341. }
  342. }
  343. /**
  344. * Informs this data source that the server has removed data.
  345. *
  346. * @param firstRowIndex
  347. * the index of the first removed row
  348. * @param count
  349. * the number of removed rows, starting from
  350. * <code>firstRowIndex</code>
  351. */
  352. protected void removeRowData(int firstRowIndex, int count) {
  353. Profiler.enter("AbstractRemoteDataSource.removeRowData");
  354. // shift indices to fill the cache correctly
  355. for (int i = firstRowIndex + count; i < cached.getEnd(); i++) {
  356. moveRowFromIndexToIndex(i, i - count);
  357. }
  358. Range removedRange = Range.withLength(firstRowIndex, count);
  359. if (cached.isSubsetOf(removedRange)) {
  360. cached = Range.withLength(0, 0);
  361. } else if (removedRange.intersects(cached)) {
  362. Range[] partitions = cached.partitionWith(removedRange);
  363. Range remainsBefore = partitions[0];
  364. Range transposedRemainsAfter = partitions[2].offsetBy(-removedRange
  365. .length());
  366. cached = remainsBefore.combineWith(transposedRemainsAfter);
  367. }
  368. assertDataChangeHandlerIsInjected();
  369. dataChangeHandler.dataRemoved(firstRowIndex, count);
  370. checkCacheCoverage();
  371. Profiler.leave("AbstractRemoteDataSource.removeRowData");
  372. }
  373. /**
  374. * Informs this data source that new data has been inserted from the server.
  375. *
  376. * @param firstRowIndex
  377. * the destination index of the new row data
  378. * @param count
  379. * the number of rows inserted
  380. */
  381. protected void insertRowData(int firstRowIndex, int count) {
  382. Profiler.enter("AbstractRemoteDataSource.insertRowData");
  383. if (cached.contains(firstRowIndex)) {
  384. int oldCacheEnd = cached.getEnd();
  385. /*
  386. * We need to invalidate the cache from the inserted row onwards,
  387. * since the cache wants to be a contiguous range. It doesn't
  388. * support holes.
  389. *
  390. * If holes were supported, we could shift the higher part of
  391. * "cached" and leave a hole the size of "count" in the middle.
  392. */
  393. cached = cached.splitAt(firstRowIndex)[0];
  394. for (int i = firstRowIndex; i < oldCacheEnd; i++) {
  395. T row = indexToRowMap.remove(Integer.valueOf(i));
  396. keyToIndexMap.remove(getRowKey(row));
  397. }
  398. }
  399. else if (firstRowIndex < cached.getStart()) {
  400. Range oldCached = cached;
  401. cached = cached.offsetBy(count);
  402. for (int i = 0; i < indexToRowMap.size(); i++) {
  403. int oldIndex = oldCached.getEnd() - i;
  404. int newIndex = cached.getEnd() - i;
  405. moveRowFromIndexToIndex(oldIndex, newIndex);
  406. }
  407. }
  408. assertDataChangeHandlerIsInjected();
  409. dataChangeHandler.dataAdded(firstRowIndex, count);
  410. checkCacheCoverage();
  411. Profiler.leave("AbstractRemoteDataSource.insertRowData");
  412. }
  413. private void moveRowFromIndexToIndex(int oldIndex, int newIndex) {
  414. T row = indexToRowMap.remove(oldIndex);
  415. if (indexToRowMap.containsKey(newIndex)) {
  416. // Old row is about to be overwritten. Remove it from keyCache.
  417. keyToIndexMap.remove(getRowKey(indexToRowMap.get(newIndex)));
  418. }
  419. indexToRowMap.put(newIndex, row);
  420. keyToIndexMap.put(getRowKey(row), newIndex);
  421. }
  422. /**
  423. * Gets the current range of cached rows
  424. *
  425. * @return the range of currently cached rows
  426. */
  427. public Range getCachedRange() {
  428. return cached;
  429. }
  430. /**
  431. * Sets the cache strategy that is used to determine how much data is
  432. * fetched and cached.
  433. * <p>
  434. * The new strategy is immediately used to evaluate whether currently cached
  435. * rows should be discarded or new rows should be fetched.
  436. *
  437. * @param cacheStrategy
  438. * a cache strategy implementation, not <code>null</code>
  439. */
  440. public void setCacheStrategy(CacheStrategy cacheStrategy) {
  441. if (cacheStrategy == null) {
  442. throw new IllegalArgumentException();
  443. }
  444. if (this.cacheStrategy != cacheStrategy) {
  445. this.cacheStrategy = cacheStrategy;
  446. checkCacheCoverage();
  447. }
  448. }
  449. private Range getMinCacheRange() {
  450. Range availableDataRange = Range.withLength(0, size());
  451. Range minCacheRange = cacheStrategy.getMinCacheRange(
  452. requestedAvailability, cached, availableDataRange);
  453. assert minCacheRange.isSubsetOf(availableDataRange);
  454. return minCacheRange;
  455. }
  456. private Range getMaxCacheRange() {
  457. Range availableDataRange = Range.withLength(0, size());
  458. Range maxCacheRange = cacheStrategy.getMaxCacheRange(
  459. requestedAvailability, cached, availableDataRange);
  460. assert maxCacheRange.isSubsetOf(availableDataRange);
  461. return maxCacheRange;
  462. }
  463. @Override
  464. public RowHandle<T> getHandle(T row) throws IllegalStateException {
  465. Object key = getRowKey(row);
  466. if (key == null) {
  467. throw new NullPointerException("key may not be null (row: " + row
  468. + ")");
  469. }
  470. if (pinnedRows.containsKey(key)) {
  471. return pinnedRows.get(key);
  472. } else if (keyToIndexMap.containsKey(key)) {
  473. return new RowHandleImpl(row, key);
  474. } else {
  475. throw new IllegalStateException("The cache of this DataSource "
  476. + "does not currently contain the row " + row);
  477. }
  478. }
  479. /**
  480. * Gets a stable key for the row object.
  481. * <p>
  482. * This method is a workaround for the fact that there is no means to force
  483. * proper implementations for {@link #hashCode()} and
  484. * {@link #equals(Object)} methods.
  485. * <p>
  486. * Since the same row object will be created several times for the same
  487. * logical data, the DataSource needs a mechanism to be able to compare two
  488. * objects, and figure out whether or not they represent the same data. Even
  489. * if all the fields of an entity would be changed, it still could represent
  490. * the very same thing (say, a person changes all of her names.)
  491. * <p>
  492. * A very usual and simple example what this could be, is an unique ID for
  493. * this object that would also be stored in a database.
  494. *
  495. * @param row
  496. * the row object for which to get the key
  497. * @return a non-null object that uniquely and consistently represents the
  498. * row object
  499. */
  500. abstract public Object getRowKey(T row);
  501. protected void resetDataAndSize(int newSize) {
  502. dropFromCache(getCachedRange());
  503. cached = Range.withLength(0, 0);
  504. assertDataChangeHandlerIsInjected();
  505. dataChangeHandler.resetDataAndSize(newSize);
  506. }
  507. private void assertDataChangeHandlerIsInjected() {
  508. assert dataChangeHandler != null : "The dataChangeHandler was "
  509. + "called before it was injected. Maybe you tried "
  510. + "to manipulate the data in the DataSource's "
  511. + "constructor instead of in overriding onAttach() "
  512. + "and doing it there?";
  513. }
  514. }