1 package com.workingdogs.village;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.ByteArrayOutputStream;
23 import java.io.PrintWriter;
24 import java.sql.Connection;
25 import java.sql.ResultSet;
26 import java.sql.SQLException;
27 import java.sql.Statement;
28 import java.util.Vector;
29
30 /***
31 * The DataSet represents a table in the database. It is extended by <a href="QueryDataSet.html">QueryDataSet</a> and <a
32 * href="TableDataSet.html">TableDataSet</a> and should not be used directly. A DataSet contains a <a
33 * href="Schema.html">Schema</a> and potentially a collection of <a href="Record.html">Records</a>.
34 *
35 * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
36 * @version $Revision: 568 $
37 */
38 public abstract class DataSet
39 {
40 /*** indicates that all records should be retrieved during a fetch */
41 protected static final int ALL_RECORDS = -1;
42
43 /*** this DataSet's schema object */
44 protected Schema schema;
45
46 /*** this DataSet's collection of Record objects */
47 protected Vector records = null;
48
49 /*** this DataSet's connection object */
50 protected Connection conn;
51
52 /*** have all records been retrieved with the fetchRecords? */
53 private boolean allRecordsRetrieved = false;
54
55 /*** number of records retrieved */
56 private int recordRetrievedCount = 0;
57
58 /*** number of records that were last fetched */
59 private int lastFetchSize = 0;
60
61 /*** the columns in the SELECT statement for this DataSet */
62 private String columns;
63
64 /*** the select string that was used to build this DataSet */
65 protected StringBuffer selectString;
66
67 /*** the KeyDef for this DataSet */
68 private KeyDef keyDefValue;
69
70 /*** the result set for this DataSet */
71 protected ResultSet resultSet;
72
73 /*** the Statement for this DataSet */
74 protected Statement stmt;
75
76 /***
77 * Private, not used
78 *
79 * @exception DataSetException
80 * @exception SQLException
81 */
82 public DataSet()
83 throws DataSetException, SQLException
84 {
85 }
86
87 /***
88 * Create a new DataSet with a connection and a Table name
89 *
90 * @param conn
91 * @param tableName
92 *
93 * @exception DataSetException
94 * @exception SQLException
95 */
96 DataSet(Connection conn, String tableName)
97 throws DataSetException, SQLException
98 {
99 this.conn = conn;
100 this.columns = "*";
101 this.schema = new Schema().schema(conn, tableName);
102 }
103
104 /***
105 * Create a new DataSet with a connection, schema and KeyDef
106 *
107 * @param conn
108 * @param schema
109 * @param keydef
110 *
111 * @exception DataSetException
112 * @exception SQLException
113 */
114 DataSet(Connection conn, Schema schema, KeyDef keydef)
115 throws DataSetException, SQLException
116 {
117 if (conn == null)
118 {
119 throw new SQLException("Database connection could not be established!");
120 }
121 else if (schema == null)
122 {
123 throw new DataSetException("You need to specify a valid schema!");
124 }
125 else if (keydef == null)
126 {
127 throw new DataSetException("You need to specify a valid KeyDef!");
128 }
129
130 this.conn = conn;
131 this.schema = schema;
132 this.columns = "*";
133
134 this.keyDefValue = keydef;
135 }
136
137 /***
138 * Create a new DataSet with a connection, tablename and KeyDef
139 *
140 * @param conn
141 * @param tableName
142 * @param keydef
143 *
144 * @exception SQLException
145 * @exception DataSetException
146 */
147 DataSet(Connection conn, String tableName, KeyDef keydef)
148 throws SQLException, DataSetException
149 {
150 this.conn = conn;
151 this.keyDefValue = keydef;
152 this.columns = "*";
153 this.schema = new Schema().schema(conn, tableName);
154 }
155
156 /***
157 * Create a new DataSet with a connection, tablename and list of columns
158 *
159 * @param conn
160 * @param tableName
161 * @param columns
162 *
163 * @exception SQLException
164 * @exception DataSetException
165 */
166 DataSet(Connection conn, String tableName, String columns)
167 throws SQLException, DataSetException
168 {
169 this.conn = conn;
170 this.columns = columns;
171 this.schema = new Schema().schema(conn, tableName, columns);
172 }
173
174 /***
175 * Create a new DataSet with a connection, tableName, columns and a KeyDef
176 *
177 * @param conn
178 * @param tableName
179 * @param columns
180 * @param keyDef
181 *
182 * @exception SQLException
183 * @exception DataSetException
184 */
185 DataSet(Connection conn, String tableName, String columns, KeyDef keyDef)
186 throws SQLException, DataSetException
187 {
188 this.conn = conn;
189 this.columns = columns;
190 this.keyDefValue = keyDef;
191 this.schema = new Schema().schema(conn, tableName, columns);
192 }
193
194 /***
195 * Gets the ResultSet for this DataSet
196 *
197 * @return the result set for this DataSet
198 *
199 * @exception SQLException
200 * @exception DataSetException
201 */
202 public ResultSet resultSet()
203 throws SQLException, DataSetException
204 {
205 if (this.resultSet == null)
206 {
207 throw new DataSetException("ResultSet is null.");
208 }
209
210 return this.resultSet;
211 }
212
213 /***
214 * Calls addRecord(DataSet)
215 *
216 * @return the added record
217 *
218 * @exception DataSetException
219 * @exception SQLException
220 */
221 public Record addRecord()
222 throws DataSetException, SQLException
223 {
224 return addRecord(this);
225 }
226
227 /***
228 * Creates a new Record within this DataSet
229 *
230 * @param ds
231 *
232 * @return the added record
233 *
234 * @exception DataSetException
235 * @exception SQLException
236 */
237 public Record addRecord(DataSet ds)
238 throws DataSetException, SQLException
239 {
240 if (ds instanceof QueryDataSet)
241 {
242 throw new DataSetException("You cannot add records to a QueryDataSet.");
243 }
244
245 if (records == null)
246 {
247 records = new Vector(10);
248 }
249
250 Record rec = new Record(ds, true);
251 rec.markForInsert();
252 records.addElement(rec);
253
254 return rec;
255 }
256
257 /***
258 * Check if all the records have been retrieve
259 *
260 * @return true if all records have been retrieved
261 */
262 public boolean allRecordsRetrieved()
263 {
264 return this.allRecordsRetrieved;
265 }
266
267 /***
268 * Set all records retrieved
269 *
270 * @param set TODO: DOCUMENT ME!
271 */
272 void setAllRecordsRetrieved(boolean set)
273 {
274 this.allRecordsRetrieved = set;
275 }
276
277 /***
278 * Remove a record from the DataSet's internal storage
279 *
280 * @param rec
281 *
282 * @return the record removed
283 *
284 * @exception DataSetException
285 */
286 public Record removeRecord(Record rec)
287 throws DataSetException
288 {
289 Record removeRec = null;
290
291 try
292 {
293 int loc = this.records.indexOf(rec);
294 removeRec = (Record) this.records.elementAt(loc);
295 this.records.removeElementAt(loc);
296 }
297 catch (Exception e)
298 {
299 throw new DataSetException("Record could not be removed!");
300 }
301
302 return removeRec;
303 }
304
305 /***
306 * Remove all records from the DataSet and nulls those records out and close() the DataSet.
307 *
308 * @return an instance of myself
309 */
310 public DataSet clearRecords()
311 {
312 this.records.removeAllElements();
313 this.records = null;
314
315 return this;
316 }
317
318 /***
319 * Removes the records from the DataSet, but does not null the records out
320 *
321 * @return an instance of myself
322 */
323 public DataSet releaseRecords()
324 {
325 this.records = null;
326 this.recordRetrievedCount = 0;
327 this.lastFetchSize = 0;
328 setAllRecordsRetrieved(false);
329
330 return this;
331 }
332
333 /***
334 * Releases the records, closes the ResultSet and the Statement, and nulls the Schema and Connection references.
335 *
336 * @exception SQLException
337 * @exception DataSetException
338 */
339 public void close()
340 throws SQLException, DataSetException
341 {
342 releaseRecords();
343 this.schema = null;
344
345 SQLException sqlEx = null;
346
347 try
348 {
349 if (this.resultSet != null)
350 {
351 resultSet().close();
352 }
353 }
354 catch (SQLException e)
355 {
356 sqlEx = e;
357 }
358
359 this.resultSet = null;
360
361 try
362 {
363 if (this.stmt != null)
364 {
365 this.stmt.close();
366 }
367 }
368 catch (SQLException e)
369 {
370 sqlEx = e;
371 }
372
373 this.conn = null;
374
375 if (sqlEx != null)
376 {
377 throw sqlEx;
378 }
379 }
380
381 /***
382 * Essentially the same as releaseRecords, but it won't work on a QueryDataSet that has been created with a ResultSet
383 *
384 * @return an instance of myself
385 *
386 * @exception DataSetException
387 * @exception SQLException
388 */
389 public DataSet reset()
390 throws DataSetException, SQLException
391 {
392 if (!((resultSet() != null) && (this instanceof QueryDataSet)))
393 {
394 return releaseRecords();
395 }
396 else
397 {
398 throw new DataSetException("You cannot call reset() on a QueryDataSet.");
399 }
400 }
401
402 /***
403 * Gets the current database connection
404 *
405 * @return a database connection
406 *
407 * @exception SQLException
408 */
409 public Connection connection()
410 throws SQLException
411 {
412 return this.conn;
413 }
414
415 /***
416 * Gets the Schema for this DataSet
417 *
418 * @return the Schema for this DataSet
419 */
420 public Schema schema()
421 {
422 return this.schema;
423 }
424
425 /***
426 * Get Record at 0 based index position
427 *
428 * @param pos
429 *
430 * @return an instance of the found Record
431 *
432 * @exception DataSetException
433 */
434 public Record getRecord(int pos)
435 throws DataSetException
436 {
437 if (containsRecord(pos))
438 {
439 Record rec = (Record) this.records.elementAt(pos);
440
441 if (this instanceof TableDataSet)
442 {
443 rec.markForUpdate();
444 }
445
446 recordRetrievedCount++;
447
448 return rec;
449 }
450
451 throw new DataSetException("Record not found at index: " + pos);
452 }
453
454 /***
455 * Find Record at 0 based index position. This is an internal alternative to getRecord which tries to be smart about the type
456 * of record it is.
457 *
458 * @param pos
459 *
460 * @return an instance of the found Record
461 *
462 * @exception DataSetException
463 */
464 Record findRecord(int pos)
465 throws DataSetException
466 {
467 if (containsRecord(pos))
468 {
469 return (Record) this.records.elementAt(pos);
470 }
471
472 throw new DataSetException("Record not found at index: " + pos);
473 }
474
475 /***
476 * Check to see if the DataSet contains a Record at 0 based position
477 *
478 * @param pos
479 *
480 * @return true if record exists
481 */
482 public boolean containsRecord(int pos)
483 {
484 try
485 {
486 if (this.records.elementAt(pos) != null)
487 {
488 return true;
489 }
490 }
491 catch (Exception e)
492 {
493 return false;
494 }
495
496 return false;
497 }
498
499 /***
500 * Causes the DataSet to hit the database and fetch all the records.
501 *
502 * @return an instance of myself
503 *
504 * @exception SQLException
505 * @exception DataSetException
506 */
507 public DataSet fetchRecords()
508 throws SQLException, DataSetException
509 {
510 return fetchRecords(ALL_RECORDS);
511 }
512
513 /***
514 * Causes the DataSet to hit the database and fetch max records.
515 *
516 * @param max
517 *
518 * @return an instance of myself
519 *
520 * @exception SQLException
521 * @exception DataSetException
522 */
523 public DataSet fetchRecords(int max)
524 throws SQLException, DataSetException
525 {
526 return fetchRecords(0, max);
527 }
528
529 /***
530 * Causes the DataSet to hit the database and fetch max records, starting at start. Record count begins at 0.
531 *
532 * @param start
533 * @param max
534 *
535 * @return an instance of myself
536 *
537 * @exception SQLException
538 * @exception DataSetException
539 */
540 public DataSet fetchRecords(int start, int max)
541 throws SQLException, DataSetException
542 {
543 if (max == 0)
544 {
545 throw new DataSetException("Max is 1 based and must be greater than 0!");
546 }
547 else if ((lastFetchSize() > 0) && (this.records != null))
548 {
549 throw new DataSetException("You must call DataSet.clearRecords() before executing DataSet.fetchRecords() again!");
550 }
551
552 if (selectString == null)
553 {
554 selectString = new StringBuffer(256);
555 selectString.append("SELECT ");
556 selectString.append(schema().attributes());
557 selectString.append(" FROM ");
558 selectString.append(schema().tableName());
559 }
560
561 try
562 {
563 if ((stmt == null) && (this.resultSet == null))
564 {
565 stmt = connection().createStatement();
566 this.resultSet = stmt.executeQuery(selectString.toString());
567 }
568
569 if (this.resultSet != null)
570 {
571 if ((this.records == null) && (max > 0))
572 {
573 this.records = new Vector(max);
574 }
575 else
576 {
577 this.records = new Vector();
578 }
579
580 int startCounter = 0;
581 int fetchCount = 0;
582
583 while (!allRecordsRetrieved())
584 {
585 if (fetchCount == max)
586 {
587 break;
588 }
589
590 if (this.resultSet.next())
591 {
592 if (startCounter >= start)
593 {
594 Record rec = new Record(this);
595 records.addElement(rec);
596 fetchCount++;
597 }
598 else
599 {
600 startCounter++;
601 }
602 }
603 else
604 {
605 setAllRecordsRetrieved(true);
606
607 break;
608 }
609 }
610
611 lastFetchSize = fetchCount;
612 }
613 }
614 catch (SQLException e)
615 {
616 if (stmt != null)
617 {
618 stmt.close();
619 }
620
621 throw new SQLException(e.getMessage());
622 }
623
624 return this;
625 }
626
627 /***
628 * The number of records that were fetched with the last fetchRecords.
629 *
630 * @return int
631 */
632 public int lastFetchSize()
633 {
634 return lastFetchSize;
635 }
636
637 /***
638 * gets the KeyDef object for this DataSet
639 *
640 * @return the keydef for this DataSet, this value can be null
641 */
642 public KeyDef keydef()
643 {
644 return this.keyDefValue;
645 }
646
647 /***
648 * This returns a represention of this DataSet
649 *
650 * @return TODO: DOCUMENT ME!
651 */
652 public String toString()
653 {
654 try
655 {
656 ByteArrayOutputStream bout = new ByteArrayOutputStream();
657 PrintWriter out = new PrintWriter(bout);
658
659 if (schema != null)
660 {
661 out.println(schema.toString());
662 }
663
664 for (int i = 0; i < size(); i++)
665 {
666 out.println(findRecord(i));
667 }
668
669 out.flush();
670
671 return bout.toString();
672 }
673 catch (DataSetException e)
674 {
675 return "{}";
676 }
677 }
678
679 /***
680 * Gets the tableName defined in the schema
681 *
682 * @return string
683 *
684 * @throws DataSetException TODO: DOCUMENT ME!
685 */
686 public String tableName()
687 throws DataSetException
688 {
689 return schema().tableName();
690 }
691
692 /***
693 * Classes extending this class must implement this method.
694 *
695 * @return the select string
696 *
697 * @throws DataSetException TODO: DOCUMENT ME!
698 */
699 public abstract String getSelectString()
700 throws DataSetException;
701
702 /***
703 * Returns the columns attribute for the DataSet
704 *
705 * @return the columns attribute for the DataSet
706 */
707 String getColumns()
708 {
709 return this.columns;
710 }
711
712 /***
713 * Gets the number of Records in this DataSet. It is 0 based.
714 *
715 * @return number of Records in this DataSet
716 */
717 public int size()
718 {
719 if (this.records == null)
720 {
721 return 0;
722 }
723
724 return this.records.size();
725 }
726 }