/*
* Copyright 2004-2015 the Seasar Foundation and the Others.
*
* 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.seasar.extension.jdbc.gen.internal.meta;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import javax.sql.DataSource;
import org.seasar.extension.jdbc.gen.dialect.GenDialect;
import org.seasar.extension.jdbc.gen.internal.exception.TableNotFoundRuntimeException;
import org.seasar.extension.jdbc.gen.meta.DbColumnMeta;
import org.seasar.extension.jdbc.gen.meta.DbForeignKeyMeta;
import org.seasar.extension.jdbc.gen.meta.DbTableMeta;
import org.seasar.extension.jdbc.gen.meta.DbTableMetaReader;
import org.seasar.extension.jdbc.gen.meta.DbUniqueKeyMeta;
import org.seasar.extension.jdbc.util.ConnectionUtil;
import org.seasar.extension.jdbc.util.DataSourceUtil;
import org.seasar.extension.jdbc.util.DatabaseMetaDataUtil;
import org.seasar.framework.exception.SQLRuntimeException;
import org.seasar.framework.log.Logger;
import org.seasar.framework.util.ArrayMap;
import org.seasar.framework.util.ResultSetUtil;
/**
* {@code DbTableMetaReader}の実装クラスです。
*
* @author taedium
*/
public class DbTableMetaReaderImpl implements DbTableMetaReader {
/** ロガー */
protected Logger logger = Logger.getLogger(DbTableMetaReaderImpl.class);
/** データソース */
protected DataSource dataSource;
/** 方言 */
protected GenDialect dialect;
/** スキーマ名 */
protected String schemaName;
/** 読み取り対象とするテーブル名のパターン */
protected Pattern tableNamePattern;
/** 読み取り非対象とするテーブル名のパターン */
protected Pattern ignoreTableNamePattern;
/** コメントを読む場合{@code true} */
protected boolean readComment;
/**
* インスタンスを構築します。
*
* @param dataSource
* データソース
* @param dialect
* 方言
* @param schemaName
* スキーマ名、デフォルトのスキーマ名を表す場合は{@code null}
* @param tableNamePattern
* 対象とするテーブル名の正規表現
* @param ignoreTableNamePattern
* 対象としないテーブル名の正規表現
* @param readComment
* コメントを読む場合{@code true}
*/
public DbTableMetaReaderImpl(DataSource dataSource, GenDialect dialect,
String schemaName, String tableNamePattern,
String ignoreTableNamePattern, boolean readComment) {
if (dataSource == null) {
throw new NullPointerException("dataSource");
}
if (dialect == null) {
throw new NullPointerException("dialect");
}
if (tableNamePattern == null) {
throw new NullPointerException("tableNamePattern");
}
if (ignoreTableNamePattern == null) {
throw new NullPointerException("ignoreTableNamePattern");
}
this.dataSource = dataSource;
this.dialect = dialect;
this.schemaName = schemaName;
this.tableNamePattern = Pattern.compile(tableNamePattern,
Pattern.CASE_INSENSITIVE);
this.ignoreTableNamePattern = Pattern.compile(ignoreTableNamePattern,
Pattern.CASE_INSENSITIVE);
this.readComment = readComment;
}
public List<DbTableMeta> read() {
Connection con = DataSourceUtil.getConnection(dataSource);
try {
DatabaseMetaData metaData = ConnectionUtil.getMetaData(con);
List<DbTableMeta> dbTableMetaList = getDbTableMetaList(metaData,
schemaName != null ? schemaName
: getDefaultSchemaName(metaData));
if (dbTableMetaList.isEmpty()) {
throw new TableNotFoundRuntimeException(dialect.getClass()
.getName(), schemaName, tableNamePattern.pattern(),
ignoreTableNamePattern.pattern());
}
for (DbTableMeta tableMeta : dbTableMetaList) {
Set<String> primaryKeySet = getPrimaryKeySet(metaData,
tableMeta);
doDbUniqueKeyMeta(metaData, tableMeta, primaryKeySet);
doDbColumnMeta(metaData, tableMeta, primaryKeySet);
doDbForeignKeyMeta(metaData, tableMeta);
}
if (readComment && !dialect.isJdbcCommentAvailable()) {
readCommentFromDictinary(con, dbTableMetaList);
}
return dbTableMetaList;
} finally {
ConnectionUtil.close(con);
}
}
/**
* 一意キーメタデータを処理します。
*
* @param metaData
* データベースメタデータ
* @param tableMeta
* テーブルメタデータ
* @param primaryKeySet
* 主キーのセット
*/
protected void doDbUniqueKeyMeta(DatabaseMetaData metaData,
DbTableMeta tableMeta, Set<String> primaryKeySet) {
for (DbUniqueKeyMeta ukMeta : getDbUniqueKeyMetaList(metaData,
tableMeta)) {
if (primaryKeySet.size() == ukMeta.getColumnNameList().size()
&& primaryKeySet.containsAll(ukMeta.getColumnNameList())) {
ukMeta.setPrimaryKey(true);
}
tableMeta.addUniqueKeyMeta(ukMeta);
}
}
/**
* カラムメタデータを処理します。
*
* @param metaData
* データベースメタデータ
* @param tableMeta
* テーブルメタデータ
* @param primaryKeySet
* 主キーのセット
*/
protected void doDbColumnMeta(DatabaseMetaData metaData,
DbTableMeta tableMeta, Set<String> primaryKeySet) {
for (DbColumnMeta columnMeta : getDbColumnMetaList(metaData, tableMeta)) {
if (primaryKeySet.contains(columnMeta.getName())) {
columnMeta.setPrimaryKey(true);
if (primaryKeySet.size() == 1) {
columnMeta.setAutoIncrement(isAutoIncrement(metaData,
tableMeta, columnMeta.getName()));
}
}
for (DbUniqueKeyMeta ukMeta : tableMeta.getUniqueKeyMetaList()) {
if (ukMeta.getColumnNameList().size() == 1) {
String ukColumnName = ukMeta.getColumnNameList().get(0);
if (columnMeta.getName().equals(ukColumnName)) {
columnMeta.setUnique(true);
}
}
}
tableMeta.addColumnMeta(columnMeta);
}
}
/**
* 外部キーメタデータを処理します。
*
* @param metaData
* データベースメタデータ
* @param tableMeta
* テーブルメタデータ
*/
protected void doDbForeignKeyMeta(DatabaseMetaData metaData,
DbTableMeta tableMeta) {
for (DbForeignKeyMeta fkMeta : getDbForeignKeyMetaList(metaData,
tableMeta)) {
for (DbUniqueKeyMeta ukMeta : tableMeta.getUniqueKeyMetaList()) {
if (fkMeta.getForeignKeyColumnNameList().equals(
ukMeta.getColumnNameList())) {
fkMeta.setUnique(true);
}
}
tableMeta.addForeignKeyMeta(fkMeta);
}
}
/**
* デフォルトのスキーマ名を返します。
*
* @param metaData
* データベースメタデータ
* @return デフォルトのスキーマ名
*/
protected String getDefaultSchemaName(DatabaseMetaData metaData) {
String userName = DatabaseMetaDataUtil.getUserName(metaData);
return dialect.getDefaultSchemaName(userName);
}
/**
* テーブルメタデータのリストを返します。
*
* @param metaData
* データベースメタデータ
* @param schemaName
* スキーマ名
* @return テーブルメタデータのリスト
*/
protected List<DbTableMeta> getDbTableMetaList(DatabaseMetaData metaData,
String schemaName) {
List<DbTableMeta> result = new ArrayList<DbTableMeta>();
try {
ResultSet rs = metaData.getTables(null, schemaName, null,
new String[] { "TABLE" });
try {
while (rs.next()) {
DbTableMeta dbTableMeta = new DbTableMeta();
dbTableMeta.setCatalogName(rs.getString("TABLE_CAT"));
dbTableMeta.setSchemaName(rs.getString("TABLE_SCHEM"));
dbTableMeta.setName(rs.getString("TABLE_NAME"));
if (readComment) {
dbTableMeta.setComment(rs.getString("REMARKS"));
}
if (isTargetTable(dbTableMeta)) {
result.add(dbTableMeta);
}
}
return result;
} finally {
ResultSetUtil.close(rs);
}
} catch (SQLException e) {
throw new SQLRuntimeException(e);
}
}
/**
* 読み取り対象のテーブルの場合{@code true}を返します。
*
* @param dbTableMeta
* テーブルメタデータ
* @return 読み取り対象のテーブルの場合{@code true}
*/
protected boolean isTargetTable(DbTableMeta dbTableMeta) {
String name = dbTableMeta.getName();
if (!tableNamePattern.matcher(name).matches()) {
return false;
}
if (ignoreTableNamePattern.matcher(name).matches()) {
return false;
}
return true;
}
/**
* カラムメタデータのリストを返します。
*
* @param metaData
* データベースメタデータ
* @param tableMeta
* テーブルメタデータ
*
* @return カラムメタデータのリスト
*/
protected List<DbColumnMeta> getDbColumnMetaList(DatabaseMetaData metaData,
DbTableMeta tableMeta) {
List<DbColumnMeta> result = new ArrayList<DbColumnMeta>();
try {
ResultSet rs = metaData.getColumns(tableMeta.getCatalogName(),
tableMeta.getSchemaName(), tableMeta.getName(), null);
try {
while (rs.next()) {
DbColumnMeta columnDesc = new DbColumnMeta();
columnDesc.setName(rs.getString("COLUMN_NAME"));
columnDesc.setSqlType(rs.getInt("DATA_TYPE"));
columnDesc.setTypeName(rs.getString("TYPE_NAME"));
columnDesc.setLength(rs.getInt("COLUMN_SIZE"));
columnDesc.setScale(rs.getInt("DECIMAL_DIGITS"));
columnDesc.setNullable(rs.getBoolean("NULLABLE"));
columnDesc.setDefaultValue(rs.getString("COLUMN_DEF"));
if (readComment) {
columnDesc.setComment(rs.getString("REMARKS"));
}
result.add(columnDesc);
}
return result;
} finally {
ResultSetUtil.close(rs);
}
} catch (SQLException ex) {
throw new SQLRuntimeException(ex);
}
}
/**
* 主キーのセットを返します。
*
* @param metaData
* データベースメタデータ
* @param tableMeta
* テーブルメタデータ
* @return 主キーのセット
*/
protected Set<String> getPrimaryKeySet(DatabaseMetaData metaData,
DbTableMeta tableMeta) {
Set<String> result = new HashSet<String>();
try {
ResultSet rs = metaData.getPrimaryKeys(tableMeta.getCatalogName(),
tableMeta.getSchemaName(), tableMeta.getName());
try {
while (rs.next()) {
result.add(rs.getString("COLUMN_NAME"));
}
} finally {
ResultSetUtil.close(rs);
}
return result;
} catch (SQLException ex) {
throw new SQLRuntimeException(ex);
}
}
/**
* 外部キーメタデータのリストを返します。
*
* @param metaData
* データベースメタデータ
* @param tableMeta
* テーブルメタデータ
* @return 外部キーメタデータのリスト
*/
protected List<DbForeignKeyMeta> getDbForeignKeyMetaList(
DatabaseMetaData metaData, DbTableMeta tableMeta) {
@SuppressWarnings("unchecked")
Map<String, DbForeignKeyMeta> map = new ArrayMap();
try {
ResultSet rs = metaData.getImportedKeys(tableMeta.getCatalogName(),
tableMeta.getSchemaName(), tableMeta.getName());
try {
while (rs.next()) {
String name = rs.getString("FK_NAME");
if (!map.containsKey(name)) {
DbForeignKeyMeta fkMeta = new DbForeignKeyMeta();
fkMeta.setName(name);
fkMeta.setPrimaryKeyCatalogName(rs
.getString("PKTABLE_CAT"));
fkMeta.setPrimaryKeySchemaName(rs
.getString("PKTABLE_SCHEM"));
fkMeta.setPrimaryKeyTableName(rs
.getString("PKTABLE_NAME"));
map.put(name, fkMeta);
}
DbForeignKeyMeta fkMeta = map.get(name);
fkMeta.addPrimaryKeyColumnName(rs
.getString("PKCOLUMN_NAME"));
fkMeta.addForeignKeyColumnName(rs
.getString("FKCOLUMN_NAME"));
}
} finally {
ResultSetUtil.close(rs);
}
DbForeignKeyMeta[] array = map.values().toArray(
new DbForeignKeyMeta[map.size()]);
return Arrays.asList(array);
} catch (SQLException ex) {
throw new SQLRuntimeException(ex);
}
}
/**
* 一意キーメタデータのリストを返します。
*
* @param metaData
* データベースメタデータ
* @param tableMeta
* テーブルメタデータ
* @return 一意キーメタデータのリスト
*/
protected List<DbUniqueKeyMeta> getDbUniqueKeyMetaList(
DatabaseMetaData metaData, DbTableMeta tableMeta) {
if (!dialect.supportsGetIndexInfo(tableMeta.getCatalogName(), tableMeta
.getSchemaName(), tableMeta.getName())) {
logger.log("WS2JDBCGen0002", new Object[] {
tableMeta.getCatalogName(), tableMeta.getSchemaName(),
tableMeta.getName() });
return Collections.emptyList();
}
@SuppressWarnings("unchecked")
Map<String, DbUniqueKeyMeta> map = new ArrayMap();
try {
ResultSet rs = metaData
.getIndexInfo(tableMeta.getCatalogName(), tableMeta
.getSchemaName(), tableMeta.getName(), true, false);
try {
while (rs.next()) {
String name = rs.getString("INDEX_NAME");
if (!map.containsKey(name)) {
DbUniqueKeyMeta ukMeta = new DbUniqueKeyMeta();
ukMeta.setName(name);
map.put(name, ukMeta);
}
DbUniqueKeyMeta ukMeta = map.get(name);
ukMeta.addColumnName(rs.getString("COLUMN_NAME"));
}
} finally {
ResultSetUtil.close(rs);
}
DbUniqueKeyMeta[] array = map.values().toArray(
new DbUniqueKeyMeta[map.size()]);
return Arrays.asList(array);
} catch (SQLException ex) {
throw new SQLRuntimeException(ex);
}
}
/**
* 列の値が自動的に増分される場合{@code true}を返します。
*
* @param metaData
* データベースメタデータ
* @param tableMeta
* テーブルメタデータ
* @param columnName
* カラム名
* @return 列が自動的に増分される場合{@code true}、そうでない場合{@code false}
*/
protected boolean isAutoIncrement(DatabaseMetaData metaData,
DbTableMeta tableMeta, String columnName) {
try {
return dialect.isAutoIncrement(metaData.getConnection(), tableMeta
.getCatalogName(), tableMeta.getSchemaName(), tableMeta
.getName(), columnName);
} catch (SQLException ex) {
throw new SQLRuntimeException(ex);
}
}
/**
* コメントをデータベースのディクショナリから直接取得します。
*
* @param connection
* コネクション
* @param dbTableMetaList
* テーブルメタデータのリスト
*/
protected void readCommentFromDictinary(Connection connection,
List<DbTableMeta> dbTableMetaList) {
try {
for (DbTableMeta tableMeta : dbTableMetaList) {
String tableComment = dialect.getTableComment(connection,
tableMeta.getCatalogName(), tableMeta.getSchemaName(),
tableMeta.getName());
tableMeta.setComment(tableComment);
Map<String, String> columnCommentMap = dialect
.getColumnCommentMap(connection, tableMeta
.getCatalogName(), tableMeta.getSchemaName(),
tableMeta.getName());
for (DbColumnMeta columnMeta : tableMeta.getColumnMetaList()) {
String columnName = columnMeta.getName();
if (columnCommentMap.containsKey(columnName)) {
String columnComment = columnCommentMap.get(columnName);
columnMeta.setComment(columnComment);
}
}
}
} catch (SQLException e) {
throw new SQLRuntimeException(e);
}
}
}