KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > jdo > spi > persistence > generator > database > DatabaseGenerator


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 in
5  * compliance with the License.
6  *
7  * You can obtain a copy of the license at
8  * https://glassfish.dev.java.net/public/CDDLv1.0.html or
9  * glassfish/bootstrap/legal/CDDLv1.0.txt.
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 Notice in each file and include the License file
15  * at glassfish/bootstrap/legal/CDDLv1.0.txt.
16  * If applicable, add the following below the CDDL Header,
17  * with the fields enclosed by brackets [] replaced by
18  * you own identifying information:
19  * "Portions Copyrighted [year] [name of copyright owner]"
20  *
21  * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
22  */

23
24 /*
25  * DatabaseGenerator.java
26  *
27  * Created on Jan 14, 2003
28  */

29
30 package com.sun.jdo.spi.persistence.generator.database;
31
32 import java.io.IOException JavaDoc;
33 import java.io.OutputStream JavaDoc;
34 import java.util.ArrayList JavaDoc;
35 import java.util.Iterator JavaDoc;
36 import java.util.HashMap JavaDoc;
37 import java.util.HashSet JavaDoc;
38 import java.util.List JavaDoc;
39 import java.util.Map JavaDoc;
40 import java.util.Map.Entry;
41 import java.util.Set JavaDoc;
42 import java.util.ResourceBundle JavaDoc;
43
44 import org.netbeans.modules.dbschema.*;
45 import org.netbeans.modules.dbschema.util.NameUtil;
46 import com.sun.jdo.api.persistence.model.mapping.*;
47 import com.sun.jdo.api.persistence.model.mapping.impl.*;
48 import com.sun.jdo.api.persistence.model.jdo.*;
49 import com.sun.jdo.api.persistence.model.jdo.impl.*;
50 import com.sun.jdo.api.persistence.model.Model;
51 import com.sun.jdo.api.persistence.model.ModelException;
52 import com.sun.jdo.spi.persistence.utility.JavaTypeHelper;
53
54 import com.sun.jdo.spi.persistence.utility.I18NHelper;
55 import com.sun.jdo.spi.persistence.utility.logging.Logger;
56
57
58 /**
59  * This class generates a database schema and a Map of mapping classes from a
60  * set of JDO classes.
61  */

