KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > oracle > toplink > essentials > tools > schemaframework > DefaultTableGenerator


1 /*
2  * The contents of this file are subject to the terms
3  * of the Common Development and Distribution License
4  * (the "License"). You may not use this file except
5  * in compliance with the License.
6  *
7  * You can obtain a copy of the license at
8  * glassfish/bootstrap/legal/CDDLv1.0.txt or
9  * https://glassfish.dev.java.net/public/CDDLv1.0.html.
10  * See the License for the specific language governing
11  * permissions and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL
14  * HEADER in each file and include the License file at
15  * glassfish/bootstrap/legal/CDDLv1.0.txt. If applicable,
16  * add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your
18  * own identifying information: Portions Copyright [yyyy]
19  * [name of copyright owner]
20  */

21 package oracle.toplink.essentials.tools.schemaframework;
22
23 import java.sql.DatabaseMetaData JavaDoc;
24 import java.sql.ResultSet JavaDoc;
25 import java.sql.SQLException JavaDoc;
26 import java.util.HashMap JavaDoc;
27 import java.util.Hashtable JavaDoc;
28 import java.util.Iterator JavaDoc;
29 import java.util.Map JavaDoc;
30 import java.util.Vector JavaDoc;
31
32 import oracle.toplink.essentials.exceptions.DatabaseException;
33 import oracle.toplink.essentials.internal.helper.ClassConstants;
34 import oracle.toplink.essentials.internal.helper.ConversionManager;
35 import oracle.toplink.essentials.internal.helper.DatabaseField;
36 import oracle.toplink.essentials.internal.helper.DatabaseTable;
37 import oracle.toplink.essentials.internal.sessions.DatabaseSessionImpl;
38 import oracle.toplink.essentials.logging.AbstractSessionLog;
39 import oracle.toplink.essentials.logging.SessionLog;
40 import oracle.toplink.essentials.mappings.AggregateCollectionMapping;
41 import oracle.toplink.essentials.mappings.DatabaseMapping;
42 import oracle.toplink.essentials.mappings.DirectCollectionMapping;
43 import oracle.toplink.essentials.mappings.DirectMapMapping;
44 import oracle.toplink.essentials.mappings.ManyToManyMapping;
45
46 import oracle.toplink.essentials.descriptors.ClassDescriptor;
47 import oracle.toplink.essentials.internal.sessions.AbstractSession;
48 import oracle.toplink.essentials.mappings.OneToManyMapping;
49 import oracle.toplink.essentials.mappings.OneToOneMapping;
50 import oracle.toplink.essentials.sessions.Project;
51 import oracle.toplink.essentials.sessions.DatabaseLogin;
52 import oracle.toplink.essentials.threetier.ServerSession;
53 import oracle.toplink.essentials.sequencing.Sequence;
54 import oracle.toplink.essentials.sequencing.DefaultSequence;
55 import oracle.toplink.essentials.sequencing.NativeSequence;
56
57
58 /**
59  * DefaultTableGenerator is a utility class used to generate a default table schema for a TopLink project object.
60  *
61  * The utility can be used in TopLink CMP for OC4J to perform the table auto creation process, which can be triggered
62  * at deployment time when TopLink project descriptor is absent (default mapping) or present.
63  *
64  * The utility can also be used to any TopLink application to perform the table drop/creation at runtime.
65  *
66  * The utility handles all direct/relational mappings, inheritance, multiple tables, interface with/without tables,
67  * optimistic version/timestamp lockings, nested relationships, BLOB/CLOB generation.
68  *
69  * The utility is platform-agnostic.
70  *
71  * Usage:
72  * - CMP
73  * 1. set "autocreate-tables=true|false, autodelete-tables=true|false" in oc4j application deployment
74  * descriptor files (config/system-application.xml, config/application.xml, or orion-application.xml in an .ear)
75  *
76  * 2. Default Mapping: the same as CMP, plus system properties setting -Dtoplink.defaultmapping.autocreate-tables='true|false'
77  * and -Dtoplink.defaultmapping.autodelete-tables='true|false'
78  *
79  * - Non-CMP:
80  * TODO: sessions.xml support (CR 4355200)
81  * 1. Configuration: through sessions.xml
82  * 2. Directly runtime call through schema framework:
83  * SchemaManager mgr = new SchemaManager(session);
84  * mgr.replaceDefaultTables(); //drop and create
85  * mgr.createDefaultTables(); //create only
86  *
87  * The utility currently only supports relational project.
88  *
89  * @author King Wang
90  * @since Oracle TopLink 10.1.3
91  */

