1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.myfaces.trinidad.model;
20
21 import java.util.Collections;
22 import java.util.List;
23 import java.util.ArrayList;
24 import javax.faces.model.DataModel;
25
26 /**
27 * The data model that is used by the Trinidad Table and Iterator components.
28 * This extends the Faces DataModel class and adds on support for
29 * rowKeys and sorting. Ordinary DataModels are still supported,
30 * and will automatically be wrapped into CollectionModels, but
31 * without the added functionality.
32 * <p>
33 * <h3>Row key support</h3>
34 * <p>
35 * In the Faces DataModel, rows are identified entirely by
36 * index. This causes major problems if the underlying data
37 * changes from one request to the next - a user request
38 * to delete one row may delete a different row because a
39 * row got added by another user, etc. To work around
40 * this, CollectionModel is based around row keys instead
41 * of indices. An implementation of CollectionModel must
42 * implement getRowKey() and setRowKey(), and handle
43 * conversion from integer indices to row keys. A trivial
44 * implementation might simply use Integer objects as
45 * the row keys, but a better version could use a unique ID
46 * in the row.
47 * <p>
48 */
49 public abstract class CollectionModel extends DataModel
50 implements RowKeyIndex, LocalRowKeyIndex
51 {
52
53 /**
54 * Gets the rowKey of the current row.
55 * rowKeys are safer to use than row indices because rowKeys are
56 * unaffected by mutations to this collection.
57 * rowKeys should have efficient implementations of
58 * {@link Object#equals} and {@link Object#hashCode} as they will be used
59 * as keys in hashtables. rowKeys should also be Serializable, so that the
60 * application can run under all JSF state-saving schemes.
61 * @return this key should be Serializable and immutable.
62 * @see #setRowKey
63 */
64 public abstract Object getRowKey();
65
66 /**
67 * Finds the row with the matching key and makes it current
68 * @param key the rowKey, previously obtained from {@link #getRowKey}.
69 */
70 public abstract void setRowKey(Object key);
71
72
73 /**
74 * Checks to see if the row at the given index is available.
75 * This method makes the given row current and calls
76 * {@link #isRowAvailable()}.
77 * Finally, the row that was current before this method was called
78 * is made current again.
79 * @see CollectionModel#isRowAvailable()
80 * @param rowIndex the index of the row to check.
81 * @return true if data for the row exists.
82 */
83 public boolean isRowAvailable(int rowIndex)
84 {
85 int oldIndex = getRowIndex();
86 try
87 {
88 setRowIndex(rowIndex);
89 return isRowAvailable();
90 }
91 finally
92 {
93 setRowIndex(oldIndex);
94 }
95 }
96
97 /**
98 * Check for an available row by row key.
99 * This method makes the given row current and calls
100 * {@link #isRowAvailable()}.
101 * Finally, the row that was current before this method was called
102 * is made current again.
103 * @see CollectionModel#isRowAvailable()
104 * @param rowKey the row key for the row to check.
105 * @return true if data for the row exists otherwise return false
106 */
107 public boolean isRowAvailable(Object rowKey)
108 {
109 Object oldKey = getRowKey();
110 try
111 {
112 setRowKey(rowKey);
113 return isRowAvailable();
114 }
115 finally
116 {
117 setRowKey(oldKey);
118 }
119 }
120
121 /**
122 * Gets the rowData at the given index.
123 * This method makes the given row current and calls
124 * {@link #getRowData()}.
125 * Finally, the row that was current before this method was called
126 * is made current again.
127 * @see CollectionModel#getRowData()
128 * @param rowIndex the index of the row to get data from.
129 * @return the data for the given row.
130 */
131 public Object getRowData(int rowIndex)
132 {
133 int oldIndex = getRowIndex();
134 try
135 {
136 setRowIndex(rowIndex);
137 return getRowData();
138 }
139 finally
140 {
141 setRowIndex(oldIndex);
142 }
143 }
144
145 /**
146 * Returns the rowData for the given rowKey without changing model currency.
147 * Implementations may choose to implement this behavior by saving and restoring the currency.
148 *
149 * @see CollectionModel#getRowData()
150 * @param rowKey the row key of the row to get data from.
151 * @return the data for the given row.
152 */
153 public Object getRowData(Object rowKey)
154 {
155 Object oldKey = getRowKey();
156 try
157 {
158 setRowKey(rowKey);
159 return getRowData();
160 }
161 finally
162 {
163 setRowKey(oldKey);
164 }
165 }
166
167 /**
168 * Return true if this collection is sortable by the given property.
169 * This implementation always returns false;
170 */
171 public boolean isSortable(String property)
172 {
173 return false;
174 }
175
176 /**
177 * Gets the criteria that this collection is sorted by.
178 * This method should never return null.
179 * This implementation always returns an empty List.
180 * @return each element in this List is of type SortCriterion.
181 * An empty list is returned if this collection is not sorted.
182 * @see SortCriterion
183 */
184 public List<SortCriterion> getSortCriteria()
185 {
186 return Collections.emptyList();
187 }
188
189 /**
190 * Sorts this collection by the given criteria.
191 * @param criteria Each element in this List must be of type SortCriterion.
192 * The empty list may be used to cancel any sort order. null should be treated
193 * the same as an empty list.
194 * @see SortCriterion
195 */
196 public void setSortCriteria(List<SortCriterion> criteria)
197 {
198 }
199
200 /**
201 * Check if a range of rows is available from a starting index.
202 * The current row does not change after this call
203 * @param startIndex the starting index for the range
204 * @param rowsToCheck number of rows to check. If rowsToCheck < 0 set
205 * startIndex = startIndex - abs(rowsToCheck) + 1. This
206 * allows for checking for row availability from the end position. For example
207 * to check for availability of n rows from the end, call
208 * isRangeAvailable(getRowCount()-1, -n)
209 * @return true if rows are available otherwise return <code>false</code>
210 */
211 public boolean areRowsAvailable(int startIndex, int rowsToCheck)
212 {
213 int oldIndex = getRowIndex();
214 try
215 {
216 if (rowsToCheck < 0)
217 {
218 rowsToCheck = Math.abs(rowsToCheck);
219 startIndex = startIndex - rowsToCheck + 1;
220 }
221 setRowIndex(startIndex);
222 return areRowsAvailable(rowsToCheck);
223 }
224 finally
225 {
226 setRowIndex(oldIndex);
227 }
228 }
229
230 /**
231 * Check if a range of rows is available from a starting row key
232 * This method makes the row with the given row key current and calls
233 * {@link #areRowsAvailable(rowsToCheck)}.
234 * The current row does not change after this call
235 * @see CollectionModel#areRowsAvailable(int).
236 * @param startRowKey the starting row key for the range
237 * @param rowsToCheck number of rows to check
238 * @return true if rows are available otherwise return false
239 */
240 public boolean areRowsAvailable(Object startRowKey, int rowsToCheck)
241 {
242 Object oldKey = getRowKey();
243 try
244 {
245 setRowKey(startRowKey);
246 return areRowsAvailable(rowsToCheck);
247 }
248 finally
249 {
250 setRowKey(oldKey);
251 }
252 }
253
254 /**
255 * Check if a range of rows is available starting from the
256 * current row. This implementation checks the start and end rows in the range
257 * for availability. If the number of requested rows is greater than the total
258 * row count, this implementation checks for available rows up to the row count.
259 * The current row does not change after this call
260 * @param rowsToCheck number of rows to check
261 * @return true rows are available otherwise return false
262 */
263 public boolean areRowsAvailable(int rowsToCheck)
264 {
265 int startIndex = getRowIndex();
266
267 if (startIndex < 0 || rowsToCheck <= 0)
268 return false;
269
270
271 long count = getRowCount();
272 if (count != -1)
273 {
274 if (startIndex >= count)
275 return false;
276
277 if (startIndex + rowsToCheck > count)
278 rowsToCheck = (int)count - startIndex;
279 }
280 int last = startIndex + rowsToCheck - 1;
281
282 try
283 {
284 // check start index
285 if (!isRowAvailable())
286 return false;
287
288 // check end index
289 setRowIndex(last);
290 return isRowAvailable();
291 }
292 finally
293 {
294 setRowIndex(startIndex);
295 }
296 }
297
298 /**
299 * <p>
300 * Adds the listener to the Set of RowKeyChangeListeners on the Collection.
301 * </p>
302 * <p>
303 * The same listener instance may be added multiple times, but will only be called once per change.
304 * </p>
305 * <p>
306 * Since the Collection may have a lifetime longer than Request, the listener implementation
307 * should take care not to maintain any references to objects only valid in the current Request.
308 * For example, if a UIComponent wishes to listen on a Collection, the UIComponent cannot use a
309 * listener that maintains a Java reference to the UIComponent instance because UIComponent
310 * instances are only valid for the current request (this also precludes the use of a non-static
311 * inner class for the listener). Instead, the UIComponent would need to use an indirect
312 * reference such as {@link ComponentReference} to dynamically find the correct instance to use.
313 * In the case where the Collection has a short lifetime, the code that adds the listener needs to
314 * ensure that it executes every time the Collection instance is reinstantiated.
315 * </p>
316 * @param listener The listener for RowKeyChangeEvents to add to the Collection
317 * @see #removeRowKeyChangeListener
318 */
319 public void addRowKeyChangeListener(RowKeyChangeListener listener)
320 {
321 if(!_rowKeyChangeListeners.contains(listener))
322 _rowKeyChangeListeners.add(listener);
323 }
324
325 /**
326 * <p>
327 * Remove an existing listener from the Set of RowKeyChangeListeners on the Collection.
328 * </p>
329 * <p>
330 * The same listener instance may be removed multiple times wihtout failure.
331 * </p>
332 *
333 * @param listener The listener for RowKeyChangeEvents to remove from the Collection
334 */
335 public void removeRowKeyChangeListener(RowKeyChangeListener listener)
336 {
337 _rowKeyChangeListeners.remove(listener);
338 }
339
340 /**
341 * Fire an existing RowKeyChangeEvent to any registered listeners.
342 * No event is fired if the given event's old and new row keys are equal and non-null.
343 * @param event The RowKeyChangeEvent object.
344 */
345 protected void fireRowKeyChange(RowKeyChangeEvent event)
346 {
347 Object oldRowKey = event.getOldRowKey();
348 Object newRowKey = event.getNewRowKey();
349 if (oldRowKey != null && newRowKey != null && oldRowKey.equals(newRowKey))
350 {
351 return;
352 }
353
354 for (RowKeyChangeListener listener: _rowKeyChangeListeners)
355 {
356 listener.onRowKeyChange(event);
357 }
358 }
359
360 //
361 // Below is the default implemenation for the LocalRowKeyIndex interface.
362 //
363
364 /**
365 * Check if a range of rows is locally available starting from a row index.
366 * @see CollectionModel#areRowsAvailable(int, int)
367 * @param startIndex starting row index to check
368 * @param rowsToCheck number of rows to check
369 * @return default implementation returns <code>false</code>
370
371 */
372 public boolean areRowsLocallyAvailable(int startIndex, int rowsToCheck)
373 {
374 return false;
375 }
376
377 /**
378 * Check if a range of rows is locally available starting from a row key.
379 * @see CollectionModel#areRowsAvailable(Object, int)
380 * @param startRowKey starting row key to check
381 * @param rowsToCheck number of rows to check
382 * @return default implementation returns <code>false</code>
383 */
384 public boolean areRowsLocallyAvailable(Object startRowKey, int rowsToCheck)
385 {
386 return false;
387 }
388
389
390 /**
391 * Check if a range of rows is locally available starting from current position.
392 * This implementation checks for a valid current index and delegates to
393 * <code>areRowsLocallyAvailable(startIndex, rowsToCheck)</code>
394 * @param rowsToCheck number of rows to check
395 * @return default implementation returns <code>false</code>
396 * @see <code>areRowsLocallyAvailable(startIndex, rowsToCheck)</code>
397 */
398 public boolean areRowsLocallyAvailable(int rowsToCheck)
399 {
400 boolean available = false;
401 int startIndex = getRowIndex();
402
403 if (startIndex >= 0)
404 {
405 available = areRowsLocallyAvailable(startIndex, rowsToCheck);
406 }
407 return available;
408 }
409
410 /**
411 * Given a row index, check if the row is locally available.
412 * @param rowIndex row index to check
413 * @return default implementation returns <code>false</code>
414 */
415 public boolean isRowLocallyAvailable(int rowIndex)
416 {
417 return false;
418 }
419
420 /**
421 * Given a row key, check if the row is locally available.
422 * @param rowKey row key to check
423 * @return default implementation returns <code>false</code>
424 */
425 public boolean isRowLocallyAvailable(Object rowKey)
426 {
427 return false;
428 }
429
430 /**
431 * Convenient API to return a row count estimate.
432 * @see CollectionModel#getRowCount
433 * @return This implementation returns exact row count
434 */
435 public int getEstimatedRowCount()
436 {
437 return getRowCount();
438 }
439
440 /**
441 * Helper API to determine if the row count returned from {@link #getEstimatedRowCount}
442 * is EXACT, or an ESTIMATE.
443 * @see CollectionModel#getRowCount
444 * @return This implementation returns exact row count
445 */
446 public LocalRowKeyIndex.Confidence getEstimatedRowCountConfidence()
447 {
448 return LocalRowKeyIndex.Confidence.EXACT;
449 }
450
451 /**
452 * Clears the row with the given index from local cache.
453 * This is a do nothing implementaion.
454 * @see #clearCachedRows(int, int)
455 * @param index row index for the row to remove from cache
456 */
457 public void clearCachedRow(int index)
458 {
459 clearCachedRows(index, 1);
460 }
461
462 /**
463 * Clears the row with the given row key from local cache.
464 * This is a do nothing implementaion which delegates to the
465 * correcsponding range based api
466 * @see #clearCachedRows(Object, int)
467 * @param rowKey row key for the row to remove from cache
468 */
469 public void clearCachedRow(Object rowKey)
470 {
471 clearCachedRows(rowKey, 1);
472 }
473
474 /**
475 * Clears a range of rows from local cache starting from a row index.
476 * This is a do nothing implemenation.
477 * @see #clearLocalCache
478 * @param startingIndex starting row index to clear the local cache from
479 * @param rowsToClear number of rows to clear
480 */
481 public void clearCachedRows(int startingIndex, int rowsToClear)
482 {
483 clearLocalCache();
484 }
485
486 /**
487 * Clears a range of rows from local cache starting from a row key
488 * This is a do nothing implemenation.
489 * @see #clearLocalCache
490 * @param startingRowKey starting row key to clear the local cache from
491 * @param rowsToClear number of rows to clear
492 */
493 public void clearCachedRows(Object startingRowKey, int rowsToClear)
494 {
495 clearLocalCache();
496 }
497
498 /**
499 * Clears the local cache.
500 * This is a do nothing implementation
501 */
502 public void clearLocalCache()
503 {
504 // do nothing
505 }
506
507 /**
508 * Returns the row caching strategy used by this implemenation. Default
509 * implementation indicates no caching supported
510 * @see LocalRowKeyIndex.LocalCachingStrategy
511 * @return caching strategy none
512 */
513 public LocalRowKeyIndex.LocalCachingStrategy getCachingStrategy()
514 {
515 return LocalRowKeyIndex.LocalCachingStrategy.NONE;
516 }
517
518 private List<RowKeyChangeListener> _rowKeyChangeListeners = new ArrayList<RowKeyChangeListener>(3);
519 }