/*
* ModeShape (http://www.modeshape.org)
*
* Licensed 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.modeshape.connector.meta.jdbc;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.modeshape.common.annotation.Immutable;
import org.modeshape.common.logging.Logger;
import org.modeshape.common.util.CheckArg;
/**
* Default {@link MetadataCollector} implementation that uses the {@link java.sql.DatabaseMetaData built-in JDBC support} for collecting
* database metadata.
*
* @see java.sql.DatabaseMetaData
*/
@Immutable
public class JdbcMetadataCollector implements MetadataCollector {
private static final Logger LOG = Logger.getLogger(JdbcMetadataCollector.class);
@Override
public DBMetadata getDatabaseMetadata( Connection conn ) throws JdbcMetadataException {
CheckArg.isNotNull(conn, "Database connection");
try {
DatabaseMetaData metaData = conn.getMetaData();
return new DBMetadata(metaData.getDatabaseProductName(), metaData.getDatabaseProductVersion(),
metaData.getDriverMajorVersion(), metaData.getDatabaseMinorVersion());
} catch (SQLException e) {
throw new JdbcMetadataException(e);
}
}
@Override
public List<String> getCatalogNames( Connection conn ) throws JdbcMetadataException {
CheckArg.isNotNull(conn, "Database connection");
List<String> catalogNames = new LinkedList<String>();
ResultSet catalogs = null;
try {
DatabaseMetaData dmd = conn.getMetaData();
catalogs = dmd.getCatalogs();
while (catalogs.next()) {
catalogNames.add(catalogs.getString("TABLE_CAT"));
}
return catalogNames;
} catch (SQLException se) {
throw new JdbcMetadataException(se);
} finally {
try {
if (catalogs != null) {
catalogs.close();
}
} catch (Exception ignore) {
LOG.debug(ignore, "Cannot close catalogs result set");
}
}
}
@Override
public List<ColumnMetadata> getColumns( Connection conn,
String catalogName,
String schemaName,
String tableName,
String columnName ) throws JdbcMetadataException {
CheckArg.isNotNull(tableName, "Table name");
List<ColumnMetadata> columnData = new LinkedList<ColumnMetadata>();
ResultSet columns = null;
try {
DatabaseMetaData dmd = conn.getMetaData();
// Adjust default values of catalogName and schemaName to the "match tables with no catalog/schema" pattern
if (catalogName == null) {
catalogName = "";
}
if (schemaName == null) {
schemaName = "";
}
columns = dmd.getColumns(catalogName, schemaName, tableName, columnName);
// Get the list of names of the columns in the result set, which may or may not match what's in the spec
Set<String> columnNames = columnsFor(columns);
while (columns.next()) {
ColumnMetadata column = new ColumnMetadata(columns.getString("COLUMN_NAME"),
columns.getInt("DATA_TYPE"),
columns.getString("TYPE_NAME"),
columns.getInt("COLUMN_SIZE"),
columns.getInt("DECIMAL_DIGITS"),
columns.getInt("NUM_PREC_RADIX"),
getNullableBoolean(columns, "NULLABLE"),
getStringIfPresent(columns, "REMARKS", columnNames),
getStringIfPresent(columns, "COLUMN_DEF", columnNames),
columns.getInt("CHAR_OCTET_LENGTH"),
columns.getInt("ORDINAL_POSITION"),
getStringIfPresent(columns, "SCOPE_CATLOG", columnNames),
getStringIfPresent(columns, "SCOPE_SCHEMA", columnNames),
getStringIfPresent(columns, "SCOPE_TABLE", columnNames),
getIntegerIfPresent(columns, "SOURCE_DATA_TYPE", columnNames));
columnData.add(column);
}
return columnData;
} catch (SQLException se) {
throw new JdbcMetadataException(se);
} finally {
try {
if (columns != null) {
columns.close();
}
} catch (Exception ignore) {
LOG.debug(ignore, "Cannot close columns result set");
}
}
}
@Override
public List<String> getSchemaNames( Connection conn,
String catalogName ) throws JdbcMetadataException {
List<String> schemaNames = new LinkedList<String>();
ResultSet schemas = null;
try {
DatabaseMetaData dmd = conn.getMetaData();
schemas = dmd.getSchemas();
Set<String> columns = columnsFor(schemas);
boolean hasCatalog = columns.contains(identifierFor(dmd, "TABLE_CATALOG"));
while (schemas.next()) {
/*
* PostgreSQL's JDBC3 driver doesn't include TABLE_CATALOG
*/
if (hasCatalog) {
String catalogNameForSchema = schemas.getString("TABLE_CATALOG");
String schemaName = schemas.getString("TABLE_SCHEM");
if ((catalogName == null && catalogNameForSchema == null)
|| (catalogName != null && catalogName.equals(catalogNameForSchema))) {
schemaNames.add(schemaName);
}
} else {
schemaNames.add(schemas.getString("TABLE_SCHEM"));
}
}
return schemaNames;
} catch (SQLException se) {
throw new JdbcMetadataException(se);
} finally {
try {
if (schemas != null) {
schemas.close();
}
} catch (Exception ignore) {
LOG.debug(ignore, "Cannot close schemas result set");
}
}
}
@Override
public List<TableMetadata> getTables( Connection conn,
String catalogName,
String schemaName,
String tableName ) throws JdbcMetadataException {
List<TableMetadata> tableData = new LinkedList<TableMetadata>();
ResultSet tables = null;
try {
DatabaseMetaData dmd = conn.getMetaData();
// Adjust default values of catalogName and schemaName to the "match tables with no catalog/schema" pattern
if (catalogName == null) {
catalogName = "";
}
if (schemaName == null) {
schemaName = "";
}
tables = dmd.getTables(catalogName, schemaName, tableName, null);
Set<String> columns = columnsFor(tables);
while (tables.next()) {
TableMetadata table = new TableMetadata(tables.getString("TABLE_NAME"),
tables.getString("TABLE_TYPE"),
tables.getString("REMARKS"),
getStringIfPresent(tables, "TYPE_CAT", columns),
getStringIfPresent(tables, "TYPE_SCHEM", columns),
getStringIfPresent(tables, "TYPE_NAME", columns),
getStringIfPresent(tables, "SELF_REFERENCING_COL_NAME", columns),
getStringIfPresent(tables, "REF_GENERATION", columns));
tableData.add(table);
}
return tableData;
} catch (SQLException se) {
throw new JdbcMetadataException(se);
} finally {
try {
if (tables != null) {
tables.close();
}
} catch (Exception ignore) {
LOG.debug(ignore, "Cannot close tables result set");
}
}
}
@Override
public List<ProcedureMetadata> getProcedures( Connection conn,
String catalogName,
String schemaName,
String procedureName ) throws JdbcMetadataException {
List<ProcedureMetadata> procedureData = new LinkedList<ProcedureMetadata>();
ResultSet procedures = null;
try {
DatabaseMetaData dmd = conn.getMetaData();
// Adjust default values of catalogName and schemaName to the "match tables with no catalog/schema" pattern
if (catalogName == null) {
catalogName = "";
}
if (schemaName == null) {
schemaName = "";
}
procedures = dmd.getProcedures(catalogName, schemaName, procedureName);
while (procedures.next()) {
ProcedureMetadata procedure = new ProcedureMetadata(procedures.getString("PROCEDURE_NAME"),
procedures.getString("REMARKS"),
procedures.getShort("PROCEDURE_TYPE"));
procedureData.add(procedure);
}
return procedureData;
} catch (SQLException se) {
throw new JdbcMetadataException(se);
} finally {
try {
if (procedures != null) {
procedures.close();
}
} catch (Exception ignore) {
}
}
}
@Override
public List<ForeignKeyMetadata> getForeignKeys( Connection conn,
String catalogName,
String schemaName,
String tableName,
String fkColumnName ) throws JdbcMetadataException {
CheckArg.isNotNull(tableName, "Table name");
List<ForeignKeyMetadata> foreignKeyMetadata = new LinkedList<ForeignKeyMetadata>();
ResultSet foreignKeys = null;
try {
DatabaseMetaData dmd = conn.getMetaData();
// Adjust default values of catalogName and schemaName to the "match tables with no catalog/schema" pattern
if (catalogName == null) {
catalogName = "";
}
if (schemaName == null) {
schemaName = "";
}
foreignKeys = dmd.getImportedKeys(catalogName, schemaName, tableName);
Set<String> columnNames = columnsFor(foreignKeys);
while (foreignKeys.next()) {
String colName = foreignKeys.getString("FKCOLUMN_NAME");
if (fkColumnName != null && !fkColumnName.equalsIgnoreCase(colName)) {
continue;
}
ForeignKeyMetadata foreignKey = new ForeignKeyMetadata(
getStringIfPresent(foreignKeys, "PKTABLE_CAT", columnNames),
getStringIfPresent(foreignKeys, "PKTABLE_SCHEM ", columnNames),
foreignKeys.getString("PKTABLE_NAME"),
foreignKeys.getString("PKCOLUMN_NAME"),
getStringIfPresent(foreignKeys, "FKTABLE_CAT", columnNames),
getStringIfPresent(foreignKeys, "FKTABLE_SCHEM", columnNames),
foreignKeys.getString("FKTABLE_NAME"),
colName,
foreignKeys.getShort("KEY_SEQ"),
foreignKeys.getShort("UPDATE_RULE"),
foreignKeys.getShort("DELETE_RULE"),
getStringIfPresent(foreignKeys, "FK_NAME", columnNames),
getStringIfPresent(foreignKeys, "PK_NAME", columnNames),
foreignKeys.getShort("DEFERRABILITY"));
foreignKeyMetadata.add(foreignKey);
}
return foreignKeyMetadata;
} catch (SQLException se) {
throw new JdbcMetadataException(se);
} finally {
try {
if (foreignKeys != null) {
foreignKeys.close();
}
} catch (Exception ignore) {
LOG.debug(ignore, "Cannot close foreignKeys result set");
}
}
}
private Boolean getNullableBoolean( ResultSet rs,
String columnName ) throws SQLException {
Boolean b = rs.getBoolean(columnName);
if (rs.wasNull()) {
b = null;
}
return b;
}
private Set<String> columnsFor( ResultSet rs ) throws SQLException {
ResultSetMetaData rmd = rs.getMetaData();
int count = rmd.getColumnCount();
Set<String> columns = new HashSet<String>(count);
for (int i = 1; i <= count; i++) {
columns.add(rmd.getColumnName(i));
}
return columns;
}
private String getStringIfPresent( ResultSet rs,
String columnName,
Set<String> resultSetColumns ) throws SQLException {
if (!resultSetColumns.contains(columnName)) {
return null;
}
return rs.getString(columnName);
}
private Integer getIntegerIfPresent( ResultSet rs,
String columnName,
Set<String> resultSetColumns ) throws SQLException {
if (!resultSetColumns.contains(columnName)) {
return null;
}
int i = rs.getInt(columnName);
if (rs.wasNull()) {
return null;
}
return i;
}
private String identifierFor( DatabaseMetaData dmd,
String rawIdentifier ) throws SQLException {
assert rawIdentifier != null;
if (dmd.storesLowerCaseIdentifiers()) {
return rawIdentifier.toLowerCase();
}
return rawIdentifier;
}
}