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.ResultSetMetaData;
61  import java.sql.SQLException;
62  import java.sql.Statement;
63  
64  import java.util.Enumeration;
65  import java.util.Hashtable;
66  
67  /***
68   * The Schema object represents the <a href="Column.html">Columns</a> in a database table. It contains a collection of <a
69   * href="Column.html">Column</a> objects.
70   *
71   * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
72   * @author John D. McNally
73   * @version $Revision: 568 $
74   */
75  public final class Schema
76  {
77      /*** TODO: DOCUMENT ME! */
78      private String tableName;
79  
80      /*** TODO: DOCUMENT ME! */
81      private String columnsAttribute;
82  
83      /*** TODO: DOCUMENT ME! */
84      private int numberOfColumns;
85  
86      /*** TODO: DOCUMENT ME! */
87      private Column [] columns;
88  
89      /*** TODO: DOCUMENT ME! */
90      private static Hashtable schemaCache = new Hashtable();
91  
92      /***
93       * This attribute is used to complement columns in the event that this schema represents more than one table.  Its keys are
94       * String contains table names and its elements are Hashtables containing columns.
95       */
96      private Hashtable tableHash = null;
97  
98      /*** TODO: DOCUMENT ME! */
99      private boolean singleTable = true;
100 
101     /***
102      * A blank Schema object
103      */
104     public Schema()
105     {
106         this.tableName = "";
107         this.columnsAttribute = null;
108         this.numberOfColumns = 0;
109     }
110 
111     /***
112      * Creates a Schema with all columns
113      *
114      * @param conn
115      * @param tableName
116      *
117      * @return an instance of myself
118      *
119      * @exception SQLException
120      * @exception DataSetException
121      */
122     public Schema schema(Connection conn, String tableName)
123             throws SQLException, DataSetException
124     {
125         return schema(conn, tableName, "*");
126     }
127 
128     /***
129      * Creates a Schema with the named columns in the columnsAttribute
130      *
131      * @param conn
132      * @param tableName
133      * @param columnsAttribute
134      *
135      * @return an instance of myself
136      *
137      * @exception SQLException
138      * @exception DataSetException
139      */
140     public synchronized Schema schema(Connection conn, String tableName, String columnsAttribute)
141             throws SQLException, DataSetException
142     {
143         if (columnsAttribute == null)
144         {
145             columnsAttribute = "*";
146         }
147 
148         Statement stmt = null;
149 
150         try
151         {
152             String keyValue = conn.getMetaData().getURL() + tableName;
153             Schema tableSchema = (Schema) schemaCache.get(keyValue);
154 
155             if (tableSchema == null)
156             {
157                 String sql = "SELECT " + columnsAttribute + " FROM " + tableName + " WHERE 1 = -1";
158                 stmt = conn.createStatement();
159 
160                 ResultSet rs = stmt.executeQuery(sql);
161 
162                 if (rs != null)
163                 {
164                     tableSchema = new Schema();
165                     tableSchema.setTableName(tableName);
166                     tableSchema.setAttributes(columnsAttribute);
167                     tableSchema.populate(rs.getMetaData(), tableName);
168                     schemaCache.put(keyValue, tableSchema);
169                 }
170                 else
171                 {
172                     throw new DataSetException("Couldn't retrieve schema for " + tableName);
173                 }
174             }
175 
176             return tableSchema;
177         }
178         finally
179         {
180             if (stmt != null)
181             {
182                 stmt.close();
183             }
184         }
185     }
186 
187     /***
188      * Appends data to the tableName that this schema was first created with.
189      *
190      * <P></p>
191      *
192      * @param app String to append to tableName
193      *
194      * @see TableDataSet#tableQualifier(java.lang.String)
195      */
196     void appendTableName(String app)
197     {
198         this.tableName = this.tableName + " " + app;
199     }
200 
201     /***
202      * List of columns to select from the table
203      *
204      * @return the list of columns to select from the table
205      */
206     public String attributes()
207     {
208         return this.columnsAttribute;
209     }
210 
211     /***
212      * Returns the requested Column object at index i
213      *
214      * @param i
215      *
216      * @return the requested column
217      *
218      * @exception DataSetException
219      */
220     public Column column(int i)
221             throws DataSetException
222     {
223         if (i == 0)
224         {
225             throw new DataSetException("Columns are 1 based");
226         }
227         else if (i > numberOfColumns)
228         {
229             throw new DataSetException("There are only " + numberOfColumns() + " available!");
230         }
231 
232         try
233         {
234             return columns[i];
235         }
236         catch (Exception e)
237         {
238             throw new DataSetException("Column number: " + numberOfColumns() + " does not exist!");
239         }
240     }
241 
242     /***
243      * Returns the requested Column object by name
244      *
245      * @param colName
246      *
247      * @return the requested column
248      *
249      * @exception DataSetException
250      */
251     public Column column(String colName)
252             throws DataSetException
253     {
254         return column(index(colName));
255     }
256 
257     /***
258      * Returns the requested Column object by name
259      *
260      * @param colName
261      *
262      * @return the requested column
263      *
264      * @exception DataSetException
265      */
266     public Column getColumn(String colName)
267             throws DataSetException
268     {
269         int dot = colName.indexOf('.');
270 
271         if (dot > 0)
272         {
273             String table = colName.substring(0, dot);
274             String col = colName.substring(dot + 1);
275 
276             return getColumn(table, col);
277         }
278 
279         return column(index(colName));
280     }
281 
282     /***
283      * Returns the requested Column object belonging to the specified table by name
284      *
285      * @param tableName
286      * @param colName
287      *
288      * @return the requested column, null if a column by the specified name does not exist.
289      *
290      * @exception DataSetException
291      */
292     public Column getColumn(String tableName, String colName)
293             throws DataSetException
294     {
295         return (Column) ((Hashtable) tableHash.get(tableName)).get(colName);
296     }
297 
298     /***
299      * Returns an array of columns
300      *
301      * @return an array of columns
302      */
303     Column [] getColumns()
304     {
305         return this.columns;
306     }
307 
308     /***
309      * returns the table name that this Schema represents
310      *
311      * @return the table name that this Schema represents
312      *
313      * @throws DataSetException TODO: DOCUMENT ME!
314      */
315     public String getTableName()
316             throws DataSetException
317     {
318         if (singleTable)
319         {
320             return tableName;
321         }
322         else
323         {
324             throw new DataSetException("This schema represents several tables.");
325         }
326     }
327 
328     /***
329      * returns all table names that this Schema represents
330      *
331      * @return the table names that this Schema represents
332      */
333     public String [] getAllTableNames()
334     {
335         Enumeration e = tableHash.keys();
336         String [] tableNames = new String[tableHash.size()];
337 
338         for (int i = 0; e.hasMoreElements(); i++)
339         {
340             tableNames[i] = (String) e.nextElement();
341         }
342 
343         return tableNames;
344     }
345 
346     /***
347      * Gets the index position of a named column.  If multiple tables are represented and they have columns with the same name,
348      * this method returns the first one listed, if the table name is not specified.
349      *
350      * @param colName
351      *
352      * @return the requested column index integer
353      *
354      * @exception DataSetException
355      */
356     public int index(String colName)
357             throws DataSetException
358     {
359         int dot = colName.indexOf('.');
360 
361         if (dot > 0)
362         {
363             String table = colName.substring(0, dot);
364             String col = colName.substring(dot + 1);
365 
366             return index(table, col);
367         }
368 
369         for (int i = 1; i <= numberOfColumns(); i++)
370         {
371             if (columns[i].name().equalsIgnoreCase(colName))
372             {
373                 return i;
374             }
375         }
376 
377         throw new DataSetException("Column name: " + colName + " does not exist!");
378     }
379 
380     /***
381      * Gets the index position of a named column.
382      *
383      * @param tableName
384      * @param colName
385      *
386      * @return the requested column index integer
387      *
388      * @exception DataSetException
389      */
390     public int index(String tableName, String colName)
391             throws DataSetException
392     {
393         for (int i = 1; i <= numberOfColumns(); i++)
394         {
395             if (columns[i].name().equalsIgnoreCase(colName) && columns[i].getTableName().equalsIgnoreCase(tableName))
396             {
397                 return i;
398             }
399         }
400 
401         throw new DataSetException("Column name: " + colName + " does not exist!");
402     }
403 
404     /***
405      * Checks to see if this DataSet represents one table in the database.
406      *
407      * @return true if only one table is represented, false otherwise.
408      */
409     public boolean isSingleTable()
410     {
411         return singleTable;
412     }
413 
414     /***
415      * Gets the number of columns in this Schema
416      *
417      * @return integer number of columns
418      */
419     public int numberOfColumns()
420     {
421         return this.numberOfColumns;
422     }
423 
424     /***
425      * Internal method which populates this Schema object with Columns.
426      *
427      * @param meta The meta data of the ResultSet used to build this Schema.
428      * @param tableName The name of the table referenced in this schema, or null if unknown or multiple tables are involved.
429      *
430      * @exception SQLException
431      * @exception DataSetException
432      */
433     void populate(ResultSetMetaData meta, String tableName)
434             throws SQLException, DataSetException
435     {
436         this.numberOfColumns = meta.getColumnCount();
437         columns = new Column[numberOfColumns() + 1];
438 
439         for (int i = 1; i <= numberOfColumns(); i++)
440         {
441             Column col = new Column();
442             col.populate(meta, i, tableName);
443             columns[i] = col;
444 
445             if ((i > 1) && !col.getTableName().equalsIgnoreCase(columns[i - 1].getTableName()))
446             {
447                 singleTable = false;
448             }
449         }
450 
451         // Avoid creating a Hashtable in the most common case where only one
452         // table is involved, even though this makes the multiple table case
453         // more expensive because the table/column info is duplicated.
454         if (singleTable)
455         {
456             // If available, use a the caller supplied table name.
457             if ((tableName != null) && (tableName.length() > 0))
458             {
459                 setTableName(tableName);
460             }
461             else
462             {
463                 // Since there's only one table involved, attempt to set the
464                 // table name to that of the first column.  Sybase jConnect
465                 // 5.2 and older will fail, in which case we are screwed.
466                 try
467                 {
468                     setTableName(meta.getTableName(1));
469                 }
470                 catch (Exception e)
471                 {
472                     setTableName("");
473                 }
474             }
475         }
476         else
477         {
478             tableHash = new Hashtable((int) ((1.25 * numberOfColumns) + 1));
479 
480             for (int i = 1; i <= numberOfColumns(); i++)
481             {
482                 if (tableHash.containsKey(columns[i].getTableName()))
483                 {
484                     ((Hashtable) tableHash.get(columns[i].getTableName())).put(columns[i].name(), columns[i]);
485                 }
486                 else
487                 {
488                     Hashtable columnHash = new Hashtable((int) ((1.25 * numberOfColumns) + 1));
489                     columnHash.put(columns[i].name(), columns[i]);
490                     tableHash.put(columns[i].getTableName(), columnHash);
491                 }
492             }
493         }
494     }
495 
496     /***
497      * Sets the columns to select from the table
498      *
499      * @param attributes comma separated list of column names
500      */
501     void setAttributes(String attributes)
502     {
503         this.columnsAttribute = attributes;
504     }
505 
506     /***
507      * Sets the table name that this Schema represents
508      *
509      * @param tableName
510      */
511     void setTableName(String tableName)
512     {
513         this.tableName = tableName;
514     }
515 
516     /***
517      * returns the table name that this Schema represents
518      *
519      * @return the table name that this Schema represents
520      *
521      * @throws DataSetException TODO: DOCUMENT ME!
522      */
523     public String tableName()
524             throws DataSetException
525     {
526         return getTableName();
527     }
528 
529     /***
530      * This returns a representation of this Schema
531      *
532      * @return a string
533      */
534     public String toString()
535     {
536         ByteArrayOutputStream bout = new ByteArrayOutputStream();
537         PrintWriter out = new PrintWriter(bout);
538         out.print('{');
539 
540         for (int i = 1; i <= numberOfColumns; i++)
541         {
542             out.print('\'');
543 
544             if (!singleTable)
545             {
546                 out.print(columns[i].getTableName() + '.');
547             }
548 
549             out.print(columns[i].name() + '\'');
550 
551             if (i < numberOfColumns)
552             {
553                 out.print(',');
554             }
555         }
556 
557         out.print('}');
558         out.flush();
559 
560         return bout.toString();
561     }
562 }