0001: /**
0002: * Copyright (C) 2006, 2007 David Bulmore, Software Sensation Inc.
0003: * All Rights Reserved.
0004: *
0005: * This file is part of JPersist.
0006: *
0007: * JPersist is free software; you can redistribute it and/or modify it under
0008: * the terms of the GNU General Public License (Version 2) as published by
0009: * the Free Software Foundation.
0010: *
0011: * JPersist is distributed in the hope that it will be useful, but WITHOUT
0012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014: * for more details.
0015: *
0016: * You should have received a copy of the GNU General Public License
0017: * along with JPersist; if not, write to the Free Software Foundation,
0018: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
0019: */package jpersist;
0020:
0021: import java.sql.Connection;
0022: import java.sql.DatabaseMetaData;
0023: import java.sql.ResultSet;
0024: import java.sql.ResultSetMetaData;
0025: import java.sql.SQLException;
0026: import java.sql.Savepoint;
0027: import java.sql.Statement;
0028: import java.util.HashMap;
0029: import java.util.Iterator;
0030: import java.util.Map;
0031: import jcommontk.utils.StringUtils;
0032: import java.util.HashSet;
0033: import java.util.Set;
0034: import java.util.logging.Level;
0035: import java.util.logging.Logger;
0036: import jcommontk.inflector.SimpleInflector;
0037: import jpersist.interfaces.ColumnMapping;
0038: import jpersist.interfaces.TableMapping;
0039:
0040: /**
0041: * This class provides database level metadata.
0042: */
0043:
0044: @SuppressWarnings("unchecked")
0045: // working to complete a Java 1.5 version
0046: public final class MetaData {
0047: public static final int STORES_UNKNOWN = 0;
0048: public static final int STORES_UPPERCASE = 1;
0049: public static final int STORES_LOWERCASE = 2;
0050: public static final int STORES_MIXEDCASE = 3;
0051:
0052: private static Logger logger = Logger.getLogger(MetaData.class
0053: .getName());
0054: private static Map metaDataMap = new HashMap();
0055:
0056: private String tableTypes[] = new String[] { "TABLE", "VIEW" },
0057: identifierQuoteString = "", searchStringEscape = "",
0058: databaseUrl;
0059: private Map tables = new HashMap(), tableCache = new HashMap(),
0060: tableNameMapping = new HashMap();
0061: private Set stripTablePrefixes, stripTableSuffixes,
0062: stripColumnPrefixes, stripColumnSuffixes;
0063: private boolean supportsGeneratedKeys, supportsSavepoints,
0064: strictClassTableMatching = false,
0065: strictMethodColumnMatching = true;
0066: private int storesCase = 0;
0067:
0068: static MetaData getMetaData(Connection connection)
0069: throws SQLException, JPersistException {
0070: String databaseUrl = connection.getMetaData().getURL();
0071: MetaData metaData = (MetaData) metaDataMap.get(databaseUrl);
0072:
0073: if (metaData != null)
0074: return metaData;
0075:
0076: return loadMetaData(connection);
0077: }
0078:
0079: static synchronized MetaData loadMetaData(Connection connection)
0080: throws SQLException, JPersistException {
0081: String databaseUrl = connection.getMetaData().getURL();
0082: MetaData metaData = (MetaData) metaDataMap.get(databaseUrl);
0083:
0084: if (metaData == null) {
0085: metaData = new MetaData();
0086: metaDataMap.put(databaseUrl, metaData);
0087: DatabaseMetaData dbMetaData = connection.getMetaData();
0088:
0089: try {
0090: logger.finer("database product name = "
0091: + dbMetaData.getDatabaseProductName());
0092: logger.finer("database product version = "
0093: + dbMetaData.getDatabaseProductVersion());
0094: logger.finer("database version = "
0095: + dbMetaData.getDatabaseMajorVersion() + "."
0096: + dbMetaData.getDatabaseMinorVersion());
0097: logger.finer("JDBC driver version = "
0098: + dbMetaData.getDriverMajorVersion() + "."
0099: + +dbMetaData.getDriverMinorVersion());
0100: logger.finer("user name = " + dbMetaData.getUserName());
0101: logger.finer("supports transactions = "
0102: + dbMetaData.supportsTransactions());
0103: logger.finer("supports multiple transactions = "
0104: + dbMetaData.supportsMultipleTransactions());
0105: logger
0106: .finer("supports transaction isolation level TRANSACTION_READ_COMMITTED = "
0107: + dbMetaData
0108: .supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_COMMITTED));
0109: logger
0110: .finer("supports transaction isolation level TRANSACTION_READ_UNCOMMITTED = "
0111: + dbMetaData
0112: .supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_UNCOMMITTED));
0113: logger
0114: .finer("supports transaction isolation level TRANSACTION_REPEATABLE_READ = "
0115: + dbMetaData
0116: .supportsTransactionIsolationLevel(Connection.TRANSACTION_REPEATABLE_READ));
0117: logger
0118: .finer("supports transaction isolation level TRANSACTION_SERIALIZABLE = "
0119: + dbMetaData
0120: .supportsTransactionIsolationLevel(Connection.TRANSACTION_SERIALIZABLE));
0121: logger
0122: .finer("supports result set TYPE_FORWARD_ONLY = "
0123: + dbMetaData
0124: .supportsResultSetType(ResultSet.TYPE_FORWARD_ONLY));
0125: logger
0126: .finer("supports result set TYPE_SCROLL_INSENSITIVE = "
0127: + dbMetaData
0128: .supportsResultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE));
0129: logger
0130: .finer("supports result set TYPE_SCROLL_SENSITIVE = "
0131: + dbMetaData
0132: .supportsResultSetType(ResultSet.TYPE_SCROLL_SENSITIVE));
0133: logger
0134: .finer("supports result set holdability CLOSE_CURSORS_AT_COMMIT = "
0135: + dbMetaData
0136: .supportsResultSetHoldability(ResultSet.CLOSE_CURSORS_AT_COMMIT));
0137: logger
0138: .finer("supports result set holdability HOLD_CURSORS_OVER_COMMIT = "
0139: + dbMetaData
0140: .supportsResultSetHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT));
0141: logger.finer("stores lower case identifiers = "
0142: + dbMetaData.storesLowerCaseIdentifiers());
0143: logger
0144: .finer("stores lower case quoted identifiers = "
0145: + dbMetaData
0146: .storesLowerCaseQuotedIdentifiers());
0147: logger.finer("stores upper case identifiers = "
0148: + dbMetaData.storesUpperCaseIdentifiers());
0149: logger
0150: .finer("stores upper case quoted identifiers = "
0151: + dbMetaData
0152: .storesUpperCaseQuotedIdentifiers());
0153: logger.finer("stores mixed case identifiers = "
0154: + dbMetaData.storesMixedCaseIdentifiers());
0155: logger
0156: .finer("stores mixed case quoted identifiers = "
0157: + dbMetaData
0158: .storesMixedCaseQuotedIdentifiers());
0159: } catch (Exception e) {
0160: logger.log(Level.WARNING, e.getMessage(), e);
0161: }
0162:
0163: logger.finer("Catalog term = "
0164: + dbMetaData.getCatalogTerm());
0165: logger.finer("Schema term = " + dbMetaData.getSchemaTerm());
0166:
0167: try {
0168: if (dbMetaData.supportsSavepoints()) {
0169: Savepoint savepoint = connection.setSavepoint();
0170: connection.releaseSavepoint(savepoint);
0171: }
0172:
0173: metaData.supportsSavepoints = dbMetaData
0174: .supportsSavepoints();
0175: } catch (Exception e) {
0176: logger
0177: .log(
0178: Level.FINE,
0179: "The database metadata reports it supports savepoints, but the database fails with setSavepoint(). Therefore, the database probably does not support savepoints",
0180: e);
0181: }
0182:
0183: logger.finer("supports savepoints = "
0184: + metaData.supportsSavepoints);
0185:
0186: if (dbMetaData.storesLowerCaseIdentifiers()
0187: || dbMetaData.storesLowerCaseQuotedIdentifiers())
0188: metaData.storesCase = STORES_LOWERCASE;
0189: else if (dbMetaData.storesUpperCaseIdentifiers()
0190: || dbMetaData.storesUpperCaseQuotedIdentifiers())
0191: metaData.storesCase = STORES_UPPERCASE;
0192: else if (dbMetaData.storesMixedCaseIdentifiers()
0193: || dbMetaData.storesMixedCaseQuotedIdentifiers())
0194: metaData.storesCase = STORES_MIXEDCASE;
0195:
0196: logger.finer("maximum concurrent connections = "
0197: + dbMetaData.getMaxConnections());
0198:
0199: metaData.identifierQuoteString = dbMetaData
0200: .getIdentifierQuoteString();
0201:
0202: if (metaData.identifierQuoteString.equals(" "))
0203: metaData.identifierQuoteString = "";
0204:
0205: logger.finer("identifier quote string = '"
0206: + metaData.identifierQuoteString + "'");
0207: logger.finer("supports generated keys = "
0208: + (metaData.supportsGeneratedKeys = dbMetaData
0209: .supportsGetGeneratedKeys()));
0210: logger.finer("search string escape = "
0211: + (metaData.searchStringEscape = dbMetaData
0212: .getSearchStringEscape()));
0213: logger.finer("database url = "
0214: + (metaData.databaseUrl = databaseUrl));
0215: }
0216:
0217: return metaData;
0218: }
0219:
0220: public String getIdentifierQuoteString() {
0221: return identifierQuoteString;
0222: }
0223:
0224: public String getSearchStringEscape() {
0225: return searchStringEscape;
0226: }
0227:
0228: public String getDatabaseUrl() {
0229: return databaseUrl;
0230: }
0231:
0232: public int getStoresCase() {
0233: return storesCase;
0234: }
0235:
0236: public boolean supportsGeneratedKeys() {
0237: return supportsGeneratedKeys;
0238: }
0239:
0240: public boolean supportsSavepoints() {
0241: return supportsSavepoints;
0242: }
0243:
0244: public void setTableTypes(String[] tableTypes) {
0245: this .tableTypes = tableTypes;
0246: }
0247:
0248: public void setStrictMethodColumnMatching(boolean trueFalse) {
0249: strictMethodColumnMatching = trueFalse;
0250: }
0251:
0252: public void setStrictClassTableMatching(boolean trueFalse) {
0253: strictClassTableMatching = trueFalse;
0254: }
0255:
0256: /**
0257: * Set of prefixes to be stripped from table names to help in class to table name matching.
0258: * @param stripTablePrefixes a set of prefixes (Strings)
0259: */
0260: public void setTablePrefixesToStrip(Set stripTablePrefixes) {
0261: this .stripTablePrefixes = stripTablePrefixes;
0262: }
0263:
0264: /**
0265: * Set of suffixes to be stripped from table names to help in class to table name matching.
0266: * @param stripTableSuffixes a set of suffixes (Strings)
0267: */
0268: public void setTableSuffixesToStrip(Set stripTableSuffixes) {
0269: this .stripTableSuffixes = stripTableSuffixes;
0270: }
0271:
0272: /**
0273: * Set of prefixes to be stripped from column names to help in method to column name matching.
0274: * @param stripColumnPrefixes a set of prefixes (Strings)
0275: */
0276: public void setColumnPrefixesToStrip(Set stripColumnPrefixes) {
0277: this .stripColumnPrefixes = stripColumnPrefixes;
0278: }
0279:
0280: /**
0281: * Set of suffixes to be stripped from column names to help in method to column name matching.
0282: * @param stripColumnSuffixes a set of suffixes (Strings)
0283: */
0284: public void setColumnSuffixesToStrip(Set stripColumnSuffixes) {
0285: this .stripColumnSuffixes = stripColumnSuffixes;
0286: }
0287:
0288: /*
0289: * Remove table from the MetaData cache. A non-harmful method. If called
0290: * by accident and the table is still needed, it will simply be reloaded.
0291: *
0292: * @param tableName name of the table to remove
0293: public void removeTableFromCache(String tableName) { tableCache.remove(normalizeName(tableName)); }
0294: */
0295:
0296: /**
0297: * Add a table mapping.
0298: *
0299: * @param searchTableName the name that should be matched
0300: * @param returnTableName the actual table name in the database
0301: */
0302: public synchronized void addTableNameMapping(
0303: String searchTableName, String returnTableName) {
0304: tableNameMapping.put(normalizeName(searchTableName),
0305: returnTableName);
0306: }
0307:
0308: public Table getTable(Connection connection,
0309: TableMapping tableMapper, ColumnMapping columnMapper,
0310: String catalogPattern, String schemaPattern,
0311: String tableName, Object object) throws SQLException,
0312: JPersistException {
0313: String searchName = normalizeName((catalogPattern != null ? catalogPattern
0314: + "."
0315: : "")
0316: + (schemaPattern != null ? schemaPattern + "." : "")
0317: + tableName);
0318: Table table = (Table) tableCache.get(searchName);
0319:
0320: // table already loaded
0321: if (table != null) {
0322: if (table instanceof NullTable)
0323: return null;
0324: } else
0325: table = tableSearch(connection, tableMapper,
0326: catalogPattern, schemaPattern, tableName, object);
0327:
0328: if (table != null && !table.isTableDetailLoaded())
0329: loadTableDetail(connection, columnMapper, table);
0330:
0331: return table;
0332: }
0333:
0334: /* load all possiblities and scan for an exact match, or a single match */
0335: synchronized Table tableSearch(Connection connection,
0336: TableMapping tableMapper, String catalogPattern,
0337: String schemaPattern, String tableName, Object object)
0338: throws SQLException, JPersistException {
0339: String searchName = normalizeName((catalogPattern != null ? catalogPattern
0340: + "."
0341: : "")
0342: + (schemaPattern != null ? schemaPattern + "." : "")
0343: + tableName);
0344: Table table = (Table) tableCache.get(searchName);
0345:
0346: // table already loaded
0347: if (table != null) {
0348: if (table instanceof NullTable)
0349: return null;
0350:
0351: return table;
0352: }
0353:
0354: String name = null, catalog = null, schema = null;
0355:
0356: if (logger.isLoggable(Level.FINER))
0357: logger.finer("Searching for table " + tableName);
0358:
0359: if (catalogPattern != null)
0360: catalog = storesCase == 0 || storesCase == STORES_UPPERCASE ? catalogPattern
0361: .toUpperCase()
0362: : catalogPattern.toLowerCase();
0363:
0364: if (schemaPattern != null)
0365: schema = storesCase == 0 || storesCase == STORES_UPPERCASE ? schemaPattern
0366: .toUpperCase()
0367: : schemaPattern.toLowerCase();
0368:
0369: // search and load TabbleMapping defined
0370: if (object != null
0371: && object instanceof TableMapping
0372: && (name = ((TableMapping) object)
0373: .getDatabaseTableName(tableName.toLowerCase())) != null) {
0374: name = storesCase == 0 || storesCase == STORES_UPPERCASE ? name
0375: .toUpperCase()
0376: : name.toLowerCase();
0377:
0378: loadTables(connection, catalog, schema, name);
0379: table = tableScan(catalog, schema, name, true);
0380: }
0381:
0382: // search and load global TabbleMapping defined
0383: if (table == null
0384: && tableMapper != null
0385: && (name = tableMapper.getDatabaseTableName(tableName
0386: .toLowerCase())) != null) {
0387: name = storesCase == 0 || storesCase == STORES_UPPERCASE ? name
0388: .toUpperCase()
0389: : name.toLowerCase();
0390:
0391: loadTables(connection, catalog, schema, name);
0392: table = tableScan(catalog, schema, name, true);
0393: }
0394:
0395: // search and load table hints
0396: if (table == null
0397: && (name = (String) tableNameMapping
0398: .get(normalizeName(tableName))) != null) {
0399: loadTables(connection, catalog, schema, name);
0400: table = tableScan(catalog, schema, name, true);
0401: }
0402:
0403: // search and load for tableName
0404: if (table == null) {
0405: loadTables(connection, catalogPattern, schemaPattern,
0406: name = tableName);
0407: table = tableScan(catalog, schema, name, true);
0408: }
0409:
0410: // search and load plurals
0411: if (table == null) {
0412: String[] plurals = SimpleInflector.pluralize(tableName);
0413:
0414: for (int i = 0; table == null && i < plurals.length; i++) {
0415: loadTables(connection, catalog, schema,
0416: name = plurals[i]);
0417: table = tableScan(catalog, schema, name, true);
0418: }
0419: }
0420:
0421: // search and load for TABLENAME
0422: if (table == null) {
0423: name = storesCase == 0 || storesCase == STORES_UPPERCASE ? tableName
0424: .toUpperCase()
0425: : tableName.toLowerCase();
0426: loadTables(connection, catalog, schema, name);
0427: table = tableScan(catalog, schema, name, true);
0428: }
0429:
0430: // search and load for TABLE_NAME
0431: if (table == null) {
0432: name = storesCase == 0 || storesCase == STORES_UPPERCASE ? StringUtils
0433: .camelCaseToUpperCaseUnderline(tableName)
0434: : StringUtils
0435: .camelCaseToLowerCaseUnderline(tableName);
0436: loadTables(connection, catalog, schema, name);
0437: table = tableScan(catalog, schema, name, true);
0438: }
0439:
0440: // search and load for opposite of TABLE_NAME
0441: if (table == null) {
0442: if (catalogPattern != null)
0443: catalog = storesCase == 0
0444: || storesCase == STORES_UPPERCASE ? catalogPattern
0445: .toLowerCase()
0446: : catalogPattern.toUpperCase();
0447:
0448: if (schemaPattern != null)
0449: schema = storesCase == 0
0450: || storesCase == STORES_UPPERCASE ? schemaPattern
0451: .toLowerCase()
0452: : schemaPattern.toUpperCase();
0453:
0454: // search and load for tablename
0455: name = storesCase == 0 || storesCase == STORES_UPPERCASE ? tableName
0456: .toLowerCase()
0457: : tableName.toUpperCase();
0458: loadTables(connection, catalog, schema, name);
0459: table = tableScan(catalog, schema, name, true);
0460:
0461: // search and load for table_name
0462: if (table == null) {
0463: name = storesCase == 0
0464: || storesCase == STORES_UPPERCASE ? StringUtils
0465: .camelCaseToLowerCaseUnderline(tableName)
0466: : StringUtils
0467: .camelCaseToUpperCaseUnderline(tableName);
0468: loadTables(connection, catalog, schema, name);
0469: table = tableScan(catalog, schema, name, true);
0470: }
0471: }
0472:
0473: if (table == null && !strictClassTableMatching)
0474: table = tableScan(catalog, schema, name, false);
0475:
0476: if (table != null)
0477: tableCache.put(searchName, table);
0478: else {
0479: tableCache.put(searchName, new NullTable());
0480:
0481: if (logger.isLoggable(Level.FINER))
0482: logger.finer("Table " + tableName + " not found!");
0483: }
0484:
0485: return table;
0486: }
0487:
0488: int loadTables(Connection connection, String catalogPattern,
0489: String schemaPattern, String tablePattern)
0490: throws SQLException, JPersistException {
0491: DatabaseMetaData metaData = connection.getMetaData();
0492: ResultSet resultSet = metaData.getTables(catalogPattern,
0493: schemaPattern, "%" + tablePattern + "%", tableTypes);
0494: int tableCount = 0;
0495:
0496: while (resultSet.next()) {
0497: Table table = new Table(resultSet.getString("table_name"),
0498: resultSet.getString("table_cat"), resultSet
0499: .getString("table_schem"), resultSet
0500: .getString("table_type"));
0501:
0502: String searchName = normalizeName((table.getCatalogName() != null ? table
0503: .getCatalogName()
0504: + "."
0505: : "")
0506: + (table.getSchemaName() != null ? table
0507: .getSchemaName()
0508: + "." : "") + table.getTableName());
0509:
0510: tables.put(searchName, table);
0511:
0512: if (logger.isLoggable(Level.FINE))
0513: logger.finer("Found table: " + table);
0514:
0515: tableCount++;
0516: }
0517:
0518: resultSet.close();
0519:
0520: return tableCount;
0521: }
0522:
0523: Table tableScan(String catalogName, String schemaName,
0524: String tableName, boolean strictMatch)
0525: throws JPersistException {
0526: String searchName = normalizeName((catalogName != null ? catalogName
0527: + "."
0528: : "")
0529: + (schemaName != null ? schemaName + "." : "")
0530: + tableName);
0531: Table table = (Table) tables.get(searchName);
0532:
0533: if (table != null)
0534: return table;
0535:
0536: tableName = normalizeName(tableName);
0537:
0538: catalogName = catalogName != null ? catalogName.toLowerCase()
0539: : "";
0540: schemaName = schemaName != null ? schemaName.toLowerCase() : "";
0541:
0542: Iterator it = tables.entrySet().iterator();
0543:
0544: while (it.hasNext()) {
0545: Table itTable = (Table) ((Map.Entry) it.next()).getValue();
0546: String matchName1 = normalizeName(itTable.getTableName()), matchName2 = null, itTableCatalog = itTable
0547: .getCatalogName() != null ? itTable
0548: .getCatalogName().toLowerCase() : "", itTableSchema = itTable
0549: .getSchemaName() != null ? itTable.getSchemaName()
0550: .toLowerCase() : "";
0551:
0552: if (stripTablePrefixes != null
0553: || stripTableSuffixes != null)
0554: matchName2 = normalizeName(stripName(itTable
0555: .getTableName(), stripTablePrefixes,
0556: stripTableSuffixes));
0557:
0558: if ((tableName.equals(matchName1) || (matchName2 != null && tableName
0559: .equals(matchName2)))
0560: && (catalogName.length() == 0 || catalogName
0561: .equals(itTableCatalog))
0562: && (schemaName.length() == 0 || schemaName
0563: .equals(itTableSchema))) {
0564: if (table == null)
0565: table = itTable;
0566: else
0567: throw new JPersistException(
0568: "Scanning produces multiple possible tables for table name '"
0569: + tableName
0570: + "'\n"
0571: + "To obtain an exact match you can further qualify the naming, or add catalog/schema qualifiers,\n or define prefix/suffix stripping, or table name mapping.");
0572: }
0573: }
0574:
0575: if (!strictMatch && table == null) {
0576: it = tables.entrySet().iterator();
0577:
0578: while (it.hasNext()) {
0579: Table itTable = (Table) ((Map.Entry) it.next())
0580: .getValue();
0581: String matchName1 = normalizeName(itTable
0582: .getTableName()), itTableCatalog = itTable
0583: .getCatalogName() != null ? itTable
0584: .getCatalogName().toLowerCase() : "", itTableSchema = itTable
0585: .getSchemaName() != null ? itTable
0586: .getSchemaName().toLowerCase() : "";
0587:
0588: if (matchName1.indexOf(tableName) != -1
0589: && (catalogName.length() == 0 || catalogName
0590: .equals(itTableCatalog))
0591: && (schemaName.length() == 0 || schemaName
0592: .equals(itTableSchema))) {
0593: if (table == null || matchName1.equals(tableName))
0594: table = itTable;
0595: else
0596: throw new JPersistException(
0597: "Scanning produces multiple possible tables for table name '"
0598: + tableName
0599: + "'\n"
0600: + "To obtain an exact match you can further qualify the naming, or add catalog/schema qualifiers,\n or define prefix/suffix stripping, or table name mapping.");
0601: }
0602: }
0603: }
0604:
0605: return table;
0606: }
0607:
0608: void loadTableDetail(Connection connection,
0609: ColumnMapping columnMapper, Table table)
0610: throws SQLException, JPersistException {
0611: Statement statement = connection.createStatement();
0612: DatabaseMetaData metaData = connection.getMetaData();
0613: ResultSet resultSet = metaData.getPrimaryKeys(table
0614: .getCatalogName(), table.getSchemaName(), table
0615: .getTableName());
0616:
0617: Map primaryKeys = new HashMap();
0618:
0619: while (resultSet.next()) {
0620: String name = resultSet.getString("column_name");
0621: primaryKeys.put(name, table.new Key(name, resultSet
0622: .getString("table_name"), resultSet
0623: .getString("table_cat"), resultSet
0624: .getString("table_schem")));
0625: }
0626:
0627: table.setPrimaryKeys(primaryKeys);
0628:
0629: resultSet.close();
0630:
0631: resultSet = metaData.getBestRowIdentifier(table
0632: .getCatalogName(), table.getSchemaName(), table
0633: .getTableName(), DatabaseMetaData.bestRowSession, true);
0634:
0635: Set bestRowIds = new HashSet();
0636:
0637: while (resultSet.next())
0638: bestRowIds.add(resultSet.getString("column_name"));
0639:
0640: table.setBestRowIds(bestRowIds);
0641:
0642: resultSet.close();
0643:
0644: resultSet = metaData.getImportedKeys(table.getCatalogName(),
0645: table.getSchemaName(), table.getTableName());
0646:
0647: Map importedKeys = new HashMap();
0648:
0649: while (resultSet.next()) {
0650: String name = resultSet.getString("fkcolumn_name");
0651: importedKeys.put(name, table.new Key(name, resultSet
0652: .getString("fktable_cat"), resultSet
0653: .getString("fktable_schem"), resultSet
0654: .getString("fktable_name"), resultSet
0655: .getString("pkcolumn_name"), resultSet
0656: .getString("pktable_cat"), resultSet
0657: .getString("pktable_schem"), resultSet
0658: .getString("pktable_name")));
0659: }
0660:
0661: table.setImportedKeys(importedKeys);
0662:
0663: resultSet.close();
0664:
0665: resultSet = metaData.getExportedKeys(table.getCatalogName(),
0666: table.getSchemaName(), table.getTableName());
0667:
0668: Map exportedKeys = new HashMap();
0669:
0670: while (resultSet.next()) {
0671: String name = resultSet.getString("pkcolumn_name");
0672: exportedKeys.put(name, table.new Key(name, resultSet
0673: .getString("pktable_cat"), resultSet
0674: .getString("pktable_schem"), resultSet
0675: .getString("pktable_name"), resultSet
0676: .getString("fkcolumn_name"), resultSet
0677: .getString("fktable_cat"), resultSet
0678: .getString("fktable_schem"), resultSet
0679: .getString("fktable_name")));
0680: }
0681:
0682: table.setExportedKeys(exportedKeys);
0683:
0684: resultSet.close();
0685:
0686: resultSet = metaData.getColumns(table.getCatalogName(), table
0687: .getSchemaName(), table.getTableName(), null);
0688:
0689: Map columns = new HashMap();
0690:
0691: while (resultSet.next()) {
0692: String columnName = resultSet.getString("COLUMN_NAME");
0693:
0694: Table.Column column = table.new Column(columnName,
0695: resultSet.getString("TYPE_NAME"), resultSet
0696: .getInt("DATA_TYPE"), resultSet
0697: .getInt("COLUMN_SIZE"), resultSet
0698: .getInt("DECIMAL_DIGITS"), resultSet
0699: .getInt("NUM_PREC_RADIX"), resultSet
0700: .getString("IS_NULLABLE").equalsIgnoreCase(
0701: "Yes") ? true : false, primaryKeys
0702: .get(columnName) != null, bestRowIds
0703: .contains(columnName));
0704:
0705: columns.put(normalizeName(columnName), column);
0706: }
0707:
0708: table.setColumns(columns);
0709:
0710: resultSet.close();
0711:
0712: if ((resultSet = statement.executeQuery("select * from "
0713: + table.getTableName() + " where 1 = 0")) != null) {
0714: ResultSetMetaData resultSetMetaData = resultSet
0715: .getMetaData();
0716:
0717: if (resultSetMetaData != null)
0718: for (int i = 0; i < resultSetMetaData.getColumnCount(); i++) {
0719: Table.Column column = table.getColumn(columnMapper,
0720: normalizeName(resultSetMetaData
0721: .getColumnName(i + 1)), null);
0722:
0723: if (column != null) {
0724: column.setAdditionalInfo(resultSetMetaData
0725: .getColumnLabel(i + 1),
0726: resultSetMetaData
0727: .getColumnClassName(i + 1),
0728: resultSetMetaData
0729: .isAutoIncrement(i + 1),
0730: resultSetMetaData.isReadOnly(i + 1),
0731: resultSetMetaData.isSearchable(i + 1));
0732:
0733: if (column.isAutoIncrement())
0734: table.setGeneratedKey(column
0735: .getColumnName());
0736: }
0737: }
0738:
0739: resultSet.close();
0740: }
0741:
0742: table.setTableDetailLoaded(true);
0743: }
0744:
0745: public class Table {
0746: private String tableName, catalogName, schemaName, type,
0747: generatedKey;
0748: private Map primaryKeys, exportedKeys, importedKeys;
0749: private Map columns, columnNameMapping = new HashMap();
0750: private boolean isTableDetailLoaded;
0751: private Set bestRowIds;
0752:
0753: Table(String tableName, String catalogName, String schemaName,
0754: String type) {
0755: this .tableName = tableName;
0756: this .catalogName = catalogName;
0757: this .schemaName = schemaName;
0758: this .type = type;
0759: }
0760:
0761: public String getTableName() {
0762: return tableName;
0763: }
0764:
0765: public String getCatalogName() {
0766: return catalogName;
0767: }
0768:
0769: public String getSchemaName() {
0770: return schemaName;
0771: }
0772:
0773: public String getType() {
0774: return type;
0775: }
0776:
0777: public String toString() {
0778: return "catalog = " + catalogName + ", schema = "
0779: + schemaName + ", table = " + tableName;
0780: }
0781:
0782: boolean isTableDetailLoaded() {
0783: return isTableDetailLoaded;
0784: }
0785:
0786: void setTableDetailLoaded(boolean isTableDetailLoaded) {
0787: this .isTableDetailLoaded = isTableDetailLoaded;
0788: }
0789:
0790: public Set getBestRowIds() {
0791: return bestRowIds;
0792: }
0793:
0794: void setBestRowIds(Set bestRowIds) {
0795: this .bestRowIds = bestRowIds;
0796: }
0797:
0798: public String getGeneratedKey() {
0799: return generatedKey;
0800: }
0801:
0802: void setGeneratedKey(String generatedKey) {
0803: this .generatedKey = generatedKey;
0804: }
0805:
0806: public String getPossibleGeneratedKey() {
0807: if (generatedKey != null)
0808: return generatedKey;
0809:
0810: Column lastColumnMatch = null;
0811:
0812: for (Iterator it = primaryKeys.keySet().iterator(); it
0813: .hasNext();) {
0814: String key = (String) it.next();
0815: Column column = (Column) columns
0816: .get(normalizeName(key));
0817:
0818: if (importedKeys.get(key) == null)
0819: lastColumnMatch = column;
0820: }
0821:
0822: if (lastColumnMatch != null)
0823: return lastColumnMatch.getColumnName();
0824:
0825: return null;
0826: }
0827:
0828: public Map getPrimaryKeys() {
0829: return primaryKeys;
0830: }
0831:
0832: void setPrimaryKeys(Map primaryKeys) {
0833: this .primaryKeys = primaryKeys;
0834: }
0835:
0836: public Map getExportedKeys() {
0837: return exportedKeys;
0838: }
0839:
0840: void setExportedKeys(Map exportedKeys) {
0841: this .exportedKeys = exportedKeys;
0842: }
0843:
0844: public Map getImportedKeys() {
0845: return importedKeys;
0846: }
0847:
0848: void setImportedKeys(Map importedKeys) {
0849: this .importedKeys = importedKeys;
0850: }
0851:
0852: public Map getColumns() {
0853: return columns;
0854: }
0855:
0856: void setColumns(Map columns) {
0857: this .columns = columns;
0858: }
0859:
0860: public synchronized void addColumnNameMapping(
0861: String searchColumnName, String returnColumnName) {
0862: columnNameMapping.put(normalizeName(searchColumnName),
0863: returnColumnName);
0864: }
0865:
0866: public Column getColumn(ColumnMapping columnMapper,
0867: String columnName, Object object)
0868: throws JPersistException {
0869: Column column = (Column) columns
0870: .get(normalizeName(columnName));
0871:
0872: if (column != null)
0873: return column;
0874:
0875: return columnSearch(columnMapper, columnName, object);
0876: }
0877:
0878: synchronized Column columnSearch(ColumnMapping columnMapper,
0879: String columnName, Object object)
0880: throws JPersistException {
0881: String normalizedColumnName = normalizeName(columnName), name = null;
0882: Column column = (Column) columns.get(normalizedColumnName);
0883:
0884: if (column != null)
0885: return column;
0886:
0887: if (object != null
0888: && object instanceof ColumnMapping
0889: && (name = ((ColumnMapping) object)
0890: .getTableColumnName(columnName
0891: .toLowerCase())) != null)
0892: column = (Column) columns.get(normalizeName(name));
0893: else if (columnMapper != null
0894: && (name = columnMapper
0895: .getTableColumnName(columnName
0896: .toLowerCase())) != null)
0897: column = (Column) columns.get(normalizeName(name));
0898: else if ((name = (String) columnNameMapping
0899: .get(normalizedColumnName)) != null)
0900: column = (Column) columns.get(normalizeName(name));
0901: else if ((stripColumnPrefixes != null || stripColumnSuffixes != null)) {
0902: Iterator it = columns.entrySet().iterator();
0903:
0904: while (it.hasNext()) {
0905: Column itColumn = (Column) ((Map.Entry) it.next())
0906: .getValue();
0907: String strippedName = normalizeName(stripName(
0908: itColumn.getColumnName(),
0909: stripColumnPrefixes, stripColumnSuffixes));
0910:
0911: if (columnName.equals(strippedName)) {
0912: if (column == null)
0913: column = itColumn;
0914: else
0915: throw new JPersistException(
0916: "Scanning produces multiple possible columns for column name '"
0917: + columnName
0918: + "' found in table '"
0919: + getTableName()
0920: + "'\n"
0921: + "To obtain an exact match you can further qualify the naming, or define prefix/suffix stripping, or column name mapping.");
0922: }
0923: }
0924: }
0925:
0926: if (!strictMethodColumnMatching && column == null) {
0927: Iterator it = columns.keySet().iterator();
0928:
0929: while (it.hasNext()) {
0930: name = (String) it.next();
0931:
0932: if (name.indexOf(normalizedColumnName) != -1) {
0933: if (column == null
0934: || name.equals(normalizedColumnName))
0935: column = (Column) columns.get(name);
0936: else
0937: throw new JPersistException(
0938: "Scanning produces multiple possible columns for column name '"
0939: + columnName
0940: + "' found in table '"
0941: + getTableName()
0942: + "'\n"
0943: + "To obtain an exact match you can further qualify the naming, or define prefix/suffix stripping, or column name mapping.");
0944: }
0945: }
0946: }
0947:
0948: if (column != null)
0949: columns.put(normalizedColumnName, column);
0950:
0951: if (column == null && logger.isLoggable(Level.FINER)
0952: && !columnName.equals("dbAssociation"))
0953: logger.finer("Column " + columnName + " not matched!");
0954:
0955: return column;
0956: }
0957:
0958: public class Column {
0959: private String columnName, columnLabel, typeName,
0960: className;
0961: private int dataType, columnSize, decimalDigits, radix;
0962: private boolean isNullable, isPrimaryKey, isRowId,
0963: isAutoIncrement, isReadOnly, isSearchable;
0964:
0965: Column(String columnName, String typeName, int dataType,
0966: int columnSize, int decimalDigits, int radix,
0967: boolean isNullable, boolean isPrimaryKey,
0968: boolean isRowId) {
0969: this .columnName = columnName;
0970: this .typeName = typeName;
0971: this .dataType = dataType;
0972: this .columnSize = columnSize;
0973: this .decimalDigits = decimalDigits;
0974: this .radix = radix;
0975: this .isNullable = isNullable;
0976: this .isPrimaryKey = isPrimaryKey;
0977: this .isRowId = isRowId;
0978: }
0979:
0980: void setAdditionalInfo(String columnLabel,
0981: String className, boolean isAutoIncrement,
0982: boolean isReadOnly, boolean isSearchable) {
0983: this .columnLabel = columnLabel;
0984: this .className = className;
0985: this .isAutoIncrement = isAutoIncrement;
0986: //this.isReadOnly = isReadOnly;
0987: this .isSearchable = isSearchable;
0988: }
0989:
0990: public String getColumnName() {
0991: return columnName;
0992: }
0993:
0994: public String getColumnLabel() {
0995: return columnLabel;
0996: }
0997:
0998: public String getClassName() {
0999: return className;
1000: }
1001:
1002: public String getTypeName() {
1003: return typeName;
1004: }
1005:
1006: public int getDataType() {
1007: return dataType;
1008: }
1009:
1010: public int getColumnSize() {
1011: return columnSize;
1012: }
1013:
1014: public int getDecimalDigits() {
1015: return decimalDigits;
1016: }
1017:
1018: public int getRadix() {
1019: return radix;
1020: }
1021:
1022: public boolean isNullable() {
1023: return isNullable;
1024: }
1025:
1026: public boolean isPrimaryKey() {
1027: return isPrimaryKey;
1028: }
1029:
1030: public boolean isRowId() {
1031: return isRowId;
1032: }
1033:
1034: public boolean isAutoIncrement() {
1035: return isAutoIncrement;
1036: }
1037:
1038: public boolean isReadOnly() {
1039: return isReadOnly || isAutoIncrement;
1040: }
1041:
1042: public boolean isSearchable() {
1043: return isSearchable;
1044: }
1045: }
1046:
1047: public class Key {
1048: private String localColumnName, localTableCatalog,
1049: localTableSchema, localTableName,
1050: foreignColumnName, foreignTableCatalog,
1051: foreignTableSchema, foreignTableName;
1052:
1053: Key(String localColumnName, String localTableCatalog,
1054: String localTableSchema, String localTableName) {
1055: this .localColumnName = localColumnName;
1056: this .localTableCatalog = localTableCatalog;
1057: this .localTableSchema = localTableSchema;
1058: this .localTableName = localTableName;
1059: }
1060:
1061: Key(String localColumnName, String localTableCatalog,
1062: String localTableSchema, String localTableName,
1063: String foreignColumnName,
1064: String foreignTableCatalog,
1065: String foreignTableSchema, String foreignTableName) {
1066: this .localColumnName = localColumnName;
1067: this .localTableCatalog = localTableCatalog;
1068: this .localTableSchema = localTableSchema;
1069: this .localTableName = localTableName;
1070: this .foreignColumnName = foreignColumnName;
1071: this .foreignTableCatalog = foreignTableCatalog;
1072: this .foreignTableSchema = foreignTableSchema;
1073: this .foreignTableName = foreignTableName;
1074: }
1075:
1076: public String getForeignColumnName() {
1077: return foreignColumnName;
1078: }
1079:
1080: public String getForeignTableCatalog() {
1081: return foreignTableCatalog;
1082: }
1083:
1084: public String getForeignTableSchema() {
1085: return foreignTableSchema;
1086: }
1087:
1088: public String getForeignTableName() {
1089: return foreignTableName;
1090: }
1091:
1092: public String getLocalColumnName() {
1093: return localColumnName;
1094: }
1095:
1096: public String getLocalTableCatalog() {
1097: return localTableCatalog;
1098: }
1099:
1100: public String getLocalTableSchema() {
1101: return localTableSchema;
1102: }
1103:
1104: public String getLocalTableName() {
1105: return localTableName;
1106: }
1107: }
1108: }
1109:
1110: class NullTable extends Table {
1111: NullTable() {
1112: super (null, null, null, null);
1113: }
1114: }
1115:
1116: static String stripName(String name, Set prefixes, Set suffixes) {
1117: if (prefixes != null)
1118: for (Iterator it = prefixes.iterator(); it.hasNext();) {
1119: String prefix = (String) it.next();
1120:
1121: if (name.startsWith(prefix)) {
1122: name = name.substring(prefix.length());
1123: break;
1124: }
1125: }
1126:
1127: if (suffixes != null)
1128: for (Iterator it = suffixes.iterator(); it.hasNext();) {
1129: String suffix = (String) it.next();
1130:
1131: if (name.endsWith(suffix)) {
1132: name = name.substring(0, name.length()
1133: - suffix.length());
1134: break;
1135: }
1136: }
1137:
1138: return name;
1139: }
1140:
1141: static String normalizeName(String name) {
1142: return name.replaceAll("_", "").toLowerCase();
1143: }
1144: }
|