001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: */
019: package org.apache.openjpa.jdbc.schema;
020:
021: import java.sql.Connection;
022: import java.sql.DatabaseMetaData;
023: import java.sql.SQLException;
024: import java.util.Arrays;
025: import java.util.Collection;
026: import java.util.Collections;
027: import java.util.EventObject;
028: import java.util.HashMap;
029: import java.util.HashSet;
030: import java.util.Iterator;
031: import java.util.LinkedList;
032: import java.util.List;
033: import java.util.Map;
034: import java.util.Set;
035: import javax.sql.DataSource;
036:
037: import org.apache.commons.lang.StringUtils;
038: import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
039: import org.apache.openjpa.jdbc.sql.DBDictionary;
040: import org.apache.openjpa.lib.log.Log;
041: import org.apache.openjpa.lib.util.Localizer;
042:
043: /**
044: * The SchemaGenerator creates {@link Schema}s matching the current
045: * database. All schemas are added to the current {@link SchemaGroup}.
046: * Note that tables whose name starts with "OPENJPA_" will be not be added
047: * to the database schema. This enables the creation of special tables
048: * that will never be dropped by the {@link SchemaTool}.
049: *
050: * @author Abe White
051: */
052: public class SchemaGenerator {
053:
054: private static final Localizer _loc = Localizer
055: .forPackage(SchemaGenerator.class);
056:
057: private final DataSource _ds;
058: private final DBDictionary _dict;
059: private final Log _log;
060: private final Object[][] _allowed;
061: private boolean _indexes = true;
062: private boolean _fks = true;
063: private boolean _pks = true;
064: private boolean _seqs = true;
065: private boolean _openjpaTables = true;
066: private SchemaGroup _group = null;
067:
068: private List _listeners = null;
069: private int _schemaObjects = 0;
070:
071: /**
072: * Constructor.
073: *
074: * @param conf configuration for connecting to the data store
075: */
076: public SchemaGenerator(JDBCConfiguration conf) {
077: // note: we cannot assert developer capabilities in this tool like
078: // we normally do because this class is also used at runtime
079:
080: _ds = conf.getDataSource2(null);
081: _log = conf.getLog(JDBCConfiguration.LOG_SCHEMA);
082:
083: // cache this now so that if the conn pool only has 1 connection we
084: // don't conflict later with the open databasemetadata connection
085: _dict = conf.getDBDictionaryInstance();
086:
087: // create a table of allowed schema and tables to reflect on
088: _allowed = parseSchemasList(conf.getSchemasList());
089: }
090:
091: /**
092: * Given a list of schema names and table names (where table names
093: * are always of the form schema.table, or just .table if the schema is
094: * unknown), creates a table mapping schema name to table list. Returns
095: * null if no args are given. If no tables are given for a particular
096: * schema, maps the schema name to null.
097: */
098: private static Object[][] parseSchemasList(String[] args) {
099: if (args == null || args.length == 0)
100: return null;
101:
102: Map schemas = new HashMap();
103: String schema, table;
104: int dotIdx;
105: Collection tables;
106: for (int i = 0; i < args.length; i++) {
107: dotIdx = args[i].indexOf('.');
108: if (dotIdx == -1) {
109: schema = args[i];
110: table = null;
111: } else if (dotIdx == 0) {
112: schema = null;
113: table = args[i].substring(1);
114: } else {
115: schema = args[i].substring(0, dotIdx);
116: table = args[i].substring(dotIdx + 1);
117: }
118:
119: // if just a schema name, map schema to null
120: if (table == null && !schemas.containsKey(schema))
121: schemas.put(schema, null);
122: else if (table != null) {
123: tables = (Collection) schemas.get(schema);
124: if (tables == null) {
125: tables = new LinkedList();
126: schemas.put(schema, tables);
127: }
128: tables.add(table);
129: }
130: }
131:
132: Object[][] parsed = new Object[schemas.size()][2];
133: Map.Entry entry;
134: int idx = 0;
135: for (Iterator itr = schemas.entrySet().iterator(); itr
136: .hasNext();) {
137: entry = (Map.Entry) itr.next();
138: tables = (Collection) entry.getValue();
139:
140: parsed[idx][0] = entry.getKey();
141: if (tables != null)
142: parsed[idx][1] = tables.toArray(new String[tables
143: .size()]);
144: idx++;
145: }
146: return parsed;
147: }
148:
149: /**
150: * Return whether indexes should be generated. Defaults to true.
151: */
152: public boolean getIndexes() {
153: return _indexes;
154: }
155:
156: /**
157: * Set whether indexes should be generated. Defaults to true.
158: */
159: public void setIndexes(boolean indexes) {
160: _indexes = indexes;
161: }
162:
163: /**
164: * Return whether foreign keys should be generated. Defaults to true.
165: */
166: public boolean getForeignKeys() {
167: return _fks;
168: }
169:
170: /**
171: * Set whether foreign keys should be generated. Defaults to true.
172: */
173: public void setForeignKeys(boolean fks) {
174: _fks = fks;
175: }
176:
177: /**
178: * Return whether primary keys should be generated. Defaults to true.
179: */
180: public boolean getPrimaryKeys() {
181: return _pks;
182: }
183:
184: /**
185: * Set whether primary keys should be generated. Defaults to true.
186: */
187: public void setPrimaryKeys(boolean pks) {
188: _pks = pks;
189: }
190:
191: /**
192: * Return whether sequences should be generated. Defaults to true.
193: */
194: public boolean getSequences() {
195: return _seqs;
196: }
197:
198: /**
199: * Set whether sequences should be generated. Defaults to true.
200: */
201: public void setSequences(boolean seqs) {
202: _seqs = seqs;
203: }
204:
205: /**
206: * Whether to generate info on special tables used by OpenJPA components
207: * for bookkeeping. Defaults to true.
208: */
209: public boolean getOpenJPATables() {
210: return _openjpaTables;
211: }
212:
213: /**
214: * Whether to generate info on special tables used by OpenJPA components
215: * for bookkeeping. Defaults to true.
216: */
217: public void setOpenJPATables(boolean openjpaTables) {
218: _openjpaTables = openjpaTables;
219: }
220:
221: /**
222: * Return the current schema group.
223: */
224: public SchemaGroup getSchemaGroup() {
225: if (_group == null)
226: _group = new SchemaGroup();
227: return _group;
228: }
229:
230: /**
231: * Set the schema group to add generated schemas to.
232: */
233: public void setSchemaGroup(SchemaGroup group) {
234: _group = group;
235: }
236:
237: /**
238: * Generate all schemas set in the configuration. This method also
239: * calls {@link #generateIndexes}, {@link #generatePrimaryKeys}, and
240: * {@link #generateForeignKeys} automatically.
241: */
242: public void generateSchemas() throws SQLException {
243: fireGenerationEvent(_loc.get("generating-schemas"));
244: generateSchemas(null);
245: }
246:
247: /**
248: * Generate the schemas and/or tables named in the given strings.
249: * This method calls {@link #generateIndexes},
250: * {@link #generatePrimaryKeys}, and {@link #generateForeignKeys}
251: * automatically.
252: */
253: public void generateSchemas(String[] schemasAndTables)
254: throws SQLException {
255: fireGenerationEvent(_loc.get("generating-schemas"));
256:
257: Object[][] schemaMap;
258: if (schemasAndTables == null || schemasAndTables.length == 0)
259: schemaMap = _allowed;
260: else
261: schemaMap = parseSchemasList(schemasAndTables);
262:
263: if (schemaMap == null) {
264: generateSchema(null, null);
265:
266: // estimate the number of schema objects we will need to visit
267: // in order to estimate progresss total for any listeners
268: int numTables = getTables(null).size();
269: _schemaObjects += numTables + (_pks ? numTables : 0)
270: + (_indexes ? numTables : 0)
271: + (_fks ? numTables : 0);
272:
273: if (_pks)
274: generatePrimaryKeys(null, null);
275: if (_indexes)
276: generateIndexes(null, null);
277: if (_fks)
278: generateForeignKeys(null, null);
279: return;
280: }
281:
282: // generate all schemas and tables
283: for (int i = 0; i < schemaMap.length; i++)
284: generateSchema((String) schemaMap[i][0],
285: (String[]) schemaMap[i][1]);
286:
287: // generate pks, indexes, fks
288: String schemaName;
289: String[] tableNames;
290: for (int i = 0; i < schemaMap.length; i++) {
291: schemaName = (String) schemaMap[i][0];
292: tableNames = (String[]) schemaMap[i][1];
293:
294: // estimate the number of schema objects we will need to visit
295: // in order to estimate progresss total for any listeners
296: int numTables = (tableNames != null) ? tableNames.length
297: : getTables(schemaName).size();
298: _schemaObjects += numTables + (_pks ? numTables : 0)
299: + (_indexes ? numTables : 0)
300: + (_fks ? numTables : 0);
301:
302: if (_pks)
303: generatePrimaryKeys(schemaName, tableNames);
304: if (_indexes)
305: generateIndexes(schemaName, tableNames);
306: if (_fks)
307: generateForeignKeys(schemaName, tableNames);
308: }
309: }
310:
311: /**
312: * Add a fully-constructed {@link Schema} matching the given database
313: * schema to the current group. No foreign keys are generated because
314: * some keys might span schemas. You must call
315: * {@link #generatePrimaryKeys}, {@link #generateIndexes}, and
316: * {@link #generateForeignKeys} separately.
317: *
318: * @param name the database name of the schema, or null for all schemas
319: * @param tableNames a list of tables to generate in the schema, or null
320: * to generate all tables
321: */
322: public void generateSchema(String name, String[] tableNames)
323: throws SQLException {
324: fireGenerationEvent(_loc.get("generating-schema", name));
325:
326: // generate tables, including columns and primary keys
327: Connection conn = _ds.getConnection();
328: DatabaseMetaData meta = conn.getMetaData();
329: try {
330: if (tableNames == null)
331: generateTables(name, null, conn, meta);
332: else
333: for (int i = 0; i < tableNames.length; i++)
334: generateTables(name, tableNames[i], conn, meta);
335:
336: if (_seqs)
337: generateSequences(name, null, conn, meta);
338: } finally {
339: // some databases require a commit after metadata to release locks
340: try {
341: conn.commit();
342: } catch (SQLException se) {
343: }
344: try {
345: conn.close();
346: } catch (SQLException se) {
347: }
348: }
349: }
350:
351: /**
352: * Generate primary key information for the given schema. This method
353: * must be called in addition to {@link #generateSchema}. It should
354: * only be called after all schemas are generated. The schema name and
355: * tables array can be null to indicate that indexes should be generated
356: * for all schemas and/or tables.
357: */
358: public void generatePrimaryKeys(String schemaName,
359: String[] tableNames) throws SQLException {
360: fireGenerationEvent(_loc.get("generating-all-primaries",
361: schemaName));
362:
363: Connection conn = _ds.getConnection();
364: DatabaseMetaData meta = conn.getMetaData();
365: try {
366: if (tableNames == null)
367: generatePrimaryKeys(schemaName, null, conn, meta);
368: else
369: for (int i = 0; i < tableNames.length; i++)
370: generatePrimaryKeys(schemaName, tableNames[i],
371: conn, meta);
372: } finally {
373: // some databases require a commit after metadata to release locks
374: try {
375: conn.commit();
376: } catch (SQLException se) {
377: }
378: try {
379: conn.close();
380: } catch (SQLException se) {
381: }
382: }
383: }
384:
385: /**
386: * Generate index information for the given schema. This method
387: * must be called in addition to {@link #generateSchema}. It should
388: * only be called after all schemas are generated. The schema name and
389: * tables array can be null to indicate that indexes should be generated
390: * for all schemas and/or tables.
391: */
392: public void generateIndexes(String schemaName, String[] tableNames)
393: throws SQLException {
394: fireGenerationEvent(_loc.get("generating-all-indexes",
395: schemaName));
396:
397: Connection conn = _ds.getConnection();
398: DatabaseMetaData meta = conn.getMetaData();
399: try {
400: if (tableNames == null)
401: generateIndexes(schemaName, null, conn, meta);
402: else
403: for (int i = 0; i < tableNames.length; i++)
404: generateIndexes(schemaName, tableNames[i], conn,
405: meta);
406: } finally {
407: // some databases require a commit after metadata to release locks
408: try {
409: conn.commit();
410: } catch (SQLException se) {
411: }
412: try {
413: conn.close();
414: } catch (SQLException se) {
415: }
416: }
417: }
418:
419: /**
420: * Generate foreign key information for the given schema. This method
421: * must be called in addition to {@link #generateSchema}. It should
422: * only be called after all schemas are generated. The schema name and
423: * tables array can be null to indicate that indexes should be generated
424: * for all schemas and/or tables.
425: */
426: public void generateForeignKeys(String schemaName,
427: String[] tableNames) throws SQLException {
428: fireGenerationEvent(_loc.get("generating-all-foreigns",
429: schemaName));
430:
431: Connection conn = _ds.getConnection();
432: DatabaseMetaData meta = conn.getMetaData();
433: try {
434: if (tableNames == null)
435: generateForeignKeys(schemaName, null, conn, meta);
436: else
437: for (int i = 0; i < tableNames.length; i++)
438: generateForeignKeys(schemaName, tableNames[i],
439: conn, meta);
440: } finally {
441: // some databases require a commit after metadata to release locks
442: try {
443: conn.commit();
444: } catch (SQLException se) {
445: }
446: try {
447: conn.close();
448: } catch (SQLException se) {
449: }
450: }
451: }
452:
453: /**
454: * Adds all tables matching the given name pattern to the schema.
455: */
456: public void generateTables(String schemaName, String tableName,
457: Connection conn, DatabaseMetaData meta) throws SQLException {
458: fireGenerationEvent(_loc.get("generating-columns", schemaName,
459: tableName));
460: if (_log.isTraceEnabled())
461: _log.trace(_loc.get("gen-tables", schemaName, tableName));
462:
463: Column[] cols = _dict.getColumns(meta, conn.getCatalog(),
464: schemaName, tableName, null, conn);
465:
466: // when we want to get all the columns for all tables, we need to build
467: // a list of tables to verify because some databases (e.g., Postgres)
468: // will include indexes in the list of columns, and there is no way to
469: // distinguish the indexes from proper columns
470: Set tableNames = null;
471: if (tableName == null || "%".equals(tableName)) {
472: Table[] tables = _dict.getTables(meta, conn.getCatalog(),
473: schemaName, tableName, conn);
474: tableNames = new HashSet();
475: for (int i = 0; tables != null && i < tables.length; i++) {
476: if (cols == null)
477: tableNames.add(tables[i].getName());
478: else
479: tableNames.add(tables[i].getName().toUpperCase());
480: }
481: }
482:
483: // if database can't handle null table name, recurse on each known name
484: if (cols == null && tableName == null) {
485: for (Iterator itr = tableNames.iterator(); itr.hasNext();)
486: generateTables(schemaName, (String) itr.next(), conn,
487: meta);
488: return;
489: }
490:
491: SchemaGroup group = getSchemaGroup();
492: Schema schema;
493: Table table;
494: String tableSchema;
495: for (int i = 0; cols != null && i < cols.length; i++) {
496: tableName = cols[i].getTableName();
497: tableSchema = StringUtils.trimToNull(cols[i]
498: .getSchemaName());
499:
500: // ignore special tables
501: if (!_openjpaTables
502: && (tableName.toUpperCase().startsWith("OPENJPA_") || tableName
503: .toUpperCase().startsWith("JDO_"))) // legacy
504: continue;
505: if (_dict.isSystemTable(tableName, tableSchema,
506: schemaName != null))
507: continue;
508:
509: // ignore tables not in list, or not allowed by schemas property
510: if (tableNames != null
511: && !tableNames.contains(tableName.toUpperCase()))
512: continue;
513: if (!isAllowedTable(tableSchema, tableName))
514: continue;
515:
516: schema = group.getSchema(tableSchema);
517: if (schema == null)
518: schema = group.addSchema(tableSchema);
519:
520: table = schema.getTable(tableName);
521: if (table == null) {
522: table = schema.addTable(tableName);
523: if (_log.isTraceEnabled())
524: _log.trace(_loc.get("col-table", table));
525: }
526:
527: if (_log.isTraceEnabled())
528: _log.trace(_loc.get("gen-column", cols[i].getName(),
529: table));
530:
531: if (table.getColumn(cols[i].getName()) == null)
532: table.importColumn(cols[i]);
533: }
534: }
535:
536: /**
537: * Return whether the given table is allowed by the user's schema list.
538: */
539: private boolean isAllowedTable(String schema, String table) {
540: if (_allowed == null)
541: return true;
542:
543: // do case-insensitive comparison on allowed table and schema names
544: String[] tables;
545: String[] anySchemaTables = null;
546: for (int i = 0; i < _allowed.length; i++) {
547: if (_allowed[i][0] == null) {
548: anySchemaTables = (String[]) _allowed[i][1];
549: if (schema == null)
550: break;
551: continue;
552: }
553: if (!StringUtils.equalsIgnoreCase(schema,
554: (String) _allowed[i][0]))
555: continue;
556:
557: if (table == null)
558: return true;
559: tables = (String[]) _allowed[i][1];
560: if (tables == null)
561: return true;
562: for (int j = 0; j < tables.length; j++)
563: if (StringUtils.equalsIgnoreCase(table, tables[j]))
564: return true;
565: }
566:
567: if (anySchemaTables != null) {
568: if (table == null)
569: return true;
570: for (int i = 0; i < anySchemaTables.length; i++)
571: if (StringUtils.equalsIgnoreCase(table,
572: anySchemaTables[i]))
573: return true;
574: }
575: return false;
576: }
577:
578: /**
579: * Generates table primary keys.
580: */
581: public void generatePrimaryKeys(String schemaName,
582: String tableName, Connection conn, DatabaseMetaData meta)
583: throws SQLException {
584: fireGenerationEvent(_loc.get("generating-primary", schemaName,
585: tableName));
586: if (_log.isTraceEnabled())
587: _log.trace(_loc.get("gen-pks", schemaName, tableName));
588:
589: // if looking for a non-existant table, just return
590: SchemaGroup group = getSchemaGroup();
591: if (tableName != null && group.findTable(tableName) == null)
592: return;
593:
594: // if the database can't use a table name wildcard, recurse on each
595: // concrete table in the requested schema(s)
596: PrimaryKey[] pks = _dict.getPrimaryKeys(meta,
597: conn.getCatalog(), schemaName, tableName, conn);
598: Table table;
599: if (pks == null && tableName == null) {
600: Collection tables = getTables(schemaName);
601: for (Iterator itr = tables.iterator(); itr.hasNext();) {
602: table = (Table) itr.next();
603: generatePrimaryKeys(table.getSchemaName(), table
604: .getName(), conn, meta);
605: }
606: return;
607: }
608:
609: Schema schema;
610: PrimaryKey pk;
611: String name;
612: String colName;
613: for (int i = 0; pks != null && i < pks.length; i++) {
614: schemaName = StringUtils.trimToNull(pks[i].getSchemaName());
615: schema = group.getSchema(schemaName);
616: if (schema == null)
617: continue;
618: table = schema.getTable(pks[i].getTableName());
619: if (table == null)
620: continue;
621:
622: colName = pks[i].getColumnName();
623: name = pks[i].getName();
624: if (_log.isTraceEnabled())
625: _log.trace(_loc.get("gen-pk", name, table, colName));
626:
627: pk = table.getPrimaryKey();
628: if (pk == null)
629: pk = table.addPrimaryKey(name);
630: pk.addColumn(table.getColumn(colName));
631: }
632: }
633:
634: /**
635: * Generates table indexes.
636: */
637: public void generateIndexes(String schemaName, String tableName,
638: Connection conn, DatabaseMetaData meta) throws SQLException {
639: fireGenerationEvent(_loc.get("generating-indexes", schemaName,
640: tableName));
641: if (_log.isTraceEnabled())
642: _log.trace(_loc.get("gen-indexes", schemaName, tableName));
643:
644: // if looking for a non-existant table, just return
645: SchemaGroup group = getSchemaGroup();
646: if (tableName != null && group.findTable(tableName) == null)
647: return;
648:
649: // if the database can't use a table name wildcard, recurse on each
650: // concrete table in the requested schema(s)
651: Index[] idxs = _dict.getIndexInfo(meta, conn.getCatalog(),
652: schemaName, tableName, false, true, conn);
653: Table table;
654: if (idxs == null && tableName == null) {
655: Collection tables = getTables(schemaName);
656: for (Iterator itr = tables.iterator(); itr.hasNext();) {
657: table = (Table) itr.next();
658: generateIndexes(table.getSchemaName(), table.getName(),
659: conn, meta);
660: }
661: return;
662: }
663:
664: Schema schema;
665: Index idx;
666: String name;
667: String colName;
668: String pkName;
669: for (int i = 0; idxs != null && i < idxs.length; i++) {
670: schemaName = StringUtils
671: .trimToNull(idxs[i].getSchemaName());
672: schema = group.getSchema(schemaName);
673: if (schema == null)
674: continue;
675: table = schema.getTable(idxs[i].getTableName());
676: if (table == null)
677: continue;
678:
679: if (table.getPrimaryKey() != null)
680: pkName = table.getPrimaryKey().getName();
681: else
682: pkName = null;
683:
684: // statistics don't have names; skip them
685: name = idxs[i].getName();
686: if (StringUtils.isEmpty(name)
687: || (pkName != null && name.equalsIgnoreCase(pkName))
688: || _dict.isSystemIndex(name, table))
689: continue;
690:
691: colName = idxs[i].getColumnName();
692: if (table.getColumn(colName) == null)
693: continue;
694:
695: if (_log.isTraceEnabled())
696: _log.trace(_loc.get("gen-index", name, table, colName));
697:
698: // same index may have multiple rows for multiple cols
699: idx = table.getIndex(name);
700: if (idx == null) {
701: idx = table.addIndex(name);
702: idx.setUnique(idxs[i].isUnique());
703: }
704: idx.addColumn(table.getColumn(colName));
705: }
706: }
707:
708: /**
709: * Generates table foreign keys.
710: */
711: public void generateForeignKeys(String schemaName,
712: String tableName, Connection conn, DatabaseMetaData meta)
713: throws SQLException {
714: fireGenerationEvent(_loc.get("generating-foreign", schemaName,
715: tableName));
716: if (_log.isTraceEnabled())
717: _log.trace(_loc.get("gen-fks", schemaName, tableName));
718:
719: // if looking for a non-existant table, just return
720: SchemaGroup group = getSchemaGroup();
721: if (tableName != null && group.findTable(tableName) == null)
722: return;
723:
724: // if the database can't use a table name wildcard, recurse on each
725: // concrete table in the requested schema(s)
726: ForeignKey[] fks = _dict.getImportedKeys(meta, conn
727: .getCatalog(), schemaName, tableName, conn);
728: Table table;
729: if (fks == null && tableName == null) {
730: Collection tables = getTables(schemaName);
731: for (Iterator itr = tables.iterator(); itr.hasNext();) {
732: table = (Table) itr.next();
733: generateForeignKeys(table.getSchemaName(), table
734: .getName(), conn, meta);
735: }
736: return;
737: }
738:
739: Schema schema;
740: Table pkTable;
741: ForeignKey fk;
742: String name;
743: String pkSchemaName;
744: String pkTableName;
745: String pkColName;
746: String fkColName;
747: int seq;
748: boolean seqWas0 = false; // some drivers incorrectly start at 0
749: Collection invalids = null;
750: for (int i = 0; fks != null && i < fks.length; i++) {
751: schemaName = StringUtils.trimToNull(fks[i].getSchemaName());
752: schema = group.getSchema(schemaName);
753: if (schema == null)
754: continue;
755: table = schema.getTable(fks[i].getTableName());
756: if (table == null)
757: continue;
758:
759: name = fks[i].getName();
760: fkColName = fks[i].getColumnName();
761: pkColName = fks[i].getPrimaryKeyColumnName();
762: seq = fks[i].getKeySequence();
763: if (seq == 0)
764: seqWas0 = true;
765: if (seqWas0)
766: seq++;
767:
768: // find pk table
769: pkSchemaName = fks[i].getPrimaryKeySchemaName();
770: pkTableName = fks[i].getPrimaryKeyTableName();
771: if (_log.isTraceEnabled())
772: _log.trace(_loc.get("gen-fk", new Object[] { name,
773: table, fkColName, pkTableName, pkColName,
774: seq + "" }));
775:
776: if (!StringUtils.isEmpty(pkSchemaName))
777: pkTableName = pkSchemaName + "." + pkTableName;
778: pkTable = group.findTable(pkTableName);
779: if (pkTable == null)
780: throw new SQLException(_loc.get("gen-nofktable", table,
781: pkTableName).getMessage());
782:
783: // this sucks, because it is *not* guaranteed to work;
784: // the fk resultset is ordered by primary key table, then
785: // sequence number within the foreign key (some drivers don't
786: // use sequence numbers correctly either); since fk names
787: // are allowed to be null, all this adds up to the fact
788: // that there is absolutely no way to distinguish between
789: // multiple multi-column fks to the same table; we're going
790: // to rely on fk name here and hope it works
791: fk = table.getForeignKey(name);
792:
793: // start a new fk?
794: if (seq == 1 || fk == null) {
795: fk = table.addForeignKey(name);
796: fk.setDeferred(fks[i].isDeferred());
797: fk.setDeleteAction(fks[i].getDeleteAction());
798: }
799:
800: if (invalids == null || !invalids.contains(fk)) {
801: try {
802: fk.join(table.getColumn(fkColName), pkTable
803: .getColumn(pkColName));
804: } catch (IllegalArgumentException iae) {
805: if (_log.isWarnEnabled())
806: _log.warn(_loc.get("bad-join", iae.toString()));
807: if (invalids == null)
808: invalids = new HashSet();
809: invalids.add(fk);
810: }
811: }
812: }
813:
814: // remove invalid fks
815: if (invalids != null) {
816: for (Iterator itr = invalids.iterator(); itr.hasNext();) {
817: fk = (ForeignKey) itr.next();
818: fk.getTable().removeForeignKey(fk);
819: }
820: }
821: }
822:
823: /**
824: * Adds all sequences matching the given name pattern to the schema.
825: */
826: public void generateSequences(String schemaName,
827: String sequenceName, Connection conn, DatabaseMetaData meta)
828: throws SQLException {
829: fireGenerationEvent(_loc
830: .get("generating-sequences", schemaName));
831: if (_log.isTraceEnabled())
832: _log.trace(_loc.get("gen-seqs", schemaName, sequenceName));
833:
834: // since all the sequences are generated under the default schema
835: // therefore, we can use the null schemaname to search
836: Sequence[] seqs = _dict.getSequences(meta, conn.getCatalog(),
837: null, sequenceName, conn);
838:
839: SchemaGroup group = getSchemaGroup();
840: Schema schema;
841: String sequenceSchema;
842: for (int i = 0; seqs != null && i < seqs.length; i++) {
843: sequenceName = seqs[i].getName();
844: sequenceSchema = StringUtils.trimToNull(seqs[i]
845: .getSchemaName());
846:
847: // ignore special tables
848: if (!_openjpaTables
849: && (sequenceName.toUpperCase().startsWith(
850: "OPENJPA_") || sequenceName.toUpperCase()
851: .startsWith("JDO_"))) // legacy
852: continue;
853: if (_dict.isSystemSequence(sequenceName, sequenceSchema,
854: schemaName != null))
855: continue;
856: if (!isAllowedTable(sequenceSchema, null))
857: continue;
858:
859: schema = group.getSchema(sequenceSchema);
860: if (schema == null)
861: schema = group.addSchema(sequenceSchema);
862: if (schema.getSequence(sequenceName) == null)
863: schema.addSequence(sequenceName);
864: }
865: }
866:
867: /**
868: * Notify any listeners that a schema object was generated. Returns
869: * true if generation should continue.
870: */
871: private void fireGenerationEvent(Object schemaObject)
872: throws SQLException {
873: if (schemaObject == null)
874: return;
875: if (_listeners == null || _listeners.size() == 0)
876: return;
877:
878: Event e = new Event(schemaObject, _schemaObjects);
879: for (Iterator i = _listeners.iterator(); i.hasNext();) {
880: Listener l = (Listener) i.next();
881: if (!l.schemaObjectGenerated(e))
882: throw new SQLException(_loc.get("refresh-cancelled")
883: .getMessage());
884: }
885: }
886:
887: /**
888: * Adds a listener for schema generation events.
889: *
890: * @param l the listener to add
891: */
892: public void addListener(Listener l) {
893: if (_listeners == null)
894: _listeners = new LinkedList();
895: _listeners.add(l);
896: }
897:
898: /**
899: * Removes a schema generation listener for schema events.
900: *
901: * @param l the listener to remove
902: * @return true if it was successfully removed
903: */
904: public boolean removeListener(Listener l) {
905: return _listeners != null && _listeners.remove(l);
906: }
907:
908: /**
909: * Return all tables for the given schema name, or all tables in
910: * the schema group if null is given.
911: */
912: private Collection getTables(String schemaName) {
913: SchemaGroup group = getSchemaGroup();
914: if (schemaName != null) {
915: Schema schema = group.getSchema(schemaName);
916: if (schema == null)
917: return Collections.EMPTY_LIST;
918: return Arrays.asList(schema.getTables());
919: }
920:
921: Schema[] schemas = group.getSchemas();
922: Collection tables = new LinkedList();
923: for (int i = 0; i < schemas.length; i++)
924: tables.addAll(Arrays.asList(schemas[i].getTables()));
925: return tables;
926: }
927:
928: /**
929: * A listener for a potentially lengthy schema generation process.
930: */
931: public static interface Listener {
932:
933: boolean schemaObjectGenerated(Event e);
934: }
935:
936: /**
937: * An event corresponding to the generation of a schema object.
938: */
939: public class Event extends EventObject {
940:
941: private final int _total;
942:
943: public Event(Object ob, int total) {
944: super (ob);
945: _total = total;
946: }
947:
948: public int getTotal() {
949: return _total;
950: }
951: }
952: }
|