View Javadoc

1   package com.workingdogs.village;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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 }