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