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