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