1 /* 2 * $Id: ResultSetDataModel.java,v 1.34 2007/04/27 22:00:09 ofung Exp $ 3 */ 4 5 /* 6 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 7 * 8 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved. 9 * 10 * The contents of this file are subject to the terms of either the GNU 11 * General Public License Version 2 only ("GPL") or the Common Development 12 * and Distribution License("CDDL") (collectively, the "License"). You 13 * may not use this file except in compliance with the License. You can obtain 14 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html 15 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific 16 * language governing permissions and limitations under the License. 17 * 18 * When distributing the software, include this License Header Notice in each 19 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt. 20 * Sun designates this particular file as subject to the "Classpath" exception 21 * as provided by Sun in the GPL Version 2 section of the License file that 22 * accompanied this code. If applicable, add the following below the License 23 * Header, with the fields enclosed by brackets [] replaced by your own 24 * identifying information: "Portions Copyrighted [year] 25 * [name of copyright owner]" 26 * 27 * Contributor(s): 28 * 29 * If you wish your version of this file to be governed by only the CDDL or 30 * only the GPL Version 2, indicate your decision by adding "[Contributor] 31 * elects to include this software in this distribution under the [CDDL or GPL 32 * Version 2] license." If you don't indicate a single choice of license, a 33 * recipient has the option to distribute your version of this file under 34 * either the CDDL, the GPL Version 2 or to extend the choice of license to 35 * its licensees as provided above. However, if you add GPL Version 2 code 36 * and therefore, elected the GPL Version 2 license, then the option applies 37 * only if the new code is made subject to such option by the copyright 38 * holder. 39 */ 40 41 package javax.faces.model; 42 43 44 import java.sql.ResultSet; 45 import java.sql.ResultSetMetaData; 46 import java.sql.SQLException; 47 import java.util.AbstractCollection; 48 import java.util.AbstractSet; 49 import java.util.Collection; 50 import java.util.Comparator; 51 import java.util.Iterator; 52 import java.util.Map; 53 import java.util.Set; 54 import java.util.TreeMap; 55 import java.io.IOException; 56 import java.io.ObjectInputStream; 57 import java.io.NotSerializableException; 58 import java.io.ObjectOutputStream; 59 60 import javax.faces.FacesException; 61 62 63 /** 64 * <p><strong>ResultSetDataModel</strong> is a convenience implementation of 65 * {@link DataModel} that wraps a <code>ResultSet</code> of Java objects. 66 * Note that the specified <code>ResultSet</code> <strong>MUST</strong> 67 * be scrollable. In addition, if input components (that will be updating 68 * model values) reference this object in value binding expressions, the 69 * specified <code>ResultSet</code> <strong>MUST</strong> be updatable.</p> 70 */ 71 72 public class ResultSetDataModel extends DataModel<Map<String,Object>> { 73 74 75 // ------------------------------------------------------------ Constructors 76 77 78 /** 79 * <p>Construct a new {@link ResultSetDataModel} with no specified 80 * wrapped data.</p> 81 */ 82 public ResultSetDataModel() { 83 84 this(null); 85 86 } 87 88 89 /** 90 * <p>Construct a new {@link ResultSetDataModel} wrapping the specified 91 * <code>ResultSet</code>.</p> 92 * 93 * @param resultSet <code>ResultSet</code> to be wrapped (if any) 94 */ 95 public ResultSetDataModel(ResultSet resultSet) { 96 97 super(); 98 setWrappedData(resultSet); 99 100 } 101 102 103 // ------------------------------------------------------ Instance Variables 104 105 106 // The current row index (zero relative) 107 private int index = -1; 108 109 110 // The metadata for the ResultSet we are wrapping (lazily instantiated) 111 private ResultSetMetaData metadata = null; 112 113 114 // The ResultSet we are wrapping 115 private ResultSet resultSet = null; 116 117 118 // Has the row at the current index been updated? 119 private boolean updated = false; 120 121 122 // -------------------------------------------------------------- Properties 123 124 125 /** 126 * <p>Return <code>true</code> if there is <code>wrappedData</code> 127 * available, and the result of calling <code>absolute()</code> on the 128 * underlying <code>ResultSet</code>, passing the current value of 129 * <code>rowIndex</code> plus one (to account for the fact that 130 * <code>ResultSet</code> uses one-relative indexing), returns 131 * <code>true</code>. Otherwise, return <code>false</code>.</p> 132 * 133 * @throws FacesException if an error occurs getting the row availability 134 */ 135 public boolean isRowAvailable() { 136 137 if (resultSet == null) { 138 return (false); 139 } else if (index < 0) { 140 return (false); 141 } 142 try { 143 if (resultSet.absolute(index + 1)) { 144 return (true); 145 } else { 146 return (false); 147 } 148 } catch (SQLException e) { 149 throw new FacesException(e); 150 } 151 152 } 153 154 155 /** 156 * <p>Return -1, since <code>ResultSet</code> does not provide a 157 * standard way to determine the number of available rows without 158 * scrolling through the entire <code>ResultSet</code>, and this can 159 * be very expensive if the number of rows is large.</p> 160 * 161 * @throws FacesException if an error occurs getting the row count 162 */ 163 public int getRowCount() { 164 165 return (-1); 166 167 } 168 169 170 /** 171 * <p>If row data is available, return a <code>Map</code> representing 172 * the values of the columns for the row specified by <code>rowIndex</code>, 173 * keyed by the corresponding column names. If no wrapped data is 174 * available, return <code>null</code>.</p> 175 * 176 * <p>If a non-<code>null</code> <code>Map</code> is returned, its behavior 177 * must correspond to the contract for a mutable <code>Map</code> as 178 * described in the JavaDocs for <code>AbstractMap</code>, with the 179 * following exceptions and specialized behavior:</p> 180 * <ul> 181 182 * <li>The <code>Map</code>, and any supporting objects it returns, 183 * must perform all column name comparisons in a 184 * case-insensitive manner. This case-insensitivity must be 185 * implemented using a case-insensitive <code>Comparator</code>, 186 * such as 187 * <code>String.CASE_INSENSITIVE_ORDER</code>.</li> 188 189 * <li>The following methods must throw 190 * <code>UnsupportedOperationException</code>: <code>clear()</code>, 191 * <code>remove()</code>.</li> 192 * <li>The <code>entrySet()</code> method must return a <code>Set</code> 193 * that has the following behavior: 194 * <ul> 195 * <li>Throw <code>UnsupportedOperationException</code> for any attempt 196 * to add or remove entries from the <code>Set</code>, either 197 * directly or indirectly through an <code>Iterator</code> 198 * returned by the <code>Set</code>.</li> 199 * <li>Updates to the <code>value</code> of an entry in this 200 * <code>set</code> must write through to the corresponding 201 * column value in the underlying <code>ResultSet</code>.</li> 202 * </ul></li> 203 * <li>The <code>keySet()</code> method must return a <code>Set</code> 204 * that throws <code>UnsupportedOperationException</code> on any 205 * attempt to add or remove keys, either directly or through an 206 * <code>Iterator</code> returned by the <code>Set</code>.</li> 207 * <li>The <code>put()</code> method must throw 208 * <code>IllegalArgumentException</code> if a key value for which 209 * <code>containsKey()</code> returns <code>false</code> is 210 * specified. However, if a key already present in the <code>Map</code> 211 * is specified, the specified value must write through to the 212 * corresponding column value in the underlying <code>ResultSet</code>. 213 * </li> 214 * <li>The <code>values()</code> method must return a 215 * <code>Collection</code> that throws 216 * <code>UnsupportedOperationException</code> on any attempt to add 217 * or remove values, either directly or through an <code>Iterator</code> 218 * returned by the <code>Collection</code>.</li> 219 * </ul> 220 * 221 * @throws FacesException if an error occurs getting the row data 222 * @throws IllegalArgumentException if now row data is available 223 * at the currently specified row index 224 */ 225 public Map<String,Object> getRowData() { 226 227 if (resultSet == null) { 228 return (null); 229 } else if (!isRowAvailable()) { 230 throw new NoRowAvailableException(); 231 } 232 try { 233 getMetaData(); 234 return (new ResultSetMap(this, String.CASE_INSENSITIVE_ORDER)); 235 } catch (SQLException e) { 236 throw new FacesException(e); 237 } 238 239 } 240 241 242 /** 243 * @throws FacesException {@inheritDoc} 244 */ 245 public int getRowIndex() { 246 247 return (index); 248 249 } 250 251 252 /** 253 * @throws FacesException {@inheritDoc} 254 * @throws IllegalArgumentException {@inheritDoc} 255 */ 256 public void setRowIndex(int rowIndex) { 257 258 if (rowIndex < -1) { 259 throw new IllegalArgumentException(); 260 } 261 262 // Tell the ResultSet that the previous row was updated if necessary 263 if (updated && (resultSet != null)) { 264 try { 265 if (!resultSet.rowDeleted()) { 266 resultSet.updateRow(); 267 } 268 updated = false; 269 } catch (SQLException e) { 270 throw new FacesException(e); 271 } 272 } 273 274 int old = index; 275 index = rowIndex; 276 if (resultSet == null) { 277 return; 278 } 279 DataModelListener [] listeners = getDataModelListeners(); 280 if ((old != index) && (listeners != null)) { 281 Object rowData = null; 282 if (isRowAvailable()) { 283 rowData = getRowData(); 284 } 285 DataModelEvent event = 286 new DataModelEvent(this, index, rowData); 287 int n = listeners.length; 288 for (int i = 0; i < n; i++) { 289 if (null != listeners[i]) { 290 listeners[i].rowSelected(event); 291 } 292 } 293 } 294 295 296 } 297 298 299 public Object getWrappedData() { 300 301 return (this.resultSet); 302 303 } 304 305 306 /** 307 * @throws ClassCastException {@inheritDoc} 308 */ 309 public void setWrappedData(Object data) { 310 311 if (data == null) { 312 metadata = null; 313 resultSet = null; 314 setRowIndex(-1); 315 } else { 316 metadata = null; 317 resultSet = (ResultSet) data; 318 index = -1; 319 setRowIndex(0); 320 } 321 } 322 323 324 // --------------------------------------------------------- Private Methods 325 326 327 /** 328 * <p>Return the <code>ResultSetMetaData</code> for the 329 * <code>ResultSet</code> we are wrapping, caching it the first time 330 * it is returned.</p> 331 * 332 * @throws FacesException if the <code>ResultSetMetaData</code> 333 * cannot be acquired 334 */ 335 private ResultSetMetaData getMetaData() { 336 337 if (metadata == null) { 338 try { 339 metadata = resultSet.getMetaData(); 340 } catch (SQLException e) { 341 throw new FacesException(e); 342 } 343 } 344 return (metadata); 345 346 } 347 348 349 /** 350 * <p>Mark the current row as having been updated, so that we will call 351 * <code>updateRow()</code> before moving elsewhere.</p> 352 */ 353 private void updated() { 354 355 this.updated = true; 356 357 } 358 359 360 // --------------------------------------------------------- Private Classes 361 362 363 // Private implementation of Map that delegates column get and put 364 // operations to the underlying ResultSet, after setting the required 365 // row index 366 // NOT SERIALIZABLE 367 @SuppressWarnings({"serial"}) 368 private static class ResultSetMap extends TreeMap<String,Object> { 369 370 private ResultSetDataModel model; 371 372 public ResultSetMap(ResultSetDataModel model, 373 Comparator<String> comparator) throws SQLException { 374 375 super(comparator); 376 this.model = model; 377 index = model.index; 378 model.resultSet.absolute(index + 1); 379 int n = model.metadata.getColumnCount(); 380 for (int i = 1; i <= n; i++) { 381 super.put(model.metadata.getColumnName(i), 382 model.metadata.getColumnName(i)); 383 } 384 } 385 386 // The zero-relative row index of our row 387 private int index; 388 389 // Removing entries is not allowed 390 public void clear() { 391 throw new UnsupportedOperationException(); 392 } 393 394 public boolean containsValue(Object value) { 395 for (Iterator i = entrySet().iterator(); i .hasNext(); ) { 396 Map.Entry entry = (Map.Entry) i.next(); 397 Object contained = entry.getValue(); 398 if (value == null) { 399 if (contained == null) { 400 return (true); 401 } 402 } else { 403 if (value.equals(contained)) { 404 return (true); 405 } 406 } 407 } 408 return (false); 409 } 410 411 public Set<Map.Entry<String,Object>> entrySet() { 412 return (new ResultSetEntries(this)); 413 } 414 415 public Object get(Object key) { 416 if (!containsKey(key)) { 417 return (null); 418 } 419 try { 420 model.resultSet.absolute(index + 1); 421 return (model.resultSet.getObject((String) realKey(key))); 422 } catch (SQLException e) { 423 throw new FacesException(e); 424 } 425 } 426 427 public Set<String> keySet() { 428 return (new ResultSetKeys(this)); 429 } 430 431 public Object put(String key, Object value) { 432 if (!containsKey(key)) { 433 throw new IllegalArgumentException(); 434 } 435 436 try { 437 model.resultSet.absolute(index + 1); 438 Object previous = model.resultSet.getObject((String) realKey(key)); 439 if ((previous == null) && (value == null)) { 440 return (previous); 441 } else if ((previous != null) && (value != null) && 442 previous.equals(value)) { 443 return (previous); 444 } 445 model.resultSet.updateObject((String) realKey(key), value); 446 model.updated(); 447 return (previous); 448 } catch (SQLException e) { 449 throw new FacesException(e); 450 } 451 } 452 453 public void putAll(Map<? extends String, ? extends Object> map) { 454 for (Map.Entry<? extends String, ? extends Object> entry : map.entrySet()) { 455 put(entry.getKey(), entry.getValue()); 456 } 457 } 458 459 // Removing entries is not allowed 460 public Object remove(Object key) { 461 throw new UnsupportedOperationException(); 462 } 463 464 public Collection<Object> values() { 465 return (new ResultSetValues(this)); 466 } 467 468 Object realKey(Object key) { 469 return (super.get(key)); 470 } 471 472 Iterator<String> realKeys() { 473 return (super.keySet().iterator()); 474 } 475 476 private void writeObject(ObjectOutputStream out) throws IOException { 477 throw new NotSerializableException(); 478 } 479 480 private void readObject(ObjectInputStream in) throws IOException { 481 throw new NotSerializableException(); 482 } 483 484 } 485 486 487 // Private implementation of Set that implements the entrySet() behavior 488 // for ResultSetMap 489 private static class ResultSetEntries extends AbstractSet<Map.Entry<String,Object>> { 490 491 public ResultSetEntries(ResultSetMap map) { 492 this.map = map; 493 } 494 495 private ResultSetMap map; 496 497 // Adding entries is not allowed 498 public boolean add(Map.Entry<String,Object> o) { 499 throw new UnsupportedOperationException(); 500 } 501 502 // Adding entries is not allowed 503 public boolean addAll(Collection c) { 504 throw new UnsupportedOperationException(); 505 } 506 507 // Removing entries is not allowed 508 public void clear() { 509 throw new UnsupportedOperationException(); 510 } 511 512 public boolean contains(Object o) { 513 if (o == null) { 514 throw new NullPointerException(); 515 } 516 if (!(o instanceof Map.Entry)) { 517 return (false); 518 } 519 Map.Entry e = (Map.Entry) o; 520 Object k = e.getKey(); 521 Object v = e.getValue(); 522 if (!map.containsKey(k)) { 523 return (false); 524 } 525 if (v == null) { 526 return (map.get(k) == null); 527 } else { 528 return (v.equals(map.get(k))); 529 } 530 } 531 532 public boolean isEmpty() { 533 return (map.isEmpty()); 534 } 535 536 public Iterator<Map.Entry<String,Object>> iterator() { 537 return (new ResultSetEntriesIterator(map)); 538 } 539 540 // Removing entries is not allowed 541 public boolean remove(Object o) { 542 throw new UnsupportedOperationException(); 543 } 544 545 // Removing entries is not allowed 546 public boolean removeAll(Collection c) { 547 throw new UnsupportedOperationException(); 548 } 549 550 // Removing entries is not allowed 551 public boolean retainAll(Collection c) { 552 throw new UnsupportedOperationException(); 553 } 554 555 public int size() { 556 return (map.size()); 557 } 558 559 } 560 561 562 // Private implementation of Iterator that implements the iterator() 563 // behavior for the Set returned by entrySet() from ResultSetMap 564 private static class ResultSetEntriesIterator implements Iterator<Map.Entry<String,Object>> { 565 566 public ResultSetEntriesIterator(ResultSetMap map) { 567 this.map = map; 568 this.keys = map.keySet().iterator(); 569 } 570 571 private ResultSetMap map = null; 572 private Iterator<String> keys = null; 573 574 public boolean hasNext() { 575 return (keys.hasNext()); 576 } 577 578 public Map.Entry<String,Object> next() { 579 String key = keys.next(); 580 return (new ResultSetEntry(map, key)); 581 } 582 583 // Removing entries is not allowed 584 public void remove() { 585 throw new UnsupportedOperationException(); 586 } 587 588 } 589 590 591 // Private implementation of Map.Entry that implements the behavior for 592 // a single entry from the Set returned by entrySet() from ResultSetMap 593 private static class ResultSetEntry implements Map.Entry<String,Object> { 594 595 public ResultSetEntry(ResultSetMap map, String key) { 596 this.map = map; 597 this.key = key; 598 } 599 600 private ResultSetMap map; 601 private String key; 602 603 public boolean equals(Object o) { 604 if (o == null) { 605 return (false); 606 } 607 if (!(o instanceof Map.Entry)) { 608 return (false); 609 } 610 Map.Entry e = (Map.Entry) o; 611 if (key == null) { 612 if (e.getKey() != null) { 613 return (false); 614 } 615 } else { 616 if (!key.equals(e.getKey())) { 617 return (false); 618 } 619 } 620 Object v = map.get(key); 621 if (v == null) { 622 if (e.getValue() != null) { 623 return (false); 624 } 625 } else { 626 if (!v.equals(e.getValue())) { 627 return (false); 628 } 629 } 630 return (true); 631 } 632 633 public String getKey() { 634 return (key); 635 } 636 637 public Object getValue() { 638 return (map.get(key)); 639 } 640 641 public int hashCode() { 642 Object value = map.get(key); 643 return (((key == null) ? 0 : key.hashCode()) ^ 644 ((value == null) ? 0 : value.hashCode())); 645 } 646 647 public Object setValue(Object value) { 648 Object previous = map.get(key); 649 map.put(key, value); 650 return (previous); 651 } 652 653 } 654 655 656 // Private implementation of Set that implements the keySet() behavior 657 // for ResultSetMap 658 private static class ResultSetKeys extends AbstractSet<String> { 659 660 public ResultSetKeys(ResultSetMap map) { 661 this.map = map; 662 } 663 664 private ResultSetMap map; 665 666 // Adding keys is not allowed 667 public boolean add(String o) { 668 throw new UnsupportedOperationException(); 669 } 670 671 // Adding keys is not allowed 672 public boolean addAll(Collection c) { 673 throw new UnsupportedOperationException(); 674 } 675 676 // Removing keys is not allowed 677 public void clear() { 678 throw new UnsupportedOperationException(); 679 } 680 681 public boolean contains(Object o) { 682 return (map.containsKey(o)); 683 } 684 685 public boolean isEmpty() { 686 return (map.isEmpty()); 687 } 688 689 public Iterator<String> iterator() { 690 return (new ResultSetKeysIterator(map)); 691 } 692 693 // Removing keys is not allowed 694 public boolean remove(Object o) { 695 throw new UnsupportedOperationException(); 696 } 697 698 // Removing keys is not allowed 699 public boolean removeAll(Collection c) { 700 throw new UnsupportedOperationException(); 701 } 702 703 // Removing keys is not allowed 704 public boolean retainAll(Collection c) { 705 throw new UnsupportedOperationException(); 706 } 707 708 public int size() { 709 return (map.size()); 710 } 711 712 } 713 714 715 // Private implementation of Iterator that implements the iterator() 716 // behavior for the Set returned by keySet() from ResultSetMap 717 private static class ResultSetKeysIterator implements Iterator<String> { 718 719 public ResultSetKeysIterator(ResultSetMap map) { 720 this.keys = map.realKeys(); 721 } 722 723 private Iterator<String> keys = null; 724 725 public boolean hasNext() { 726 return (keys.hasNext()); 727 } 728 729 public String next() { 730 return (keys.next()); 731 } 732 733 // Removing keys is not allowed 734 public void remove() { 735 throw new UnsupportedOperationException(); 736 } 737 738 } 739 740 741 // Private implementation of Collection that implements the behavior 742 // for the Collection returned by values() from ResultSetMap 743 private static class ResultSetValues extends AbstractCollection<Object> { 744 745 public ResultSetValues(ResultSetMap map) { 746 this.map = map; 747 } 748 749 private ResultSetMap map; 750 751 public boolean add(Object o) { 752 throw new UnsupportedOperationException(); 753 } 754 755 public boolean addAll(Collection c) { 756 throw new UnsupportedOperationException(); 757 } 758 759 public void clear() { 760 throw new UnsupportedOperationException(); 761 } 762 763 public boolean contains(Object value) { 764 return (map.containsValue(value)); 765 } 766 767 public Iterator<Object> iterator() { 768 return (new ResultSetValuesIterator(map)); 769 } 770 771 public boolean remove(Object o) { 772 throw new UnsupportedOperationException(); 773 } 774 775 public boolean removeAll(Collection c) { 776 throw new UnsupportedOperationException(); 777 } 778 779 public boolean retainAll(Collection c) { 780 throw new UnsupportedOperationException(); 781 } 782 783 public int size() { 784 return (map.size()); 785 } 786 787 } 788 789 790 // Private implementation of Iterator that implements the behavior 791 // for the Iterator returned by values().iterator() from ResultSetMap 792 private static class ResultSetValuesIterator implements Iterator<Object> { 793 794 public ResultSetValuesIterator(ResultSetMap map) { 795 this.map = map; 796 this.keys = map.keySet().iterator(); 797 } 798 799 private ResultSetMap map; 800 private Iterator<String> keys; 801 802 public boolean hasNext() { 803 return (keys.hasNext()); 804 } 805 806 public Object next() { 807 return (map.get(keys.next())); 808 } 809 810 public void remove() { 811 throw new UnsupportedOperationException(); 812 } 813 814 } 815 816 817 }