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 /** 519 * Ensure that the model has at least rowCount number of rows. This is especially 520 * useful for collection model that support paging. The default implementation 521 * is a no-op. 522 * 523 * @param rowCount the number of rows the model should hold. 524 */ 525 public void ensureRowsAvailable(int rowCount) 526 { 527 return; 528 } 529 530 /** 531 * Gets the row limit of this collection model. Default is to return UNKNOWN_ROW_LIMIT. 532 * Subclasses should override this method if row limit is enforced. 533 * @return the maximum number of rows this collection can hold, possible return values are: 534 * A positive number: the maximum number of rows the collection can hold 535 * UNKNOWN_ROW_LIMIT: row limit is unknown. 536 * UNLIMITED_ROW: there is no limit 537 */ 538 public int getRowLimit() 539 { 540 return UNKNOWN_ROW_LIMIT; 541 } 542 543 public final static int UNLIMITED_ROW = -2; 544 public final static int UNKNOWN_ROW_LIMIT = -1; 545 546 private List<RowKeyChangeListener> _rowKeyChangeListeners = new ArrayList<RowKeyChangeListener>(3); 547 }