View Javadoc

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