KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > mondrian > rolap > aggmatcher > JdbcSchema


1 /*
2 // $Id: //open/mondrian/src/main/mondrian/rolap/aggmatcher/JdbcSchema.java#18 $
3 // This software is subject to the terms of the Common Public License
4 // Agreement, available at the following URL:
5 // http://www.opensource.org/licenses/cpl.html.
6 // Copyright (C) 2005-2006 Julian Hyde and others
7 // All Rights Reserved.
8 // You must accept the terms of that agreement to use this software.
9 */

10
11 package mondrian.rolap.aggmatcher;
12
13 import mondrian.olap.MondrianProperties;
14 import mondrian.olap.MondrianDef;
15 import mondrian.olap.Util;
16 import mondrian.rolap.RolapAggregator;
17 import mondrian.rolap.RolapStar;
18 import mondrian.rolap.sql.SqlQuery;
19 import mondrian.resource.MondrianResource;
20
21 import javax.sql.DataSource JavaDoc;
22
23 import org.apache.log4j.Logger;
24
25 import java.lang.ref.SoftReference JavaDoc;
26 import java.io.PrintWriter JavaDoc;
27 import java.io.StringWriter JavaDoc;
28 import java.sql.ResultSet JavaDoc;
29 import java.sql.Connection JavaDoc;
30 import java.sql.DatabaseMetaData JavaDoc;
31 import java.sql.Types JavaDoc;
32 import java.sql.SQLException JavaDoc;
33 import java.util.*;
34
35 /**
36  * This class is used to scrape a database and store information about its
37  * tables and columnIter.
38  *
39  * <p>The structure of this information is as follows:
40  * A database has tables. A table has columnIter. A column has one or more usages.
41  * A usage might be a column being used as a foreign key or as part of a
42  * measure.
43  *
44  * <p> Tables are created when calling code requests the set of available
45  * tables. This call <code>getTables()</code> causes all tables to be loaded.
46  * But a table's columnIter are not loaded until, on a table-by-table basis,
47  * a request is made to get the set of columnIter associated with the table.
48  * Since, the AggTableManager first attempts table name matches (recognition)
49  * most tables do not match, so why load their columnIter.
50  * Of course, as a result, there are a host of methods that can throw an
51  * {@link SQLException}, rats.
52  *
53  * @author Richard M. Emberson
54  * @version $Id: //open/mondrian/src/main/mondrian/rolap/aggmatcher/JdbcSchema.java#18 $
55  */

