| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.netbeans.modules.db.metadata.model.jdbc; |
| |
| import java.sql.DatabaseMetaData; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.util.*; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import org.netbeans.modules.db.metadata.model.MetadataUtilities; |
| import org.netbeans.modules.db.metadata.model.api.Index.IndexType; |
| import org.netbeans.modules.db.metadata.model.api.*; |
| import org.netbeans.modules.db.metadata.model.spi.TableImplementation; |
| import org.openide.util.NbBundle; |
| |
| /** |
| * |
| * @author Andrei Badea |
| */ |
| public class JDBCTable extends TableImplementation { |
| |
| private static final Logger LOGGER = Logger.getLogger(JDBCTable.class.getName()); |
| |
| private final JDBCSchema jdbcSchema; |
| private final String name; |
| private final boolean system; |
| |
| private Map<String, Column> columns; |
| private Map<String, Index> indexes; |
| private Map<String, ForeignKey> foreignKeys; |
| |
| private PrimaryKey primaryKey; |
| |
| // Need a marker because there may be *no* primary key, and we don't want |
| // to hit the database over and over again when there is no primary key |
| private boolean primaryKeyInitialized = false; |
| private static final String SQL_EXCEPTION_NOT_YET_IMPLEMENTED = "not yet implemented"; |
| |
| public JDBCTable(JDBCSchema jdbcSchema, String name, boolean system) { |
| this.jdbcSchema = jdbcSchema; |
| this.name = name; |
| this.system = system; |
| } |
| |
| @Override |
| public final Schema getParent() { |
| return jdbcSchema.getSchema(); |
| } |
| |
| @Override |
| public final String getName() { |
| return name; |
| } |
| |
| @Override |
| public final Collection<Column> getColumns() { |
| return initColumns().values(); |
| } |
| |
| @Override |
| public final Column getColumn(String name) { |
| return MetadataUtilities.find(name, initColumns()); |
| } |
| |
| @Override |
| public PrimaryKey getPrimaryKey() { |
| return initPrimaryKey(); |
| } |
| |
| @Override |
| public Index getIndex(String indexName) { |
| return MetadataUtilities.find(indexName, initIndexes()); |
| } |
| |
| @Override |
| public Collection<Index> getIndexes() { |
| return initIndexes().values(); |
| } |
| |
| @Override |
| public Collection<ForeignKey> getForeignKeys() { |
| return initForeignKeys().values(); |
| } |
| |
| @Override |
| public ForeignKey getForeignKeyByInternalName(String name) { |
| return MetadataUtilities.find(name, initForeignKeys()); |
| } |
| |
| @Override |
| public final void refresh() { |
| columns = null; |
| primaryKey = null; |
| primaryKeyInitialized = false; |
| } |
| |
| @Override |
| public boolean isSystem() { |
| return system; |
| } |
| |
| @Override |
| public String toString() { |
| return "JDBCTable[name='" + name + "']"; // NOI18N |
| } |
| |
| protected JDBCColumn createJDBCColumn(ResultSet rs) throws SQLException { |
| int position = 0; |
| JDBCValue jdbcValue; |
| if (isOdbc(rs)) { |
| jdbcValue = JDBCValue.createTableColumnValueODBC(rs, this.getTable()); |
| } else { |
| position = rs.getInt("ORDINAL_POSITION"); |
| jdbcValue = JDBCValue.createTableColumnValue(rs, this.getTable()); |
| } |
| return new JDBCColumn(this.getTable(), position, jdbcValue); |
| } |
| |
| /** Returns true if this table is under ODBC connection. In such a case |
| * some meta data like ORDINAL_POSITION or ASC_OR_DESC are not supported. */ |
| private boolean isOdbc(ResultSet rs) throws SQLException { |
| boolean odbc = jdbcSchema.getJDBCCatalog().getJDBCMetadata().getDmd().getURL().startsWith("jdbc:odbc"); //NOI18N |
| if (odbc) { |
| try { |
| rs.getInt("PRECISION"); |
| return true; |
| } catch (SQLException e) { |
| // ignore and return false at the end; Probably MS Access driver which supports standards |
| } |
| } |
| return false; |
| } |
| |
| protected JDBCPrimaryKey createJDBCPrimaryKey(String pkName, Collection<Column> pkcols) { |
| return new JDBCPrimaryKey(this.getTable(), pkName, pkcols); |
| } |
| |
| protected void createColumns() { |
| Map<String, Column> newColumns = new LinkedHashMap<String, Column>(); |
| try { |
| ResultSet rs = MetadataUtilities.getColumns( |
| jdbcSchema.getJDBCCatalog().getJDBCMetadata().getDmd(), |
| jdbcSchema.getJDBCCatalog().getName(), jdbcSchema.getName(), |
| name, "%"); // NOI18N |
| if (rs != null) { |
| try { |
| while (rs.next()) { |
| Column column = createJDBCColumn(rs).getColumn(); |
| newColumns.put(column.getName(), column); |
| LOGGER.log(Level.FINE, "Created column {0}", column); //NOI18N |
| } |
| } finally { |
| rs.close(); |
| } |
| } |
| } catch (SQLException e) { |
| filterSQLException(e); |
| } |
| columns = Collections.unmodifiableMap(newColumns); |
| } |
| |
| protected void createIndexes() { |
| Map<String, Index> newIndexes = new LinkedHashMap<String, Index>(); |
| try { |
| ResultSet rs = MetadataUtilities.getIndexInfo( |
| jdbcSchema.getJDBCCatalog().getJDBCMetadata().getDmd(), |
| jdbcSchema.getJDBCCatalog().getName(), jdbcSchema.getName(), |
| name, false, true); |
| if (rs != null) { |
| try { |
| JDBCIndex index = null; |
| String currentIndexName = null; |
| while (rs.next()) { |
| // Ignore Indices marked statistic |
| // explicit: TYPE == DatabaseMetaData or |
| // implicit: ORDINAL_POSITION == 0 |
| // @see java.sql.DatabaseMetaData#getIndexInfo |
| if (rs.getShort("TYPE") //NOI18N |
| == DatabaseMetaData.tableIndexStatistic |
| || rs.getInt("ORDINAL_POSITION") == 0) { //NOI18N |
| continue; |
| } |
| |
| String indexName = MetadataUtilities.trimmed(rs.getString("INDEX_NAME")); //NOI18N |
| if (index == null || !(currentIndexName.equals(indexName))) { |
| index = createJDBCIndex(indexName, rs); |
| LOGGER.log(Level.FINE, "Created index {0}", index); //NOI18N |
| |
| newIndexes.put(index.getName(), index.getIndex()); |
| currentIndexName = indexName; |
| } |
| |
| JDBCIndexColumn idx = createJDBCIndexColumn(index, rs); |
| if (idx == null) { |
| LOGGER.log(Level.INFO, "Cannot create index column for {0} from {1}", //NOI18N |
| new Object[]{indexName, rs}); |
| } else { |
| IndexColumn col = idx.getIndexColumn(); |
| index.addColumn(col); |
| LOGGER.log(Level.FINE, "Added column {0} to index {1}", //NOI18N |
| new Object[]{col.getName(), indexName}); |
| } |
| } |
| } finally { |
| rs.close(); |
| } |
| } |
| } catch (SQLException e) { |
| filterSQLException(e); |
| } |
| |
| indexes = Collections.unmodifiableMap(newIndexes); |
| } |
| |
| protected JDBCIndex createJDBCIndex(String name, ResultSet rs) { |
| IndexType type = IndexType.OTHER; |
| boolean isUnique = false; |
| try { |
| type = JDBCUtils.getIndexType(rs.getShort("TYPE")); |
| isUnique = !rs.getBoolean("NON_UNIQUE"); |
| } catch (SQLException e) { |
| filterSQLException(e); |
| } |
| return new JDBCIndex(this.getTable(), name, type, isUnique); |
| } |
| |
| protected JDBCIndexColumn createJDBCIndexColumn(JDBCIndex parent, ResultSet rs) { |
| Column column = null; |
| int position = 0; |
| Ordering ordering = Ordering.NOT_SUPPORTED; |
| try { |
| column = getColumn(rs.getString("COLUMN_NAME")); |
| if (!isOdbc(rs)) { |
| position = rs.getInt("ORDINAL_POSITION"); |
| ordering = JDBCUtils.getOrdering(MetadataUtilities.trimmed(rs.getString("ASC_OR_DESC"))); |
| } |
| } catch (SQLException e) { |
| filterSQLException(e); |
| } |
| if (column == null) { |
| LOGGER.log(Level.INFO, "Cannot get column for index {0} from {1}", //NOI18N |
| new Object[] {parent, rs}); |
| return null; |
| } |
| return new JDBCIndexColumn(parent.getIndex(), column.getName(), column, position, ordering); |
| } |
| |
| protected void createForeignKeys() { |
| Map<String,ForeignKey> newKeys = new LinkedHashMap<String,ForeignKey>(); |
| try { |
| ResultSet rs = MetadataUtilities.getImportedKeys( |
| jdbcSchema.getJDBCCatalog().getJDBCMetadata().getDmd(), |
| jdbcSchema.getJDBCCatalog().getName(), jdbcSchema.getName(), |
| name); |
| if (rs != null) { |
| try { |
| JDBCForeignKey fkey = null; |
| String currentKeyName = null; |
| while (rs.next()) { |
| String keyName = MetadataUtilities.trimmed(rs.getString("FK_NAME")); |
| // We have to assume that if the foreign key name is null, then this is a *new* |
| // foreign key, even if the last foreign key name was also null. |
| if (fkey == null || keyName == null || !(currentKeyName.equals(keyName))) { |
| fkey = createJDBCForeignKey(keyName, rs); |
| LOGGER.log(Level.FINE, "Created foreign key {0}", keyName); //NOI18N |
| |
| newKeys.put(fkey.getInternalName(), fkey.getForeignKey()); |
| currentKeyName = keyName; |
| } |
| |
| ForeignKeyColumn col = createJDBCForeignKeyColumn(fkey, rs).getForeignKeyColumn(); |
| fkey.addColumn(col); |
| LOGGER.log(Level.FINE, "Added foreign key column {0} to foreign key {1}", //NOI18N |
| new Object[]{col.getName(), keyName}); |
| } |
| } finally { |
| rs.close(); |
| } |
| } |
| } catch (SQLException e) { |
| filterSQLException(e); |
| } |
| |
| foreignKeys = Collections.unmodifiableMap(newKeys); |
| } |
| |
| protected JDBCForeignKey createJDBCForeignKey(String name, ResultSet rs) { |
| return new JDBCForeignKey(this.getTable(), name); |
| } |
| |
| protected JDBCForeignKeyColumn createJDBCForeignKeyColumn(JDBCForeignKey parent, ResultSet rs) { |
| Table table; |
| String colname; |
| Column referredColumn = null; |
| Column referringColumn = null; |
| int position = 0; |
| |
| try { |
| table = findReferredTable(rs); |
| colname = MetadataUtilities.trimmed(rs.getString("PKCOLUMN_NAME")); // NOI18N |
| referredColumn = table.getColumn(colname); |
| if (referredColumn == null) { |
| throwColumnNotFoundException(table, colname); |
| } |
| |
| colname = MetadataUtilities.trimmed(rs.getString("FKCOLUMN_NAME")); |
| referringColumn = getColumn(colname); |
| |
| if (referringColumn == null) { |
| throwColumnNotFoundException(this.getTable(), colname); |
| } |
| position = rs.getInt("KEY_SEQ"); |
| } catch (SQLException e) { |
| filterSQLException(e); |
| } |
| return new JDBCForeignKeyColumn(parent.getForeignKey(), referringColumn.getName(), referringColumn, referredColumn, position); |
| } |
| |
| private void throwColumnNotFoundException(Table table, String colname) |
| throws MetadataException { |
| String message = getMessage("ERR_COL_NOT_FOUND", //NOI18N |
| table.getParent().getParent().getName(), |
| table.getParent().getName(), table.getName(), colname); |
| MetadataException e = new MetadataException(message); |
| LOGGER.log(Level.INFO, message, e); |
| throw e; |
| } |
| |
| private String getMessage(String key, String ... args) { |
| return NbBundle.getMessage(JDBCTable.class, key, args); |
| } |
| |
| private Table findReferredTable(ResultSet rs) { |
| JDBCMetadata metadata = jdbcSchema.getJDBCCatalog().getJDBCMetadata(); |
| Catalog catalog; |
| Schema schema; |
| Table table = null; |
| |
| try { |
| String catalogName = MetadataUtilities.trimmed(rs.getString("PKTABLE_CAT")); // NOI18N |
| if (catalogName == null || catalogName.length() == 0) { |
| catalog = jdbcSchema.getParent(); |
| } else { |
| catalog = metadata.getCatalog(catalogName); |
| if (catalog == null) { |
| throw new MetadataException(getMessage("ERR_CATALOG_NOT_FOUND", catalogName)); // NOI18N |
| } |
| } |
| |
| String schemaName = MetadataUtilities.trimmed(rs.getString("PKTABLE_SCHEM")); // NOI18N |
| |
| if (schemaName == null || schemaName.length() == 0) { |
| schema = catalog.getSyntheticSchema(); |
| } else { |
| schema = catalog.getSchema(schemaName); |
| if (schema == null) { |
| throw new MetadataException(getMessage("ERR_SCHEMA_NOT_FOUND", schemaName, catalog.getName())); |
| } |
| } |
| |
| String tableName = MetadataUtilities.trimmed(rs.getString("PKTABLE_NAME")); |
| table = schema.getTable(tableName); |
| |
| if (table == null) { |
| throw new MetadataException(getMessage("ERR_TABLE_NOT_FOUND", catalogName, schemaName, tableName)); |
| } |
| |
| } catch (SQLException e) { |
| filterSQLException(e); |
| } |
| |
| return table; |
| } |
| |
| protected void createPrimaryKey() { |
| String pkname = null; |
| Collection<Column> pkcols = new ArrayList<Column>(); |
| try { |
| ResultSet rs = MetadataUtilities.getPrimaryKeys( |
| jdbcSchema.getJDBCCatalog().getJDBCMetadata().getDmd(), |
| jdbcSchema.getJDBCCatalog().getName(), jdbcSchema.getName(), |
| name); |
| if (rs != null) { |
| try { |
| while (rs.next()) { |
| if (pkname == null) { |
| pkname = MetadataUtilities.trimmed(rs.getString("PK_NAME")); |
| } |
| String colName = MetadataUtilities.trimmed(rs.getString("COLUMN_NAME")); |
| pkcols.add(getColumn(colName)); |
| } |
| } finally { |
| rs.close(); |
| } |
| } |
| } catch (SQLException e) { |
| filterSQLException(e); |
| } |
| |
| primaryKey = createJDBCPrimaryKey(pkname, Collections.unmodifiableCollection(pkcols)).getPrimaryKey(); |
| } |
| |
| private Map<String, Column> initColumns() { |
| if (columns != null) { |
| return columns; |
| } |
| LOGGER.log(Level.FINE, "Initializing columns in {0}", this); |
| createColumns(); |
| return columns; |
| } |
| |
| private Map<String, Index> initIndexes() { |
| if (indexes != null) { |
| return indexes; |
| } |
| LOGGER.log(Level.FINE, "Initializing indexes in {0}", this); |
| |
| createIndexes(); |
| return indexes; |
| } |
| |
| private Map<String,ForeignKey> initForeignKeys() { |
| if (foreignKeys != null) { |
| return foreignKeys; |
| } |
| LOGGER.log(Level.FINE, "Initializing foreign keys in {0}", this); |
| |
| createForeignKeys(); |
| return foreignKeys; |
| } |
| |
| private PrimaryKey initPrimaryKey() { |
| if (primaryKeyInitialized) { |
| return primaryKey; |
| } |
| LOGGER.log(Level.FINE, "Initializing columns in {0}", this); |
| // These need to be initialized first. |
| getColumns(); |
| createPrimaryKey(); |
| primaryKeyInitialized = true; |
| return primaryKey; |
| } |
| |
| private void filterSQLException(SQLException x) throws MetadataException { |
| if (SQL_EXCEPTION_NOT_YET_IMPLEMENTED.equalsIgnoreCase(x.getMessage())) { |
| Logger.getLogger(JDBCTable.class.getName()).log(Level.FINE, x.getLocalizedMessage(), x); |
| } else { |
| throw new MetadataException(x); |
| } |
| } |
| } |