001: package com.workingdogs.village;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.io.ByteArrayOutputStream;
023: import java.io.PrintWriter;
024:
025: import java.sql.Connection;
026: import java.sql.ResultSet;
027: import java.sql.SQLException;
028: import java.sql.Statement;
029:
030: import java.util.Vector;
031:
032: /**
033: * The DataSet represents a table in the database. It is extended by <a href="QueryDataSet.html">QueryDataSet</a> and <a
034: * href="TableDataSet.html">TableDataSet</a> and should not be used directly. A DataSet contains a <a
035: * href="Schema.html">Schema</a> and potentially a collection of <a href="Record.html">Records</a>.
036: *
037: * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
038: * @version $Revision: 568 $
039: */
040: public abstract class DataSet {
041: /** indicates that all records should be retrieved during a fetch */
042: protected static final int ALL_RECORDS = -1;
043:
044: /** this DataSet's schema object */
045: protected Schema schema;
046:
047: /** this DataSet's collection of Record objects */
048: protected Vector records = null;
049:
050: /** this DataSet's connection object */
051: protected Connection conn;
052:
053: /** have all records been retrieved with the fetchRecords? */
054: private boolean allRecordsRetrieved = false;
055:
056: /** number of records retrieved */
057: private int recordRetrievedCount = 0;
058:
059: /** number of records that were last fetched */
060: private int lastFetchSize = 0;
061:
062: /** the columns in the SELECT statement for this DataSet */
063: private String columns;
064:
065: /** the select string that was used to build this DataSet */
066: protected StringBuffer selectString;
067:
068: /** the KeyDef for this DataSet */
069: private KeyDef keyDefValue;
070:
071: /** the result set for this DataSet */
072: protected ResultSet resultSet;
073:
074: /** the Statement for this DataSet */
075: protected Statement stmt;
076:
077: /**
078: * Private, not used
079: *
080: * @exception DataSetException
081: * @exception SQLException
082: */
083: public DataSet() throws DataSetException, SQLException {
084: }
085:
086: /**
087: * Create a new DataSet with a connection and a Table name
088: *
089: * @param conn
090: * @param tableName
091: *
092: * @exception DataSetException
093: * @exception SQLException
094: */
095: DataSet(Connection conn, String tableName) throws DataSetException,
096: SQLException {
097: this .conn = conn;
098: this .columns = "*";
099: this .schema = new Schema().schema(conn, tableName);
100: }
101:
102: /**
103: * Create a new DataSet with a connection, schema and KeyDef
104: *
105: * @param conn
106: * @param schema
107: * @param keydef
108: *
109: * @exception DataSetException
110: * @exception SQLException
111: */
112: DataSet(Connection conn, Schema schema, KeyDef keydef)
113: throws DataSetException, SQLException {
114: if (conn == null) {
115: throw new SQLException(
116: "Database connection could not be established!");
117: } else if (schema == null) {
118: throw new DataSetException(
119: "You need to specify a valid schema!");
120: } else if (keydef == null) {
121: throw new DataSetException(
122: "You need to specify a valid KeyDef!");
123: }
124:
125: this .conn = conn;
126: this .schema = schema;
127: this .columns = "*";
128:
129: this .keyDefValue = keydef;
130: }
131:
132: /**
133: * Create a new DataSet with a connection, tablename and KeyDef
134: *
135: * @param conn
136: * @param tableName
137: * @param keydef
138: *
139: * @exception SQLException
140: * @exception DataSetException
141: */
142: DataSet(Connection conn, String tableName, KeyDef keydef)
143: throws SQLException, DataSetException {
144: this .conn = conn;
145: this .keyDefValue = keydef;
146: this .columns = "*";
147: this .schema = new Schema().schema(conn, tableName);
148: }
149:
150: /**
151: * Create a new DataSet with a connection, tablename and list of columns
152: *
153: * @param conn
154: * @param tableName
155: * @param columns
156: *
157: * @exception SQLException
158: * @exception DataSetException
159: */
160: DataSet(Connection conn, String tableName, String columns)
161: throws SQLException, DataSetException {
162: this .conn = conn;
163: this .columns = columns;
164: this .schema = new Schema().schema(conn, tableName, columns);
165: }
166:
167: /**
168: * Create a new DataSet with a connection, tableName, columns and a KeyDef
169: *
170: * @param conn
171: * @param tableName
172: * @param columns
173: * @param keyDef
174: *
175: * @exception SQLException
176: * @exception DataSetException
177: */
178: DataSet(Connection conn, String tableName, String columns,
179: KeyDef keyDef) throws SQLException, DataSetException {
180: this .conn = conn;
181: this .columns = columns;
182: this .keyDefValue = keyDef;
183: this .schema = new Schema().schema(conn, tableName, columns);
184: }
185:
186: /**
187: * Gets the ResultSet for this DataSet
188: *
189: * @return the result set for this DataSet
190: *
191: * @exception SQLException
192: * @exception DataSetException
193: */
194: public ResultSet resultSet() throws SQLException, DataSetException {
195: if (this .resultSet == null) {
196: throw new DataSetException("ResultSet is null.");
197: }
198:
199: return this .resultSet;
200: }
201:
202: /**
203: * Calls addRecord(DataSet)
204: *
205: * @return the added record
206: *
207: * @exception DataSetException
208: * @exception SQLException
209: */
210: public Record addRecord() throws DataSetException, SQLException {
211: return addRecord(this );
212: }
213:
214: /**
215: * Creates a new Record within this DataSet
216: *
217: * @param ds
218: *
219: * @return the added record
220: *
221: * @exception DataSetException
222: * @exception SQLException
223: */
224: public Record addRecord(DataSet ds) throws DataSetException,
225: SQLException {
226: if (ds instanceof QueryDataSet) {
227: throw new DataSetException(
228: "You cannot add records to a QueryDataSet.");
229: }
230:
231: if (records == null) {
232: records = new Vector(10);
233: }
234:
235: Record rec = new Record(ds, true);
236: rec.markForInsert();
237: records.addElement(rec);
238:
239: return rec;
240: }
241:
242: /**
243: * Check if all the records have been retrieve
244: *
245: * @return true if all records have been retrieved
246: */
247: public boolean allRecordsRetrieved() {
248: return this .allRecordsRetrieved;
249: }
250:
251: /**
252: * Set all records retrieved
253: *
254: * @param set TODO: DOCUMENT ME!
255: */
256: void setAllRecordsRetrieved(boolean set) {
257: this .allRecordsRetrieved = set;
258: }
259:
260: /**
261: * Remove a record from the DataSet's internal storage
262: *
263: * @param rec
264: *
265: * @return the record removed
266: *
267: * @exception DataSetException
268: */
269: public Record removeRecord(Record rec) throws DataSetException {
270: Record removeRec = null;
271:
272: try {
273: int loc = this .records.indexOf(rec);
274: removeRec = (Record) this .records.elementAt(loc);
275: this .records.removeElementAt(loc);
276: } catch (Exception e) {
277: throw new DataSetException("Record could not be removed!");
278: }
279:
280: return removeRec;
281: }
282:
283: /**
284: * Remove all records from the DataSet and nulls those records out and close() the DataSet.
285: *
286: * @return an instance of myself
287: */
288: public DataSet clearRecords() {
289: this .records.removeAllElements();
290: this .records = null;
291:
292: return this ;
293: }
294:
295: /**
296: * Removes the records from the DataSet, but does not null the records out
297: *
298: * @return an instance of myself
299: */
300: public DataSet releaseRecords() {
301: this .records = null;
302: this .recordRetrievedCount = 0;
303: this .lastFetchSize = 0;
304: setAllRecordsRetrieved(false);
305:
306: return this ;
307: }
308:
309: /**
310: * Releases the records, closes the ResultSet and the Statement, and nulls the Schema and Connection references.
311: *
312: * @exception SQLException
313: * @exception DataSetException
314: */
315: public void close() throws SQLException, DataSetException {
316: releaseRecords();
317: this .schema = null;
318:
319: if ((this .resultSet != null) && !(this instanceof QueryDataSet)) {
320: resultSet().close();
321: }
322:
323: this .resultSet = null;
324:
325: if (this .stmt != null) {
326: this .stmt.close();
327: }
328:
329: this .conn = null;
330: }
331:
332: /**
333: * Essentially the same as releaseRecords, but it won't work on a QueryDataSet that has been created with a ResultSet
334: *
335: * @return an instance of myself
336: *
337: * @exception DataSetException
338: * @exception SQLException
339: */
340: public DataSet reset() throws DataSetException, SQLException {
341: if (!((resultSet() != null) && (this instanceof QueryDataSet))) {
342: return releaseRecords();
343: } else {
344: throw new DataSetException(
345: "You cannot call reset() on a QueryDataSet.");
346: }
347: }
348:
349: /**
350: * Gets the current database connection
351: *
352: * @return a database connection
353: *
354: * @exception SQLException
355: */
356: public Connection connection() throws SQLException {
357: return this .conn;
358: }
359:
360: /**
361: * Gets the Schema for this DataSet
362: *
363: * @return the Schema for this DataSet
364: */
365: public Schema schema() {
366: return this .schema;
367: }
368:
369: /**
370: * Get Record at 0 based index position
371: *
372: * @param pos
373: *
374: * @return an instance of the found Record
375: *
376: * @exception DataSetException
377: */
378: public Record getRecord(int pos) throws DataSetException {
379: if (containsRecord(pos)) {
380: Record rec = (Record) this .records.elementAt(pos);
381:
382: if (this instanceof TableDataSet) {
383: rec.markForUpdate();
384: }
385:
386: recordRetrievedCount++;
387:
388: return rec;
389: }
390:
391: throw new DataSetException("Record not found at index: " + pos);
392: }
393:
394: /**
395: * Find Record at 0 based index position. This is an internal alternative to getRecord which tries to be smart about the type
396: * of record it is.
397: *
398: * @param pos
399: *
400: * @return an instance of the found Record
401: *
402: * @exception DataSetException
403: */
404: Record findRecord(int pos) throws DataSetException {
405: if (containsRecord(pos)) {
406: return (Record) this .records.elementAt(pos);
407: }
408:
409: throw new DataSetException("Record not found at index: " + pos);
410: }
411:
412: /**
413: * Check to see if the DataSet contains a Record at 0 based position
414: *
415: * @param pos
416: *
417: * @return true if record exists
418: */
419: public boolean containsRecord(int pos) {
420: try {
421: if (this .records.elementAt(pos) != null) {
422: return true;
423: }
424: } catch (Exception e) {
425: return false;
426: }
427:
428: return false;
429: }
430:
431: /**
432: * Causes the DataSet to hit the database and fetch all the records.
433: *
434: * @return an instance of myself
435: *
436: * @exception SQLException
437: * @exception DataSetException
438: */
439: public DataSet fetchRecords() throws SQLException, DataSetException {
440: return fetchRecords(ALL_RECORDS);
441: }
442:
443: /**
444: * Causes the DataSet to hit the database and fetch max records.
445: *
446: * @param max
447: *
448: * @return an instance of myself
449: *
450: * @exception SQLException
451: * @exception DataSetException
452: */
453: public DataSet fetchRecords(int max) throws SQLException,
454: DataSetException {
455: return fetchRecords(0, max);
456: }
457:
458: /**
459: * Causes the DataSet to hit the database and fetch max records, starting at start. Record count begins at 0.
460: *
461: * @param start
462: * @param max
463: *
464: * @return an instance of myself
465: *
466: * @exception SQLException
467: * @exception DataSetException
468: */
469: public DataSet fetchRecords(int start, int max)
470: throws SQLException, DataSetException {
471: if (max == 0) {
472: throw new DataSetException(
473: "Max is 1 based and must be greater than 0!");
474: } else if ((lastFetchSize() > 0) && (this .records != null)) {
475: throw new DataSetException(
476: "You must call DataSet.clearRecords() before executing DataSet.fetchRecords() again!");
477: }
478:
479: if (selectString == null) {
480: selectString = new StringBuffer(256);
481: selectString.append("SELECT ");
482: selectString.append(schema().attributes());
483: selectString.append(" FROM ");
484: selectString.append(schema().tableName());
485: }
486:
487: try {
488: if ((stmt == null) && (this .resultSet == null)) {
489: stmt = connection().createStatement();
490: this .resultSet = stmt.executeQuery(selectString
491: .toString());
492: }
493:
494: if (this .resultSet != null) {
495: if ((this .records == null) && (max > 0)) {
496: this .records = new Vector(max);
497: } else {
498: this .records = new Vector();
499: }
500:
501: int startCounter = 0;
502: int fetchCount = 0;
503:
504: while (!allRecordsRetrieved()) {
505: if (fetchCount == max) {
506: break;
507: }
508:
509: if (this .resultSet.next()) {
510: if (startCounter >= start) {
511: Record rec = new Record(this );
512: records.addElement(rec);
513: fetchCount++;
514: } else {
515: startCounter++;
516: }
517: } else {
518: setAllRecordsRetrieved(true);
519:
520: break;
521: }
522: }
523:
524: lastFetchSize = fetchCount;
525: }
526: } catch (SQLException e) {
527: if (stmt != null) {
528: stmt.close();
529: }
530:
531: throw new SQLException(e.getMessage());
532: }
533:
534: return this ;
535: }
536:
537: /**
538: * The number of records that were fetched with the last fetchRecords.
539: *
540: * @return int
541: */
542: public int lastFetchSize() {
543: return lastFetchSize;
544: }
545:
546: /**
547: * gets the KeyDef object for this DataSet
548: *
549: * @return the keydef for this DataSet, this value can be null
550: */
551: public KeyDef keydef() {
552: return this .keyDefValue;
553: }
554:
555: /**
556: * This returns a represention of this DataSet
557: *
558: * @return TODO: DOCUMENT ME!
559: */
560: public String toString() {
561: try {
562: ByteArrayOutputStream bout = new ByteArrayOutputStream();
563: PrintWriter out = new PrintWriter(bout);
564:
565: if (schema != null) {
566: out.println(schema.toString());
567: }
568:
569: for (int i = 0; i < size(); i++) {
570: out.println(findRecord(i));
571: }
572:
573: out.flush();
574:
575: return bout.toString();
576: } catch (DataSetException e) {
577: return "{}";
578: }
579: }
580:
581: /**
582: * Gets the tableName defined in the schema
583: *
584: * @return string
585: *
586: * @throws DataSetException TODO: DOCUMENT ME!
587: */
588: public String tableName() throws DataSetException {
589: return schema().tableName();
590: }
591:
592: /**
593: * Calculates the maxColumnWidths for the column in a DataSet I really don't know what this is used for so it isn't
594: * implemented.
595: *
596: * @param with_heading
597: *
598: * @return int
599: *
600: * @exception DataSetException
601: * @exception SQLException
602: */
603: public int[] maxColumnWidths(boolean with_heading)
604: throws DataSetException, SQLException {
605: if (schema() == null) {
606: throw new DataSetException("Schema is null!");
607: }
608:
609: throw new DataSetException("Not yet implemented!");
610: }
611:
612: /**
613: * Classes extending this class must implement this method.
614: *
615: * @return the select string
616: *
617: * @throws DataSetException TODO: DOCUMENT ME!
618: */
619: public abstract String getSelectString() throws DataSetException;
620:
621: /**
622: * Returns the columns attribute for the DataSet
623: *
624: * @return the columns attribute for the DataSet
625: */
626: String getColumns() {
627: return this .columns;
628: }
629:
630: /**
631: * Gets the number of Records in this DataSet. It is 0 based.
632: *
633: * @return number of Records in this DataSet
634: */
635: public int size() {
636: if (this .records == null) {
637: return 0;
638: }
639:
640: return this.records.size();
641: }
642: }
|