blob: a799ae73f0933a3b91e63e7012415f80f6813e24 [file] [log] [blame]
/*
* 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.
*/
package org.apache.openjpa.jdbc.schema;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EventObject;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.sql.DataSource;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.identifier.DBIdentifier;
import org.apache.openjpa.jdbc.identifier.DBIdentifier.DBIdentifierType;
import org.apache.openjpa.jdbc.identifier.Normalizer;
import org.apache.openjpa.jdbc.identifier.QualifiedDBIdentifier;
import org.apache.openjpa.jdbc.sql.DBDictionary;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.util.Localizer;
/**
* The SchemaGenerator creates {@link Schema}s matching the current
* database. All schemas are added to the current {@link SchemaGroup}.
* Note that tables whose name starts with "OPENJPA_" will be not be added
* to the database schema. This enables the creation of special tables
* that will never be dropped by the {@link SchemaTool}.
*
* @author Abe White
*/
public class SchemaGenerator {
private static final Localizer _loc = Localizer.forPackage
(SchemaGenerator.class);
private final DataSource _ds;
private final DBDictionary _dict;
private final Log _log;
private final Object[][] _allowed;
private boolean _indexes = true;
private boolean _fks = true;
private boolean _pks = true;
private boolean _seqs = true;
private boolean _openjpaTables = true;
private SchemaGroup _group = null;
private List<Listener> _listeners = null;
private int _schemaObjects = 0;
private Connection _conn = null;
/**
* Constructor.
*
* @param conf configuration for connecting to the data store
*/
public SchemaGenerator(JDBCConfiguration conf) {
// note: we cannot assert developer capabilities in this tool like
// we normally do because this class is also used at runtime
_ds = conf.getDataSource2(null);
_log = conf.getLog(JDBCConfiguration.LOG_SCHEMA);
// cache this now so that if the conn pool only has 1 connection we
// don't conflict later with the open databasemetadata connection
_dict = conf.getDBDictionaryInstance();
// create a table of allowed schema and tables to reflect on
String[] schemaArray = conf.getSchemasList();
DBIdentifier[] names = new DBIdentifier[schemaArray == null ? 0 : schemaArray.length];
for (int i = 0; i < names.length; i++) {
String[] splitName = Normalizer.splitName(schemaArray[i]);
if (splitName == null || splitName.length == 0) {
continue;
}
if (splitName.length == 1) {
names[i] = DBIdentifier.newSchema(schemaArray[i]);
} else {
names[i] = DBIdentifier.newTable(schemaArray[i]);
}
}
_allowed = parseSchemasList(names);
}
/**
* Given a list of schema names and table names (where table names
* are always of the form schema.table, or just .table if the schema is
* unknown), creates a table mapping schema name to table list. Returns
* null if no args are given. If no tables are given for a particular
* schema, maps the schema name to null.
*/
private static Object[][] parseSchemasList(DBIdentifier[] args) {
if (args == null || args.length == 0)
return null;
Map<DBIdentifier, Collection<DBIdentifier>> schemas = new HashMap<>();
DBIdentifier schema = DBIdentifier.NULL, table = DBIdentifier.NULL;
Collection<DBIdentifier> tables = null;
for (DBIdentifier arg : args) {
QualifiedDBIdentifier path = QualifiedDBIdentifier.getPath(arg);
schema = path.getSchemaName();
table = path.getIdentifier();
// if just a schema name, map schema to null
if (DBIdentifier.isNull(table) && !schemas.containsKey(schema))
schemas.put(schema, null);
else if (!DBIdentifier.isNull(table)) {
tables = schemas.computeIfAbsent(schema, k -> new LinkedList<>());
tables.add(table);
}
}
Object[][] parsed = new Object[schemas.size()][2];
Map.Entry<DBIdentifier, Collection<DBIdentifier>> entry;
int idx = 0;
for (Map.Entry<DBIdentifier, Collection<DBIdentifier>> dbIdentifierCollectionEntry : schemas.entrySet()) {
entry = dbIdentifierCollectionEntry;
tables = entry.getValue();
parsed[idx][0] = entry.getKey();
if (tables != null)
parsed[idx][1] = tables.toArray(new DBIdentifier[tables.size()]);
idx++;
}
return parsed;
}
/**
* Return whether indexes should be generated. Defaults to true.
*/
public boolean getIndexes() {
return _indexes;
}
/**
* Set whether indexes should be generated. Defaults to true.
*/
public void setIndexes(boolean indexes) {
_indexes = indexes;
}
/**
* Return whether foreign keys should be generated. Defaults to true.
*/
public boolean getForeignKeys() {
return _fks;
}
/**
* Set whether foreign keys should be generated. Defaults to true.
*/
public void setForeignKeys(boolean fks) {
_fks = fks;
}
/**
* Return whether primary keys should be generated. Defaults to true.
*/
public boolean getPrimaryKeys() {
return _pks;
}
/**
* Set whether primary keys should be generated. Defaults to true.
*/
public void setPrimaryKeys(boolean pks) {
_pks = pks;
}
/**
* Return whether sequences should be generated. Defaults to true.
*/
public boolean getSequences() {
return _seqs;
}
/**
* Set whether sequences should be generated. Defaults to true.
*/
public void setSequences(boolean seqs) {
_seqs = seqs;
}
/**
* Whether to generate info on special tables used by OpenJPA components
* for bookkeeping. Defaults to true.
*/
public boolean getOpenJPATables() {
return _openjpaTables;
}
/**
* Whether to generate info on special tables used by OpenJPA components
* for bookkeeping. Defaults to true.
*/
public void setOpenJPATables(boolean openjpaTables) {
_openjpaTables = openjpaTables;
}
/**
* Return the current schema group.
*/
public SchemaGroup getSchemaGroup() {
if (_group == null)
_group = new SchemaGroup();
return _group;
}
/**
* Set the schema group to add generated schemas to.
*/
public void setSchemaGroup(SchemaGroup group) {
_group = group;
}
/**
* Generate all schemas set in the configuration. This method also
* calls {@link #generateIndexes}, {@link #generatePrimaryKeys}, and
* {@link #generateForeignKeys} automatically.
*/
public void generateSchemas()
throws SQLException {
fireGenerationEvent(_loc.get("generating-schemas"));
generateSchemas((DBIdentifier[])null);
}
/**
* @deprecated
*/
@Deprecated
public void generateSchemas(String[] schemasAndTables)
throws SQLException {
generateSchemas(DBIdentifier.toArray(schemasAndTables, DBIdentifierType.TABLE));
}
/**
* Generate the schemas and/or tables named in the given strings.
* This method calls {@link #generateIndexes},
* {@link #generatePrimaryKeys}, and {@link #generateForeignKeys}
* automatically.
*/
public void generateSchemas(DBIdentifier[] schemasAndTables) throws SQLException {
fireGenerationEvent(_loc.get("generating-schemas"));
// generate all schemas and tables
try {
getConn();
Object[][] schemaMap;
if (schemasAndTables == null || schemasAndTables.length == 0)
schemaMap = _allowed;
else
schemaMap = parseSchemasList(schemasAndTables);
if (schemaMap == null) {
generateSchema(DBIdentifier.NULL, (DBIdentifier[]) null);
// estimate the number of schema objects we will need to visit
// in order to estimate progress total for any listeners
int numTables = getTables(null).size();
_schemaObjects +=
numTables + (_pks ? numTables : 0) + (_indexes ? numTables : 0) + (_fks ? numTables : 0);
if (_pks)
generatePrimaryKeys(DBIdentifier.NULL, null);
if (_indexes)
generateIndexes(DBIdentifier.NULL, null);
if (_fks)
generateForeignKeys(DBIdentifier.NULL, null);
return;
}
for (Object[] value : schemaMap) {
generateSchema((DBIdentifier) value[0], (DBIdentifier[]) value[1]);
}
// generate pks, indexes, fks
DBIdentifier schemaName = DBIdentifier.NULL;
DBIdentifier[] tableNames;
for (Object[] objects : schemaMap) {
schemaName = (DBIdentifier) objects[0];
tableNames = (DBIdentifier[]) objects[1];
// estimate the number of schema objects we will need to visit
// in order to estimate progress total for any listeners
int numTables = (tableNames != null) ? tableNames.length : getTables(schemaName).size();
_schemaObjects +=
numTables + (_pks ? numTables : 0) + (_indexes ? numTables : 0) + (_fks ? numTables : 0);
if (_pks) {
generatePrimaryKeys(schemaName, tableNames);
}
if (_indexes) {
generateIndexes(schemaName, tableNames);
}
if (_fks) {
generateForeignKeys(schemaName, tableNames);
}
}
} finally {
closeConn();
}
}
/**
* @param name
* @param tableNames
* @deprecated
*/
@Deprecated
public void generateSchema(String name, String[] tableNames)
throws SQLException {
generateSchema(DBIdentifier.newSchema(name),
DBIdentifier.toArray(tableNames, DBIdentifierType.TABLE));
}
/**
* Add a fully-constructed {@link Schema} matching the given database
* schema to the current group. No foreign keys are generated because
* some keys might span schemas. You must call
* {@link #generatePrimaryKeys}, {@link #generateIndexes}, and
* {@link #generateForeignKeys} separately.
*
* @param name the database name of the schema, or null for all schemas
* @param tableNames a list of tables to generate in the schema, or null
* to generate all tables
*/
public void generateSchema(DBIdentifier name, DBIdentifier[] tableNames)
throws SQLException {
fireGenerationEvent(_loc.get("generating-schema", name));
// generate tables, including columns and primary keys
DatabaseMetaData meta = _conn.getMetaData();
try {
if (tableNames == null)
generateTables(name, DBIdentifier.NULL, _conn, meta);
else
for (DBIdentifier tableName : tableNames) {
generateTables(name, tableName, _conn, meta);
}
if (_seqs) {
generateSequences(name, DBIdentifier.NULL, _conn, meta);
}
} finally {
// some databases require a commit after metadata to release locks
try {
_conn.commit();
} catch (SQLException se) {
}
}
}
/**
* Generate primary key information for the given schema. This method
* must be called in addition to {@link #generateSchema}. It should
* only be called after all schemas are generated. The schema name and
* tables array can be null to indicate that indexes should be generated
* for all schemas and/or tables.
* @deprecated
*/
@Deprecated
public void generatePrimaryKeys(String schemaName, String[] tableNames)
throws SQLException {
generatePrimaryKeys(DBIdentifier.newSchema(schemaName),
DBIdentifier.toArray(tableNames, DBIdentifierType.TABLE));
}
/**
* Generate primary key information for the given schema. This method
* must be called in addition to {@link #generateSchema}. It should
* only be called after all schemas are generated. The schema name and
* tables array can be null to indicate that indexes should be generated
* for all schemas and/or tables.
*/
public void generatePrimaryKeys(DBIdentifier schemaName, DBIdentifier[] tableNames)
throws SQLException {
fireGenerationEvent(_loc.get("generating-all-primaries", schemaName));
DatabaseMetaData meta = _conn.getMetaData();
try {
if (tableNames == null)
generatePrimaryKeys(schemaName, null, _conn, meta);
else
for (DBIdentifier tableName : tableNames) {
generatePrimaryKeys(schemaName, tableName, _conn, meta);
}
} finally {
// some databases require a commit after metadata to release locks
try {
_conn.commit();
} catch (SQLException se) {
}
}
}
/**
* Generate index information for the given schema. This method
* must be called in addition to {@link #generateSchema}. It should
* only be called after all schemas are generated. The schema name and
* tables array can be null to indicate that indexes should be generated
* for all schemas and/or tables.
* @deprecated
*/
@Deprecated
public void generateIndexes(String schemaName, String[] tableNames)
throws SQLException {
generateIndexes(DBIdentifier.newSchema(schemaName),
DBIdentifier.toArray(tableNames, DBIdentifierType.TABLE));
}
/**
* Generate index information for the given schema. This method
* must be called in addition to {@link #generateSchema}. It should
* only be called after all schemas are generated. The schema name and
* tables array can be null to indicate that indexes should be generated
* for all schemas and/or tables.
*/
public void generateIndexes(DBIdentifier schemaName, DBIdentifier[] tableNames)
throws SQLException {
fireGenerationEvent(_loc.get("generating-all-indexes", schemaName));
DatabaseMetaData meta = _conn.getMetaData();
try {
if (tableNames == null)
generateIndexes(schemaName, null, _conn, meta);
else
for (DBIdentifier tableName : tableNames) {
generateIndexes(schemaName, tableName, _conn, meta);
}
} finally {
// some databases require a commit after metadata to release locks
try {
_conn.commit();
} catch (SQLException se) {
}
}
}
/**
* Generate foreign key information for the given schema. This method
* must be called in addition to {@link #generateSchema}. It should
* only be called after all schemas are generated. The schema name and
* tables array can be null to indicate that indexes should be generated
* for all schemas and/or tables.
* @deprecated
*/
@Deprecated
public void generateForeignKeys(String schemaName, String[] tableNames)
throws SQLException {
generateForeignKeys(DBIdentifier.newSchema(schemaName),
DBIdentifier.toArray(tableNames, DBIdentifierType.TABLE));
}
/**
* Generate foreign key information for the given schema. This method
* must be called in addition to {@link #generateSchema}. It should
* only be called after all schemas are generated. The schema name and
* tables array can be null to indicate that indexes should be generated
* for all schemas and/or tables.
*/
public void generateForeignKeys(DBIdentifier schemaName, DBIdentifier[] tableNames)
throws SQLException {
fireGenerationEvent(_loc.get("generating-all-foreigns", schemaName));
DatabaseMetaData meta = _conn.getMetaData();
try {
if (tableNames == null)
generateForeignKeys(schemaName, null, _conn, meta);
else
for (DBIdentifier tableName : tableNames) {
generateForeignKeys(schemaName, tableName, _conn, meta);
}
} finally {
// some databases require a commit after metadata to release locks
try {
_conn.commit();
} catch (SQLException se) {
}
}
}
/**
* @deprecated
*/
@Deprecated
public void generateTables(String schemaName, String tableName,
Connection conn, DatabaseMetaData meta)
throws SQLException {
generateTables(DBIdentifier.newSchema(schemaName),
DBIdentifier.newTable(tableName), conn, meta);
}
/**
* Adds all tables matching the given name pattern to the schema.
*/
public void generateTables(DBIdentifier schemaName, DBIdentifier tableName,
Connection conn, DatabaseMetaData meta)
throws SQLException {
fireGenerationEvent(_loc.get("generating-columns", schemaName,
tableName));
if (_log.isTraceEnabled())
_log.trace(_loc.get("gen-tables", schemaName, tableName));
Column[] cols = _dict.getColumns(meta,
DBIdentifier.newCatalog(conn.getCatalog()), schemaName,
tableName, null, conn);
// when we want to get all the columns for all tables, we need to build
// a list of tables to verify because some databases (e.g., Postgres)
// will include indexes in the list of columns, and there is no way to
// distinguish the indexes from proper columns
Set<DBIdentifier> tableNames = null;
if (DBIdentifier.isNull(tableName) || "%".equals(tableName.getName())) {
Table[] tables = _dict.getTables(meta, DBIdentifier.newCatalog(conn.getCatalog()),
schemaName, tableName, conn);
tableNames = new HashSet<>();
for (int i = 0; tables != null && i < tables.length; i++) {
if (cols == null) {
tableNames.add(tables[i].getIdentifier());
}
else {
DBIdentifier sName = DBIdentifier.toUpper(tables[i].getIdentifier());
tableNames.add(sName);
}
}
}
// if database can't handle null table name, recurse on each known name
if (cols == null && DBIdentifier.isNull(tableName)) {
for (DBIdentifier name : tableNames) {
generateTables(schemaName, name, conn, meta);
}
return;
}
SchemaGroup group = getSchemaGroup();
Schema schema;
Table table;
DBIdentifier tableSchema = DBIdentifier.NULL;
DBIdentifier baseTableName = (tableName == null) ? DBIdentifier.NULL : tableName.clone();
for (int i = 0; cols != null && i < cols.length; i++) {
if (DBIdentifier.isNull(baseTableName) || baseTableName.equals("%")) {
tableName = cols[i].getTableIdentifier();
} else {
tableName = baseTableName;
}
if (DBIdentifier.isNull(schemaName)) {
tableSchema = DBIdentifier.trimToNull(cols[i].getSchemaIdentifier());
}
else {
tableSchema = schemaName;
}
// ignore special tables
if (!_openjpaTables &&
(tableName.getName().toUpperCase(Locale.ENGLISH).startsWith("OPENJPA_")
|| tableName.getName().toUpperCase(Locale.ENGLISH).startsWith("JDO_"))) // legacy
continue;
if (_dict.isSystemTable(tableName, tableSchema, !DBIdentifier.isNull(schemaName)))
continue;
// ignore tables not in list, or not allowed by schemas property
if (tableNames != null
&& !tableNames.contains(DBIdentifier.toUpper(tableName)))
continue;
if (!isAllowedTable(tableSchema, tableName))
continue;
schema = group.getSchema(tableSchema);
if (schema == null)
schema = group.addSchema(tableSchema);
table = schema.getTable(tableName);
if (table == null) {
table = schema.addTable(tableName);
if (_log.isTraceEnabled())
_log.trace(_loc.get("col-table", table));
}
if (_log.isTraceEnabled())
_log.trace(_loc.get("gen-column", cols[i].getIdentifier(), table));
if (table.getColumn(cols[i].getIdentifier()) == null) {
table.importColumn(cols[i]);
}
}
}
/**
* Return whether the given table is allowed by the user's schema list.
*/
private boolean isAllowedTable(DBIdentifier schema, DBIdentifier table) {
if (_allowed == null)
return true;
// do case-insensitive comparison on allowed table and schema names
DBIdentifier[] tables;
DBIdentifier[] anySchemaTables = null;
for (Object[] objects : _allowed) {
if (objects[0] == null) {
anySchemaTables = (DBIdentifier[]) objects[1];
if (schema == null)
break;
continue;
}
if (!schema.equals(objects[0]))
continue;
if (table == null)
return true;
tables = (DBIdentifier[]) objects[1];
if (tables == null)
return true;
for (DBIdentifier dbIdentifier : tables)
if (table.equals(dbIdentifier))
return true;
}
if (anySchemaTables != null) {
if (table == null)
return true;
for (DBIdentifier anySchemaTable : anySchemaTables)
if (table.equals(anySchemaTable))
return true;
}
return false;
}
/**
* Generates table primary keys.
* @deprecated
*/
@Deprecated
public void generatePrimaryKeys(String schemaName, String tableName,
Connection conn, DatabaseMetaData meta)
throws SQLException {
generatePrimaryKeys(DBIdentifier.newSchema(schemaName), DBIdentifier.newTable(tableName),
conn, meta);
}
public void generatePrimaryKeys(DBIdentifier schemaName, DBIdentifier tableName,
Connection conn, DatabaseMetaData meta)
throws SQLException {
fireGenerationEvent(_loc.get("generating-primary",
schemaName, tableName));
if (_log.isTraceEnabled())
_log.trace(_loc.get("gen-pks", schemaName, tableName));
// if looking for a non-existant table, just return
SchemaGroup group = getSchemaGroup();
if (tableName != null && !tableName.isNull() &&
group.findTable(QualifiedDBIdentifier.getPath(tableName)) == null)
return;
// if the database can't use a table name wildcard, recurse on each
// concrete table in the requested schema(s)
PrimaryKey[] pks = _dict.getPrimaryKeys(meta,
DBIdentifier.newCatalog(conn.getCatalog()),
schemaName, tableName, conn);
Table table;
if (pks == null && tableName == null) {
Collection<Table> tables = getTables(schemaName);
for (Table value : tables) {
table = value;
generatePrimaryKeys(table.getSchemaIdentifier(),
table.getIdentifier(), conn, meta);
}
return;
}
Schema schema;
PrimaryKey pk;
DBIdentifier name = DBIdentifier.NULL;
DBIdentifier colName = DBIdentifier.NULL;
for (int i = 0; pks != null && i < pks.length; i++) {
schemaName = DBIdentifier.trimToNull(pks[i].getSchemaIdentifier());
schema = group.getSchema(schemaName);
if (schema == null)
continue;
table = schema.getTable(pks[i].getTableIdentifier());
if (table == null)
continue;
colName = pks[i].getColumnIdentifier();
name = pks[i].getIdentifier();
if (_log.isTraceEnabled())
_log.trace(_loc.get("gen-pk", name, table, colName));
pk = table.getPrimaryKey();
if (pk == null)
pk = table.addPrimaryKey(name);
pk.addColumn(table.getColumn(colName));
}
}
/**
* Generates table indexes.
* @deprecated
*/
@Deprecated
public void generateIndexes(String schemaName, String tableName,
Connection conn, DatabaseMetaData meta)
throws SQLException {
generateIndexes(DBIdentifier.newSchema(schemaName), DBIdentifier.newTable(tableName),
conn, meta);
}
public void generateIndexes(DBIdentifier schemaName, DBIdentifier tableName,
Connection conn, DatabaseMetaData meta)
throws SQLException {
fireGenerationEvent(_loc.get("generating-indexes",
schemaName, tableName));
if (_log.isTraceEnabled())
_log.trace(_loc.get("gen-indexes", schemaName, tableName));
// if looking for a non-existant table, just return
SchemaGroup group = getSchemaGroup();
if (tableName != null && group.findTable(QualifiedDBIdentifier.getPath(tableName)) == null)
return;
// if the database can't use a table name wildcard, recurse on each
// concrete table in the requested schema(s)
Index[] idxs = _dict.getIndexInfo(meta, DBIdentifier.newCatalog(conn.getCatalog()),
schemaName, tableName, false, true, conn);
Table table;
if (idxs == null && tableName == null) {
Collection<Table> tables = getTables(schemaName);
for (Table value : tables) {
table = value;
generateIndexes(table.getSchemaIdentifier(),
table.getIdentifier(), conn, meta);
}
return;
}
Schema schema;
Index idx;
DBIdentifier name = DBIdentifier.NULL;
DBIdentifier colName = DBIdentifier.NULL;
DBIdentifier pkName = DBIdentifier.NULL;
for (int i = 0; idxs != null && i < idxs.length; i++) {
schemaName = DBIdentifier.trimToNull(idxs[i].getSchemaIdentifier());
schema = group.getSchema(schemaName);
if (schema == null)
continue;
table = schema.getTable(idxs[i].getTableIdentifier());
if (table == null)
continue;
if (table.getPrimaryKey() != null)
pkName = table.getPrimaryKey().getIdentifier();
else
pkName = null;
// statistics don't have names; skip them
name = idxs[i].getIdentifier();
if (DBIdentifier.isEmpty(name)
|| (pkName != null && name.equals(pkName))
|| _dict.isSystemIndex(name, table))
continue;
colName = idxs[i].getColumnIdentifier();
if (table.getColumn(colName) == null)
continue;
if (_log.isTraceEnabled())
_log.trace(_loc.get("gen-index", name, table, colName));
// same index may have multiple rows for multiple cols
idx = table.getIndex(name);
if (idx == null) {
idx = table.addIndex(name);
idx.setUnique(idxs[i].isUnique());
}
idx.addColumn(table.getColumn(colName));
}
}
/**
* Generates table foreign keys.
*/
public void generateForeignKeys(String schemaName, String tableName,
Connection conn, DatabaseMetaData meta)
throws SQLException {
generateForeignKeys(DBIdentifier.newSchema(schemaName), DBIdentifier.newTable(tableName),
conn, meta);
}
public void generateForeignKeys(DBIdentifier schemaName, DBIdentifier tableName,
Connection conn, DatabaseMetaData meta)
throws SQLException {
fireGenerationEvent(_loc.get("generating-foreign",
schemaName, tableName));
if (_log.isTraceEnabled())
_log.trace(_loc.get("gen-fks", schemaName, tableName));
// if looking for a non-existant table, just return
SchemaGroup group = getSchemaGroup();
if (!DBIdentifier.isNull(tableName) && group.findTable(QualifiedDBIdentifier.getPath(tableName)) == null)
return;
// if the database can't use a table name wildcard, recurse on each
// concrete table in the requested schema(s)
ForeignKey[] fks = _dict.getImportedKeys(meta, DBIdentifier.newCatalog(conn.getCatalog()),
schemaName, tableName, conn);
Table table;
if (fks == null && DBIdentifier.isNull(tableName)) {
Collection<Table> tables = getTables(schemaName);
for (Table value : tables) {
table = value;
generateForeignKeys(table.getSchemaIdentifier(),
table.getIdentifier(), conn, meta);
}
return;
}
Schema schema;
Table pkTable;
ForeignKey fk;
DBIdentifier name = DBIdentifier.NULL;
DBIdentifier pkSchemaName = DBIdentifier.NULL;
DBIdentifier pkTableName = DBIdentifier.NULL;
DBIdentifier pkColName = DBIdentifier.NULL;
DBIdentifier fkColName = DBIdentifier.NULL;
int seq;
boolean seqWas0 = false; // some drivers incorrectly start at 0
Collection<ForeignKey> invalids = null;
for (int i = 0; fks != null && i < fks.length; i++) {
schemaName = DBIdentifier.trimToNull(fks[i].getSchemaIdentifier());
schema = group.getSchema(schemaName);
if (schema == null)
continue;
table = schema.getTable(fks[i].getTableIdentifier());
if (table == null)
continue;
name = fks[i].getIdentifier();
fkColName = fks[i].getColumnIdentifier();
pkColName = fks[i].getPrimaryKeyColumnIdentifier();
seq = fks[i].getKeySequence();
if (seq == 0)
seqWas0 = true;
if (seqWas0)
seq++;
// find pk table
pkSchemaName = fks[i].getPrimaryKeySchemaIdentifier();
if(_dict.getTrimSchemaName()) {
pkSchemaName= DBIdentifier.trimToNull(pkSchemaName);
}
pkTableName = fks[i].getPrimaryKeyTableIdentifier();
if (_log.isTraceEnabled())
_log.trace(_loc.get("gen-fk", new Object[]{ name, table,
fkColName, pkTableName, pkColName, seq + "" }));
pkTable = group.findTable(QualifiedDBIdentifier.newPath(pkSchemaName, pkTableName));
if (pkTable == null)
throw new SQLException(_loc.get("gen-nofktable",
table, pkTableName).getMessage());
// this sucks, because it is *not* guaranteed to work;
// the fk resultset is ordered by primary key table, then
// sequence number within the foreign key (some drivers don't
// use sequence numbers correctly either); since fk names
// are allowed to be null, all this adds up to the fact
// that there is absolutely no way to distinguish between
// multiple multi-column fks to the same table; we're going
// to rely on fk name here and hope it works
fk = table.getForeignKey(name);
// start a new fk?
if (seq == 1 || fk == null) {
fk = table.addForeignKey(name);
fk.setDeferred(fks[i].isDeferred());
fk.setDeleteAction(fks[i].getDeleteAction());
}
if (invalids == null || !invalids.contains(fk)) {
try {
Column fkCol = table.getColumn(fkColName);
if (fkCol == null) {
throw new IllegalArgumentException(_loc.get(
"no-column", fkColName, table.getIdentifier())
.getMessage());
}
fk.join(fkCol, pkTable.getColumn(pkColName));
} catch (IllegalArgumentException iae) {
if (_log.isWarnEnabled())
_log.warn(_loc.get("bad-join", iae.toString()));
if (invalids == null)
invalids = new HashSet<>();
invalids.add(fk);
}
}
}
// remove invalid fks
if (invalids != null) {
for (ForeignKey invalid : invalids) {
fk = invalid;
fk.getTable().removeForeignKey(fk);
}
}
}
/**
* Adds all sequences matching the given name pattern to the schema.
* @deprecated
*/
@Deprecated
public void generateSequences(String schemaName, String sequenceName,
Connection conn, DatabaseMetaData meta)
throws SQLException {
generateSequences(DBIdentifier.newSchema(schemaName),
DBIdentifier.newSequence(sequenceName), conn, meta);
}
public void generateSequences(DBIdentifier schemaName, DBIdentifier sequenceName,
Connection conn, DatabaseMetaData meta)
throws SQLException {
fireGenerationEvent(_loc.get("generating-sequences", schemaName));
if (_log.isTraceEnabled())
_log.trace(_loc.get("gen-seqs", schemaName, sequenceName));
// since all the sequences are generated under the default schema
// therefore, we can use the null schemaname to search
Sequence[] seqs = _dict.getSequences(meta, DBIdentifier.newCatalog(conn.getCatalog()),
DBIdentifier.NULL, sequenceName, conn);
SchemaGroup group = getSchemaGroup();
Schema schema;
DBIdentifier sequenceSchema = DBIdentifier.NULL;
for (int i = 0; seqs != null && i < seqs.length; i++) {
sequenceName = seqs[i].getIdentifier();
sequenceSchema = DBIdentifier.trimToNull(seqs[i].getSchemaIdentifier());
// ignore special tables
String seqUpper = DBIdentifier.toUpper(sequenceName).getName();
if (!_openjpaTables &&
(seqUpper.startsWith("OPENJPA_")
|| seqUpper.startsWith("JDO_"))) // legacy
continue;
if (_dict.isSystemSequence(sequenceName, sequenceSchema,
schemaName != null, conn))
continue;
if (!isAllowedTable(sequenceSchema, null))
continue;
schema = group.getSchema(sequenceSchema);
if (schema == null) {
schema = group.addSchema(sequenceSchema);
}
if (schema.getSequence(sequenceName) == null) {
schema.addSequence(sequenceName);
}
}
}
/**
* Notify any listeners that a schema object was generated. Returns
* true if generation should continue.
*/
private void fireGenerationEvent(Object schemaObject)
throws SQLException {
if (schemaObject == null)
return;
if (_listeners == null || _listeners.size() == 0)
return;
Event e = new Event(schemaObject, _schemaObjects);
for (Listener l : _listeners) {
if (!l.schemaObjectGenerated(e))
throw new SQLException(_loc.get("refresh-cancelled")
.getMessage());
}
}
/**
* Adds a listener for schema generation events.
*
* @param l the listener to add
*/
public void addListener(Listener l) {
if (_listeners == null)
_listeners = new LinkedList<>();
_listeners.add(l);
}
/**
* Removes a schema generation listener for schema events.
*
* @param l the listener to remove
* @return true if it was successfully removed
*/
public boolean removeListener(Listener l) {
return _listeners != null && _listeners.remove(l);
}
/**
* Return all tables for the given schema name, or all tables in
* the schema group if null is given.
*/
private Collection<Table> getTables(DBIdentifier schemaName) {
SchemaGroup group = getSchemaGroup();
if (!DBIdentifier.isNull(schemaName)) {
Schema schema = group.getSchema(schemaName);
if (schema == null)
return Collections.emptyList();
return Arrays.asList(schema.getTables());
}
Schema[] schemas = group.getSchemas();
Collection<Table> tables = new LinkedList<>();
for (Schema schema : schemas) {
tables.addAll(Arrays.asList(schema.getTables()));
}
return tables;
}
/**
* A listener for a potentially lengthy schema generation process.
*/
public interface Listener {
boolean schemaObjectGenerated(Event e);
}
/**
* An event corresponding to the generation of a schema object.
*/
public static class Event extends EventObject {
private static final long serialVersionUID = 1L;
private final int _total;
public Event(Object ob, int total) {
super(ob);
_total = total;
}
public int getTotal() {
return _total;
}
}
private void getConn() throws SQLException {
if (_conn == null) {
_conn = _ds.getConnection();
}
}
private void closeConn() throws SQLException {
if (_conn != null ) {
if(! _conn.isClosed()) {
_conn.close();
}
}
}
}