1 package com.workingdogs.village;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.ByteArrayOutputStream;
23 import java.io.PrintWriter;
24 import java.sql.Connection;
25 import java.sql.DatabaseMetaData;
26 import java.sql.PreparedStatement;
27 import java.sql.ResultSet;
28 import java.sql.ResultSetMetaData;
29 import java.sql.SQLException;
30 import java.util.ArrayList;
31 import java.util.Enumeration;
32 import java.util.Hashtable;
33 import java.util.Iterator;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.TreeMap;
37
38 /***
39 * The Schema object represents the <a href="Column.html">Columns</a> in a database table. It contains a collection of <a
40 * href="Column.html">Column</a> objects.
41 *
42 * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
43 * @author John D. McNally
44 * @version $Revision: 568 $
45 */
46 public final class Schema
47 {
48 /*** TODO: DOCUMENT ME! */
49 private String tableName;
50
51 /*** TODO: DOCUMENT ME! */
52 private String columnsAttribute;
53
54 /*** TODO: DOCUMENT ME! */
55 private int numberOfColumns;
56
57 /*** TODO: DOCUMENT ME! */
58 private Column [] columns;
59 private Map columnNumberByName;
60
61 /*** TODO: DOCUMENT ME! */
62 private static final Hashtable schemaCache = new Hashtable();
63
64 /***
65 * This attribute is used to complement columns in the event that this schema represents more than one table. Its keys are
66 * String contains table names and its elements are Hashtables containing columns.
67 */
68 private Hashtable tableHash = null;
69
70 /*** TODO: DOCUMENT ME! */
71 private boolean singleTable = true;
72
73 /***
74 * A blank Schema object
75 */
76 public Schema()
77 {
78 this.tableName = "";
79 this.columnsAttribute = null;
80 this.numberOfColumns = 0;
81 }
82
83 /***
84 * Initialize all table schemas reachable from this connection
85 *
86 * @param conn a database connection
87 * @throws SQLException if retrieving the database meta data is unsuccessful
88 */
89 public static void initSchemas(Connection conn) throws SQLException
90 {
91 ResultSet allCol = null;
92
93 try
94 {
95 DatabaseMetaData databaseMetaData = conn.getMetaData();
96 String connURL = databaseMetaData.getURL();
97 allCol = databaseMetaData.getColumns(
98 conn.getCatalog(), null, null, null);
99
100 while (true)
101 {
102 Schema schema = new Schema();
103
104 schema.setAttributes("*");
105 schema.singleTable = true;
106 schema.populate(allCol);
107
108 if (schema.numberOfColumns > 0)
109 {
110 String keyValue = connURL + schema.tableName;
111
112 synchronized (schemaCache)
113 {
114 schemaCache.put(keyValue, schema);
115 }
116 }
117 else
118 {
119 break;
120 }
121 }
122 }
123 finally
124 {
125 if (allCol != null)
126 {
127 try
128 {
129 allCol.close();
130 }
131 catch (SQLException e)
132 {
133
134 }
135 }
136 }
137 }
138
139 /***
140 * Creates a Schema with all columns
141 *
142 * @param conn
143 * @param tableName
144 *
145 * @return an instance of myself
146 *
147 * @exception SQLException
148 * @exception DataSetException
149 */
150 public Schema schema(Connection conn, String tableName)
151 throws SQLException, DataSetException
152 {
153 return schema(conn, tableName, "*");
154 }
155
156 /***
157 * Creates a Schema with the named columns in the columnsAttribute
158 *
159 * @param conn
160 * @param tableName
161 * @param columnsAttribute
162 *
163 * @return an instance of myself
164 *
165 * @exception SQLException
166 * @exception DataSetException
167 */
168 public Schema schema(Connection conn, String tableName, String columnsAttribute)
169 throws SQLException, DataSetException
170 {
171 if (columnsAttribute == null)
172 {
173 columnsAttribute = "*";
174 }
175
176 PreparedStatement stmt = null;
177
178 try
179 {
180 Schema tableSchema = null;
181 String keyValue = conn.getMetaData().getURL() + tableName;
182
183 synchronized (schemaCache)
184 {
185 tableSchema = (Schema) schemaCache.get(keyValue);
186
187 if (tableSchema == null)
188 {
189 String sql = "SELECT " + columnsAttribute + " FROM " + tableName + " WHERE 1 = -1";
190
191 stmt = conn.prepareStatement(sql);
192
193 if (stmt != null)
194 {
195 stmt.executeQuery();
196 tableSchema = this;
197 tableSchema.setTableName(tableName);
198 tableSchema.setAttributes(columnsAttribute);
199 tableSchema.populate(stmt.getMetaData(), tableName, null);
200 schemaCache.put(keyValue, tableSchema);
201 }
202 else
203 {
204 throw new DataSetException("Couldn't retrieve schema for " + tableName);
205 }
206 }
207 }
208
209 return tableSchema;
210 }
211 finally
212 {
213 if (stmt != null)
214 {
215 try
216 {
217 stmt.close();
218 }
219 catch (SQLException e)
220 {
221
222 }
223 }
224 }
225 }
226
227 /***
228 * Appends data to the tableName that this schema was first created with.
229 *
230 * <P></p>
231 *
232 * @param app String to append to tableName
233 *
234 * @see TableDataSet#tableQualifier(java.lang.String)
235 */
236 void appendTableName(String app)
237 {
238 this.tableName = this.tableName + " " + app;
239 }
240
241 /***
242 * List of columns to select from the table
243 *
244 * @return the list of columns to select from the table
245 */
246 public String attributes()
247 {
248 return this.columnsAttribute;
249 }
250
251 /***
252 * Returns the requested Column object at index i
253 *
254 * @param i
255 *
256 * @return the requested column
257 *
258 * @exception DataSetException
259 */
260 public Column column(int i)
261 throws DataSetException
262 {
263 if (i == 0)
264 {
265 throw new DataSetException("Columns are 1 based");
266 }
267 else if (i > numberOfColumns)
268 {
269 throw new DataSetException("There are only " + numberOfColumns() + " available!");
270 }
271
272 try
273 {
274 return columns[i];
275 }
276 catch (Exception e)
277 {
278 throw new DataSetException("Column number: " + numberOfColumns() + " does not exist!");
279 }
280 }
281
282 /***
283 * Returns the requested Column object by name
284 *
285 * @param colName
286 *
287 * @return the requested column
288 *
289 * @exception DataSetException
290 */
291 public Column column(String colName)
292 throws DataSetException
293 {
294 return column(index(colName));
295 }
296
297 /***
298 * Returns the requested Column object by name
299 *
300 * @param colName
301 *
302 * @return the requested column
303 *
304 * @exception DataSetException
305 */
306 public Column getColumn(String colName)
307 throws DataSetException
308 {
309 int dot = colName.indexOf('.');
310
311 if (dot > 0)
312 {
313 String table = colName.substring(0, dot);
314 String col = colName.substring(dot + 1);
315
316 return getColumn(table, col);
317 }
318
319 return column(index(colName));
320 }
321
322 /***
323 * Returns the requested Column object belonging to the specified table by name
324 *
325 * @param tableName
326 * @param colName
327 *
328 * @return the requested column, null if a column by the specified name does not exist.
329 *
330 * @exception DataSetException
331 */
332 public Column getColumn(String tableName, String colName)
333 throws DataSetException
334 {
335 return (Column) ((Hashtable) tableHash.get(tableName)).get(colName);
336 }
337
338 /***
339 * Returns an array of columns
340 *
341 * @return an array of columns
342 */
343 Column [] getColumns()
344 {
345 return this.columns;
346 }
347
348 /***
349 * returns the table name that this Schema represents
350 *
351 * @return the table name that this Schema represents
352 *
353 * @throws DataSetException TODO: DOCUMENT ME!
354 */
355 public String getTableName()
356 throws DataSetException
357 {
358 if (singleTable)
359 {
360 return tableName;
361 }
362 else
363 {
364 throw new DataSetException("This schema represents several tables.");
365 }
366 }
367
368 /***
369 * returns all table names that this Schema represents
370 *
371 * @return the table names that this Schema represents
372 */
373 public String [] getAllTableNames()
374 {
375 Enumeration e = tableHash.keys();
376 String [] tableNames = new String[tableHash.size()];
377
378 for (int i = 0; e.hasMoreElements(); i++)
379 {
380 tableNames[i] = (String) e.nextElement();
381 }
382
383 return tableNames;
384 }
385
386 /***
387 * Gets the index position of a named column. If multiple tables are represented and they have columns with the same name,
388 * this method returns the first one listed, if the table name is not specified.
389 *
390 * @param colName
391 *
392 * @return the requested column index integer
393 *
394 * @exception DataSetException
395 */
396 public int index(String colName)
397 throws DataSetException
398 {
399 Integer position = (Integer) columnNumberByName.get(colName);
400
401 if (position != null)
402 {
403 return position.intValue();
404 }
405 else
406 {
407 throw new DataSetException("Column name: " + colName + " does not exist!");
408 }
409 }
410
411 /***
412 * Gets the index position of a named column.
413 *
414 * @param tableName
415 * @param colName
416 *
417 * @return the requested column index integer
418 *
419 * @exception DataSetException
420 */
421 public int index(String tableName, String colName)
422 throws DataSetException
423 {
424 return index(tableName + "." + colName);
425 }
426
427 /***
428 * Checks to see if this DataSet represents one table in the database.
429 *
430 * @return true if only one table is represented, false otherwise.
431 */
432 public boolean isSingleTable()
433 {
434 return singleTable;
435 }
436
437 /***
438 * Gets the number of columns in this Schema
439 *
440 * @return integer number of columns
441 */
442 public int numberOfColumns()
443 {
444 return this.numberOfColumns;
445 }
446
447 /***
448 * Internal method which populates this Schema object with Columns.
449 *
450 * @param meta The meta data of the ResultSet used to build this Schema.
451 * @param tableName The name of the table referenced in this schema, or null if unknown or multiple tables are involved.
452 * @param conn The connection whose URL serves as a cache key prefix
453 *
454 * @exception SQLException
455 * @exception DataSetException
456 */
457 void populate(ResultSetMetaData meta, String tableName, Connection conn)
458 throws SQLException, DataSetException
459 {
460 this.numberOfColumns = meta.getColumnCount();
461 columns = new Column[numberOfColumns() + 1];
462 columnNumberByName = new TreeMap(String.CASE_INSENSITIVE_ORDER);
463
464 String connURL = (conn != null) ? conn.getMetaData().getURL() : null;
465
466 for (int i = 1; i <= numberOfColumns(); i++)
467 {
468 String metaColumnName = meta.getColumnName(i);
469 String metaTableName = null;
470
471
472 try
473 {
474 metaTableName = meta.getTableName(i);
475
476
477
478
479 if ((metaTableName == null) || metaTableName.equals(""))
480 {
481 if (tableName != null)
482 {
483 metaTableName = tableName;
484 }
485 else
486 {
487 metaTableName = "";
488 }
489 }
490 }
491 catch (RuntimeException e)
492 {
493 if (tableName != null)
494 {
495 metaTableName = tableName;
496 }
497 else
498 {
499 metaTableName = "";
500 }
501 }
502
503 Column col = null;
504
505 if (metaTableName.length() > 0 && connURL != null)
506 {
507 Schema tableSchema = null;
508
509 synchronized (schemaCache)
510 {
511 tableSchema = (Schema) schemaCache.get(connURL + metaTableName);
512 }
513
514 if (tableSchema != null)
515 {
516 try
517 {
518 col = tableSchema.column(metaColumnName);
519 }
520 catch (DataSetException e)
521 {
522
523 }
524 }
525 }
526
527
528 if (col == null)
529 {
530 col = new Column();
531 col.populate(meta, i, metaTableName, metaColumnName);
532 }
533
534 columns[i] = col;
535 Integer position = new Integer(i);
536 columnNumberByName.put(metaColumnName, position);
537 columnNumberByName.put(metaTableName + "." + metaColumnName, position);
538
539 if ((i > 1) && !col.getTableName().equalsIgnoreCase(columns[i - 1].getTableName()))
540 {
541 singleTable = false;
542 }
543 }
544
545
546
547
548 if (singleTable)
549 {
550
551 if ((tableName != null) && (tableName.length() > 0))
552 {
553 setTableName(tableName);
554 }
555 else
556 {
557
558
559
560 try
561 {
562 setTableName(columns[1].getTableName());
563 }
564 catch (Exception e)
565 {
566 setTableName("");
567 }
568 }
569 }
570 else
571 {
572 tableHash = new Hashtable((int) ((1.25 * numberOfColumns) + 1));
573
574 for (int i = 1; i <= numberOfColumns(); i++)
575 {
576 Hashtable columnHash;
577
578 if (tableHash.containsKey(columns[i].getTableName()))
579 {
580 columnHash = (Hashtable) tableHash.get(columns[i].getTableName());
581 }
582 else
583 {
584 columnHash = new Hashtable((int) ((1.25 * numberOfColumns) + 1));
585 tableHash.put(columns[i].getTableName(), columnHash);
586 }
587
588 columnHash.put(columns[i].name(), columns[i]);
589 }
590 }
591 }
592
593 /***
594 * Internal method which populates this Schema object with Columns.
595 *
596 * @param meta The meta data of the database connection used to build this Schema.
597 *
598 * @exception SQLException
599 */
600 void populate(ResultSet dbMeta)
601 throws SQLException
602 {
603 List cols = new ArrayList();
604 String tableName = null;
605 columnNumberByName = new TreeMap(String.CASE_INSENSITIVE_ORDER);
606
607 while (dbMeta.next())
608 {
609 if (tableName == null)
610 {
611 tableName = dbMeta.getString(3);
612 setTableName(tableName);
613 }
614 else if (!tableName.equals(dbMeta.getString(3)))
615 {
616 dbMeta.previous();
617 break;
618 }
619
620 Column c = new Column();
621
622 c.populate(tableName,
623 dbMeta.getString(4),
624 dbMeta.getString(6),
625 dbMeta.getInt(5),
626 dbMeta.getInt(11) == DatabaseMetaData.columnNullable);
627
628 cols.add(c);
629
630 Integer position = new Integer(dbMeta.getInt(17));
631 columnNumberByName.put(c.name(), position);
632 columnNumberByName.put(tableName + "." + c.name(), position);
633 }
634
635 if (!cols.isEmpty())
636 {
637 this.numberOfColumns = cols.size();
638 columns = new Column[numberOfColumns() + 1];
639
640 int i = 1;
641 for (Iterator col = cols.iterator(); col.hasNext();)
642 {
643 columns[i++] = (Column) col.next();
644 }
645 }
646 }
647
648 /***
649 * Sets the columns to select from the table
650 *
651 * @param attributes comma separated list of column names
652 */
653 void setAttributes(String attributes)
654 {
655 this.columnsAttribute = attributes;
656 }
657
658 /***
659 * Sets the table name that this Schema represents
660 *
661 * @param tableName
662 */
663 void setTableName(String tableName)
664 {
665 this.tableName = tableName;
666 }
667
668 /***
669 * returns the table name that this Schema represents
670 *
671 * @return the table name that this Schema represents
672 *
673 * @throws DataSetException TODO: DOCUMENT ME!
674 */
675 public String tableName()
676 throws DataSetException
677 {
678 return getTableName();
679 }
680
681 /***
682 * This returns a representation of this Schema
683 *
684 * @return a string
685 */
686 public String toString()
687 {
688 ByteArrayOutputStream bout = new ByteArrayOutputStream();
689 PrintWriter out = new PrintWriter(bout);
690 out.print('{');
691
692 for (int i = 1; i <= numberOfColumns; i++)
693 {
694 out.print('\'');
695
696 if (!singleTable)
697 {
698 out.print(columns[i].getTableName() + '.');
699 }
700
701 out.print(columns[i].name() + '\'');
702
703 if (i < numberOfColumns)
704 {
705 out.print(',');
706 }
707 }
708
709 out.print('}');
710 out.flush();
711
712 return bout.toString();
713 }
714 }