92 public class DefaultTableGenerator {
93     //the project object used to generate the default data schema.
94
Project project = null;
95
96     //used to track the table definition: keyed by the table name, and valued
97
//by the table definition object
98
private Map JavaDoc tableMap = null;
99
100     //used to track th field definition: keyed by the database field object, and
101
//valued by the field definition.
102
private Map JavaDoc fieldMap = null;
103
104     /**
105      * Default construcotr
106      */

107     public DefaultTableGenerator(Project project) {
108         this.project = project;
109         tableMap = new HashMap JavaDoc();
110         fieldMap = new HashMap JavaDoc();
111     }
112
113     /**
114      * Generate a default TableCreator object from the TopLink project object.
115      */

116     public TableCreator generateDefaultTableCreator() {
117         TableCreator tblCreator = new TableCreator();
118
119         //go through each descriptor and build the table/field definitions out of mappings
120
Iterator JavaDoc descIter = project.getDescriptors().values().iterator();
121
122         while (descIter.hasNext()) {
123             ClassDescriptor desc = (ClassDescriptor)descIter.next();
124
125             //aggregate RelationalDescriptor does not contains table/field data
126
if (!desc.isAggregateDescriptor()) {
127                 initTableSchema((ClassDescriptor)desc);
128             }
129         }
130
131         //Post init the schema for relation table and direct collection/map tables, and several special mapping handlings.
132
descIter = project.getOrderedDescriptors().iterator();
133
134         while (descIter.hasNext()) {
135             ClassDescriptor desc = (ClassDescriptor)descIter.next();
136
137             if (!desc.isAggregateDescriptor()) {
138                 postInitTableSchema(desc);
139             }
140         }
141
142         tblCreator.addTableDefinitions(tableMap.values());
143
144         return tblCreator;
145     }
146
147     /**
148      * Generate a default TableCreator object from the TopLink project object,
149      * and porform the table existence check through jdbc table metadata, and filter out
150      * tables which are already in the database.
151      */

152     public TableCreator generateFilteredDefaultTableCreator(AbstractSession session) throws DatabaseException {
153         TableCreator tblCreator = generateDefaultTableCreator();
154
155         try {
156             //table exisitence check.
157
java.sql.Connection JavaDoc conn = null;
158             if (session.isServerSession()) {
159                 //acquire a connection from the pool
160
conn = ((ServerSession)session).getDefaultConnectionPool().acquireConnection().getConnection();
161             } else if (session.isDatabaseSession()) {
162                 conn = ((DatabaseSessionImpl)session).getAccessor().getConnection();
163             }
164             if (conn == null) {
165                 //TODO: this is not pretty, connection is not obtained for some reason.
166
return tblCreator;
167             }
168             DatabaseMetaData JavaDoc dbMetaData = conn.getMetaData();
169             ResultSet JavaDoc resultSet = dbMetaData.getTables(null, dbMetaData.getUserName(), null, new String JavaDoc[] { "TABLE" });
170             java.util.List JavaDoc tablesInDatabase = new java.util.ArrayList JavaDoc();
171
172             while (resultSet.next()) {
173                 //save all tables from the database
174
tablesInDatabase.add(resultSet.getString("TABLE_NAME"));
175             }
176
177             resultSet.close();
178
179             java.util.List JavaDoc existedTables = new java.util.ArrayList JavaDoc();
180             java.util.List JavaDoc existedTableNames = new java.util.ArrayList JavaDoc();
181             Iterator JavaDoc tblDefIter = tblCreator.getTableDefinitions().iterator();
182
183             while (tblDefIter.hasNext()) {
184                 TableDefinition tblDef = (TableDefinition) tblDefIter.next();
185
186                 //check if the to-be-created table is already in the database
187
if (tablesInDatabase.contains(tblDef.getFullName())) {
188                     existedTables.add(tblDef);
189                     existedTableNames.add(tblDef.getFullName());
190                 }
191             }
192
193             if (!existedTableNames.isEmpty()) {
194                 session.getSessionLog().log(SessionLog.FINEST, "skip_create_existing_tables", existedTableNames);
195
196                 //remove the existed tables, won't create them.
197
tblCreator.getTableDefinitions().removeAll(existedTables);
198             }
199         } catch (SQLException JavaDoc sqlEx) {
200             throw DatabaseException.errorRetrieveDbMetadataThroughJDBCConnection();
201         }
202
203         return tblCreator;
204     }
205
206     /**
207      * Build tables/fields infomation into the table creator object from a TopLink descriptor.
208      * This should handle most of the direct/relational mappings except many-to-many and direct
209      * collection/map mappings, witch must be down in postInit method.
210      */

211     protected void initTableSchema(ClassDescriptor desc) {
212         TableDefinition tblDef = null;
213         DatabaseTable dbTbl = null;
214         Iterator JavaDoc dbTblIter = desc.getTables().iterator();
215
216         //create a table definition for each mapped database table
217
while (dbTblIter.hasNext()) {
218             dbTbl = (DatabaseTable) dbTblIter.next();
219             tblDef = getTableDefFromDBTable(dbTbl);
220         }
221
222         //build each field definition and figure out which table it goes
223
Iterator JavaDoc fieldIter = desc.getFields().iterator();
224         DatabaseField dbField = null;
225
226         while (fieldIter.hasNext()) {
227             dbField = (DatabaseField) fieldIter.next();
228
229             boolean isPKField = false;
230
231             //first check if the filed is a pk field in the default table.
232
isPKField = desc.getPrimaryKeyFields().contains(dbField);
233
234             //then check if the field is a pk field in the secondary table(s), this is only applied to the multiple tables case.
235
Map JavaDoc secondaryKeyMap = (Map JavaDoc) desc.getAdditionalTablePrimaryKeyFields().get(dbField.getTable());
236
237             if (secondaryKeyMap != null) {
238                 isPKField = isPKField || secondaryKeyMap.containsValue(dbField);
239             }
240
241             //build or retrieve the field definition.
242
FieldDefinition fieldDef = getFieldDefFromDBField(dbField, isPKField);
243             if (isPKField) {
244                 // Check if the generation strategy is IDENTITY
245
String JavaDoc sequenceName = desc.getSequenceNumberName();
246                 DatabaseLogin login = project.getLogin();
247                 Sequence seq = login.getSequence(sequenceName);
248                 if(seq instanceof DefaultSequence) {
249                     seq = login.getDefaultSequence();
250                 }
251                 //The native sequence whose value should be aquired after insert is identity sequence
252
boolean isIdentity = seq instanceof NativeSequence && seq.shouldAcquireValueAfterInsert();
253                 fieldDef.setIsIdentity(isIdentity);
254             }
255
256             //find the table the field belongs to, and add it to the table, ony if not already added.
257
tblDef = (TableDefinition) tableMap.get(dbField.getTableName());
258
259             if (!tblDef.getFields().contains(fieldDef)) {
260                 tblDef.addField(fieldDef);
261             }
262         }
263     }
264
265     /**
266      * Build additional table/field definitions for the dscriptor, like relation table
267      * and direct-collection, direct-map table, as well as reset LOB type for serialized
268      * object mapping and type conversion maping for LOB usage
269      */

270     private void postInitTableSchema(ClassDescriptor desc) {
271         Iterator JavaDoc mappingIter = desc.getMappings().iterator();
272
273         while (mappingIter.hasNext()) {
274             DatabaseMapping mapping = (DatabaseMapping) mappingIter.next();
275
276             if (mapping.isManyToManyMapping()) {
277                 buildRelationTableDefinition((ManyToManyMapping) mapping);
278             } else if (mapping.isDirectCollectionMapping()) {
279                 buildDirectCollectionTableDefinition((DirectCollectionMapping) mapping, desc);
280             } else if (mapping.isAggregateCollectionMapping()) {
281                 //need to figure out the target foreign key field and add it into the aggregate target table
282
addForeignkeyFieldToAggregateTargetTable((AggregateCollectionMapping) mapping);
283             } else if (mapping.isForeignReferenceMapping()) {
284                 if (mapping.isOneToOneMapping())
285                     addForeignKeyFieldToSourceTargetTable((OneToOneMapping) mapping);
286                 else if (mapping.isOneToManyMapping())
287                     addForeignKeyFieldToSourceTargetTable((OneToManyMapping) mapping);
288             }
289         }
290         processAdditionalTablePkFields(desc);
291     }
292
293     /**
294      * Build relation table definitions for all many-to-many relationships in a TopLink desciptor.
295      */

296     private void buildRelationTableDefinition(ManyToManyMapping mapping) {
297         //first create relation table
298
TableDefinition tblDef = getTableDefFromDBTable(mapping.getRelationTable());
299
300         DatabaseField dbField = null;
301         DatabaseField parentDBField = null;
302         FieldDefinition fldDef = null;
303
304         //add source foreign key fields into the relation table
305
Vector JavaDoc srcFkFields = mapping.getSourceRelationKeyFields();
306
307         for (int index = 0; index < srcFkFields.size(); index++) {
308             parentDBField = (DatabaseField) mapping.getSourceKeyFields().get(index);
309             dbField = resolveDatabaseField((DatabaseField) srcFkFields.get(index), parentDBField);
310             setFieldToRelationTable(dbField, tblDef);
311             setForeignKeyForRelationTable(dbField, parentDBField);
312         }
313
314         //add target foreign key fields into the relation table
315
Vector JavaDoc targFkFields = mapping.getTargetRelationKeyFields();
316
317         for (int index = 0; index < targFkFields.size(); index++) {
318             parentDBField = (DatabaseField) mapping.getTargetKeyFields().get(index);
319             dbField = resolveDatabaseField((DatabaseField) targFkFields.get(index), parentDBField);
320             setFieldToRelationTable(dbField, tblDef);
321             setForeignKeyForRelationTable(dbField, parentDBField);
322         }
323     }
324
325     private void setForeignKeyForRelationTable(final DatabaseField dbField, final DatabaseField parentDBField) {
326         FieldDefinition fldDef;
327         fldDef = getFieldDefFromDBField(dbField, false);
328         fldDef.setForeignKeyFieldName(parentDBField.getTable().getName() + "." + parentDBField.getName());
329         return;
330     }
331
332     /**
333      * Build direct collection table definitions in a TopLink desciptor
334      */

335     private void buildDirectCollectionTableDefinition(DirectCollectionMapping mapping, ClassDescriptor desc) {
336         //first create direct collection table
337
TableDefinition tblDef = getTableDefFromDBTable(mapping.getReferenceTable());
338
339         DatabaseField dbField = null;
340
341         //add the table reference key(s)
342
Vector JavaDoc refPkFields = mapping.getReferenceKeyFields();
343
344         for (int index = 0; index < refPkFields.size(); index++) {
345             dbField = resolveDatabaseField((DatabaseField) refPkFields.get(index), (DatabaseField) mapping.getSourceKeyFields().get(index));
346             tblDef.addField(getDirectCollectionReferenceKeyFieldDefFromDBField(dbField));
347         }
348
349         //add the direct collection field to the table.
350
tblDef.addField(getFieldDefFromDBField(mapping.getDirectField(), false));
351
352         //if the mapping is direct-map field, add the direct key field to the table as well.
353
if (mapping.isDirectMapMapping()) {
354             dbField = ((DirectMapMapping) mapping).getDirectKeyField();
355             tblDef.addField(getFieldDefFromDBField(dbField, false));
356         }
357     }
358
359     /**
360      * Add the foreign key to the aggregate collection mapping target table
361      */

362     private void addForeignkeyFieldToAggregateTargetTable(AggregateCollectionMapping mapping) {
363         //unlike normal one-to-many mapping, aggregate collection mapping does not have 1:1 back reference
364
//mapping, so the target foreign key fields are not stored in the target descriptor.
365
Iterator JavaDoc targFKIter = mapping.getTargetForeignKeyFields().iterator();
366
367         while (targFKIter.hasNext()) {
368             DatabaseField dbField = (DatabaseField) targFKIter.next();
369
370             //retrive the target table denifition
371
TableDefinition targTblDef = getTableDefFromDBTable(dbField.getTable());
372
373             //add the target foreign key field definition to the table definition
374
targTblDef.addField(getFieldDefFromDBField(dbField, false));
375         }
376     }
377
378     private void addForeignKeyFieldToSourceTargetTable(OneToOneMapping mapping) {
379         if (!mapping.isForeignKeyRelationship()) {
380             return;
381         }
382  
383         Map JavaDoc srcFields = mapping.getSourceToTargetKeyFields();
384         addForeignKeyFieldToFieldDefinition(srcFields);
385     }
386     
387     private void addForeignKeyFieldToSourceTargetTable(OneToManyMapping mapping) {
388         Map JavaDoc srcFields = mapping.getTargetForeignKeysToSourceKeys();
389         addForeignKeyFieldToFieldDefinition(srcFields);
390     }
391
392     private void addForeignKeyFieldToFieldDefinition(final Map JavaDoc srcFields) {
393         DatabaseField dbSrcField = null;
394         DatabaseField dbTrgField = null;
395         
396         //add source foreign key fields into the table
397
for (Iterator JavaDoc srcFkFields = srcFields.keySet().iterator(); srcFkFields.hasNext();) {
398             dbSrcField = (DatabaseField)srcFkFields.next();
399             dbTrgField = (DatabaseField)srcFields.get(dbSrcField);
400             
401             FieldDefinition srcFldDef = (FieldDefinition)fieldMap.get(dbSrcField);
402             FieldDefinition trgFldDef = (FieldDefinition)fieldMap.get(dbTrgField);
403             
404             srcFldDef.setForeignKeyFieldName(
405                 dbTrgField.getTable().getQualifiedName() + "." + dbTrgField.getName());
406             
407             // Also ensure that the type, size and subsize of the foreign key field is
408
// same as that of the original field.
409
srcFldDef.setType(trgFldDef.getType());
410             srcFldDef.setSize(trgFldDef.getSize());
411             srcFldDef.setSubSize(trgFldDef.getSubSize());
412         }
413     }
414
415     /**
416      * Build a table definition object from a database table object
417      */

418     private TableDefinition getTableDefFromDBTable(DatabaseTable dbTbl) {
419         TableDefinition tblDef = (TableDefinition) this.tableMap.get(dbTbl.getName());
420
421         if (tblDef == null) {
422             //table not built yet, simply built it
423
tblDef = new TableDefinition();
424             tblDef.setName(dbTbl.getName());
425             tblDef.setQualifier(dbTbl.getTableQualifier());
426             tblDef.setUniqueKeys(dbTbl.getUniqueConstraints());
427             tableMap.put(dbTbl.getName(), tblDef);
428         }
429
430         return tblDef;
431     }
432
433     /**
434      * Resolve the foreign key database field metadata in relation table or direct collection/map table.
435      * Those metadata includes type, and maybe dbtype/size/subsize if DatabaseField carries those info.
436      */

437     private DatabaseField resolveDatabaseField(DatabaseField childField, DatabaseField parentField) {
438         //set through the type from the source table key field to the relation or direct collection table key field.
439
DatabaseField reslovedDatabaseField = new DatabaseField();
440         reslovedDatabaseField.setName(childField.getName());
441         reslovedDatabaseField.setType(getFieldDefFromDBField(parentField, true).getType());
442
443         return reslovedDatabaseField;
444     }
445
446     /**
447      * Build a field definition object from a database field.
448      */

449     private FieldDefinition getFieldDefFromDBField(DatabaseField dbField, boolean isPrimaryKey) {
450         FieldDefinition fieldDef = (FieldDefinition) this.fieldMap.get(dbField);
451
452         if (fieldDef == null) {
453             //not built yet, build one
454
fieldDef = new FieldDefinition();
455             fieldDef.setName(dbField.getName());
456
457             if (dbField.getColumnDefinition().length() > 0) {
458                 // This column definition would include the complete definition of the
459
// column like type, size, "NULL/NOT NULL" clause, unique key clause
460
fieldDef.setTypeName(dbField.getColumnDefinition());
461             } else {
462                 Class JavaDoc fieldType = dbField.getType();
463
464                 // Check if the user field is a String and only then allow the length specified
465
// in the @Column annotation to be set on the field.
466
if ((fieldType != null)) {
467                     if (fieldType.equals(ClassConstants.STRING) ||
468                        fieldType.equals(ClassConstants.APCHAR) ||
469                        fieldType.equals(ClassConstants.ACHAR)) {
470                         // The field size is defaulted to "255" or use the user supplied length
471
fieldDef.setSize(dbField.getLength());
472                     } else {
473                         if (dbField.getPrecision() > 0) {
474                             fieldDef.setSize(dbField.getPrecision());
475                             fieldDef.setSubSize(dbField.getScale());
476                         }
477                     }
478                 }
479
480                 if ((fieldType == null) || (!fieldType.isPrimitive() &&
481                         (new DatabaseSessionImpl(project).getPlatform().getFieldTypeDefinition(fieldType) == null))) {
482                     //TODO: log a warning for inaccessiable type or not convertable type.
483
AbstractSessionLog.getLog().log(SessionLog.FINEST, "field_type_set_to_java_lang_string", dbField.getQualifiedName(), fieldType);
484
485                     //set the default type (lang.String) to all un-resolved java type, like null, Number, util.Date, NChar/NType, Calendar
486
//sql.Blob/Clob, Object, or unknown type). Please refer to bug 4352820.
487
fieldDef.setType(ClassConstants.STRING);
488                 } else {
489                     //need to convert the primitive type if applied.
490
fieldDef.setType(ConversionManager.getObjectClass(fieldType));
491                 }
492
493                 fieldDef.setShouldAllowNull(dbField.isNullable());
494                 fieldDef.setUnique(dbField.isUnique());
495             }
496
497             fieldDef.setIsPrimaryKey(isPrimaryKey);
498             fieldMap.put(dbField, fieldDef);
499         }
500
501         return fieldDef;
502     }
503     
504     /**
505      * Build a field definition object from a database field.
506      */

507     private FieldDefinition getDirectCollectionReferenceKeyFieldDefFromDBField(DatabaseField dbField) {
508         FieldDefinition fieldDef = (FieldDefinition)getFieldDefFromDBField(dbField, true).clone();
509         //direct collection/map table reference kye filed is not unique, need to set it as non-pk.
510
fieldDef.setIsPrimaryKey(false);
511         return fieldDef;
512     }
513
514     /**
515      * Build and add a field definition object to relation table
516      */

517     private void setFieldToRelationTable(DatabaseField dbField, TableDefinition tblDef) {
518         FieldDefinition fieldDef = getFieldDefFromDBField(dbField, false);
519
520         if (!tblDef.getFields().contains(fieldDef)) {
521             //only add the field once, to avoid add twice if m:m is bi-directional.
522
tblDef.addField(getFieldDefFromDBField(dbField, false));
523             fieldDef.setIsPrimaryKey(true);
524         }
525     }
526
527     private void processAdditionalTablePkFields(ClassDescriptor desc) {
528         DatabaseTable dbTbl = null;
529         Iterator JavaDoc dbTblIter = desc.getTables().iterator();
530         while (dbTblIter.hasNext()) {
531             dbTbl = (DatabaseTable) dbTblIter.next();
532             Map JavaDoc srcFields = (HashMap JavaDoc)desc.getAdditionalTablePrimaryKeyFields().get(dbTbl);
533             if (null != srcFields) {
534                 addJoinColumnsFkFieldToFieldDefinition(srcFields);
535             }
536         }
537     }
538    
539     private void addJoinColumnsFkFieldToFieldDefinition(final Map JavaDoc srcFields) {
540         DatabaseField dbSrcField = null;
541         DatabaseField dbTrgField = null;
542         
543         //add source foreign key fields into the table
544
for (Iterator JavaDoc srcFkFields = srcFields.keySet().iterator(); srcFkFields.hasNext();) {
545             dbSrcField = (DatabaseField)srcFkFields.next();
546             dbTrgField = (DatabaseField)srcFields.get(dbSrcField);
547             FieldDefinition srcFldDef = (FieldDefinition)fieldMap.get(dbSrcField);
548             FieldDefinition trgFldDef = (FieldDefinition)fieldMap.get(dbTrgField);
549             trgFldDef.setForeignKeyFieldName(
550                 dbSrcField.getTable().getQualifiedName() + "." + dbSrcField.getName());
551         }
552     }
553 }
554
Popular Tags