56 public class JdbcSchema {
57     private static final Logger LOGGER =
58         Logger.getLogger(JdbcSchema.class);
59
60     private static final MondrianResource mres = MondrianResource.instance();
61
62     /**
63      * Get the Logger.
64      */

65     public Logger getLogger() {
66         return LOGGER;
67     }
68
69     public interface Factory {
70         JdbcSchema makeDB(DataSource JavaDoc dataSource);
71         void clearDB(JdbcSchema db);
72         void removeDB(JdbcSchema db);
73     }
74
75     private static final Map<DataSource JavaDoc, SoftReference JavaDoc<JdbcSchema>> dbMap =
76         new HashMap<DataSource JavaDoc, SoftReference JavaDoc<JdbcSchema>>();
77
78     /**
79      * How often between sweeping through the dbMap looking for nulls.
80      */

81     private static final int SWEEP_COUNT = 10;
82     private static int sweepDBCount = 0;
83
84     public static class StdFactory implements Factory {
85         StdFactory() {
86         }
87         public JdbcSchema makeDB(DataSource JavaDoc dataSource) {
88             JdbcSchema db = new JdbcSchema(dataSource);
89             return db;
90         }
91         public void clearDB(JdbcSchema db) {
92             // NoOp
93
}
94         public void removeDB(JdbcSchema db) {
95             // NoOp
96
}
97     }
98
99     private static Factory factory;
100
101     private static void makeFactory() {
102         if (factory == null) {
103             String JavaDoc classname =
104                     MondrianProperties.instance().JdbcFactoryClass.get();
105             if (classname == null) {
106                 factory = new StdFactory();
107             } else {
108                 try {
109                     Class JavaDoc<?> clz = Class.forName(classname);
110                     factory = (Factory) clz.newInstance();
111                 } catch (ClassNotFoundException JavaDoc ex) {
112                     throw mres.BadJdbcFactoryClassName.ex(classname);
113                 } catch (InstantiationException JavaDoc ex) {
114                     throw mres.BadJdbcFactoryInstantiation.ex(classname);
115                 } catch (IllegalAccessException JavaDoc ex) {
116                     throw mres.BadJdbcFactoryAccess.ex(classname);
117                 }
118             }
119         }
120     }
121
122     /**
123      * Create or retrieve an instance of the JdbcSchema for the given
124      * DataSource.
125      *
126      * @param dataSource
127      * @return
128      */

129     public static synchronized JdbcSchema makeDB(DataSource JavaDoc dataSource) {
130         makeFactory();
131
132         JdbcSchema db = null;
133         SoftReference JavaDoc<JdbcSchema> ref = dbMap.get(dataSource);
134         if (ref != null) {
135             db = ref.get();
136         }
137         if (db == null) {
138             db = factory.makeDB(dataSource);
139             dbMap.put(dataSource, new SoftReference JavaDoc<JdbcSchema>(db));
140         }
141
142         sweepDB();
143
144         return db;
145     }
146     /**
147      * Clear information in a JdbcSchema associated with a DataSource.
148      *
149      * @param dataSource
150      */

151     public static synchronized void clearDB(DataSource JavaDoc dataSource) {
152         makeFactory();
153
154         SoftReference JavaDoc<JdbcSchema> ref = dbMap.get(dataSource);
155         if (ref != null) {
156             JdbcSchema db = ref.get();
157             if (db != null) {
158                 factory.clearDB(db);
159                 db.clear();
160             } else {
161                 dbMap.remove(dataSource);
162             }
163         }
164         sweepDB();
165     }
166
167     /**
168      * Remove a JdbcSchema associated with a DataSource.
169      *
170      * @param dataSource
171      */

172     public static synchronized void removeDB(DataSource JavaDoc dataSource) {
173         makeFactory();
174
175         SoftReference JavaDoc<JdbcSchema> ref = dbMap.remove(dataSource);
176         if (ref != null) {
177             JdbcSchema db = ref.get();
178             if (db != null) {
179                 factory.removeDB(db);
180                 db.remove();
181             }
182         }
183         sweepDB();
184     }
185
186     /**
187      * Every SWEEP_COUNT calls to this method, go through all elements of
188      * the dbMap removing all that either have null values (null SoftReference)
189      * or those with SoftReference with null content.
190      */

191     private static void sweepDB() {
192         if (sweepDBCount > SWEEP_COUNT) {
193             Iterator<SoftReference JavaDoc<JdbcSchema>> it = dbMap.values().iterator();
194             while (it.hasNext()) {
195                 SoftReference JavaDoc<JdbcSchema> ref = it.next();
196                 if ((ref == null) || (ref.get() == null)) {
197                     try {
198                         it.remove();
199                     } catch (Exception JavaDoc ex) {
200                         // Should not happen, but might still like to
201
// know that something's funky.
202
LOGGER.warn(ex);
203                     }
204                 }
205
206             }
207             // reset
208
sweepDBCount = 0;
209         }
210     }
211
212
213     //
214
// Types of column usages.
215
//
216
public static final int UNKNOWN_COLUMN_USAGE = 0x0001;
217     public static final int FOREIGN_KEY_COLUMN_USAGE = 0x0002;
218     public static final int MEASURE_COLUMN_USAGE = 0x0004;
219     public static final int LEVEL_COLUMN_USAGE = 0x0008;
220     public static final int FACT_COUNT_COLUMN_USAGE = 0x0010;
221     public static final int IGNORE_COLUMN_USAGE = 0x0020;
222
223     public static final String JavaDoc UNKNOWN_COLUMN_NAME = "UNKNOWN";
224     public static final String JavaDoc FOREIGN_KEY_COLUMN_NAME = "FOREIGN_KEY";
225     public static final String JavaDoc MEASURE_COLUMN_NAME = "MEASURE";
226     public static final String JavaDoc LEVEL_COLUMN_NAME = "LEVEL";
227     public static final String JavaDoc FACT_COUNT_COLUMN_NAME = "FACT_COUNT";
228     public static final String JavaDoc IGNORE_COLUMN_NAME = "IGNORE";
229
230     /**
231      * Enumeration of ways that an aggregate table can use a column.
232      */

233     enum UsageType {
234         UNKNOWN,
235         FOREIGN_KEY,
236         MEASURE,
237         LEVEL,
238         FACT_COUNT,
239         IGNORE
240     }
241
242     /**
243      * Determine if the parameter represents a single column type, i.e., the
244      * column only has one usage.
245      *
246      * @param columnType
247      * @return true if column has only one usage.
248      */

249     public static boolean isUniqueColumnType(Set JavaDoc<UsageType> columnType) {
250         return columnType.size() == 1;
251     }
252
253     /**
254      * Maps from column type enum to column type name or list of names if the
255      * parameter represents more than on usage.
256      */

257     public static String JavaDoc convertColumnTypeToName(Set JavaDoc<UsageType> columnType) {
258         if (columnType.size() == 1) {
259             return columnType.iterator().next().name();
260         }
261         // it's a multi-purpose column
262
StringBuilder JavaDoc buf = new StringBuilder JavaDoc();
263         int k = 0;
264         for (UsageType usage : columnType) {
265             if (k++ > 0) {
266                 buf.append('|');
267             }
268             buf.append(usage.name());
269         }
270         return buf.toString();
271     }
272
273     /**
274      * Converts a {@link java.sql.Types} value to a {@link SqlQuery.Datatype}.
275      */

276     public static SqlQuery.Datatype getDatatype(int javaType) {
277         switch (javaType) {
278         case Types.TINYINT:
279         case Types.SMALLINT:
280         case Types.INTEGER:
281         case Types.BIGINT:
282             return SqlQuery.Datatype.Integer;
283         case Types.FLOAT:
284         case Types.REAL:
285         case Types.DOUBLE:
286         case Types.NUMERIC:
287         case Types.DECIMAL:
288             return SqlQuery.Datatype.Numeric;
289         case Types.BOOLEAN:
290             return SqlQuery.Datatype.Boolean;
291         case Types.DATE:
292             return SqlQuery.Datatype.Date;
293         case Types.TIME:
294             return SqlQuery.Datatype.Time;
295         case Types.TIMESTAMP:
296             return SqlQuery.Datatype.Timestamp;
297         case Types.CHAR:
298         case Types.VARCHAR:
299         default:
300             return SqlQuery.Datatype.String;
301         }
302     }
303
304     /**
305      * Returns true if the parameter is a java.sql.Type text type.
306      */

307     public static boolean isText(int javaType) {
308         switch (javaType) {
309         case Types.CHAR:
310         case Types.VARCHAR:
311         case Types.LONGVARCHAR:
312             return true;
313         default:
314             return false;
315         }
316     }
317
318     enum TableUsageType {
319         UNKNOWN,
320         FACT,
321         AGG
322     }
323
324     /**
325      * A table in a database.
326      */

327     public class Table {
328
329         /**
330          * A column in a table.
331          */

332         public class Column {
333
334             /**
335              * A usage of a column.
336              */

337             public class Usage {
338                 private final UsageType usageType;
339                 private String JavaDoc symbolicName;
340                 private RolapAggregator aggregator;
341
342                 ////////////////////////////////////////////////////
343
//
344
// These instance variables are used to hold
345
// stuff which is determines at one place and
346
// then used somewhere else. Generally, a usage
347
// is created before all of its "stuff" can be
348
// determined, hence, usage is not a set of classes,
349
// rather its one class with a bunch of instance
350
// variables which may or may not be used.
351
//
352

353                 // measure stuff
354
public RolapStar.Measure rMeasure;
355
356                 // hierarchy stuff
357
public MondrianDef.Relation relation;
358                 public MondrianDef.Expression joinExp;
359                 public String JavaDoc levelColumnName;
360
361                 // level
362
public RolapStar.Column rColumn;
363
364                 // for subtables
365
public RolapStar.Table rTable;
366                 public String JavaDoc rightJoinConditionColumnName;
367
368                 // It is used to hold the (possible null) prefix to
369
// use during aggregate table generation (See AggGen).
370
public String JavaDoc usagePrefix;
371                 //
372
////////////////////////////////////////////////////
373

374                 Usage(UsageType usageType) {
375                     this.usageType = usageType;
376                 }
377
378                 /**
379                  * This is the column with which this usage is associated.
380                  *
381                  * @return the usage's column.
382                  */

383                 public Column getColumn() {
384                     return JdbcSchema.Table.Column.this;
385                 }
386
387                 /**
388                  * The column usage type.
389                  */

390                 public UsageType getUsageType() {
391                     return usageType;
392                 }
393
394                 /**
395                  * Set the symbolic (logical) name associated with this usage.
396                  * For example, this might be the measure's name.
397                  *
398                  * @param symbolicName
399                  */

400                 public void setSymbolicName(final String JavaDoc symbolicName) {
401                     this.symbolicName = symbolicName;
402                 }
403
404                 /**
405                  * Get usage's symbolic name.
406                  */

407                 public String JavaDoc getSymbolicName() {
408                     return symbolicName;
409                 }
410
411                 /**
412                  * Set the aggregator associated with this usage (if its a
413                  * measure usage).
414                  *
415                  * @param aggregator
416                  */

417                 public void setAggregator(final RolapAggregator aggregator) {
418                     this.aggregator = aggregator;
419                 }
420
421                 /**
422                  * Get the aggregator associated with this usage (if its a
423                  * measure usage, otherwise null).
424                  */

425                 public RolapAggregator getAggregator() {
426                     return aggregator;
427                 }
428
429                 public String JavaDoc toString() {
430                     StringWriter JavaDoc sw = new StringWriter JavaDoc(64);
431                     PrintWriter JavaDoc pw = new PrintWriter JavaDoc(sw);
432                     print(pw, "");
433                     pw.flush();
434                     return sw.toString();
435                 }
436
437                 public void print(final PrintWriter JavaDoc pw, final String JavaDoc prefix) {
438                     if (getSymbolicName() != null) {
439                         pw.print("symbolicName=");
440                         pw.print(getSymbolicName());
441                     }
442                     if (getAggregator() != null) {
443                         pw.print(", aggregator=");
444                         pw.print(getAggregator().getName());
445                     }
446                     pw.print(", columnType=");
447                     pw.print(getUsageType().name());
448                 }
449             }
450
451             /** This is the name of the column. */
452             private final String JavaDoc name;
453
454             /** This is the java.sql.Type enum of the column in the database. */
455             private int type;
456             /**
457              * This is the java.sql.Type name of the column in the database.
458              */

459             private String JavaDoc typeName;
460
461             /** This is the size of the column in the database. */
462             private int columnSize;
463
464             /** The number of fractional digits. */
465             private int decimalDigits;
466
467             /** Radix (typically either 10 or 2). */
468             private int numPrecRadix;
469
470             /** For char types the maximum number of bytes in the column. */
471             private int charOctetLength;
472
473             /**
474              * False means the column definitely does not allow NULL values.
475              */

476             private boolean isNullable;
477
478             public final MondrianDef.Column column;
479
480             private final List<JdbcSchema.Table.Column.Usage> usages;
481
482             /**
483              * This contains the enums of all of the column's usages.
484              */

485             private final Set JavaDoc<UsageType> usageTypes =
486                 Util.enumSetNoneOf(UsageType.class);
487
488             private Column(final String JavaDoc name) {
489                 this.name = name;
490                 this.column =
491                     new MondrianDef.Column(
492                         JdbcSchema.Table.this.getName(),
493                         name);
494                 this.usages = new ArrayList<JdbcSchema.Table.Column.Usage>();
495             }
496
497             /**
498              * For testing ONLY
499             JdbcSchema.Table.Column copy() {
500                 Column column = new Column(name);
501                 column.type = type;
502                 column.typeName = typeName;
503                 column.columnSize = columnSize;
504                 column.decimalDigits = decimalDigits;
505                 column.numPrecRadix = numPrecRadix;
506                 column.charOctetLength = charOctetLength;
507                 column.isNullable = isNullable;
508
509                 return column;
510             }
511              */

512             /**
513              * For testing ONLY
514             void clearUsages() {
515                 // empty
516             }
517              */

518
519
520             /**
521              * This is the column's name in the database, not a symbolic name.
522              */

523             public String JavaDoc getName() {
524                 return name;
525             }
526
527             /**
528              * Set the columnIter java.sql.Type enun of the column.
529              *
530              * @param type
531              */

532             private void setType(final int type) {
533                 this.type = type;
534             }
535
536             /**
537              * Get the columnIter java.sql.Type enun of the column.
538              */

539             public int getType() {
540                 return type;
541             }
542
543             /**
544              * Set the columnIter java.sql.Type name.
545              *
546              * @param typeName
547              */

548             private void setTypeName(final String JavaDoc typeName) {
549                 this.typeName = typeName;
550             }
551
552             /**
553              * Get the columnIter java.sql.Type name.
554              */

555             public String JavaDoc getTypeName() {
556                 return typeName;
557             }
558
559             /**
560              * Get this column's table.
561              */

562             public Table getTable() {
563                 return JdbcSchema.Table.this;
564             }
565
566             /**
567              * Return true if this column is numeric.
568              */

569             public SqlQuery.Datatype getDatatype() {
570                 return JdbcSchema.getDatatype(getType());
571             }
572
573             /**
574              * Set the size in bytes of the column in the database.
575              *
576              * @param columnSize
577              */

578             private void setColumnSize(final int columnSize) {
579                 this.columnSize = columnSize;
580             }
581
582             /**
583              * Get the size in bytes of the column in the database.
584              *
585              */

586             public int getColumnSize() {
587                 return columnSize;
588             }
589
590             /**
591              * Set number of fractional digits.
592              *
593              * @param decimalDigits
594              */

595             private void setDecimalDigits(final int decimalDigits) {
596                 this.decimalDigits = decimalDigits;
597             }
598
599             /**
600              * Get number of fractional digits.
601              */

602             public int getDecimalDigits() {
603                 return decimalDigits;
604             }
605
606             /**
607              * Set Radix (typically either 10 or 2).
608              *
609              * @param numPrecRadix
610              */

611             private void setNumPrecRadix(final int numPrecRadix) {
612                 this.numPrecRadix = numPrecRadix;
613             }
614
615             /**
616              * Get Radix (typically either 10 or 2).
617              */

618             public int getNumPrecRadix() {
619                 return numPrecRadix;
620             }
621
622             /**
623              * For char types the maximum number of bytes in the column.
624              *
625              * @param charOctetLength
626              */

627             private void setCharOctetLength(final int charOctetLength) {
628                 this.charOctetLength = charOctetLength;
629             }
630
631             /**
632              * For char types the maximum number of bytes in the column.
633              */

634             public int getCharOctetLength() {
635                 return charOctetLength;
636             }
637
638             /**
639              * False means the column definitely does not allow NULL values.
640              *
641              * @param isNullable
642              */

643             private void setIsNullable(final boolean isNullable) {
644                 this.isNullable = isNullable;
645             }
646
647             /**
648              * False means the column definitely does not allow NULL values.
649              */

650             public boolean isNullable() {
651                 return isNullable;
652             }
653
654             /**
655              * How many usages does this column have. A column has
656              * between 0 and N usages. It has no usages if usages is some
657              * administrative column. It has one usage if, for example, its
658              * the fact_count column or a level column (for a collapsed
659              * dimension aggregate). It might have 2 usages if its a foreign key
660              * that is also used as a measure. If its a column used in N
661              * measures, then usages will have N usages.
662              */

663             public int numberOfUsages() {
664                 return usages.size();
665             }
666
667             /**
668              * Return true if the column has at least one usage.
669              */

670             public boolean hasUsage() {
671                 return (usages.size() != 0);
672             }
673
674             /**
675              * Return true if the column has at least one usage of the given
676              * column type.
677              */

678             public boolean hasUsage(UsageType columnType) {
679                 return usageTypes.contains(columnType);
680             }
681
682             /**
683              * Get an iterator over all usages.
684              */

685             public List<Usage> getUsages() {
686                 return usages;
687             }
688
689             /**
690              * Get an iterator over all usages of the given column type.
691              */

692             public Iterator<Usage> getUsages(UsageType usageType) {
693
694                 // Yes, this is legal.
695
class ColumnTypeIterator implements Iterator<Usage> {
696                     private final Iterator<Usage> usageIter;
697                     private final UsageType usageType;
698                     private Usage nextUsage;
699
700                     ColumnTypeIterator(
701                         final List<Usage> usages,
702                         final UsageType columnType)
703                     {
704                         this.usageIter = usages.iterator();
705                         this.usageType = columnType;
706                     }
707
708                     public boolean hasNext() {
709                         while (usageIter.hasNext()) {
710                             Usage usage = usageIter.next();
711                             if (usage.getUsageType() == this.usageType) {
712                                 nextUsage = usage;
713                                 return true;
714                             }
715
716                         }
717                         nextUsage = null;
718                         return false;
719                     }
720
721                     public Usage next() {
722                         return nextUsage;
723                     }
724
725                     public void remove() {
726                         usageIter.remove();
727                     }
728                 }
729
730                 return new ColumnTypeIterator(getUsages(), usageType);
731             }
732
733             /**
734              * Create a new usage of a given column type.
735              */

736             public Usage newUsage(UsageType usageType) {
737                 this.usageTypes.add(usageType);
738
739                 Usage usage = new Usage(usageType);
740                 usages.add(usage);
741                 return usage;
742             }
743
744             public String JavaDoc toString() {
745                 StringWriter JavaDoc sw = new StringWriter JavaDoc(256);
746                 PrintWriter JavaDoc pw = new PrintWriter JavaDoc(sw);
747                 print(pw, "");
748                 pw.flush();
749                 return sw.toString();
750             }
751
752             public void print(final PrintWriter JavaDoc pw, final String JavaDoc prefix) {
753                 pw.print(prefix);
754                 pw.print("name=");
755                 pw.print(getName());
756                 pw.print(", typename=");
757                 pw.print(getTypeName());
758                 pw.print(", size=");
759                 pw.print(getColumnSize());
760
761                 switch (getType()) {
762                 case Types.TINYINT:
763                 case Types.SMALLINT:
764                 case Types.INTEGER:
765                 case Types.BIGINT:
766                 case Types.FLOAT:
767                 case Types.REAL:
768                 case Types.DOUBLE:
769                     break;
770                 case Types.NUMERIC:
771                 case Types.DECIMAL:
772                     pw.print(", decimalDigits=");
773                     pw.print(getDecimalDigits());
774                     pw.print(", numPrecRadix=");
775                     pw.print(getNumPrecRadix());
776                     break;
777                 case Types.CHAR:
778                 case Types.VARCHAR:
779                     pw.print(", charOctetLength=");
780                     pw.print(getCharOctetLength());
781                     break;
782                 case Types.LONGVARCHAR:
783                 case Types.DATE:
784                 case Types.TIME:
785                 case Types.TIMESTAMP:
786                 case Types.BINARY:
787                 case Types.VARBINARY:
788                 case Types.LONGVARBINARY:
789                 default:
790                     break;
791                 }
792                 pw.print(", isNullable=");
793                 pw.print(isNullable());
794
795                 if (hasUsage()) {
796                     pw.print(" Usages [");
797                     for (Usage usage : getUsages()) {
798                         pw.print('(');
799                         usage.print(pw, prefix);
800                         pw.print(')');
801                     }
802                     pw.println("]");
803                 }
804             }
805         }
806
807         /** Name of table. */
808         private final String JavaDoc name;
809         /** Map from column name to column. */
810         private Map<String JavaDoc, Column> columnMap;
811         /** Sum of all of the table's column's column sizes. */
812         private int totalColumnSize;
813         /**
814          * Is the table a fact, aggregate or other table type.
815          * Note: this assumes that a table has only ONE usage.
816          */

817         private TableUsageType tableUsageType;
818
819         /**
820          * Typical table types are: "TABLE", "VIEW", "SYSTEM TABLE",
821          * "GLOBAL TEMPORARY", "LOCAL TEMPORARY", "ALIAS", "SYNONYM".
822          * (Depends what comes out of JDBC.)
823          */

824         private final String JavaDoc tableType;
825
826         // mondriandef stuff
827
public MondrianDef.Table table;
828
829         private boolean allColumnsLoaded;
830
831         private Table(final String JavaDoc name, String JavaDoc tableType) {
832             this.name = name;
833             this.tableUsageType = TableUsageType.UNKNOWN;
834             this.tableType = tableType;
835         }
836
837         public void load() throws SQLException JavaDoc {
838             loadColumns();
839         }
840
841         /**
842          * For testing ONLY
843         JdbcSchema.Table copy() {
844             Table table = new Table(name);
845             table.totalColumnSize = totalColumnSize;
846             table.tableUsage = tableUsage;
847             table.tableType = tableType;
848
849             Map m = table.getColumnMap();
850             for (Iterator usageIter = getColumns(); usageIter.hasNext(); ) {
851                 Column column = (Column) usageIter.next();
852                 m.put(column.getName(), column.copy());
853             }
854
855             return table;
856         }
857          */

858         /**
859          * For testing ONLY
860         void clearUsages() {
861             this.tableUsage = UNKNOWN_TABLE_USAGE;
862             for (Iterator usageIter = getColumns(); usageIter.hasNext(); ) {
863                 Column column = (Column) usageIter.next();
864                 column.clearUsages();
865             }
866         }
867          */

868
869         /**
870          * Get the name of the table.
871          */

872         public String JavaDoc getName() {
873             return name;
874         }
875
876         /**
877          * Get the total size of a row (sum of the column sizes).
878          */

879         public int getTotalColumnSize() {
880             return totalColumnSize;
881         }
882
883         /**
884          * Get the number of rows in the table.
885          */

886         public int getNumberOfRows() {
887             return -1;
888         }
889
890         /**
891          * Returns the collection of columns in this Table.
892          */

893         public Collection<Column> getColumns() {
894             return getColumnMap().values();
895         }
896
897         /**
898          * Returns an iterator over all column usages of a given type.
899          */

900         public Iterator<JdbcSchema.Table.Column.Usage> getColumnUsages(
901             final UsageType usageType)
902         {
903
904             class CTIterator implements Iterator<JdbcSchema.Table.Column.Usage> {
905                 private final Iterator<Column> columnIter;
906                 private final UsageType columnType;
907                 private Iterator<JdbcSchema.Table.Column.Usage> usageIter;
908                 private JdbcSchema.Table.Column.Usage nextObject;
909
910                 CTIterator(Collection<Column> columns, UsageType columnType) {
911                     this.columnIter = columns.iterator();
912                     this.columnType = columnType;
913                 }
914
915                 public boolean hasNext() {
916                     while (true) {
917                         while ((usageIter == null) || ! usageIter.hasNext()) {
918                             if (! columnIter.hasNext()) {
919                                 nextObject = null;
920                                 return false;
921                             }
922                             Column c = columnIter.next();
923                             usageIter = c.getUsages().iterator();
924                         }
925                         JdbcSchema.Table.Column.Usage usage = usageIter.next();
926                         if (usage.getUsageType() == columnType) {
927                             nextObject = usage;
928                             return true;
929                         }
930                     }
931                 }
932                 public JdbcSchema.Table.Column.Usage next() {
933                     return nextObject;
934                 }
935                 public void remove() {
936                     usageIter.remove();
937                 }
938             }
939             return new CTIterator(getColumns(), usageType);
940         }
941
942         /**
943          * Get a column by its name.
944          */

945         public Column getColumn(final String JavaDoc columnName) {
946             return getColumnMap().get(columnName);
947         }
948
949         /**
950          * Return true if this table contains a column with the given name.
951          */

952         public boolean constainsColumn(final String JavaDoc columnName) {
953             return getColumnMap().containsKey(columnName);
954         }
955
956         /**
957          * Set the table usage (fact, aggregate or other).
958          *
959          * @param tableUsageType
960          */

961         public void setTableUsageType(final TableUsageType tableUsageType) {
962             // if usageIter has already been set, then usageIter can NOT be reset
963
if ((this.tableUsageType != TableUsageType.UNKNOWN) &&
964                     (this.tableUsageType != tableUsageType)) {
965
966                 throw mres.AttemptToChangeTableUsage.ex(
967                     getName(),
968                     this.tableUsageType.name(),
969                     tableUsageType.name());
970             }
971             this.tableUsageType = tableUsageType;
972         }
973
974         /**
975          * Get the table's usage type.
976          */

977         public TableUsageType getTableUsageType() {
978             return tableUsageType;
979         }
980
981         /**
982          * Get the table's type.
983          */

984         public String JavaDoc getTableType() {
985             return tableType;
986         }
987
988         public String JavaDoc toString() {
989             StringWriter JavaDoc sw = new StringWriter JavaDoc(256);
990             PrintWriter JavaDoc pw = new PrintWriter JavaDoc(sw);
991             print(pw, "");
992             pw.flush();
993             return sw.toString();
994         }
995         public void print(final PrintWriter JavaDoc pw, final String JavaDoc prefix) {
996             pw.print(prefix);
997             pw.println("Table:");
998             String JavaDoc subprefix = prefix + " ";
999             String JavaDoc subsubprefix = subprefix + " ";
1000
1001            pw.print(subprefix);
1002            pw.print("name=");
1003            pw.print(getName());
1004            pw.print(", type=");
1005            pw.print(getTableType());
1006            pw.print(", usage=");
1007            pw.println(getTableUsageType().name());
1008
1009            pw.print(subprefix);
1010            pw.print("totalColumnSize=");
1011            pw.println(getTotalColumnSize());
1012
1013            pw.print(subprefix);
1014            pw.println("Columns: [");
1015            for (Column column : getColumnMap().values()) {
1016                column.print(pw, subsubprefix);
1017                pw.println();
1018            }
1019            pw.print(subprefix);
1020            pw.println("]");
1021        }
1022
1023        /**
1024         * Get all of the columnIter associated with a table and create Column
1025         * objects with the column's name, type, type name and column size.
1026         *
1027         * @throws SQLException
1028         */

1029        private void loadColumns() throws SQLException JavaDoc {
1030            if (! allColumnsLoaded) {
1031                Connection JavaDoc conn = getDataSource().getConnection();
1032                try {
1033                    DatabaseMetaData JavaDoc dmd = conn.getMetaData();
1034
1035                    String JavaDoc schema = JdbcSchema.this.getSchemaName();
1036                    String JavaDoc catalog = JdbcSchema.this.getCatalogName();
1037                    String JavaDoc tableName = getName();
1038                    String JavaDoc columnNamePattern = "%";
1039
1040                    ResultSet JavaDoc rs = null;
1041                    try {
1042                        Map<String JavaDoc, Column> map = getColumnMap();
1043                        rs = dmd.getColumns(catalog,
1044                                            schema,
1045                                            tableName,
1046                                            columnNamePattern);
1047                        while (rs.next()) {
1048                            String JavaDoc name = rs.getString(4);
1049                            int type = rs.getInt(5);
1050                            String JavaDoc typeName = rs.getString(6);
1051                            int columnSize = rs.getInt(7);
1052                            int decimalDigits = rs.getInt(9);
1053                            int numPrecRadix = rs.getInt(10);
1054                            int charOctetLength = rs.getInt(16);
1055                            String JavaDoc isNullable = rs.getString(18);
1056
1057                            Column column = new Column(name);
1058                            column.setType(type);
1059                            column.setTypeName(typeName);
1060                            column.setColumnSize(columnSize);
1061                            column.setDecimalDigits(decimalDigits);
1062                            column.setNumPrecRadix(numPrecRadix);
1063                            column.setCharOctetLength(charOctetLength);
1064                            column.setIsNullable(!"NO".equals(isNullable));
1065
1066                            map.put(name, column);
1067                            totalColumnSize += column.getColumnSize();
1068                        }
1069                    } finally {
1070                        if (rs != null) {
1071                            rs.close();
1072                        }
1073                    }
1074                } finally {
1075                    try {
1076                        conn.close();
1077                    } catch (SQLException JavaDoc e) {
1078                        //ignore
1079
}
1080                }
1081
1082                allColumnsLoaded = true;
1083            }
1084        }
1085
1086        private Map<String JavaDoc, Column> getColumnMap() {
1087            if (columnMap == null) {
1088                columnMap = new HashMap<String JavaDoc, Column>();
1089            }
1090            return columnMap;
1091        }
1092    }
1093
1094    private DataSource JavaDoc dataSource;
1095    private String JavaDoc schema;
1096    private String JavaDoc catalog;
1097    private boolean allTablesLoaded;
1098    private Map<String JavaDoc, Table> tables;
1099
1100    JdbcSchema(final DataSource JavaDoc dataSource) {
1101        this.dataSource = dataSource;
1102        this.tables = new HashMap<String JavaDoc, Table>();
1103    }
1104
1105    /**
1106     * This forces the tables to be loaded.
1107     *
1108     * @throws SQLException
1109     */

1110    public void load() throws SQLException JavaDoc {
1111        loadTables();
1112    }
1113
1114    /**
1115     * For testing ONLY
1116    JdbcSchema copy() {
1117        JdbcSchema jdbcSchema = new JdbcSchema(dataSource);
1118        jdbcSchema.setSchemaName(getSchemaName());
1119        jdbcSchema.setCatalogName(getCatalogName());
1120
1121        Map m = jdbcSchema.getTablesMap();
1122        for (Iterator usageIter = getTables(); usageIter.hasNext(); ) {
1123            Table table = (Table) usageIter.next();
1124            m.put(table.getName(), table.copy());
1125        }
1126
1127        return jdbcSchema;
1128    }
1129     */

1130
1131    /**
1132     * For testing ONLY
1133    void clearUsages() {
1134        for (Iterator usageIter = getTables(); usageIter.hasNext(); ) {
1135            Table table = (Table) usageIter.next();
1136            table.clearUsages();
1137        }
1138    }
1139     */

1140
1141    protected void clear() {
1142        // keep the DataSource, clear/reset everything else
1143
allTablesLoaded = false;
1144        schema = null;
1145        catalog = null;
1146        tables.clear();
1147    }
1148    protected void remove() {
1149        // set ALL instance variables to null
1150
clear();
1151        dataSource = null;
1152    }
1153
1154    /**
1155     * This is used for testing allowing one to load tables and their columnIter
1156     * from more than one datasource
1157     */

1158    void resetAllTablesLoaded() {
1159        allTablesLoaded = false;
1160    }
1161
1162    public DataSource JavaDoc getDataSource() {
1163        return dataSource;
1164    }
1165
1166    protected void setDataSource(DataSource JavaDoc dataSource) {
1167        this.dataSource = dataSource;
1168    }
1169
1170    /**
1171     * Set the database's schema name.
1172     *
1173     * @param schema
1174     */

1175    public void setSchemaName(final String JavaDoc schema) {
1176        this.schema = schema;
1177    }
1178
1179    /**
1180     * Get the database's schema name.
1181     */

1182    public String JavaDoc getSchemaName() {
1183        return schema;
1184    }
1185
1186    /**
1187     * Set the database's catalog name.
1188     */

1189    public void setCatalogName(final String JavaDoc catalog) {
1190        this.catalog = catalog;
1191    }
1192
1193    /**
1194     * Get the database's catalog name.
1195     */

1196    public String JavaDoc getCatalogName() {
1197        return catalog;
1198    }
1199
1200    /**
1201     * Get iterator over the database's tables.
1202     */

1203    public synchronized Collection<Table> getTables() {
1204        return getTablesMap().values();
1205    }
1206
1207    /**
1208     * Get a table by name.
1209     */

1210    public synchronized Table getTable(final String JavaDoc tableName) {
1211        return getTablesMap().get(tableName);
1212    }
1213
1214    public String JavaDoc toString() {
1215        StringWriter JavaDoc sw = new StringWriter JavaDoc(256);
1216        PrintWriter JavaDoc pw = new PrintWriter JavaDoc(sw);
1217        print(pw, "");
1218        pw.flush();
1219        return sw.toString();
1220    }
1221    public void print(final PrintWriter JavaDoc pw, final String JavaDoc prefix) {
1222        pw.print(prefix);
1223        pw.println("JdbcSchema:");
1224        String JavaDoc subprefix = prefix + " ";
1225        String JavaDoc subsubprefix = subprefix + " ";
1226
1227        pw.print(subprefix);
1228        pw.println("Tables: [");
1229        Iterator it = getTablesMap().values().iterator();
1230        while (it.hasNext()) {
1231            Table table = (Table) it.next();
1232            table.print(pw, subsubprefix);
1233        }
1234        pw.print(subprefix);
1235        pw.println("]");
1236    }
1237
1238    /**
1239     * This method gets all of the tables (and views) in the database.
1240     * If called a second time, this method is a no-op.
1241     *
1242     * @throws SQLException
1243     */

1244    private void loadTables() throws SQLException JavaDoc {
1245        if (! allTablesLoaded) {
1246            Connection JavaDoc conn = getDataSource().getConnection();
1247            DatabaseMetaData JavaDoc dmd = conn.getMetaData();
1248
1249            String JavaDoc schema = getSchemaName();
1250            String JavaDoc catalog = getCatalogName();
1251            String JavaDoc[] tableTypes = { "TABLE", "VIEW" };
1252            String JavaDoc tableName = "%";
1253
1254            ResultSet JavaDoc rs = null;
1255            try {
1256                rs = dmd.getTables(catalog,
1257                                   schema,
1258                                   tableName,
1259                                   tableTypes);
1260                if (rs != null) {
1261                    while (rs.next()) {
1262                        addTable(rs);
1263                    }
1264                } else {
1265                    getLogger().debug("ERROR: rs == null");
1266                }
1267            } finally {
1268                if (rs != null) {
1269                    rs.close();
1270                }
1271            }
1272            try {
1273                conn.close();
1274            } catch (SQLException JavaDoc e) {
1275                //ignore
1276
}
1277
1278            allTablesLoaded = true;
1279        }
1280    }
1281
1282    /**
1283     * Make a Table from an ResultSet - the table's name is the ResultSet third
1284     * entry.
1285     *
1286     * @param rs
1287     * @throws SQLException
1288     */

1289    protected void addTable(final ResultSet JavaDoc rs) throws SQLException JavaDoc {
1290        String JavaDoc name = rs.getString(3);
1291        String JavaDoc tableType = rs.getString(4);
1292        Table table = new Table(name, tableType);
1293
1294        tables.put(table.getName(), table);
1295    }
1296
1297    private Map<String JavaDoc, Table> getTablesMap() {
1298        return tables;
1299    }
1300
1301    public static synchronized void clearAllDBs() {
1302        factory = null;
1303        makeFactory();
1304    }
1305}
1306
1307// End JdbcSchema.java
1308
Popular Tags