1 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 ; 22 23 import org.apache.log4j.Logger; 24 25 import java.lang.ref.SoftReference ; 26 import java.io.PrintWriter ; 27 import java.io.StringWriter ; 28 import java.sql.ResultSet ; 29 import java.sql.Connection ; 30 import java.sql.DatabaseMetaData ; 31 import java.sql.Types ; 32 import java.sql.SQLException ; 33 import java.util.*; 34 35 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 65 public Logger getLogger() { 66 return LOGGER; 67 } 68 69 public interface Factory { 70 JdbcSchema makeDB(DataSource dataSource); 71 void clearDB(JdbcSchema db); 72 void removeDB(JdbcSchema db); 73 } 74 75 private static final Map<DataSource , SoftReference <JdbcSchema>> dbMap = 76 new HashMap<DataSource , SoftReference <JdbcSchema>>(); 77 78 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 dataSource) { 88 JdbcSchema db = new JdbcSchema(dataSource); 89 return db; 90 } 91 public void clearDB(JdbcSchema db) { 92 } 94 public void removeDB(JdbcSchema db) { 95 } 97 } 98 99 private static Factory factory; 100 101 private static void makeFactory() { 102 if (factory == null) { 103 String classname = 104 MondrianProperties.instance().JdbcFactoryClass.get(); 105 if (classname == null) { 106 factory = new StdFactory(); 107 } else { 108 try { 109 Class <?> clz = Class.forName(classname); 110 factory = (Factory) clz.newInstance(); 111 } catch (ClassNotFoundException ex) { 112 throw mres.BadJdbcFactoryClassName.ex(classname); 113 } catch (InstantiationException ex) { 114 throw mres.BadJdbcFactoryInstantiation.ex(classname); 115 } catch (IllegalAccessException ex) { 116 throw mres.BadJdbcFactoryAccess.ex(classname); 117 } 118 } 119 } 120 } 121 122 129 public static synchronized JdbcSchema makeDB(DataSource dataSource) { 130 makeFactory(); 131 132 JdbcSchema db = null; 133 SoftReference <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 <JdbcSchema>(db)); 140 } 141 142 sweepDB(); 143 144 return db; 145 } 146 151 public static synchronized void clearDB(DataSource dataSource) { 152 makeFactory(); 153 154 SoftReference <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 172 public static synchronized void removeDB(DataSource dataSource) { 173 makeFactory(); 174 175 SoftReference <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 191 private static void sweepDB() { 192 if (sweepDBCount > SWEEP_COUNT) { 193 Iterator<SoftReference <JdbcSchema>> it = dbMap.values().iterator(); 194 while (it.hasNext()) { 195 SoftReference <JdbcSchema> ref = it.next(); 196 if ((ref == null) || (ref.get() == null)) { 197 try { 198 it.remove(); 199 } catch (Exception ex) { 200 LOGGER.warn(ex); 203 } 204 } 205 206 } 207 sweepDBCount = 0; 209 } 210 } 211 212 213 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 UNKNOWN_COLUMN_NAME = "UNKNOWN"; 224 public static final String FOREIGN_KEY_COLUMN_NAME = "FOREIGN_KEY"; 225 public static final String MEASURE_COLUMN_NAME = "MEASURE"; 226 public static final String LEVEL_COLUMN_NAME = "LEVEL"; 227 public static final String FACT_COUNT_COLUMN_NAME = "FACT_COUNT"; 228 public static final String IGNORE_COLUMN_NAME = "IGNORE"; 229 230 233 enum UsageType { 234 UNKNOWN, 235 FOREIGN_KEY, 236 MEASURE, 237 LEVEL, 238 FACT_COUNT, 239 IGNORE 240 } 241 242 249 public static boolean isUniqueColumnType(Set <UsageType> columnType) { 250 return columnType.size() == 1; 251 } 252 253 257 public static String convertColumnTypeToName(Set <UsageType> columnType) { 258 if (columnType.size() == 1) { 259 return columnType.iterator().next().name(); 260 } 261 StringBuilder buf = new StringBuilder (); 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 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 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 327 public class Table { 328 329 332 public class Column { 333 334 337 public class Usage { 338 private final UsageType usageType; 339 private String symbolicName; 340 private RolapAggregator aggregator; 341 342 353 public RolapStar.Measure rMeasure; 355 356 public MondrianDef.Relation relation; 358 public MondrianDef.Expression joinExp; 359 public String levelColumnName; 360 361 public RolapStar.Column rColumn; 363 364 public RolapStar.Table rTable; 366 public String rightJoinConditionColumnName; 367 368 public String usagePrefix; 371 374 Usage(UsageType usageType) { 375 this.usageType = usageType; 376 } 377 378 383 public Column getColumn() { 384 return JdbcSchema.Table.Column.this; 385 } 386 387 390 public UsageType getUsageType() { 391 return usageType; 392 } 393 394 400 public void setSymbolicName(final String symbolicName) { 401 this.symbolicName = symbolicName; 402 } 403 404 407 public String getSymbolicName() { 408 return symbolicName; 409 } 410 411 417 public void setAggregator(final RolapAggregator aggregator) { 418 this.aggregator = aggregator; 419 } 420 421 425 public RolapAggregator getAggregator() { 426 return aggregator; 427 } 428 429 public String toString() { 430 StringWriter sw = new StringWriter (64); 431 PrintWriter pw = new PrintWriter (sw); 432 print(pw, ""); 433 pw.flush(); 434 return sw.toString(); 435 } 436 437 public void print(final PrintWriter pw, final String 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 452 private final String name; 453 454 455 private int type; 456 459 private String typeName; 460 461 462 private int columnSize; 463 464 465 private int decimalDigits; 466 467 468 private int numPrecRadix; 469 470 471 private int charOctetLength; 472 473 476 private boolean isNullable; 477 478 public final MondrianDef.Column column; 479 480 private final List<JdbcSchema.Table.Column.Usage> usages; 481 482 485 private final Set <UsageType> usageTypes = 486 Util.enumSetNoneOf(UsageType.class); 487 488 private Column(final String 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 512 518 519 520 523 public String getName() { 524 return name; 525 } 526 527 532 private void setType(final int type) { 533 this.type = type; 534 } 535 536 539 public int getType() { 540 return type; 541 } 542 543 548 private void setTypeName(final String typeName) { 549 this.typeName = typeName; 550 } 551 552 555 public String getTypeName() { 556 return typeName; 557 } 558 559 562 public Table getTable() { 563 return JdbcSchema.Table.this; 564 } 565 566 569 public SqlQuery.Datatype getDatatype() { 570 return JdbcSchema.getDatatype(getType()); 571 } 572 573 578 private void setColumnSize(final int columnSize) { 579 this.columnSize = columnSize; 580 } 581 582 586 public int getColumnSize() { 587 return columnSize; 588 } 589 590 595 private void setDecimalDigits(final int decimalDigits) { 596 this.decimalDigits = decimalDigits; 597 } 598 599 602 public int getDecimalDigits() { 603 return decimalDigits; 604 } 605 606 611 private void setNumPrecRadix(final int numPrecRadix) { 612 this.numPrecRadix = numPrecRadix; 613 } 614 615 618 public int getNumPrecRadix() { 619 return numPrecRadix; 620 } 621 622 627 private void setCharOctetLength(final int charOctetLength) { 628 this.charOctetLength = charOctetLength; 629 } 630 631 634 public int getCharOctetLength() { 635 return charOctetLength; 636 } 637 638 643 private void setIsNullable(final boolean isNullable) { 644 this.isNullable = isNullable; 645 } 646 647 650 public boolean isNullable() { 651 return isNullable; 652 } 653 654 663 public int numberOfUsages() { 664 return usages.size(); 665 } 666 667 670 public boolean hasUsage() { 671 return (usages.size() != 0); 672 } 673 674 678 public boolean hasUsage(UsageType columnType) { 679 return usageTypes.contains(columnType); 680 } 681 682 685 public List<Usage> getUsages() { 686 return usages; 687 } 688 689 692 public Iterator<Usage> getUsages(UsageType usageType) { 693 694 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 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 toString() { 745 StringWriter sw = new StringWriter (256); 746 PrintWriter pw = new PrintWriter (sw); 747 print(pw, ""); 748 pw.flush(); 749 return sw.toString(); 750 } 751 752 public void print(final PrintWriter pw, final String 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 808 private final String name; 809 810 private Map<String , Column> columnMap; 811 812 private int totalColumnSize; 813 817 private TableUsageType tableUsageType; 818 819 824 private final String tableType; 825 826 public MondrianDef.Table table; 828 829 private boolean allColumnsLoaded; 830 831 private Table(final String name, String tableType) { 832 this.name = name; 833 this.tableUsageType = TableUsageType.UNKNOWN; 834 this.tableType = tableType; 835 } 836 837 public void load() throws SQLException { 838 loadColumns(); 839 } 840 841 858 868 869 872 public String getName() { 873 return name; 874 } 875 876 879 public int getTotalColumnSize() { 880 return totalColumnSize; 881 } 882 883 886 public int getNumberOfRows() { 887 return -1; 888 } 889 890 893 public Collection<Column> getColumns() { 894 return getColumnMap().values(); 895 } 896 897 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 945 public Column getColumn(final String columnName) { 946 return getColumnMap().get(columnName); 947 } 948 949 952 public boolean constainsColumn(final String columnName) { 953 return getColumnMap().containsKey(columnName); 954 } 955 956 961 public void setTableUsageType(final TableUsageType tableUsageType) { 962 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 977 public TableUsageType getTableUsageType() { 978 return tableUsageType; 979 } 980 981 984 public String getTableType() { 985 return tableType; 986 } 987 988 public String toString() { 989 StringWriter sw = new StringWriter (256); 990 PrintWriter pw = new PrintWriter (sw); 991 print(pw, ""); 992 pw.flush(); 993 return sw.toString(); 994 } 995 public void print(final PrintWriter pw, final String prefix) { 996 pw.print(prefix); 997 pw.println("Table:"); 998 String subprefix = prefix + " "; 999 String 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 1029 private void loadColumns() throws SQLException { 1030 if (! allColumnsLoaded) { 1031 Connection conn = getDataSource().getConnection(); 1032 try { 1033 DatabaseMetaData dmd = conn.getMetaData(); 1034 1035 String schema = JdbcSchema.this.getSchemaName(); 1036 String catalog = JdbcSchema.this.getCatalogName(); 1037 String tableName = getName(); 1038 String columnNamePattern = "%"; 1039 1040 ResultSet rs = null; 1041 try { 1042 Map<String , Column> map = getColumnMap(); 1043 rs = dmd.getColumns(catalog, 1044 schema, 1045 tableName, 1046 columnNamePattern); 1047 while (rs.next()) { 1048 String name = rs.getString(4); 1049 int type = rs.getInt(5); 1050 String 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 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 e) { 1078 } 1080 } 1081 1082 allColumnsLoaded = true; 1083 } 1084 } 1085 1086 private Map<String , Column> getColumnMap() { 1087 if (columnMap == null) { 1088 columnMap = new HashMap<String , Column>(); 1089 } 1090 return columnMap; 1091 } 1092 } 1093 1094 private DataSource dataSource; 1095 private String schema; 1096 private String catalog; 1097 private boolean allTablesLoaded; 1098 private Map<String , Table> tables; 1099 1100 JdbcSchema(final DataSource dataSource) { 1101 this.dataSource = dataSource; 1102 this.tables = new HashMap<String , Table>(); 1103 } 1104 1105 1110 public void load() throws SQLException { 1111 loadTables(); 1112 } 1113 1114 1130 1131 1140 1141 protected void clear() { 1142 allTablesLoaded = false; 1144 schema = null; 1145 catalog = null; 1146 tables.clear(); 1147 } 1148 protected void remove() { 1149 clear(); 1151 dataSource = null; 1152 } 1153 1154 1158 void resetAllTablesLoaded() { 1159 allTablesLoaded = false; 1160 } 1161 1162 public DataSource getDataSource() { 1163 return dataSource; 1164 } 1165 1166 protected void setDataSource(DataSource dataSource) { 1167 this.dataSource = dataSource; 1168 } 1169 1170 1175 public void setSchemaName(final String schema) { 1176 this.schema = schema; 1177 } 1178 1179 1182 public String getSchemaName() { 1183 return schema; 1184 } 1185 1186 1189 public void setCatalogName(final String catalog) { 1190 this.catalog = catalog; 1191 } 1192 1193 1196 public String getCatalogName() { 1197 return catalog; 1198 } 1199 1200 1203 public synchronized Collection<Table> getTables() { 1204 return getTablesMap().values(); 1205 } 1206 1207 1210 public synchronized Table getTable(final String tableName) { 1211 return getTablesMap().get(tableName); 1212 } 1213 1214 public String toString() { 1215 StringWriter sw = new StringWriter (256); 1216 PrintWriter pw = new PrintWriter (sw); 1217 print(pw, ""); 1218 pw.flush(); 1219 return sw.toString(); 1220 } 1221 public void print(final PrintWriter pw, final String prefix) { 1222 pw.print(prefix); 1223 pw.println("JdbcSchema:"); 1224 String subprefix = prefix + " "; 1225 String 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 1244 private void loadTables() throws SQLException { 1245 if (! allTablesLoaded) { 1246 Connection conn = getDataSource().getConnection(); 1247 DatabaseMetaData dmd = conn.getMetaData(); 1248 1249 String schema = getSchemaName(); 1250 String catalog = getCatalogName(); 1251 String [] tableTypes = { "TABLE", "VIEW" }; 1252 String tableName = "%"; 1253 1254 ResultSet 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 e) { 1275 } 1277 1278 allTablesLoaded = true; 1279 } 1280 } 1281 1282 1289 protected void addTable(final ResultSet rs) throws SQLException { 1290 String name = rs.getString(3); 1291 String tableType = rs.getString(4); 1292 Table table = new Table(name, tableType); 1293 1294 tables.put(table.getName(), table); 1295 } 1296 1297 private Map<String , Table> getTablesMap() { 1298 return tables; 1299 } 1300 1301 public static synchronized void clearAllDBs() { 1302 factory = null; 1303 makeFactory(); 1304 } 1305} 1306 1307 | Popular Tags |