blob: 139310c685275bde0cc02ff301fb65a85f9513d6 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 1998, 2012 Oracle. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Oracle - initial API and implementation from Oracle TopLink
******************************************************************************/
package org.eclipse.persistence.tools.db.relational.spi.jdbc;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import org.eclipse.persistence.tools.db.relational.spi.ExternalDatabase;
import org.eclipse.persistence.tools.db.relational.spi.ExternalTableDescription;
import org.eclipse.persistence.tools.utility.ObjectTools;
import org.eclipse.persistence.tools.utility.collection.ListTools;
import org.eclipse.persistence.tools.utility.iterator.ResultSetIterator;
/**
* This external database derives all of its metadata from a client-supplied JDBC connection.
*
* All the JDBC implementation classes have an embarassing
* preponderance of "defensive" programming that allow us to
* work with even the most misbehaved of JDBC drivers. This is
* because most of the exceptions encountered are the result
* of bugs in the wide variety of JDBC drivers we interact with,
* not bugs in Workbench code. (At least that's the hope....)
* An alternative to the various bits of defensive programming,
* we could pass in a listener whenever there is the possibility
* of an (SQL) exception. The listener would be notified of any
* exceptions, but we would continue processing with pre-
* determined defaults.
* ~bjv
*
* @version 2.6
*/
@SuppressWarnings("nls")
final class JDBCExternalDatabase implements ExternalDatabase {
private final Connection connection;
private static final String[] EMPTY_STRING_ARRAY = new String[0];
/**
*
*/
JDBCExternalDatabase(Connection connection) {
super();
this.connection = connection;
}
// ********** ExternalDatabase implementation **********
/**
* @see org.eclipse.persistence.tools.db.relational.spi.ExternalDatabase#getCatalogNames()
*/
@Override
public String[] getCatalogNames() {
ResultSet resultSet;
try {
resultSet = this.metaData().getCatalogs();
} catch (SQLException ex) {
return EMPTY_STRING_ARRAY; // defensive - this is not fatal if unsupported by driver
}
return this.buildStringArray(resultSet);
}
/**
* @see org.eclipse.persistence.tools.db.relational.spi.ExternalDatabase#getSchemaNames()
*/
@Override
public String[] getSchemaNames() {
ResultSet resultSet;
try {
resultSet = this.metaData().getSchemas();
} catch (SQLException ex) {
return EMPTY_STRING_ARRAY; // defensive - this is not fatal if unsupported by driver
}
return this.buildStringArray(resultSet);
}
/**
* @see org.eclipse.persistence.tools.db.relational.spi.ExternalDatabase#getTableTypeNames()
*/
@Override
public String[] getTableTypeNames() {
ResultSet resultSet;
try {
resultSet = this.metaData().getTableTypes();
} catch (SQLException ex) {
return EMPTY_STRING_ARRAY; // defensive - this is not fatal if unsupported by driver
}
return this.buildStringArray(resultSet);
}
/**
* @see org.eclipse.persistence.tools.db.relational.spi.ExternalDatabase#getTableDescriptions(String, String, String, String[])
*/
@Override
public ExternalTableDescription[] getTableDescriptions(String catalogName, String schemaNamePattern, String tableNamePattern, String[] tableTypeNames) {
// #setCatalog(String) is used by Sybase, MS SQL Server, and others(?)
// to set a "local context" for later interactions with the server;
// "If the driver does not support catalogs, it will silently ignore this request."
if (catalogName != null) {
try {
this.getConnection().setCatalog(catalogName);
} catch (SQLException ex) {
// see comment above...
throw new RuntimeException(ex);
}
}
ResultSet resultSet;
try {
resultSet = this.metaData().getTables(catalogName, schemaNamePattern, tableNamePattern, tableTypeNames);
} catch (SQLException ex) {
// if #getTables() does not work, there's not much we can do...
throw new RuntimeException(ex);
}
List<ExternalTableDescription> tableDescriptions = ListTools.list(new ResultSetIterator<ExternalTableDescription>(
resultSet,
new ExternalTableDescriptionResultSetAdapter())
);
return tableDescriptions.toArray(new ExternalTableDescription[tableDescriptions.size()]);
}
/**
* @see org.eclipse.persistence.tools.db.relational.spi.ExternalDatabase#getTableDescriptions()
*/
@Override
public ExternalTableDescription[] getTableDescriptions() {
// query for *all* the tables on the database - could be painful...
return this.getTableDescriptions(null, null, "%", null);
}
// ********** queries **********
private Connection getConnection() {
if (this.connection == null) {
throw new IllegalStateException("not connected");
}
return this.connection;
}
DatabaseMetaData metaData() {
try {
return this.getConnection().getMetaData();
} catch (SQLException ex) {
// if we can't get meta-data, there's not much we can do...
throw new RuntimeException(ex);
}
}
/**
* @see Object#toString()
*/
@Override
public String toString() {
return ObjectTools.toString(this);
}
// ********** behavior **********
/**
* convert the specified single-column result set to an array of strings
*/
private String[] buildStringArray(ResultSet resultSet) {
List<String> strings = ListTools.list(new ResultSetIterator<String>(resultSet, new StringResultSetAdapter()));
return strings.toArray(new String[strings.size()]);
}
// ********** inner classes **********
/**
* Trim a single-column result set of strings.
*/
private static class StringResultSetAdapter implements ResultSetIterator.Adapter<String> {
@Override
public String buildNext(ResultSet rs) throws SQLException {
// result set column indexes are 1-based
String string = rs.getString(1);
// * Returns fixed-length strings
return (string == null) ? null : string.trim();
}
}
/**
* Convert the rows into external table descriptions.
* @see java.sql.DatabaseMetaData#getTables(String, String, String, String[])
*/
private class ExternalTableDescriptionResultSetAdapter implements ResultSetIterator.Adapter<ExternalTableDescription> {
@Override
public ExternalTableDescription buildNext(ResultSet rs) throws SQLException {
return new JDBCExternalTableDescription(rs, JDBCExternalDatabase.this);
}
}
}