blob: 56d00a0a46a5f5476ba058caaa302dc598b140cf [file] [log] [blame]
package org.apache.ddlutils.task;
/*
* 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.
*/
import java.io.File;
import java.io.FileOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
import org.apache.commons.collections.set.ListOrderedSet;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.dom4j.Document;
import org.dom4j.DocumentFactory;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
/**
* A simple helper task that dumps information about a database using JDBC.
*
* @version $Revision: 289996 $
* @ant.task name="dumpMetadata"
*/
public class DumpMetadataTask extends Task
{
/** Methods that are filtered when enumerating the properties. */
private static final String[] IGNORED_PROPERTY_METHODS = { "getConnection", "getCatalogs", "getSchemas" };
/** The data source to use for accessing the database. */
private BasicDataSource _dataSource;
/** The file to write the dump to. */
private File _outputFile = null;
/** The encoding of the XML output file. */
private String _outputEncoding = "UTF-8";
/** The database catalog(s) to read. */
private String _catalogPattern = "%";
/** The database schema(s) to read. */
private String _schemaPattern = "%";
/** The pattern for reading all tables. */
private String _tablePattern = "%";
/** The pattern for reading all procedures. */
private String _procedurePattern = "%";
/** The pattern for reading all columns. */
private String _columnPattern = "%";
/** The tables types to read; <code>null</code> or an empty list means that we shall read every type. */
private String[] _tableTypes = null;
/** Whether to read tables. */
private boolean _dumpTables = true;
/** Whether to read procedures. */
private boolean _dumpProcedures = true;
/**
* Adds the data source to use for accessing the database.
*
* @param dataSource The data source
*/
public void addConfiguredDatabase(BasicDataSource dataSource)
{
_dataSource = dataSource;
}
/**
* Specifies the output file to which the database metadata is written to.
*
* @param outputFile The output file
* @ant.required
*/
public void setOutputFile(File outputFile)
{
_outputFile = outputFile;
}
/**
* Specifies the encoding of the output file.
*
* @param encoding The encoding
* @ant.not-required Per default, <code>UTF-8</code> is used.
*/
public void setOutputEncoding(String encoding)
{
_outputEncoding = encoding;
}
/**
* Sets the catalog pattern used when accessing the database.
*
* @param catalogPattern The catalog pattern
* @ant.not-required Per default, no specific catalog is used (value <code>%</code>).
*/
public void setCatalogPattern(String catalogPattern)
{
_catalogPattern = ((catalogPattern == null) || (catalogPattern.length() == 0) ? null : catalogPattern);
}
/**
* Sets the schema pattern used when accessing the database.
*
* @param schemaPattern The schema pattern
* @ant.not-required Per default, no specific schema is used (value <code>%</code>).
*/
public void setSchemaPattern(String schemaPattern)
{
_schemaPattern = ((schemaPattern == null) || (schemaPattern.length() == 0) ? null : schemaPattern);
}
/**
* Specifies the table to be processed. For details see
* <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/sql/DatabaseMetaData.html#getTables(java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String[])">java.sql.DatabaseMetaData#getTables</a>.
*
* @param tablePattern The table pattern
* @ant.not-required By default, all tables are read (value <code>%</code>).
*/
public void setTablePattern(String tablePattern)
{
_tablePattern = ((tablePattern == null) || (tablePattern.length() == 0) ? null : tablePattern);
}
/**
* Specifies the procedures to be processed. For details and typical table types see
* <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/sql/DatabaseMetaData.html#getProcedures(java.lang.String,%20java.lang.String,%20java.lang.String)">java.sql.DatabaseMetaData#getProcedures</a>.
*
* @param procedurePattern The procedure pattern
* @ant.not-required By default, all procedures are read (value <code>%</code>).
*/
public void setProcedurePattern(String procedurePattern)
{
_procedurePattern = ((procedurePattern == null) || (procedurePattern.length() == 0) ? null : procedurePattern);
}
/**
* Specifies the columns to be processed. For details and typical table types see
* <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/sql/DatabaseMetaData.html#getColumns(java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String[])">java.sql.DatabaseMetaData#getColumns</a>.
*
* @param columnPattern The column pattern
* @ant.not-required By default, all columns are read (value <code>%</code>).
*/
public void setColumnPattern(String columnPattern)
{
_columnPattern = ((columnPattern == null) || (columnPattern.length() == 0) ? null : columnPattern);
}
/**
* Specifies the table types to be processed. For details and typical table types see
* <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/sql/DatabaseMetaData.html#getTables(java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String[])">java.sql.DatabaseMetaData#getTables</a>.
*
* @param tableTypes The table types to read
* @ant.not-required By default, all types of tables are read.
*/
public void setTableTypes(String tableTypes)
{
ArrayList types = new ArrayList();
if (tableTypes != null)
{
StringTokenizer tokenizer = new StringTokenizer(tableTypes, ",");
while (tokenizer.hasMoreTokens())
{
String token = tokenizer.nextToken().trim();
if (token.length() > 0)
{
types.add(token);
}
}
}
_tableTypes = (String[])types.toArray(new String[types.size()]);
}
/**
* Specifies whether procedures shall be read from the database.
*
* @param readProcedures <code>true</code> if procedures shall be read
* @ant.not-required By default, procedures are read.
*/
public void setDumpProcedures(boolean readProcedures)
{
_dumpProcedures = readProcedures;
}
/**
* Specifies whether tables shall be read from the database.
*
* @param readTables <code>true</code> if tables shall be read
* @ant.not-required By default, tables are read.
*/
public void setDumpTables(boolean readTables)
{
_dumpTables = readTables;
}
/**
* {@inheritDoc}
*/
public void execute() throws BuildException
{
if (_dataSource == null)
{
log("No data source specified, so there is nothing to do.", Project.MSG_INFO);
return;
}
Connection connection = null;
try
{
Document document = DocumentFactory.getInstance().createDocument();
Element root = document.addElement("metadata");
root.addAttribute("driverClassName", _dataSource.getDriverClassName());
connection = _dataSource.getConnection();
dumpMetaData(root, connection.getMetaData());
OutputFormat outputFormat = OutputFormat.createPrettyPrint();
XMLWriter xmlWriter = null;
outputFormat.setEncoding(_outputEncoding);
if (_outputFile == null)
{
xmlWriter = new XMLWriter(System.out, outputFormat);
}
else
{
xmlWriter = new XMLWriter(new FileOutputStream(_outputFile), outputFormat);
}
xmlWriter.write(document);
xmlWriter.close();
}
catch (Exception ex)
{
throw new BuildException(ex);
}
finally
{
if (connection != null)
{
try
{
connection.close();
}
catch (SQLException ex)
{}
}
}
}
/**
* Dumps the database meta data into XML elements under the given element.
*
* @param element The XML element
* @param metaData The meta data
*/
private void dumpMetaData(Element element, DatabaseMetaData metaData) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, SQLException
{
// We rather iterate over the methods because most metadata properties
// do not follow the bean naming standard
Method[] methods = metaData.getClass().getMethods();
Set filtered = new HashSet(Arrays.asList(IGNORED_PROPERTY_METHODS));
for (int idx = 0; idx < methods.length; idx++)
{
// only no-arg methods that return something and that are not defined in Object
// we also filter certain methods
if ((methods[idx].getParameterTypes().length == 0) &&
(methods[idx].getReturnType() != null) &&
(Object.class != methods[idx].getDeclaringClass()) &&
!filtered.contains(methods[idx].getName()))
{
dumpProperty(element, metaData, methods[idx]);
}
}
dumpCatalogsAndSchemas(element, metaData);
if (_dumpTables)
{
dumpTables(element, metaData);
}
if (_dumpProcedures)
{
dumpProcedures(element, metaData);
}
}
/**
* Dumps the property represented by the given method.
*
* @param parent The parent XML element
* @param obj The instance we're working on
* @param propGetter The method for accessing the property
*/
private void dumpProperty(Element parent, Object obj, Method propGetter)
{
try
{
addProperty(parent, getPropertyName(propGetter.getName()), propGetter.invoke(obj, null));
}
catch (Throwable ex)
{
log("Could not dump property "+propGetter.getName()+" because of "+ex.getMessage(), Project.MSG_WARN);
}
}
/**
* Adds a property to the given element, either as an attribute (primitive value or
* string) or as a sub element.
*
* @param element The XML element
* @param name The name of the property
* @param value The value of the property
*/
private void addProperty(Element element, String name, Object value)
{
if (value != null)
{
if (value.getClass().isArray())
{
addArrayProperty(element, name, (Object[])value);
}
else if (value.getClass().isPrimitive() || (value instanceof String))
{
element.addAttribute(name, value.toString());
}
else if (value instanceof ResultSet)
{
addResultSetProperty(element, name, (ResultSet)value);
}
}
}
/**
* Adds a property to the given XML element that is represented as an array.
*
* @param element The XML element
* @param name The name of the property
* @param values The values of the property
*/
private void addArrayProperty(Element element, String name, Object[] values)
{
String propName = name;
if (propName.endsWith("s"))
{
propName = propName.substring(0, propName.length() - 1);
}
Element arrayElem = element.addElement(propName + "s");
for (int idx = 0; idx < values.length; idx++)
{
addProperty(arrayElem, "value", values[idx]);
}
}
/**
* Adds a property to the given XML element that is represented as a result set.
*
* @param element The XML element
* @param name The name of the property
* @param result The values of the property as a result set
*/
private void addResultSetProperty(Element element, String name, ResultSet result)
{
try
{
String propName = name;
if (propName.endsWith("s"))
{
propName = propName.substring(0, propName.length() - 1);
}
Element resultSetElem = element.addElement(propName + "s");
ResultSetMetaData metaData = result.getMetaData();
while (result.next())
{
Element curRow = resultSetElem.addElement(propName);
for (int idx = 1; idx <= metaData.getColumnCount(); idx++)
{
Object value = result.getObject(idx);
addProperty(curRow, metaData.getColumnLabel(idx), value);
}
}
}
catch (SQLException ex)
{
ex.printStackTrace();
}
}
/**
* Derives the property name from the given method name.
*
* @param methodName The method name
* @return The property name
*/
private String getPropertyName(String methodName)
{
if (methodName.startsWith("get"))
{
if (Character.isLowerCase(methodName.charAt(4)))
{
return Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
}
else
{
return methodName.substring(3);
}
}
else if (methodName.startsWith("is"))
{
if (Character.isLowerCase(methodName.charAt(3)))
{
return Character.toLowerCase(methodName.charAt(2)) + methodName.substring(3);
}
else
{
return methodName.substring(2);
}
}
else
{
return methodName;
}
}
/**
* Dumps the catalogs and schemas of the database.
*
* @param parent The parent element
* @param metaData The database meta data
*/
private void dumpCatalogsAndSchemas(Element parent, DatabaseMetaData metaData) throws SQLException
{
// Next we determine and dump the catalogs
Element catalogsElem = parent.addElement("catalogs");
ResultSet result = metaData.getCatalogs();
try
{
while (result.next())
{
String catalogName = getString(result, "TABLE_CAT");
if ((catalogName != null) && (catalogName.length() > 0))
{
Element catalogElem = catalogsElem.addElement("catalog");
catalogElem.addAttribute("name", catalogName);
}
}
}
finally
{
if (result != null)
{
result.close();
}
}
Element schemasElem = parent.addElement("schemas");
// We also dump the schemas (some dbs only support one of the two)
result = metaData.getSchemas();
try
{
while (result.next())
{
String schemaName = getString(result, "TABLE_SCHEM");
if ((schemaName != null) && (schemaName.length() > 0))
{
Element schemaElem = schemasElem.addElement("schema");
schemaElem.addAttribute("name", schemaName);
}
}
}
finally
{
if (result != null)
{
result.close();
}
}
}
/**
* Dumps all tables.
*
* @param parent The parent element
* @param metaData The database metadata
*/
private void dumpTables(Element parent, DatabaseMetaData metaData) throws SQLException
{
String[] tableTypes = _tableTypes;
ResultSet result = null;
if ((tableTypes == null) || (tableTypes.length == 0))
{
// First we need the list of supported table types
ArrayList tableTypeList = new ArrayList();
result = metaData.getTableTypes();
try
{
while (result.next())
{
tableTypeList.add(getString(result, "TABLE_TYPE"));
}
}
finally
{
if (result != null)
{
result.close();
}
}
tableTypes = (String[])tableTypeList.toArray(new String[tableTypeList.size()]);
}
try
{
result = metaData.getTables(_catalogPattern, _schemaPattern, _tablePattern, tableTypes);
}
catch (SQLException ex)
{
log("Could not determine the tables: "+ex.getMessage(), Project.MSG_ERR);
return;
}
Element tablesElem = parent.addElement("tables");
Set columns = getColumnsInResultSet(result);
try
{
while (result.next())
{
String tableName = getString(result, "TABLE_NAME");
if ((tableName == null) || (tableName.length() == 0))
{
continue;
}
Element tableElem = tablesElem.addElement("table");
String catalog = getString(result, "TABLE_CAT");
String schema = getString(result, "TABLE_SCHEM");
log("Reading table " + ((schema != null) && (schema.length() > 0) ? schema + "." : "") + tableName, Project.MSG_INFO);
tableElem.addAttribute("name", tableName);
if (catalog != null)
{
tableElem.addAttribute("catalog", catalog);
}
if (schema != null)
{
tableElem.addAttribute("schema", schema);
}
addStringAttribute(result, columns, "TABLE_TYPE", tableElem, "type");
addStringAttribute(result, columns, "REMARKS", tableElem, "remarks");
addStringAttribute(result, columns, "TYPE_NAME", tableElem, "typeName");
addStringAttribute(result, columns, "TYPE_CAT", tableElem, "typeCatalog");
addStringAttribute(result, columns, "TYPE_SCHEM", tableElem, "typeSchema");
addStringAttribute(result, columns, "SELF_REFERENCING_COL_NAME", tableElem, "identifierColumn");
addStringAttribute(result, columns, "REF_GENERATION", tableElem, "identifierGeneration");
dumpColumns(tableElem, metaData, catalog, schema, tableName);
dumpPKs(tableElem, metaData, catalog, schema, tableName);
dumpVersionColumns(tableElem, metaData, catalog, schema, tableName);
dumpFKs(tableElem, metaData, catalog, schema, tableName);
dumpIndices(tableElem, metaData, catalog, schema, tableName);
}
}
finally
{
if (result != null)
{
result.close();
}
}
}
/**
* Dumps the columns of the indicated table.
*
* @param tableElem The XML element for the table
* @param metaData The database metadata
* @param catalogName The catalog name
* @param schemaName The schema name
* @param tableName The table name
*/
private void dumpColumns(Element tableElem, DatabaseMetaData metaData, String catalogName, String schemaName, String tableName) throws SQLException
{
ResultSet result = null;
try
{
result = metaData.getColumns(catalogName, schemaName, tableName, _columnPattern);
}
catch (SQLException ex)
{
log("Could not determine the columns for table '"+tableName+"': "+ex.getMessage(), Project.MSG_ERR);
return;
}
Set columns = getColumnsInResultSet(result);
try
{
while (result.next())
{
String columnName = getString(result, "COLUMN_NAME");
if ((columnName == null) || (columnName.length() == 0))
{
continue;
}
Element columnElem = tableElem.addElement("column");
columnElem.addAttribute("name", columnName);
addIntAttribute(result, columns, "DATA_TYPE", columnElem, "typeCode");
addStringAttribute(result, columns, "TYPE_NAME", columnElem, "type");
addIntAttribute(result, columns, "COLUMN_SIZE", columnElem, "size");
addIntAttribute(result, columns, "DECIMAL_DIGITS", columnElem, "digits");
addIntAttribute(result, columns, "NUM_PREC_RADIX", columnElem, "precision");
if (columns.contains("NULLABLE"))
{
switch (result.getInt("NULLABLE"))
{
case DatabaseMetaData.columnNoNulls:
columnElem.addAttribute("nullable", "false");
break;
case DatabaseMetaData.columnNullable:
columnElem.addAttribute("nullable", "true");
break;
default:
columnElem.addAttribute("nullable", "unknown");
break;
}
}
addStringAttribute(result, columns, "REMARKS", columnElem, "remarks");
addStringAttribute(result, columns, "COLUMN_DEF", columnElem, "defaultValue");
addIntAttribute(result, columns, "CHAR_OCTET_LENGTH", columnElem, "maxByteLength");
addIntAttribute(result, columns, "ORDINAL_POSITION", columnElem, "index");
if (columns.contains("IS_NULLABLE"))
{
String value = getString(result, "IS_NULLABLE");
if ("no".equalsIgnoreCase(value))
{
columnElem.addAttribute("isNullable", "false");
}
else if ("yes".equalsIgnoreCase(value))
{
columnElem.addAttribute("isNullable", "true");
}
else
{
columnElem.addAttribute("isNullable", "unknown");
}
}
addStringAttribute(result, columns, "SCOPE_CATLOG", columnElem, "refCatalog");
addStringAttribute(result, columns, "SCOPE_SCHEMA", columnElem, "refSchema");
addStringAttribute(result, columns, "SCOPE_TABLE", columnElem, "refTable");
addShortAttribute(result, columns, "SOURCE_DATA_TYPE", columnElem, "sourceTypeCode");
}
}
finally
{
if (result != null)
{
result.close();
}
}
}
/**
* Dumps the primary key columns of the indicated table.
*
* @param tableElem The XML element for the table
* @param metaData The database metadata
* @param catalogName The catalog name
* @param schemaName The schema name
* @param tableName The table name
*/
private void dumpPKs(Element tableElem, DatabaseMetaData metaData, String catalogName, String schemaName, String tableName) throws SQLException
{
ResultSet result = null;
try
{
result = metaData.getPrimaryKeys(catalogName, schemaName, tableName);
}
catch (SQLException ex)
{
log("Could not determine the primary key columns for table '"+tableName+"': "+ex.getMessage(), Project.MSG_ERR);
return;
}
Set columns = getColumnsInResultSet(result);
try
{
while (result.next())
{
String columnName = getString(result, "COLUMN_NAME");
if ((columnName == null) || (columnName.length() == 0))
{
continue;
}
Element pkElem = tableElem.addElement("primaryKey");
pkElem.addAttribute("column", columnName);
addStringAttribute(result, columns, "PK_NAME", pkElem, "name");
addShortAttribute(result, columns, "KEY_SEQ", pkElem, "sequenceNumberInPK");
}
}
finally
{
if (result != null)
{
result.close();
}
}
}
/**
* Dumps the versioned (auto-updating) columns of the indicated table.
*
* @param tableElem The XML element for the table
* @param metaData The database metadata
* @param catalogName The catalog name
* @param schemaName The schema name
* @param tableName The table name
*/
private void dumpVersionColumns(Element tableElem, DatabaseMetaData metaData, String catalogName, String schemaName, String tableName) throws SQLException
{
ResultSet result = null;
try
{
result = metaData.getVersionColumns(catalogName, schemaName, tableName);
}
catch (SQLException ex)
{
log("Could not determine the versioned columns for table '"+tableName+"': "+ex.getMessage(), Project.MSG_ERR);
return;
}
Set columns = getColumnsInResultSet(result);
try
{
while (result.next())
{
String columnName = getString(result, "COLUMN_NAME");
if ((columnName == null) || (columnName.length() == 0))
{
continue;
}
Element columnElem = tableElem.addElement("versionedColumn");
columnElem.addAttribute("column", columnName);
addIntAttribute(result, columns, "DATA_TYPE", columnElem, "typeCode");
addStringAttribute(result, columns, "TYPE_NAME", columnElem, "type");
addIntAttribute(result, columns, "BUFFER_LENGTH", columnElem, "size");
addIntAttribute(result, columns, "COLUMN_SIZE", columnElem, "precision");
addShortAttribute(result, columns, "DECIMAL_DIGITS", columnElem, "scale");
if (columns.contains("PSEUDO_COLUMN"))
{
switch (result.getShort("PSEUDO_COLUMN"))
{
case DatabaseMetaData.versionColumnPseudo:
columnElem.addAttribute("columnType", "pseudo column");
break;
case DatabaseMetaData.versionColumnNotPseudo:
columnElem.addAttribute("columnType", "real column");
break;
default:
columnElem.addAttribute("columnType", "unknown");
break;
}
}
}
}
finally
{
if (result != null)
{
result.close();
}
}
}
/**
* Dumps the foreign key columns of the indicated table to other tables.
*
* @param tableElem The XML element for the table
* @param metaData The database metadata
* @param catalogName The catalog name
* @param schemaName The schema name
* @param tableName The table name
*/
private void dumpFKs(Element tableElem, DatabaseMetaData metaData, String catalogName, String schemaName, String tableName) throws SQLException
{
ResultSet result = null;
try
{
result = metaData.getImportedKeys(catalogName, schemaName, tableName);
}
catch (SQLException ex)
{
log("Could not determine the foreign keys for table '"+tableName+"': "+ex.getMessage(), Project.MSG_ERR);
return;
}
Set columns = getColumnsInResultSet(result);
try
{
while (result.next())
{
Element fkElem = tableElem.addElement("foreignKey");
addStringAttribute(result, columns, "FK_NAME", fkElem, "name");
addStringAttribute(result, columns, "PK_NAME", fkElem, "primaryKeyName");
addStringAttribute(result, columns, "PKCOLUMN_NAME", fkElem, "column");
addStringAttribute(result, columns, "FKTABLE_CAT", fkElem, "foreignCatalog");
addStringAttribute(result, columns, "FKTABLE_SCHEM", fkElem, "foreignSchema");
addStringAttribute(result, columns, "FKTABLE_NAME", fkElem, "foreignTable");
addStringAttribute(result, columns, "FKCOLUMN_NAME", fkElem, "foreignColumn");
addShortAttribute(result, columns, "KEY_SEQ", fkElem, "sequenceNumberInFK");
if (columns.contains("UPDATE_RULE"))
{
switch (result.getShort("UPDATE_RULE"))
{
case DatabaseMetaData.importedKeyNoAction:
fkElem.addAttribute("updateRule", "no action");
break;
case DatabaseMetaData.importedKeyCascade:
fkElem.addAttribute("updateRule", "cascade PK change");
break;
case DatabaseMetaData.importedKeySetNull:
fkElem.addAttribute("updateRule", "set FK to NULL");
break;
case DatabaseMetaData.importedKeySetDefault:
fkElem.addAttribute("updateRule", "set FK to default");
break;
default:
fkElem.addAttribute("updateRule", "unknown");
break;
}
}
if (columns.contains("DELETE_RULE"))
{
switch (result.getShort("DELETE_RULE"))
{
case DatabaseMetaData.importedKeyNoAction:
case DatabaseMetaData.importedKeyRestrict:
fkElem.addAttribute("deleteRule", "no action");
break;
case DatabaseMetaData.importedKeyCascade:
fkElem.addAttribute("deleteRule", "cascade PK change");
break;
case DatabaseMetaData.importedKeySetNull:
fkElem.addAttribute("deleteRule", "set FK to NULL");
break;
case DatabaseMetaData.importedKeySetDefault:
fkElem.addAttribute("deleteRule", "set FK to default");
break;
default:
fkElem.addAttribute("deleteRule", "unknown");
break;
}
}
if (columns.contains("DEFERRABILITY"))
{
switch (result.getShort("DEFERRABILITY"))
{
case DatabaseMetaData.importedKeyInitiallyDeferred:
fkElem.addAttribute("deferrability", "initially deferred");
break;
case DatabaseMetaData.importedKeyInitiallyImmediate:
fkElem.addAttribute("deferrability", "immediately deferred");
break;
case DatabaseMetaData.importedKeyNotDeferrable:
fkElem.addAttribute("deferrability", "not deferred");
break;
default:
fkElem.addAttribute("deferrability", "unknown");
break;
}
}
}
}
finally
{
if (result != null)
{
result.close();
}
}
}
/**
* Dumps the indices of the indicated table.
*
* @param tableElem The XML element for the table
* @param metaData The database metadata
* @param catalogName The catalog name
* @param schemaName The schema name
* @param tableName The table name
*/
private void dumpIndices(Element tableElem, DatabaseMetaData metaData, String catalogName, String schemaName, String tableName) throws SQLException
{
ResultSet result = null;
try
{
result = metaData.getIndexInfo(catalogName, schemaName, tableName, false, false);
}
catch (SQLException ex)
{
log("Could not determine the indices for table '"+tableName+"': "+ex.getMessage(), Project.MSG_ERR);
return;
}
Set columns = getColumnsInResultSet(result);
try
{
while (result.next())
{
Element indexElem = tableElem.addElement("index");
addStringAttribute(result, columns, "INDEX_NAME", indexElem, "name");
addBooleanAttribute(result, columns, "NON_UNIQUE", indexElem, "nonUnique");
addStringAttribute(result, columns, "INDEX_QUALIFIER", indexElem, "indexCatalog");
if (columns.contains("TYPE"))
{
switch (result.getShort("TYPE"))
{
case DatabaseMetaData.tableIndexStatistic:
indexElem.addAttribute("type", "table statistics");
break;
case DatabaseMetaData.tableIndexClustered:
indexElem.addAttribute("type", "clustered");
break;
case DatabaseMetaData.tableIndexHashed:
indexElem.addAttribute("type", "hashed");
break;
case DatabaseMetaData.tableIndexOther:
indexElem.addAttribute("type", "other");
break;
default:
indexElem.addAttribute("type", "unknown");
break;
}
}
addStringAttribute(result, columns, "COLUMN_NAME", indexElem, "column");
addShortAttribute(result, columns, "ORDINAL_POSITION", indexElem, "sequenceNumberInIndex");
if (columns.contains("ASC_OR_DESC"))
{
String value = getString(result, "ASC_OR_DESC");
if ("A".equalsIgnoreCase(value))
{
indexElem.addAttribute("sortOrder", "ascending");
}
else if ("D".equalsIgnoreCase(value))
{
indexElem.addAttribute("sortOrder", "descending");
}
else
{
indexElem.addAttribute("sortOrder", "unknown");
}
}
addIntAttribute(result, columns, "CARDINALITY", indexElem, "cardinality");
addIntAttribute(result, columns, "PAGES", indexElem, "pages");
addStringAttribute(result, columns, "FILTER_CONDITION", indexElem, "filter");
}
}
finally
{
if (result != null)
{
result.close();
}
}
}
/**
* Dumps all procedures.
*
* @param parent The parent element
* @param metaData The database metadata
*/
private void dumpProcedures(Element parent, DatabaseMetaData metaData) throws SQLException
{
ResultSet result = null;
try
{
result = metaData.getProcedures(_catalogPattern, _schemaPattern, _procedurePattern);
}
catch (SQLException ex)
{
log("Could not determine the procedures: "+ex.getMessage(), Project.MSG_ERR);
return;
}
Element proceduresElem = parent.addElement("procedures");
Set columns = getColumnsInResultSet(result);
try
{
while (result.next())
{
String procedureName = getString(result, "PROCEDURE_NAME");
if ((procedureName == null) || (procedureName.length() == 0))
{
continue;
}
Element procedureElem = proceduresElem.addElement("procedure");
String catalog = getString(result, "PROCEDURE_CAT");
String schema = getString(result, "PROCEDURE_SCHEM");
log("Reading procedure " + ((schema != null) && (schema.length() > 0) ? schema + "." : "") + procedureName, Project.MSG_INFO);
procedureElem.addAttribute("name", procedureName);
if (catalog != null)
{
procedureElem.addAttribute("catalog", catalog);
}
if (schema != null)
{
procedureElem.addAttribute("schema", schema);
}
addStringAttribute(result, columns, "REMARKS", procedureElem, "remarks");
if (columns.contains("PROCEDURE_TYPE"))
{
switch (result.getShort("PROCEDURE_TYPE"))
{
case DatabaseMetaData.procedureReturnsResult:
procedureElem.addAttribute("type", "returns result");
break;
case DatabaseMetaData.procedureNoResult:
procedureElem.addAttribute("type", "doesn't return result");
break;
case DatabaseMetaData.procedureResultUnknown:
procedureElem.addAttribute("type", "may return result");
break;
default:
procedureElem.addAttribute("type", "unknown");
break;
}
}
dumpProcedure(procedureElem, metaData, "%", "%", procedureName);
}
}
finally
{
if (result != null)
{
result.close();
}
}
}
/**
* Dumps the contents of the indicated procedure.
*
* @param procedureElem The XML element for the procedure
* @param metaData The database metadata
* @param catalogName The catalog name
* @param schemaName The schema name
* @param procedureName The procedure name
*/
private void dumpProcedure(Element procedureElem, DatabaseMetaData metaData, String catalogName, String schemaName, String procedureName) throws SQLException
{
ResultSet result = null;
try
{
result = metaData.getProcedureColumns(catalogName, schemaName, procedureName, _columnPattern);
}
catch (SQLException ex)
{
log("Could not determine the columns for procedure '"+procedureName+"': "+ex.getMessage(), Project.MSG_ERR);
return;
}
Set columns = getColumnsInResultSet(result);
try
{
while (result.next())
{
String columnName = getString(result, "COLUMN_NAME");
if ((columnName == null) || (columnName.length() == 0))
{
continue;
}
Element columnElem = procedureElem.addElement("column");
columnElem.addAttribute("name", columnName);
if (columns.contains("COLUMN_TYPE"))
{
switch (result.getShort("COLUMN_TYPE"))
{
case DatabaseMetaData.procedureColumnIn:
columnElem.addAttribute("type", "in parameter");
break;
case DatabaseMetaData.procedureColumnInOut:
columnElem.addAttribute("type", "in/out parameter");
break;
case DatabaseMetaData.procedureColumnOut:
columnElem.addAttribute("type", "out parameter");
break;
case DatabaseMetaData.procedureColumnReturn:
columnElem.addAttribute("type", "return value");
break;
case DatabaseMetaData.procedureColumnResult:
columnElem.addAttribute("type", "result column in ResultSet");
break;
default:
columnElem.addAttribute("type", "unknown");
break;
}
}
addIntAttribute(result, columns, "DATA_TYPE", columnElem, "typeCode");
addStringAttribute(result, columns, "TYPE_NAME", columnElem, "type");
addIntAttribute(result, columns, "LENGTH", columnElem, "length");
addIntAttribute(result, columns, "PRECISION", columnElem, "precision");
addShortAttribute(result, columns, "SCALE", columnElem, "short");
addShortAttribute(result, columns, "RADIX", columnElem, "radix");
if (columns.contains("NULLABLE"))
{
switch (result.getInt("NULLABLE"))
{
case DatabaseMetaData.procedureNoNulls:
columnElem.addAttribute("nullable", "false");
break;
case DatabaseMetaData.procedureNullable:
columnElem.addAttribute("nullable", "true");
break;
default:
columnElem.addAttribute("nullable", "unknown");
break;
}
}
addStringAttribute(result, columns, "REMARKS", columnElem, "remarks");
}
}
finally
{
if (result != null)
{
result.close();
}
}
}
/**
* If the result set contains the indicated column, extracts its value and sets an attribute at the given element.
*
* @param result The result set
* @param columns The columns in the result set
* @param columnName The name of the column in the result set
* @param element The element to add the attribute
* @param attrName The name of the attribute to set
* @return The string value or <code>null</code>
*/
private String addStringAttribute(ResultSet result, Set columns, String columnName, Element element, String attrName) throws SQLException
{
String value = null;
if (columns.contains(columnName))
{
value = getString(result, columnName);
element.addAttribute(attrName, value);
}
return value;
}
/**
* If the result set contains the indicated column, extracts its int value and sets an attribute at the given element.
*
* @param result The result set
* @param columns The columns in the result set
* @param columnName The name of the column in the result set
* @param element The element to add the attribute
* @param attrName The name of the attribute to set
* @return The string value or <code>null</code>
*/
private String addIntAttribute(ResultSet result, Set columns, String columnName, Element element, String attrName) throws SQLException
{
String value = null;
if (columns.contains(columnName))
{
try
{
value = String.valueOf(result.getInt(columnName));
}
catch (SQLException ex)
{
// A few databases do not comply with the jdbc spec and return a string (or null),
// so lets try this just in case
value = result.getString(columnName);
if (value != null)
{
try
{
Integer.parseInt(value);
}
catch (NumberFormatException parseEx)
{
// its no int returned as a string, so lets re-throw the original exception
throw ex;
}
}
}
element.addAttribute(attrName, value);
}
return value;
}
/**
* If the result set contains the indicated column, extracts its short value and sets an attribute at the given element.
*
* @param result The result set
* @param columns The columns in the result set
* @param columnName The name of the column in the result set
* @param element The element to add the attribute
* @param attrName The name of the attribute to set
* @return The string value or <code>null</code>
*/
private String addShortAttribute(ResultSet result, Set columns, String columnName, Element element, String attrName) throws SQLException
{
String value = null;
if (columns.contains(columnName))
{
try
{
value = String.valueOf(result.getShort(columnName));
}
catch (SQLException ex)
{
// A few databases do not comply with the jdbc spec and return a string (or null),
// so lets try strings this just in case
value = result.getString(columnName);
if (value != null)
{
try
{
Short.parseShort(value);
}
catch (NumberFormatException parseEx)
{
// its no short returned as a string, so lets re-throw the original exception
throw ex;
}
}
}
element.addAttribute(attrName, value);
}
return value;
}
/**
* If the result set contains the indicated column, extracts its boolean value and sets an attribute at the given element.
*
* @param result The result set
* @param columns The columns in the result set
* @param columnName The name of the column in the result set
* @param element The element to add the attribute
* @param attrName The name of the attribute to set
* @return The string value or <code>null</code>
*/
private String addBooleanAttribute(ResultSet result, Set columns, String columnName, Element element, String attrName) throws SQLException
{
String value = null;
if (columns.contains(columnName))
{
value = String.valueOf(result.getBoolean(columnName));
element.addAttribute(attrName, value);
}
return value;
}
/**
* Extracts a string from the result set.
*
* @param result The result set
* @param columnName The name of the column in the result set
* @return The string value
*/
private String getString(ResultSet result, String columnName) throws SQLException
{
return result.getString(columnName);
}
/**
* Determines the columns that are present in the given result set.
*
* @param resultSet The result set
* @return The columns
*/
private Set getColumnsInResultSet(ResultSet resultSet) throws SQLException
{
ListOrderedSet result = new ListOrderedSet();
ResultSetMetaData metaData = resultSet.getMetaData();
for (int idx = 1; idx <= metaData.getColumnCount(); idx++)
{
result.add(metaData.getColumnName(idx).toUpperCase());
}
return result;
}
}