View Javadoc

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 }