62 public class DatabaseGenerator {
63     /** @see DatabaseGenerationConstants#DOT */
64     private static final char DOT = DatabaseGenerationConstants.DOT;
65
66     /** Holds Java type information */
67     private final Model model;
68
69     /** Provides information on how database info should be generated */
70     private final MappingPolicy mappingPolicy;
71
72     /** List of NameTuple objects which holds persistence class name,
73      * desired table name and hash class name for database generation. */

74     private final List JavaDoc pcClasses;
75
76     /** Map from persistence-capable class names to MappingClassElement's. */
77     // See also DatabaseGenerator.Results.mappingClasses.
78
private final Map JavaDoc mappingClasses = new HashMap JavaDoc();
79     
80     /** Generated database schema. */
81     private final SchemaElement schema;
82
83     /**
84      * Used to recognize and remove classname suffixes. See {@link
85      * #getShortClassName}.
86      */

87     private final String JavaDoc classSuffix;
88
89     /** The logger */
90     private static final Logger logger =
91         LogHelperDatabaseGenerator.getLogger();
92     
93     /** I18N message handler */
94     private static final ResourceBundle JavaDoc messages =
95         I18NHelper.loadBundle(DatabaseGenerator.class);
96
97
98     /**
99      * Contains the results of invoking DatabaseGenerator.generate()
100      */

101     public class Results {
102         /** Generated database schema. */
103         private final SchemaElement schema;
104
105         /** Set of generated MappingClassElement's. */
106         // Notice that this is a Set, while the outer class has a Map. This
107
// is intentional: DatabaseGenerator.addRelationships needs to get a
108
// MappingClassElement for a pc class name, but the clients of the
109
// DatabaseGenerator should only need the mapping classes.
110
private final Set JavaDoc mappingClasses;
111
112         Results(SchemaElement schema, Map JavaDoc mappingClasses) {
113             this.schema = schema;
114             this.mappingClasses = new HashSet JavaDoc(mappingClasses.values());
115         }
116
117         /** @return Generated SchemaElement. */
118         public SchemaElement getSchema() {
119             return schema;
120         }
121
122         /** @return Generated mapping classes. */
123         public Set JavaDoc getMappingClasses() {
124             return mappingClasses;
125         }
126     }
127     
128
129     /**
130      * This class holds three strings which contain three type of information.
131      * For database generation, for each persistence class, we need to have
132      * persistence class name to look up persistence class, desired table name
133      * for table name and hash class name for unique table name. Depending on
134      * the caller's option, hash class name can be same as persistence class
135      * name or it can be different from persistence class name.
136      */

137     public static class NameTuple {
138
139         /** persistence class name */
140         private final String JavaDoc persistenceClassName;
141
142         /** desired table name */
143         private final String JavaDoc desiredTableName;
144
145         /** hash class name */
146         private final String JavaDoc hashClassName;
147
148         /**
149          * An object holds three string objects.
150          * @param persistenceClassName persistence class name for
151          * persistence class look up
152          * @param desiredTableName it can be used for table name
153          * @param hashClassName it can be used for unique table name
154          */

155         public NameTuple(
156             String JavaDoc persistenceClassName, String JavaDoc desiredTableName,
157                     String JavaDoc hashClassName) {
158
159             this.persistenceClassName = persistenceClassName;
160             this.desiredTableName = desiredTableName;
161             this.hashClassName = (hashClassName != null) ?
162                 hashClassName : persistenceClassName;
163         }
164
165         /**
166          * An object holds three string objects.
167          * @param persistenceClassName persistence class name
168          * @param desiredTableName name for creating table name
169          */

170         public NameTuple(String JavaDoc persistenceClassName, String JavaDoc desiredTableName) {
171             this(persistenceClassName, desiredTableName, null);
172         }
173
174         /** @return persistence class name. */
175         public String JavaDoc getPersistenceClassName() {
176             return persistenceClassName;
177         }
178
179         /** @return hash class name. */
180         public String JavaDoc getHashClassName() {
181             return hashClassName;
182         }
183
184         /** @return desired table name. */
185         public String JavaDoc getDesiredTableName() {
186             return desiredTableName;
187         }
188     }
189
190     /**
191      * Generate database schema and mapping model from given map of
192      * persistence class name.
193      * @param model Holds java type information.
194      * @param pcClasses A list of NameTuple objects containing persistence
195      * class name and table names.
196      * @param mappingPolicy Determines how dbvendor and user influence
197      * generated schema.
198      * @param schemaName Identifies the generated schema.
199      * @param classSuffix Class name suffix that should be removed when
200      * creating table names from class names.
201      */

202     private DatabaseGenerator(
203             Model model, List JavaDoc pcClasses, MappingPolicy mappingPolicy,
204             String JavaDoc schemaName, String JavaDoc classSuffix)
205             throws DBException {
206
207         this.model = model;
208         this.pcClasses = pcClasses;
209         this.mappingPolicy = mappingPolicy;
210         this.schema = DBElementFactory.createSchema(schemaName);
211         this.classSuffix = classSuffix;
212     }
213
214     /**
215      * Generate database schema and mapping model from given map of
216      * persistence class names. The schema is not put into the SchemaElement
217      * cache as a result of this generation. The next call to
218      * SchemaElement.forName will result in the schema being placed in the
219      * cache if it is not already there. If it is already there, it is the
220      * caller's responsibility to remove the old version if desired using
221      * SchemaElement's removeFromCache method before a SchemaElement.forName
222      * call. The generated schema is saved in <i>outputDir.schemaName</i>.
223      * @param model Holds java type information.
224      * @param pcClasses A List of NameTuple objects containing persistence
225      * class name and table names.
226      * @param mappingPolicy Determines how dbvendor and user influence
227      * generated schema.
228      * @param schemaName Identifies the generated schema.
229      * @param classSuffix Class name suffix that should be removed when
230      * creating table names from class names.
231      * @param generateMappingClasses if it is true, generate mapping classes
232      * also. <em>Currently MappingClassElement's are <b>always</b>
233      * generated, and the parameter's value is ignored.</em>
234      * @return A DatabaseGenerator.Results instance which holds the results
235      * of generation.
236      */

237     public static Results generate(
238             Model model, List JavaDoc pcClasses, MappingPolicy mappingPolicy,
239             String JavaDoc schemaName, String JavaDoc classSuffix,
240             boolean generateMappingClasses)
241             throws DBException, IOException JavaDoc, ModelException {
242
243         DatabaseGenerator generator = new DatabaseGenerator(
244                 model, pcClasses, mappingPolicy,
245                 schemaName, classSuffix);
246
247         Results rc = generator.generate();
248
249         mappingPolicy.resetCounter();
250
251         return rc;
252     }
253
254     /**
255      * Generate database schema and mapping classes. Iterate over all
256      * persistence-capable classes, generating a table for each. Within each
257      * persistence-capable class, iterate over all fields and make columns
258      * for each. Then handle relationships separately, see {@link
259      * #addRelationships}.
260      * @return A DatabaseGenerator.Results instance which holds the results
261      * of generation.
262      */

263     private Results generate() throws DBException, ModelException {
264         for (Iterator JavaDoc i = pcClasses.iterator(); i.hasNext();) {
265             NameTuple nameTuple = (NameTuple) i.next();
266             String JavaDoc pcClassName = nameTuple.getPersistenceClassName();
267             String JavaDoc desiredTableName = nameTuple.getDesiredTableName();
268
269             PersistenceClassElement pcClass =
270                     model.getPersistenceClass(pcClassName);
271
272             String JavaDoc tableName = mappingPolicy.getTableName(
273                     desiredTableName, getShortClassName(nameTuple.getHashClassName()));
274             TableElement table = DBElementFactory.createAndAttachTable(
275                     schema, tableName);
276             UniqueKeyElement pKey = DBElementFactory.createAndAttachPrimaryKey(
277                     table,
278                     mappingPolicy.getPrimaryKeyConstraintName(
279                             table.getName().getName()));
280             MappingClassElement mappingClass = createMappingClass(
281                     pcClass, table);
282
283             PersistenceFieldElement[] fields = pcClass.getFields();
284             if (fields != null) {
285                 for (int j = 0; j < fields.length; j++) {
286                     PersistenceFieldElement field = fields[j];
287                     String JavaDoc fieldName = field.getName();
288                     if (!(field instanceof RelationshipElement)) {
289                         String JavaDoc columnName = mappingPolicy.getColumnName(
290                                 desiredTableName, fieldName, tableName);
291                         String JavaDoc fieldType = model.getFieldType(
292                                 pcClassName, fieldName);
293                         String JavaDoc fullFieldName =
294                                 new StringBuffer JavaDoc(desiredTableName)
295                                 .append(DOT).append(fieldName).toString();
296                         JDBCInfo columnType =
297                             DBElementFactory.getColumnType(
298                                     fullFieldName,
299                                     fieldType,
300                                     mappingPolicy);
301                         if (logger.isLoggable(Logger.FINEST)) {
302                             logger.fine(
303                                     "DBGenerator.generate: " // NOI18N
304
+ tableName + "." + columnName + ": " // NOI18N
305
+ columnType.toString());
306                         }
307                         ColumnElement column =
308                                 DBElementFactory.createAndAttachColumn(
309                                         columnName, table, columnType);
310                         MappingFieldElement mappingField =
311                                 createAndAttachMappingField(
312                                         fieldName, mappingClass, column);
313
314                         if (field.isKey()) {
315                             column.setNullable(false);
316                             pKey.addColumn(column);
317                             pKey.getAssociatedIndex().addColumn(column);
318                             mappingClass.getTable(tableName).addKeyColumn(
319                                     column);
320                         }
321                     }
322                 }
323             }
324             mappingClasses.put(pcClassName, mappingClass);
325         }
326         addRelationships();
327
328         return new Results(schema, mappingClasses);
329     }
330
331     /**
332      * Get the primary table element for the mapping class.
333      * @param mappingClass That which is associated with the table.
334      * @return Table that is associated with mapping class.
335      * @throws DBException
336      */

337     private TableElement getPrimaryTable(MappingClassElement mappingClass)
338             throws DBException {
339
340         List JavaDoc tables = mappingClass.getTables();
341
342         MappingTableElement tbl = (MappingTableElement) tables.get(0);
343         if (tbl != null) {
344             DBIdentifier tblName = DBIdentifier.create(tbl.getTable());
345             return schema.getTable(tblName);
346         } else {
347             return null;
348         }
349     }
350
351     /**
352      * Create mapping class and associated table and PC class
353      * @param pcClass PC class that mapping class associated
354      * @param table table element that mapping class associated
355      * @return MappingClassElement associated with table and PC class
356      * @throws ModelException
357      */

358     private MappingClassElement createMappingClass(
359             PersistenceClassElement pcClass, TableElement table)
360             throws ModelException {
361
362          MappingClassElement mappingClass =
363              new MappingClassElementImpl(pcClass);
364
365          mappingClass.setDatabaseRoot(schema);
366          mappingClass.addTable(table);
367          return mappingClass;
368     }
369
370     /**
371      * Create mapping field and add to mapping class
372      * @param fieldName a String for field name
373      * @param mappingClass mapping class object that field belong to
374      * @return mapping field object
375      * @throws ModelException
376      */

377     private MappingFieldElement createAndAttachMappingField(
378             String JavaDoc fieldName, MappingClassElement mappingClass,
379             ColumnElement column) throws ModelException {
380
381         MappingFieldElement mappingField =
382                 new MappingFieldElementImpl(fieldName, mappingClass);
383
384         mappingClass.addField(mappingField);
385         mappingField.addColumn(column);
386         if (column.isBlobType()) {
387             mappingField.setFetchGroup(MappingFieldElement.GROUP_NONE);
388         } else {
389             mappingField.setFetchGroup(MappingFieldElement.GROUP_DEFAULT);
390         }
391         return mappingField;
392     }
393
394
395     /**
396      * Create and add mapping relationship with column pairs to mapping class.
397      * The column pair for mappingRelationship is same order as the column
398      * pair from foreign key. It is used for 1-1 or 1-M relationship
399      * It is column pair between local table and foreign table
400      * @param relationName relationship name for the declaring mapping class
401      * @param mappingClass mapping class that holds the relationship
402      * @param foreign key which hold column pair for the relationship
403      * @throws ModelException
404      */

405     private void addMappingRelationship(String JavaDoc relationName,
406             MappingClassElement declaringClass, ForeignKeyElement fkey)
407             throws ModelException {
408
409         MappingRelationshipElement impl = new MappingRelationshipElementImpl(
410                 relationName, declaringClass);
411         ColumnPairElement [] pairs = fkey.getColumnPairs();
412
413         for (int i = 0; i < pairs.length; i++) {
414             ColumnPairElement pair = pairs[i];
415             impl.addColumn(pair);
416         }
417         declaringClass.addField(impl);
418     }
419
420     /**
421      * Create and add MappingRelationship with associated column pairs
422      * for join table. The column pair for mappingRelationship is same
423      * order as the column pair from foreign key.
424      * It is for column pairs between the join table and the foreign table
425      * @param relationName a String for relation name
426      * @param mappingClass mapping class that holds the relationship
427      * @param fkeyForeign holding column pair information for the relationship
428      * @throws ModelException
429      */

430     private void addAssocMappingRelationship(String JavaDoc relationName,
431             MappingClassElement declaringClass, ForeignKeyElement fkeyForeign)
432             throws ModelException {
433
434         MappingRelationshipElement impl =
435                 (MappingRelationshipElement) declaringClass.getField(
436                         relationName);
437
438         if (null == impl) {
439             impl = new MappingRelationshipElementImpl(
440                     relationName, declaringClass);
441             declaringClass.addField(impl);
442         }
443
444         // Add column pair for join table and foreign table
445
ColumnPairElement [] pairs = fkeyForeign.getColumnPairs();
446         for (int i = 0; i < pairs.length; i++) {
447             ColumnPairElement pair = pairs[i];
448             impl.addAssociatedColumn(pair);
449         }
450     }
451
452     /**
453      * Create and add MappingRelationship with local inverse column pair
454      * (for join table) or inverse column pair
455      * (for non join table) for referenced table.
456      * It is for column pairs between local table and join table
457      * or for column pairs between local table and foreign table (contains
458      * foreign key)
459      * The column pair for mappingRelationship is inverse order
460      * as the column pair from foreign key. Foreign key is not in the passing
461      * mapping class but in the inverse mapping class
462      * @param relationName a String for relation name
463      * @param mappingClass mapping class that holds the relationship
464      * @param fkeyForeign holding column pair information for the relationship
465      * @throws ModelException
466      * @throws DBException
467      */

468     private void addInverseMappingRelationship(String JavaDoc relationName,
469             MappingClassElement declaringClass, ForeignKeyElement fkey,
470             boolean isJoin)
471             throws ModelException, DBException {
472
473         MappingRelationshipElement impl =
474                 (MappingRelationshipElement) declaringClass.getField(
475                         relationName);
476
477         // for join table, need to add two MappingRelationshipElement
478
if (null == impl) {
479             impl = new MappingRelationshipElementImpl(relationName,
480                 declaringClass);
481             declaringClass.addField(impl);
482         }
483
484         TableElement declaringTbl = getPrimaryTable(declaringClass);
485         ColumnPairElement [] pairs = fkey.getColumnPairs();
486
487         // Column pair get inverted since adding to referenced table
488
for (int i = 0; i < pairs.length; i++) {
489             ColumnPairElement pair = pairs[i];
490             ColumnPairElement inversePair = DBElementFactory.createColumnPair(
491                     pair.getReferencedColumn(), pair.getLocalColumn(),
492                     declaringTbl);
493
494             if (isJoin) {
495                 impl.addLocalColumn(inversePair);
496             } else {
497                 impl.addColumn(inversePair);
498             }
499         }
500     }
501
502     /**
503      * Create and add a relationship.
504      * @param srcTable Source table of the relationship.
505      * @param relTable Related table.
506      * @param relName Name of the relationship.
507      * @param inverseRelName Name of the inverse relationship.
508      * @param mappingClass Mapping information for the source table.
509      * @param relMappingClass Mapping information for the related table.
510      * @param uniqueId Id that can be appened to relName to distinguish it
511      * from other relNames in the database.
512      * @param srcIsJoin True if srcTable is a join table
513      * @return ForeignKeyElement representing the relationship.
514      */

515     private ForeignKeyElement createRelationship(TableElement srcTable,
516             TableElement relTable, String JavaDoc relName, String JavaDoc inverseRelName,
517             MappingClassElement mappingClass,
518             MappingClassElement relMappingClass,
519             String JavaDoc uniqueId, boolean srcIsJoin)
520             throws DBException, ModelException {
521
522         ForeignKeyElement fKey = DBElementFactory.createAndAttachForeignKey(
523                  srcTable, relTable, relName, mappingPolicy, uniqueId);
524
525         if (srcIsJoin) {
526             addInverseMappingRelationship(relName, mappingClass,
527                     fKey, true);
528             addAssocMappingRelationship(inverseRelName, relMappingClass, fKey);
529         } else {
530             addMappingRelationship(relName, mappingClass, fKey);
531             addInverseMappingRelationship(inverseRelName, relMappingClass,
532                     fKey, false);
533         }
534         return fKey;
535     }
536
537     /**
538      * Generate relationships for schema and mapping model from
539      * mappingClasses which already have all mapping fields populated.
540      * @throws DBException
541      * @throws ModelExpception
542      */

543     private void addRelationships()
544         throws DBException, ModelException {
545         if (logger.isLoggable(Logger.FINE)) {
546             logger.fine("add relationship"); // NOI18N
547
}
548
549         Map JavaDoc relationFKey = new HashMap JavaDoc();
550
551         // This is a list of 1-1 relationships that are deferred for
552
// processing until all other relationships are processed. Deferral
553
// allows us to concentrate foreign keys on one side of the
554
// relationship.
555
List JavaDoc deferredRelationships = new ArrayList JavaDoc();
556
557         for (Iterator JavaDoc i = mappingClasses.values().iterator(); i.hasNext();) {
558             MappingClassElement mappingClass = (MappingClassElement) i.next();
559             String JavaDoc pcClassName = mappingClass.getName();
560             PersistenceClassElement pcClass =
561                 model.getPersistenceClass(pcClassName);
562             validateModel(pcClass, "pcClass", pcClassName); // NOI18N
563
TableElement sourceTable = getPrimaryTable(mappingClass);
564             validateModel(sourceTable, "sourceTable", pcClassName); // NOI18N
565

566             // Create a string that can keep names unique
567
String JavaDoc uniqueId = getShortClassName(pcClassName);
568             int want = 8; // Ideally, take this many chars from end
569
int end = uniqueId.length();
570             int start = want > end ? 0 : end - want;
571             uniqueId = uniqueId.substring(start, end);
572
573             RelationshipElement [] rels = pcClass.getRelationships();
574             if (rels != null) {
575                 for (int j = 0; j < rels.length; j++) {
576
577                     // relationship
578
RelationshipElement relation = rels[j];
579                     String JavaDoc relationName = relation.getName();
580                     int upperBound = relation.getUpperBound();
581
582                     // inverseRelationship
583
String JavaDoc inverseRelName =
584                             relation.getInverseRelationshipName();
585                     validateModel(inverseRelName,
586                                   "inverseRelName", relationName); // NOI18N
587
String JavaDoc relClassName = model.getRelatedClass(relation);
588                     validateModel(relClassName,
589                                   "relClassName", relationName); // NOI18N
590

591                     // get related MappingClass and PersistenceClass
592
MappingClassElement relMappingClass =
593                         (MappingClassElement) mappingClasses.get(relClassName);
594                     validateModel(relMappingClass,
595                                   "relMappingClass", relClassName); // NOI18N
596
PersistenceClassElement relClass =
597                             model.getPersistenceClass(relClassName);
598                     validateModel(relClass,
599                                   "relClass", relClassName); // NOI18N
600
RelationshipElement inverseRelation =
601                             relClass.getRelationship(inverseRelName);
602                     validateModel(inverseRelation,
603                                   "inverseRelation", inverseRelName); // NOI18N
604
TableElement relTable = getPrimaryTable(relMappingClass);
605                     validateModel(relTable,
606                                   "relTable", relClassName); // NOI18N
607
int relUpperBound = inverseRelation.getUpperBound();
608
609                     if (logger.isLoggable(Logger.FINE)) {
610                         logger.fine(
611                                 "Before adding relationship:" // NOI18N
612
+ getTblInfo("sourceTable", sourceTable, relationName) // NOI18N
613
+ getTblInfo("relTable", relTable, inverseRelName)); // NOI18N
614
}
615
616                     // XXX Suggest making each block below a separate method.
617

618                     if ((upperBound > 1) && (relUpperBound > 1)) {
619                         // M-N relationship, create new table
620
if (logger.isLoggable(Logger.FINE)) {
621                             logger.fine("M-N relationship"); // NOI18N
622
}
623
624                         ForeignKeyElement fKey = getMappedForeignKey(
625                                 relation, inverseRelation, relationFKey);
626                         if (fKey == null) {
627                             TableElement joinTable =
628                                 DBElementFactory.createAndAttachTable(
629                                         schema,
630                                         mappingPolicy.getJoinTableName(
631                                                 sourceTable.getName().getName(),
632                                                 relTable.getName().getName()));
633                             fKey = createRelationship(
634                                     joinTable, sourceTable, relationName,
635                                     inverseRelName, mappingClass,
636                                     relMappingClass, uniqueId, true);
637                             relationFKey.put(relation, fKey);
638                             ForeignKeyElement fKey2 = createRelationship(
639                                     joinTable, relTable, inverseRelName,
640                                     relationName, relMappingClass,
641                                     mappingClass, uniqueId, true);
642                             relationFKey.put(inverseRelation, fKey2);
643                         }
644
645                     } else if ((upperBound > 1) && (relUpperBound == 1)) {
646                         // M-1 relationship, add foreign key at upper bound
647
// equal 1 side. So here, we do nothing: We add
648
// relationships at the 1 side for 1-M relationships,
649
// and the current mapping class is the many side.
650

651                         if (logger.isLoggable(Logger.FINE)) {
652                             logger.fine("M-1 relationship: skip"); // NOI18N
653
}
654
655                     } else if ((upperBound == 1) && (relUpperBound >1)) {
656                         // 1-M relationship, add foreign key at upperBound =
657
// 1 side
658

659                         if (logger.isLoggable(Logger.FINE)) {
660                             logger.fine("1-M relationship"); // NOI18N
661
}
662
663                         ForeignKeyElement fKey = getMappedForeignKey(relation,
664                             inverseRelation, relationFKey);
665                         if (fKey == null) {
666                             fKey = createRelationship(sourceTable, relTable,
667                                 relationName, inverseRelName, mappingClass,
668                                 relMappingClass, uniqueId, false);
669                             relationFKey.put(relation, fKey);
670                         }
671
672                     } else if ((upperBound == 1) && (relUpperBound == 1)) {
673                         // 1-1 relationship, add foreign key at either side.
674
// Check existence of foreign key at the other side
675
// before adding one to here. If there is cascade
676
// delete in this side, add FK. Otherwise, defer
677
// adding it until all other relationships are added.
678

679                         ForeignKeyElement fKey = getMappedForeignKey(relation,
680                                 inverseRelation, relationFKey);
681                         if (fKey == null) {
682                             if (relation.getDeleteAction() ==
683                                 RelationshipElement.CASCADE_ACTION) {
684                                 if (logger.isLoggable(Logger.FINE)) {
685                                     logger.fine("1-1 relationship: cascade(this)"); // NOI18N
686
}
687                                 fKey = createRelationship(
688                                         sourceTable, relTable, relationName,
689                                         inverseRelName, mappingClass,
690                                         relMappingClass, uniqueId, false);
691                                 relationFKey.put(relation, fKey);
692                             } else if (inverseRelation.getDeleteAction() ==
693                                        RelationshipElement.CASCADE_ACTION) {
694                                 if (logger.isLoggable(Logger.FINE)) {
695                                     logger.fine("1-1 relationship: cascade(inverse)"); // NOI18N
696
}
697                                 fKey = createRelationship(
698                                         relTable, sourceTable,
699                                         inverseRelName, relationName,
700                                         relMappingClass, mappingClass,
701                                         uniqueId, false);
702                                 relationFKey.put(inverseRelation, fKey);
703                             } else {
704                                 if (logger.isLoggable(Logger.FINE)) {
705                                     logger.fine("1-1 relationship: defer"); // NOI18N
706
}
707                                 deferredRelationships.add(
708                                         new DeferredRelationship(
709                                                 relation, inverseRelation,
710                                                 sourceTable, relTable,
711                                                 relationName, inverseRelName,
712                                                 mappingClass, relMappingClass,
713                                                 uniqueId));
714                             }
715                         }
716                     }
717                     if (logger.isLoggable(Logger.FINE)) {
718                         logger.fine(
719                                 "After adding relationship:" // NOI18N
720
+ getTblInfo("sourceTable", sourceTable, relationName) // NOI18N
721
+ getTblInfo("relTable", relTable, inverseRelName)); // NOI18N
722
}
723                 }
724             }
725         }
726         
727         if (deferredRelationships.size() > 0) {
728             addDeferredRelationships(deferredRelationships, relationFKey);
729         }
730     }
731
732     /**
733      * Generate foreign keys for relationships that were deferred; see {@link
734      * addRelationships}.
735      * @param deferredRelationships List of 1-1 relationships which are not
736      * yet mapped by foreign keys
737      * @param relationFKey Map from RelationshipElement to ForeignKeyElement,
738      * indicating which relationships have already been mapped.
739      */

740     private void addDeferredRelationships(
741             List JavaDoc deferredRelationships, Map JavaDoc relationFKey)
742             throws DBException, ModelException {
743
744         for (Iterator JavaDoc i = deferredRelationships.iterator(); i.hasNext();) {
745             DeferredRelationship dr = (DeferredRelationship)i.next();
746
747             RelationshipElement relation = dr.getRelation();
748             RelationshipElement inverseRelation = dr.getInverseRelation();
749
750             ForeignKeyElement fKey =
751                 getMappedForeignKey(relation, inverseRelation, relationFKey);
752
753             // Only map if not already mapped
754
if (fKey == null) {
755
756                 TableElement sourceTable = dr.getSourceTable();
757                 TableElement relTable = dr.getRelTable();
758
759                 String JavaDoc relationName = dr.getRelationName();
760                 String JavaDoc inverseRelName = dr.getInverseRelName();
761
762                 MappingClassElement mappingClass = dr.getMappingClass();
763                 MappingClassElement relMappingClass = dr.getRelMappingClass();
764
765                 String JavaDoc uniqueId = dr.getUniqueId();
766                 
767                 // If this side already has any foreign keys, map it on this
768
// side, else on the other side.
769
ForeignKeyElement keys[] = sourceTable.getForeignKeys();
770                 if (null != keys && keys.length > 0) {
771                     fKey = createRelationship(
772                             sourceTable, relTable,
773                             relationName, inverseRelName,
774                             mappingClass, relMappingClass,
775                             uniqueId, false);
776                     if (logger.isLoggable(Logger.FINE)) {
777                         logger.fine(
778                                 "1-1 deferred relationship (this)" // NOI18N
779
+ getTblInfo("sourceTable", sourceTable, relationName) // NOI18N
780
+ getTblInfo("relTable", relTable, inverseRelName)); // NOI18N
781
}
782                 } else {
783                     fKey = createRelationship(
784                             relTable, sourceTable,
785                             inverseRelName, relationName,
786                             relMappingClass, mappingClass,
787                             uniqueId, false);
788                     if (logger.isLoggable(Logger.FINE)) {
789                         logger.fine(
790                                 "1-1 deferred relationship (inverse)" // NOI18N
791
+ getTblInfo("sourceTable", sourceTable, relationName) // NOI18N
792
+ getTblInfo("relTable", relTable, inverseRelName)); // NOI18N
793
}
794                 }
795                 relationFKey.put(relation, fKey);
796             }
797         }
798     }
799
800
801     /**
802      * A DeferredRelationship instance represents all the information required
803      * to create a foreign key from the relationship.
804      */

805     static class DeferredRelationship {
806         private final RelationshipElement relation;
807         private final RelationshipElement inverseRelation;
808         private final TableElement sourceTable;
809         private final TableElement relTable;
810         private final String JavaDoc relationName;
811         private final String JavaDoc inverseRelName;
812         private final MappingClassElement mappingClass;
813         private final MappingClassElement relMappingClass;
814         private final String JavaDoc uniqueId;
815         
816         
817         DeferredRelationship(RelationshipElement relation,
818                              RelationshipElement inverseRelation,
819                              TableElement sourceTable,
820                              TableElement relTable,
821                              String JavaDoc relationName,
822                              String JavaDoc inverseRelName,
823                              MappingClassElement mappingClass,
824                              MappingClassElement relMappingClass,
825                              String JavaDoc uniqueId) {
826
827             this.relation = relation;
828             this.inverseRelation = inverseRelation;
829             this.sourceTable = sourceTable;
830             this.relTable = relTable;
831             this.relationName = relationName;
832             this.inverseRelName = inverseRelName;
833             this.mappingClass = mappingClass;
834             this.relMappingClass = relMappingClass;
835             this.uniqueId = uniqueId;
836         }
837
838         RelationshipElement getRelation() { return relation; }
839         RelationshipElement getInverseRelation() { return inverseRelation; }
840
841         TableElement getSourceTable() { return sourceTable; }
842         TableElement getRelTable() { return relTable; }
843
844         String JavaDoc getRelationName() { return relationName; }
845         String JavaDoc getInverseRelName() { return inverseRelName; }
846
847         MappingClassElement getMappingClass() { return mappingClass; }
848         MappingClassElement getRelMappingClass() { return relMappingClass; }
849
850         String JavaDoc getUniqueId() { return uniqueId; }
851     }
852
853
854     
855     /**
856      * Check if the relationship has been visited
857      * @param relation current visiting relationship
858      * @param inverseRelation inverse relationship
859      * @param relationFKey a map to hold relation and foreign key
860      * @return the foreign key element or null
861      */

862     private ForeignKeyElement getMappedForeignKey(
863             RelationshipElement relation, RelationshipElement inverseRelation,
864             Map JavaDoc relationFKey) {
865
866         ForeignKeyElement fkey =
867                 (ForeignKeyElement) relationFKey.get(relation);
868
869         if (fkey == null) {
870             return (ForeignKeyElement) relationFKey.get(inverseRelation);
871         } else {
872             return fkey;
873         }
874     }
875
876     /**
877      * Computes the class name (without package) for the supplied
878      * class name and trims off the class suffix.
879      * @param className the fully qualified name of the class
880      * @return the class name (without package and class suffix)
881      * for the supplied class name
882      */

883     private String JavaDoc getShortClassName(String JavaDoc className) {
884         String JavaDoc shortName = JavaTypeHelper.getShortClassName(className);
885
886         if ((classSuffix != null) && (!shortName.equals(classSuffix))) {
887             int index = shortName.lastIndexOf(classSuffix);
888             if (index != -1) {
889                 shortName = shortName.substring(0, index);
890             }
891         }
892         return shortName;
893     }
894
895     /**
896      * Assert that the given object reference is <em>not</em> null.
897      * @param o Object reference that is checked for null.
898      * @param failedItem String that names the item that is being checked.
899      * @param accessor String which names an object that was use to try and
900       * get object <code>o</code>.
901      * @throws ModelException If o is null.
902      */

903     private void validateModel(Object JavaDoc o,
904                                String JavaDoc failedItem,
905                                String JavaDoc accessor) throws ModelException {
906         if (null == o) {
907             String JavaDoc msg = I18NHelper.getMessage(
908                     messages,
909                     "EXC_InvalidRelationshipMapping", // NOI18N
910
failedItem,
911                     accessor);
912             logger.log(Logger.SEVERE, msg);
913             throw new ModelException(msg);
914         }
915     }
916
917     /**
918      * Debug support. Returns a string describing the table and it's keys
919      * (only the first key is listed).
920      * @param tblName name of the table described
921      * @param tbl table being described
922      * @param relName name of a relationship
923      */

924     private static String JavaDoc getTblInfo(String JavaDoc tblName, TableElement tbl, String JavaDoc relName) {
925         int numFK = tbl.getForeignKeys().length;
926         ForeignKeyElement fk = null;
927         if (numFK > 0) {
928             fk = tbl.getForeignKeys()[0];
929         }
930         return " " + tblName + "=" + tbl.toString()
931             + ", # keys=" + numFK // NOI18N
932
+ ", 1st key=" + fk // NOI18N
933
+ "; relationship Name=" + relName; // NOI18N
934
}
935 }
936
Popular Tags