package org.whole.lang.rdb.codebase;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.HashMap;
import java.util.Map;
import org.whole.lang.builders.IBuilderOperation;
import org.whole.lang.commons.builders.ICommonsBuilder;
import org.whole.lang.commons.reflect.CommonsLanguageKit;
import org.whole.lang.factories.RegistryConfigurations;
import org.whole.lang.rdb.builders.IRDBBuilder;
import org.whole.lang.rdb.factories.RDBEntityFactory;
import org.whole.lang.rdb.model.ActionEnum;
import org.whole.lang.rdb.model.Database;
import org.whole.lang.rdb.model.DeferrabilityEnum;
import org.whole.lang.rdb.model.IdMethodEnum;
import org.whole.lang.rdb.model.IndexTypeEnum;
import org.whole.lang.rdb.model.OrderEnum;
import org.whole.lang.rdb.model.TypeEnum;
import org.whole.lang.rdb.model.TypeEnum.Value;
import org.whole.lang.rdb.reflect.RDBLanguageKit;
import org.whole.lang.templates.AbstractTemplateFactory;
/**
* @author Enrico Persiani
*/
public class DBSchemaTemplateFactory extends AbstractTemplateFactory<Database> {
protected Connection connection;
protected String catalog;
protected String schema;
protected DatabaseMetaData dmd;
protected IRDBBuilder rdbb;
protected ICommonsBuilder cb;
protected RDBEntityFactory rdbef;
public DBSchemaTemplateFactory(Connection connection, String catalog, String schema) {
this.connection = connection;
this.catalog = catalog;
this.schema = schema;
this.rdbef = RDBEntityFactory.instance(RegistryConfigurations.RESOLVER);
}
public void apply(IBuilderOperation op) {
cb = (ICommonsBuilder) op.wGetBuilder(CommonsLanguageKit.URI);
rdbb = (IRDBBuilder) op.wGetBuilder(RDBLanguageKit.URI);
try {
dmd = connection.getMetaData();
buildDatabase();
} catch (Exception e) {
throw new IllegalStateException("cannot import database schema", e);
}
}
protected void buildStringData(String name) {
if (name != null)
rdbb.StringData(name);
else
cb.Resolver();
}
protected void buildDatabase() throws SQLException {
rdbb.Database_();
rdbb.URI(dmd.getURL() != null ? dmd.getURL() : "urn:lang:rdb:generated");
rdbb.Namespace("org.whole.lang.rdb.generated");
buildSchemas();
rdbb._Database();
}
protected void buildSchemas() throws SQLException {
rdbb.Schemas_();
if (schema != null)
buildSchema(schema);
else {
ResultSet rs = dmd.getSchemas();
while (rs.next())
buildSchema(rs.getString("TABLE_SCHEM"));
rs.close();
}
rdbb._Schemas();
}
protected void buildSchema(String schema) throws SQLException {
rdbb.Schema_();
buildStringData(schema.length() == 0 ? null : schema);
cb.Resolver();// modelName
rdbb.Tables_();
ResultSet rs = dmd.getTables(catalog, schema, null, new String[] { "TABLE" });
while (rs.next())
buildTable(rs.getString("TABLE_NAME"), rs.getString("REMARKS"));
rs.close();
rdbb._Tables();
rdbb._Schema();
}
protected void buildTable(String name, String remarks) throws SQLException {
rdbb.Table_();
buildStringData(name);
cb.Resolver();
buildColums(name);
buildPrimaryKey(name);
buildForeignKeys(name);
buildIndexes(name);
buildStringData(remarks);
rdbb._Table();
}
protected void buildColums(String name) throws SQLException{
ResultSet rs = dmd.getColumns(catalog, schema, name, "%");
if (!rs.next()) {
cb.Resolver();
return;
}
rdbb.Columns_();
do {
rdbb.Column_();
buildStringData(rs.getString("COLUMN_NAME"));
cb.Resolver(); // featureName
cb.Resolver(); // entityName
String sqlType = getSqlTypesMap().get(rs.getInt("DATA_TYPE"));
Value value = TypeEnum.instance.valueOf(sqlType);
rdbb.Type(value);
switch (value.getOrdinal()) {
// SP
case TypeEnum.NUMERIC_ord:
case TypeEnum.DECIMAL_ord:
rdbb.IntData(rs.getInt("COLUMN_SIZE"));
rdbb.IntData(rs.getInt("DECIMAL_DIGITS"));
break;
// Precision
case TypeEnum.TIME_ord:
case TypeEnum.TIMESTAMP_ord:
case TypeEnum.FLOAT_ord:
// Length
case TypeEnum.CHAR_ord:
case TypeEnum.VARCHAR_ord:
case TypeEnum.LONGVARCHAR_ord:
case TypeEnum.BINARY_ord:
case TypeEnum.VARBINARY_ord:
case TypeEnum.LONGVARBINARY_ord:
case TypeEnum.ARRAY_ord:
rdbb.IntData(rs.getInt("COLUMN_SIZE"));
cb.Resolver();
break;
// NO
default:
case TypeEnum.DATE_ord:
case TypeEnum.BIT_ord:
case TypeEnum.BOOLEAN_ord:
case TypeEnum.BIGINT_ord:
case TypeEnum.INTEGER_ord:
case TypeEnum.SMALLINT_ord:
case TypeEnum.TINYINT_ord:
case TypeEnum.REAL_ord:
case TypeEnum.DOUBLE_ord:
case TypeEnum.TEXT_ord:
case TypeEnum.BLOB_ord:
case TypeEnum.CLOB_ord:
case TypeEnum.DATALINK_ord:
case TypeEnum.REF_ord:
cb.Resolver();
cb.Resolver();
break;
}
int nullable = rs.getInt("NULLABLE");
if (nullable != DatabaseMetaData.columnNullableUnknown)
rdbb.BooleanData(nullable == DatabaseMetaData.columnNullable);
else
cb.Resolver();
String defaultValue = rs.getString("COLUMN_DEF");
if (defaultValue != null)
rdbb.StringData(defaultValue);
else
cb.Resolver();
buildStringData(rs.getString("REMARKS"));
rdbb._Columns();
} while (rs.next());
rs.close();
rdbb._Columns();
}
protected void buildPrimaryKey(String name) throws SQLException {
ResultSet rs = dmd.getPrimaryKeys(catalog, schema, name);
if (!rs.next()) {
cb.Resolver();
return;
}
rdbb.PrimaryKey_();
buildStringData(rs.getString("PK_NAME"));
rdbb.IdMethod(IdMethodEnum.IDENTITY);
rdbb.ColumnNames_();
Map<Integer, String> namesMap = new HashMap<Integer, String>();
do {
namesMap.put(rs.getShort("KEY_SEQ")-1, rs.getString("COLUMN_NAME"));
} while (rs.next());
rs.close();
for (int i=0, size=namesMap.size(); i<size; i++)
buildStringData(namesMap.get(i));
rdbb._ColumnNames();
rdbb._PrimaryKey();
}
protected void buildAction(short actionRule) {
switch (actionRule) {
case DatabaseMetaData.importedKeyRestrict:
rdbb.Action(ActionEnum.RESTRICT);
break;
case DatabaseMetaData.importedKeyCascade:
rdbb.Action(ActionEnum.CASCADE);
break;
case DatabaseMetaData.importedKeySetNull:
rdbb.Action(ActionEnum.SET_NULL);
break;
case DatabaseMetaData.importedKeySetDefault:
rdbb.Action(ActionEnum.SET_DEFAULT);
break;
case DatabaseMetaData.importedKeyNoAction:
default:
cb.Resolver();
break;
}
}
protected void buildForeignKeys(String name) throws SQLException {
ResultSet rs = dmd.getImportedKeys(catalog, schema, name);
if (!rs.next()) {
cb.Resolver();
return;
}
String lastPkTableName = null;
rdbb.ForeignKeys_();
do {
String pkTableName = rs.getString("PKTABLE_NAME");
if (!pkTableName.equals(lastPkTableName)) {
if (lastPkTableName != null) {
// terminate previous foreign key if any
rdbb._ColumnReferences();
rdbb._ForeignKey();
}
lastPkTableName = pkTableName;
rdbb.ForeignKey_();
buildStringData(rs.getString("FK_NAME"));
buildStringData(pkTableName);
buildAction(rs.getShort("UPDATE_RULE"));
buildAction(rs.getShort("DELETE_RULE"));
switch (rs.getShort("DEFERRABILITY")) {
case DatabaseMetaData.importedKeyInitiallyDeferred:
rdbb.Deferrability(DeferrabilityEnum.INITIALLY_DEFERRED);
break;
case DatabaseMetaData.importedKeyInitiallyImmediate:
rdbb.Deferrability(DeferrabilityEnum.INITIALLY_IMMEDIATE);
break;
case DatabaseMetaData.importedKeyNotDeferrable:
default:
cb.Resolver();
}
rdbb.ColumnReferences_();
}
rdbb.ColumnReference_();
rdbb.StringData(rs.getString("FKCOLUMN_NAME"));
rdbb.StringData(rs.getString("PKCOLUMN_NAME"));
rdbb._ColumnReference();
} while (rs.next());
rs.close();
// terminate last foreign key
rdbb._ColumnReferences();
rdbb._ForeignKey();
rdbb._ForeignKeys();
}
protected void buildIndexes(String name) throws SQLException {
ResultSet rs = dmd.getIndexInfo(catalog, schema, name, false, true);
if (!rs.next()) {
cb.Resolver();
return;
}
String lastIndexName = null;
do {
short type = rs.getShort("TYPE");
if (type == DatabaseMetaData.tableIndexStatistic)
continue;
if (lastIndexName == null)
rdbb.Indices_();
String indexName = rs.getString("INDEX_NAME");
if (!indexName.equals(lastIndexName)) {
if (lastIndexName != null) {
// terminate previous index if any
rdbb._ColumnIndices();
rdbb._Index();
}
lastIndexName = indexName;
rdbb.Index_();
buildStringData(indexName);
buildStringData(rs.getString("INDEX_QUALIFIER"));
rdbb.BooleanData(!rs.getBoolean("NON_UNIQUE"));
switch (type) {
case DatabaseMetaData.tableIndexHashed:
rdbb.IndexType(IndexTypeEnum.HASHED);
break;
case DatabaseMetaData.tableIndexClustered:
rdbb.IndexType(IndexTypeEnum.HASHED);
break;
case DatabaseMetaData.tableIndexOther:
cb.Resolver();
break;
}
rdbb.ColumnIndices_();
}
rdbb.ColumnIndex_();
rdbb.StringData(rs.getString("COLUMN_NAME"));
String order = rs.getString("ASC_OR_DESC");
if (order != null)
rdbb.Order(order.charAt(0) == 'A' ?
OrderEnum.ASCENDING : OrderEnum.DESCENDING);
rdbb._ColumnIndex();
} while (rs.next());
rs.close();
// terminate last index if any
if (lastIndexName != null) {
rdbb._ColumnIndices();
rdbb._Index();
rdbb._Indices();
} else
cb.Resolver();
}
private Map<Integer, String> typesMap;
public void setSqlTypesMap(Map<Integer, String> typesMap) {
this.typesMap = typesMap;
}
public Map<Integer, String> getSqlTypesMap() {
if (typesMap == null) {
typesMap = new HashMap<Integer, String>();
for (Field field : Types.class.getFields())
try {
typesMap.put((Integer) field.get(null), field.getName());
} catch (Exception e) {
throw new IllegalStateException("cannot calculate sql type mapping", e);
}
}
return typesMap;
}
}