blob: c5f2ac1921398513c42c41dc166dea131912f04f [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.io.IOException;
import java.io.OutputStream;
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.ddlutils.io.PrettyPrintingXmlWriter;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
/**
* 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 {@link DatabaseMetaData#getTables(String, String, String, String[])}.
*
* @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 {@link DatabaseMetaData#getProcedures(String, String, String)}.
*
* @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 {@link DatabaseMetaData#getColumns(String, String, String, String)}.
*
* @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 {@link DatabaseMetaData#getTables(String, String, String, String[])}.
*
* @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;
OutputStream output = null;
try
{
connection = _dataSource.getConnection();
if (_outputFile == null)
{
output = System.out;
}
else
{
output = new FileOutputStream(_outputFile);
}
PrettyPrintingXmlWriter xmlWriter = new PrettyPrintingXmlWriter(output, _outputEncoding);
xmlWriter.writeDocumentStart();
xmlWriter.writeElementStart(null, "metadata");
xmlWriter.writeAttribute(null, "driverClassName", _dataSource.getDriverClassName());
dumpMetaData(xmlWriter, connection.getMetaData());
xmlWriter.writeDocumentEnd();
}
catch (Exception ex)
{
throw new BuildException(ex);
}
finally
{
if (connection != null)
{
try
{
connection.close();
}
catch (SQLException ex)
{}
}
if ((_outputFile != null) && (output != null))
{
try
{
output.close();
}
catch (IOException 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(PrettyPrintingXmlWriter xmlWriter, 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(xmlWriter, metaData, methods[idx]);
}
}
dumpCatalogsAndSchemas(xmlWriter, metaData);
if (_dumpTables)
{
dumpTables(xmlWriter, metaData);
}
if (_dumpProcedures)
{
dumpProcedures(xmlWriter, 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(PrettyPrintingXmlWriter xmlWriter, Object obj, Method propGetter)
{
try
{
addProperty(xmlWriter, getPropertyName(propGetter.getName()), propGetter.invoke(obj, null));
}
catch (Throwable ex)
{
log("Could not dump property "+propGetter.getName()+": "+ex.getStackTrace(), Project.MSG_ERR);
}
}
/**
* 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(PrettyPrintingXmlWriter xmlWriter, String name, Object value)
{
if (value != null)
{
if (value.getClass().isArray())
{
addArrayProperty(xmlWriter, name, (Object[])value);
}
else if (value.getClass().isPrimitive() || (value instanceof String))
{
xmlWriter.writeAttribute(null, name, value.toString());
}
else if (value instanceof ResultSet)
{
addResultSetProperty(xmlWriter, 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(PrettyPrintingXmlWriter xmlWriter, String name, Object[] values)
{
String propName = name;
if (propName.endsWith("s"))
{
propName = propName.substring(0, propName.length() - 1);
}
xmlWriter.writeElementStart(null, propName + "s");
for (int idx = 0; idx < values.length; idx++)
{
addProperty(xmlWriter, "value", values[idx]);
}
xmlWriter.writeElementEnd();
}
/**
* 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(PrettyPrintingXmlWriter xmlWriter, String name, ResultSet result)
{
String propName = name;
if (propName.endsWith("s"))
{
propName = propName.substring(0, propName.length() - 1);
}
try
{
ResultSetMetaData metaData = result.getMetaData();
xmlWriter.writeElementStart(null, propName + "s");
try
{
while (result.next())
{
xmlWriter.writeElementStart(null, propName);
try
{
for (int idx = 1; idx <= metaData.getColumnCount(); idx++)
{
Object value = result.getObject(idx);
addProperty(xmlWriter, metaData.getColumnLabel(idx), value);
}
}
finally
{
xmlWriter.writeElementEnd();
}
}
}
finally
{
xmlWriter.writeElementEnd();
}
}
catch (SQLException ex)
{
log("Could not read the result set metadata: "+ex.getStackTrace(), Project.MSG_ERR);
}
}
/**
* 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;
}
}
private static interface ResultSetXmlOperation
{
public ResultSet getResultSet() throws SQLException;
public void handleRow(PrettyPrintingXmlWriter xmlWriter, ResultSet result) throws SQLException;
public void handleError(SQLException ex);
}
private void performResultSetXmlOperation(PrettyPrintingXmlWriter xmlWriter, String name, ResultSetXmlOperation op)
{
ResultSet result = null;
try
{
result = op.getResultSet();
if (name != null)
{
xmlWriter.writeElementStart(null, name);
}
try
{
while (result.next())
{
op.handleRow(xmlWriter, result);
}
}
finally
{
if (name != null)
{
xmlWriter.writeElementEnd();
}
}
}
catch (SQLException ex)
{
op.handleError(ex);
}
finally
{
if (result != null)
{
try
{
result.close();
}
catch (SQLException ex)
{
log("Could not close a result set: " + ex.getStackTrace(), Project.MSG_ERR);
}
}
}
}
/**
* Dumps the catalogs and schemas of the database.
*
* @param parent The parent element
* @param metaData The database meta data
*/
private void dumpCatalogsAndSchemas(PrettyPrintingXmlWriter xmlWriter, final DatabaseMetaData metaData)
{
performResultSetXmlOperation(xmlWriter, "catalogs", new ResultSetXmlOperation()
{
public ResultSet getResultSet() throws SQLException
{
return metaData.getCatalogs();
}
public void handleRow(PrettyPrintingXmlWriter xmlWriter, ResultSet result) throws SQLException
{
String catalogName = result.getString("TABLE_CAT");
if ((catalogName != null) && (catalogName.length() > 0))
{
xmlWriter.writeElementStart(null, "catalog");
xmlWriter.writeAttribute(null, "name", catalogName);
xmlWriter.writeElementEnd();
}
}
public void handleError(SQLException ex)
{
log("Could not read the catalogs from the result set: " + ex.getStackTrace(), Project.MSG_ERR);
}
});
performResultSetXmlOperation(xmlWriter, "schemas", new ResultSetXmlOperation()
{
public ResultSet getResultSet() throws SQLException
{
return metaData.getSchemas();
}
public void handleRow(PrettyPrintingXmlWriter xmlWriter, ResultSet result) throws SQLException
{
String schemaName = result.getString("TABLE_SCHEM");
if ((schemaName != null) && (schemaName.length() > 0))
{
xmlWriter.writeElementStart(null, "schema");
xmlWriter.writeAttribute(null, "name", schemaName);
xmlWriter.writeElementEnd();
}
}
public void handleError(SQLException ex)
{
log("Could not read the schemas from the result set: " + ex.getStackTrace(), Project.MSG_ERR);
}
});
}
/**
* Dumps all tables.
*
* @param parent The parent element
* @param metaData The database metadata
*/
private void dumpTables(PrettyPrintingXmlWriter xmlWriter, final DatabaseMetaData metaData)
{
// First we need the list of supported table types
final ArrayList tableTypeList = new ArrayList();
performResultSetXmlOperation(xmlWriter, "tableTypes", new ResultSetXmlOperation()
{
public ResultSet getResultSet() throws SQLException
{
return metaData.getTableTypes();
}
public void handleRow(PrettyPrintingXmlWriter xmlWriter, ResultSet result) throws SQLException
{
String tableType = result.getString("TABLE_TYPE");
tableTypeList.add(tableType);
xmlWriter.writeElementStart(null, "tableType");
xmlWriter.writeAttribute(null, "name", tableType);
xmlWriter.writeElementEnd();
}
public void handleError(SQLException ex)
{
log("Could not read the table types from the result set: " + ex.getStackTrace(), Project.MSG_ERR);
}
});
final String[] tableTypesToRead;
if ((_tableTypes == null) || (_tableTypes.length == 0))
{
tableTypesToRead = (String[])tableTypeList.toArray(new String[tableTypeList.size()]);
}
else
{
tableTypesToRead = _tableTypes;
}
performResultSetXmlOperation(xmlWriter, "tables", new ResultSetXmlOperation()
{
public ResultSet getResultSet() throws SQLException
{
return metaData.getTables(_catalogPattern, _schemaPattern, _tablePattern, tableTypesToRead);
}
public void handleRow(PrettyPrintingXmlWriter xmlWriter, ResultSet result) throws SQLException
{
Set columns = getColumnsInResultSet(result);
String tableName = result.getString("TABLE_NAME");
if ((tableName != null) && (tableName.length() > 0))
{
String catalog = result.getString("TABLE_CAT");
String schema = result.getString("TABLE_SCHEM");
log("Reading table " + ((schema != null) && (schema.length() > 0) ? schema + "." : "") + tableName, Project.MSG_INFO);
xmlWriter.writeElementStart(null, "table");
xmlWriter.writeAttribute(null, "name", tableName);
if (catalog != null)
{
xmlWriter.writeAttribute(null, "catalog", catalog);
}
if (schema != null)
{
xmlWriter.writeAttribute(null, "schema", schema);
}
addStringAttribute(xmlWriter, "type", result, columns, "TABLE_TYPE");
addStringAttribute(xmlWriter, "remarks", result, columns, "REMARKS");
addStringAttribute(xmlWriter, "typeName", result, columns, "TYPE_NAME");
addStringAttribute(xmlWriter, "typeCatalog", result, columns, "TYPE_CAT");
addStringAttribute(xmlWriter, "typeSchema", result, columns, "TYPE_SCHEM");
addStringAttribute(xmlWriter, "identifierColumn", result, columns, "SELF_REFERENCING_COL_NAME");
addStringAttribute(xmlWriter, "identifierGeneration", result, columns, "REF_GENERATION");
dumpColumns(xmlWriter, metaData, catalog, schema, tableName);
dumpPKs(xmlWriter, metaData, catalog, schema, tableName);
dumpVersionColumns(xmlWriter, metaData, catalog, schema, tableName);
dumpFKs(xmlWriter, metaData, catalog, schema, tableName);
dumpIndexes(xmlWriter, metaData, catalog, schema, tableName);
xmlWriter.writeElementEnd();
}
}
public void handleError(SQLException ex)
{
log("Could not read the tables from the result set: " + ex.getStackTrace(), Project.MSG_ERR);
}
});
}
/**
* 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(PrettyPrintingXmlWriter xmlWriter,
final DatabaseMetaData metaData,
final String catalogName,
final String schemaName,
final String tableName) throws SQLException
{
performResultSetXmlOperation(xmlWriter, null, new ResultSetXmlOperation()
{
public ResultSet getResultSet() throws SQLException
{
return metaData.getColumns(catalogName, schemaName, tableName, _columnPattern);
}
public void handleRow(PrettyPrintingXmlWriter xmlWriter, ResultSet result) throws SQLException
{
Set columns = getColumnsInResultSet(result);
String columnName = result.getString("COLUMN_NAME");
if ((columnName != null) && (columnName.length() > 0))
{
xmlWriter.writeElementStart(null, "column");
xmlWriter.writeAttribute(null, "name", columnName);
addIntAttribute(xmlWriter, "typeCode", result, columns, "DATA_TYPE");
addStringAttribute(xmlWriter, "type", result, columns, "TYPE_NAME");
addIntAttribute(xmlWriter, "size", result, columns, "COLUMN_SIZE");
addIntAttribute(xmlWriter, "digits", result, columns, "DECIMAL_DIGITS");
addIntAttribute(xmlWriter, "precision", result, columns, "NUM_PREC_RADIX");
if (columns.contains("NULLABLE"))
{
try
{
switch (result.getInt("NULLABLE"))
{
case DatabaseMetaData.columnNoNulls:
xmlWriter.writeAttribute(null, "nullable", "false");
break;
case DatabaseMetaData.columnNullable:
xmlWriter.writeAttribute(null, "nullable", "true");
break;
default:
xmlWriter.writeAttribute(null, "nullable", "unknown");
break;
}
}
catch (SQLException ex)
{
log("Could not read the NULLABLE value for colum '" + columnName + "' of table '" + tableName + "' from the result set: " + ex.getStackTrace(), Project.MSG_ERR);
}
}
addStringAttribute(xmlWriter, "remarks", result, columns, "REMARKS");
addStringAttribute(xmlWriter, "defaultValue", result, columns, "COLUMN_DEF");
addIntAttribute(xmlWriter, "maxByteLength", result, columns, "CHAR_OCTET_LENGTH");
addIntAttribute(xmlWriter, "index", result, columns, "ORDINAL_POSITION");
if (columns.contains("IS_NULLABLE"))
{
try
{
String value = result.getString("IS_NULLABLE");
if ("no".equalsIgnoreCase(value))
{
xmlWriter.writeAttribute(null, "isNullable", "false");
}
else if ("yes".equalsIgnoreCase(value))
{
xmlWriter.writeAttribute(null, "isNullable", "true");
}
else
{
xmlWriter.writeAttribute(null, "isNullable", "unknown");
}
}
catch (SQLException ex)
{
log("Could not read the IS_NULLABLE value for colum '" + columnName + "' of table '" + tableName + "' from the result set: " + ex.getStackTrace(), Project.MSG_ERR);
}
}
addStringAttribute(xmlWriter, "refCatalog", result, columns, "SCOPE_CATLOG");
addStringAttribute(xmlWriter, "refSchema", result, columns, "SCOPE_SCHEMA");
addStringAttribute(xmlWriter, "refTable", result, columns, "SCOPE_TABLE");
addShortAttribute(xmlWriter, "sourceTypeCode", result, columns, "SOURCE_DATA_TYPE");
xmlWriter.writeElementEnd();
}
}
public void handleError(SQLException ex)
{
log("Could not read the colums for table '" + tableName + "' from the result set: "+ex.getStackTrace(), Project.MSG_ERR);
}
});
}
/**
* 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(PrettyPrintingXmlWriter xmlWriter,
final DatabaseMetaData metaData,
final String catalogName,
final String schemaName,
final String tableName) throws SQLException
{
performResultSetXmlOperation(xmlWriter, null, new ResultSetXmlOperation()
{
public ResultSet getResultSet() throws SQLException
{
return metaData.getPrimaryKeys(catalogName, schemaName, tableName);
}
public void handleRow(PrettyPrintingXmlWriter xmlWriter, ResultSet result) throws SQLException
{
Set columns = getColumnsInResultSet(result);
String columnName = result.getString("COLUMN_NAME");
if ((columnName != null) && (columnName.length() > 0))
{
xmlWriter.writeElementStart(null, "primaryKey");
xmlWriter.writeAttribute(null, "column", columnName);
addStringAttribute(xmlWriter, "name", result, columns, "PK_NAME");
addShortAttribute(xmlWriter, "sequenceNumberInPK", result, columns, "KEY_SEQ");
xmlWriter.writeElementEnd();
}
}
public void handleError(SQLException ex)
{
log("Could not read the primary keys for table '" + tableName + "' from the result set: " + ex.getStackTrace(), Project.MSG_ERR);
}
});
}
/**
* 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(PrettyPrintingXmlWriter xmlWriter,
final DatabaseMetaData metaData,
final String catalogName,
final String schemaName,
final String tableName) throws SQLException
{
performResultSetXmlOperation(xmlWriter, null, new ResultSetXmlOperation()
{
public ResultSet getResultSet() throws SQLException
{
return metaData.getVersionColumns(catalogName, schemaName, tableName);
}
public void handleRow(PrettyPrintingXmlWriter xmlWriter, ResultSet result) throws SQLException
{
Set columns = getColumnsInResultSet(result);
String columnName = result.getString("COLUMN_NAME");
if ((columnName != null) && (columnName.length() > 0))
{
xmlWriter.writeElementStart(null, "versionedColumn");
xmlWriter.writeAttribute(null, "column", columnName);
addIntAttribute(xmlWriter, "typeCode", result, columns, "DATA_TYPE");
addStringAttribute(xmlWriter, "type", result, columns, "TYPE_NAME");
addIntAttribute(xmlWriter, "size", result, columns, "BUFFER_LENGTH");
addIntAttribute(xmlWriter, "precision", result, columns, "COLUMN_SIZE");
addShortAttribute(xmlWriter, "scale", result, columns, "DECIMAL_DIGITS");
if (columns.contains("PSEUDO_COLUMN"))
{
try
{
switch (result.getShort("PSEUDO_COLUMN"))
{
case DatabaseMetaData.versionColumnPseudo:
xmlWriter.writeAttribute(null, "columnType", "pseudo column");
break;
case DatabaseMetaData.versionColumnNotPseudo:
xmlWriter.writeAttribute(null, "columnType", "real column");
break;
default:
xmlWriter.writeAttribute(null, "columnType", "unknown");
break;
}
}
catch (SQLException ex)
{
log("Could not read the PSEUDO_COLUMN value for versioned colum '" + columnName + "' of table '" + tableName + "' from the result set: " + ex.getStackTrace(), Project.MSG_ERR);
}
}
xmlWriter.writeElementEnd();
}
}
public void handleError(SQLException ex)
{
log("Could not read the versioned columns for table '" + tableName + "' from the result set: " + ex.getStackTrace(), Project.MSG_ERR);
}
});
}
/**
* 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(PrettyPrintingXmlWriter xmlWriter,
final DatabaseMetaData metaData,
final String catalogName,
final String schemaName,
final String tableName) throws SQLException
{
performResultSetXmlOperation(xmlWriter, null, new ResultSetXmlOperation()
{
public ResultSet getResultSet() throws SQLException
{
return metaData.getImportedKeys(catalogName, schemaName, tableName);
}
public void handleRow(PrettyPrintingXmlWriter xmlWriter, ResultSet result) throws SQLException
{
Set columns = getColumnsInResultSet(result);
xmlWriter.writeElementStart(null, "foreignKey");
addStringAttribute(xmlWriter, "name", result, columns, "FK_NAME");
addStringAttribute(xmlWriter, "primaryKeyName", result, columns, "PK_NAME");
addStringAttribute(xmlWriter, "column", result, columns, "PKCOLUMN_NAME");
addStringAttribute(xmlWriter, "foreignCatalog", result, columns, "FKTABLE_CAT");
addStringAttribute(xmlWriter, "foreignSchema", result, columns, "FKTABLE_SCHEM");
addStringAttribute(xmlWriter, "foreignTable", result, columns, "FKTABLE_NAME");
addStringAttribute(xmlWriter, "foreignColumn", result, columns, "FKCOLUMN_NAME");
addShortAttribute(xmlWriter, "sequenceNumberInFK", result, columns, "KEY_SEQ");
if (columns.contains("UPDATE_RULE"))
{
try
{
switch (result.getShort("UPDATE_RULE"))
{
case DatabaseMetaData.importedKeyNoAction:
xmlWriter.writeAttribute(null, "updateRule", "no action");
break;
case DatabaseMetaData.importedKeyCascade:
xmlWriter.writeAttribute(null, "updateRule", "cascade PK change");
break;
case DatabaseMetaData.importedKeySetNull:
xmlWriter.writeAttribute(null, "updateRule", "set FK to NULL");
break;
case DatabaseMetaData.importedKeySetDefault:
xmlWriter.writeAttribute(null, "updateRule", "set FK to default");
break;
default:
xmlWriter.writeAttribute(null, "updateRule", "unknown");
break;
}
}
catch (SQLException ex)
{
log("Could not read the UPDATE_RULE value for a foreign key of table '" + tableName + "' from the result set: " + ex.getStackTrace(), Project.MSG_ERR);
}
}
if (columns.contains("DELETE_RULE"))
{
try
{
switch (result.getShort("DELETE_RULE"))
{
case DatabaseMetaData.importedKeyNoAction:
case DatabaseMetaData.importedKeyRestrict:
xmlWriter.writeAttribute(null, "deleteRule", "no action");
break;
case DatabaseMetaData.importedKeyCascade:
xmlWriter.writeAttribute(null, "deleteRule", "cascade PK change");
break;
case DatabaseMetaData.importedKeySetNull:
xmlWriter.writeAttribute(null, "deleteRule", "set FK to NULL");
break;
case DatabaseMetaData.importedKeySetDefault:
xmlWriter.writeAttribute(null, "deleteRule", "set FK to default");
break;
default:
xmlWriter.writeAttribute(null, "deleteRule", "unknown");
break;
}
}
catch (SQLException ex)
{
log("Could not read the DELETE_RULE value for a foreign key of table '" + tableName + "' from the result set: " + ex.getStackTrace(), Project.MSG_ERR);
}
}
if (columns.contains("DEFERRABILITY"))
{
try
{
switch (result.getShort("DEFERRABILITY"))
{
case DatabaseMetaData.importedKeyInitiallyDeferred:
xmlWriter.writeAttribute(null, "deferrability", "initially deferred");
break;
case DatabaseMetaData.importedKeyInitiallyImmediate:
xmlWriter.writeAttribute(null, "deferrability", "immediately deferred");
break;
case DatabaseMetaData.importedKeyNotDeferrable:
xmlWriter.writeAttribute(null, "deferrability", "not deferred");
break;
default:
xmlWriter.writeAttribute(null, "deferrability", "unknown");
break;
}
}
catch (SQLException ex)
{
log("Could not read the DEFERRABILITY value for a foreign key of table '" + tableName + "' from the result set: " + ex.getStackTrace(), Project.MSG_ERR);
}
}
xmlWriter.writeElementEnd();
}
public void handleError(SQLException ex)
{
log("Could not determine the foreign keys for table '" + tableName + "': " + ex.getStackTrace(), Project.MSG_ERR);
}
});
}
/**
* Dumps the indexes 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 dumpIndexes(PrettyPrintingXmlWriter xmlWriter,
final DatabaseMetaData metaData,
final String catalogName,
final String schemaName,
final String tableName) throws SQLException
{
performResultSetXmlOperation(xmlWriter, null, new ResultSetXmlOperation()
{
public ResultSet getResultSet() throws SQLException
{
return metaData.getIndexInfo(catalogName, schemaName, tableName, false, false);
}
public void handleRow(PrettyPrintingXmlWriter xmlWriter, ResultSet result) throws SQLException
{
Set columns = getColumnsInResultSet(result);
xmlWriter.writeElementStart(null, "index");
addStringAttribute(xmlWriter, "name", result, columns, "INDEX_NAME");
addBooleanAttribute(xmlWriter, "nonUnique", result, columns, "NON_UNIQUE");
addStringAttribute(xmlWriter, "indexCatalog", result, columns, "INDEX_QUALIFIER");
if (columns.contains("TYPE"))
{
try
{
switch (result.getShort("TYPE"))
{
case DatabaseMetaData.tableIndexStatistic:
xmlWriter.writeAttribute(null, "type", "table statistics");
break;
case DatabaseMetaData.tableIndexClustered:
xmlWriter.writeAttribute(null, "type", "clustered");
break;
case DatabaseMetaData.tableIndexHashed:
xmlWriter.writeAttribute(null, "type", "hashed");
break;
case DatabaseMetaData.tableIndexOther:
xmlWriter.writeAttribute(null, "type", "other");
break;
default:
xmlWriter.writeAttribute(null, "type", "unknown");
break;
}
}
catch (SQLException ex)
{
log("Could not read the TYPE value for an index of table '" + tableName + "' from the result set: " + ex.getStackTrace(), Project.MSG_ERR);
}
}
addStringAttribute(xmlWriter, "column", result, columns, "COLUMN_NAME");
addShortAttribute(xmlWriter, "sequenceNumberInIndex", result, columns, "ORDINAL_POSITION");
if (columns.contains("ASC_OR_DESC"))
{
try
{
String value = result.getString("ASC_OR_DESC");
if ("A".equalsIgnoreCase(value))
{
xmlWriter.writeAttribute(null, "sortOrder", "ascending");
}
else if ("D".equalsIgnoreCase(value))
{
xmlWriter.writeAttribute(null, "sortOrder", "descending");
}
else
{
xmlWriter.writeAttribute(null, "sortOrder", "unknown");
}
}
catch (SQLException ex)
{
log("Could not read the ASC_OR_DESC value for an index of table '" + tableName + "' from the result set: " + ex.getStackTrace(), Project.MSG_ERR);
}
}
addIntAttribute(xmlWriter, "cardinality", result, columns, "CARDINALITY");
addIntAttribute(xmlWriter, "pages", result, columns, "PAGES");
addStringAttribute(xmlWriter, "filter", result, columns, "FILTER_CONDITION");
}
public void handleError(SQLException ex)
{
log("Could not read the indexes for table '" + tableName + "' from the result set: " + ex.getStackTrace(), Project.MSG_ERR);
}
});
}
/**
* Dumps all procedures.
*
* @param parent The parent element
* @param metaData The database metadata
*/
private void dumpProcedures(PrettyPrintingXmlWriter xmlWriter, final DatabaseMetaData metaData) throws SQLException
{
performResultSetXmlOperation(xmlWriter, "procedures", new ResultSetXmlOperation()
{
public ResultSet getResultSet() throws SQLException
{
return metaData.getProcedures(_catalogPattern, _schemaPattern, _procedurePattern);
}
public void handleRow(PrettyPrintingXmlWriter xmlWriter, ResultSet result) throws SQLException
{
Set columns = getColumnsInResultSet(result);
String procedureName = result.getString("PROCEDURE_NAME");
if ((procedureName != null) && (procedureName.length() > 0))
{
String catalog = result.getString("PROCEDURE_CAT");
String schema = result.getString("PROCEDURE_SCHEM");
log("Reading procedure " + ((schema != null) && (schema.length() > 0) ? schema + "." : "") + procedureName, Project.MSG_INFO);
xmlWriter.writeElementStart(null, "procedure");
xmlWriter.writeAttribute(null, "name", procedureName);
if (catalog != null)
{
xmlWriter.writeAttribute(null, "catalog", catalog);
}
if (schema != null)
{
xmlWriter.writeAttribute(null, "schema", schema);
}
addStringAttribute(xmlWriter, "remarks", result, columns, "REMARKS");
if (columns.contains("PROCEDURE_TYPE"))
{
try
{
switch (result.getShort("PROCEDURE_TYPE"))
{
case DatabaseMetaData.procedureReturnsResult:
xmlWriter.writeAttribute(null, "type", "returns result");
break;
case DatabaseMetaData.procedureNoResult:
xmlWriter.writeAttribute(null, "type", "doesn't return result");
break;
case DatabaseMetaData.procedureResultUnknown:
xmlWriter.writeAttribute(null, "type", "may return result");
break;
default:
xmlWriter.writeAttribute(null, "type", "unknown");
break;
}
}
catch (SQLException ex)
{
log("Could not read the PROCEDURE_TYPE value for the procedure '" + procedureName + "' from the result set: " + ex.getStackTrace(), Project.MSG_ERR);
}
}
dumpProcedure(xmlWriter, metaData, "%", "%", procedureName);
xmlWriter.writeElementEnd();
}
}
public void handleError(SQLException ex)
{
log("Could not read the procedures from the result set: " + ex.getStackTrace(), Project.MSG_ERR);
}
});
}
/**
* 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(PrettyPrintingXmlWriter xmlWriter,
final DatabaseMetaData metaData,
final String catalogName,
final String schemaName,
final String procedureName) throws SQLException
{
performResultSetXmlOperation(xmlWriter, null, new ResultSetXmlOperation()
{
public ResultSet getResultSet() throws SQLException
{
return metaData.getProcedureColumns(catalogName, schemaName, procedureName, _columnPattern);
}
public void handleRow(PrettyPrintingXmlWriter xmlWriter, ResultSet result) throws SQLException
{
Set columns = getColumnsInResultSet(result);
String columnName = result.getString("COLUMN_NAME");
if ((columnName != null) && (columnName.length() > 0))
{
xmlWriter.writeElementStart(null, "column");
xmlWriter.writeAttribute(null, "name", columnName);
if (columns.contains("COLUMN_TYPE"))
{
try
{
switch (result.getShort("COLUMN_TYPE"))
{
case DatabaseMetaData.procedureColumnIn:
xmlWriter.writeAttribute(null, "type", "in parameter");
break;
case DatabaseMetaData.procedureColumnInOut:
xmlWriter.writeAttribute(null, "type", "in/out parameter");
break;
case DatabaseMetaData.procedureColumnOut:
xmlWriter.writeAttribute(null, "type", "out parameter");
break;
case DatabaseMetaData.procedureColumnReturn:
xmlWriter.writeAttribute(null, "type", "return value");
break;
case DatabaseMetaData.procedureColumnResult:
xmlWriter.writeAttribute(null, "type", "result column in ResultSet");
break;
default:
xmlWriter.writeAttribute(null, "type", "unknown");
break;
}
}
catch (SQLException ex)
{
log("Could not read the COLUMN_TYPE value for the column '" + columnName + "' of procedure '" + procedureName + "' from the result set: " + ex.getStackTrace(), Project.MSG_ERR);
}
}
addIntAttribute(xmlWriter, "typeCode", result, columns, "DATA_TYPE");
addStringAttribute(xmlWriter, "type", result, columns, "TYPE_NAME");
addIntAttribute(xmlWriter, "length", result, columns, "LENGTH");
addIntAttribute(xmlWriter, "precision", result, columns, "PRECISION");
addShortAttribute(xmlWriter, "short", result, columns, "SCALE");
addShortAttribute(xmlWriter, "radix", result, columns, "RADIX");
if (columns.contains("NULLABLE"))
{
try
{
switch (result.getInt("NULLABLE"))
{
case DatabaseMetaData.procedureNoNulls:
xmlWriter.writeAttribute(null, "nullable", "false");
break;
case DatabaseMetaData.procedureNullable:
xmlWriter.writeAttribute(null, "nullable", "true");
break;
default:
xmlWriter.writeAttribute(null, "nullable", "unknown");
break;
}
}
catch (SQLException ex)
{
log("Could not read the NULLABLE value for the column '" + columnName + "' of procedure '" + procedureName + "' from the result set: " + ex.getStackTrace(), Project.MSG_ERR);
}
}
addStringAttribute(xmlWriter, "remarks", result, columns, "REMARKS");
}
}
public void handleError(SQLException ex)
{
log("Could not read the columns for procedure '"+procedureName+"' from the result set: " + ex.getStackTrace(), Project.MSG_ERR);
}
});
}
/**
* 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
*/
private void addStringAttribute(PrettyPrintingXmlWriter xmlWriter, String attrName, ResultSet result, Set columns, String columnName) throws SQLException
{
if (columns.contains(columnName))
{
try
{
xmlWriter.writeAttribute(null, attrName, result.getString(columnName));
}
catch (SQLException ex)
{
log("Could not read the value from result set column " + columnName + ":" + ex.getStackTrace(), Project.MSG_ERR);
}
}
}
/**
* 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
*/
private void addIntAttribute(PrettyPrintingXmlWriter xmlWriter, String attrName, ResultSet result, Set columns, String columnName) throws SQLException
{
if (columns.contains(columnName))
{
try
{
xmlWriter.writeAttribute(null, attrName, 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
String value = result.getString(columnName);
if (value != null)
{
try
{
xmlWriter.writeAttribute(null, attrName, new Integer(value).toString());
}
catch (NumberFormatException parseEx)
{
log("Could not parse the value from result set column " + columnName + ":" + ex.getStackTrace(), Project.MSG_ERR);
}
}
}
}
}
/**
* 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
*/
private void addShortAttribute(PrettyPrintingXmlWriter xmlWriter, String attrName, ResultSet result, Set columns, String columnName) throws SQLException
{
if (columns.contains(columnName))
{
try
{
xmlWriter.writeAttribute(null, attrName, 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
String value = result.getString(columnName);
if (value != null)
{
try
{
xmlWriter.writeAttribute(null, attrName, new Short(value).toString());
}
catch (NumberFormatException parseEx)
{
log("Could not parse the value from result set column " + columnName + ":" + ex.getStackTrace(), Project.MSG_ERR);
}
}
}
}
}
/**
* 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
*/
private void addBooleanAttribute(PrettyPrintingXmlWriter xmlWriter, String attrName, ResultSet result, Set columns, String columnName) throws SQLException
{
if (columns.contains(columnName))
{
try
{
xmlWriter.writeAttribute(null, attrName, String.valueOf(result.getBoolean(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
String value = result.getString(columnName);
if (value != null)
{
try
{
xmlWriter.writeAttribute(null, attrName, new Boolean(value).toString());
}
catch (NumberFormatException parseEx)
{
log("Could not parse the value from result set column " + columnName + ":" + ex.getStackTrace(), Project.MSG_ERR);
}
}
}
}
}
/**
* 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;
}
}