FileDocCategorySizeDatePackage
DatabaseGenerator.javaAPI DocGlassfish v2 API41255Fri May 04 22:34:40 BST 2007com.sun.jdo.spi.persistence.generator.database

DatabaseGenerator

public class DatabaseGenerator extends Object
This class generates a database schema and a Map of mapping classes from a set of JDO classes.

Fields Summary
private static final char
DOT
private final com.sun.jdo.api.persistence.model.Model
model
Holds Java type information
private final MappingPolicy
mappingPolicy
Provides information on how database info should be generated
private final List
pcClasses
List of NameTuple objects which holds persistence class name, desired table name and hash class name for database generation.
private final Map
mappingClasses
Map from persistence-capable class names to MappingClassElement's.
private final SchemaElement
schema
Generated database schema.
private final String
classSuffix
Used to recognize and remove classname suffixes. See {@link #getShortClassName}.
private static final com.sun.jdo.spi.persistence.utility.logging.Logger
logger
The logger
private static final ResourceBundle
messages
I18N message handler
Constructors Summary
private DatabaseGenerator(com.sun.jdo.api.persistence.model.Model model, List pcClasses, MappingPolicy mappingPolicy, String schemaName, String classSuffix)
Generate database schema and mapping model from given map of persistence class name.

param
model Holds java type information.
param
pcClasses A list of NameTuple objects containing persistence class name and table names.
param
mappingPolicy Determines how dbvendor and user influence generated schema.
param
schemaName Identifies the generated schema.
param
classSuffix Class name suffix that should be removed when creating table names from class names.


        this.model = model;
        this.pcClasses = pcClasses;
        this.mappingPolicy = mappingPolicy;
        this.schema = DBElementFactory.createSchema(schemaName);
        this.classSuffix = classSuffix;
    
Methods Summary
private voidaddAssocMappingRelationship(java.lang.String relationName, MappingClassElement declaringClass, ForeignKeyElement fkeyForeign)
Create and add MappingRelationship with associated column pairs for join table. The column pair for mappingRelationship is same order as the column pair from foreign key. It is for column pairs between the join table and the foreign table

param
relationName a String for relation name
param
mappingClass mapping class that holds the relationship
param
fkeyForeign holding column pair information for the relationship
throws
ModelException


        MappingRelationshipElement impl =
                (MappingRelationshipElement) declaringClass.getField(
                        relationName);

        if (null == impl) {
            impl = new MappingRelationshipElementImpl(
                    relationName, declaringClass);
            declaringClass.addField(impl);
        }

        // Add column pair for join table and foreign table
        ColumnPairElement [] pairs = fkeyForeign.getColumnPairs();
        for (int i = 0; i < pairs.length; i++) {
            ColumnPairElement pair = pairs[i];
            impl.addAssociatedColumn(pair);
        }
    
private voidaddDeferredRelationships(java.util.List deferredRelationships, java.util.Map relationFKey)
Generate foreign keys for relationships that were deferred; see {@link addRelationships}.

param
deferredRelationships List of 1-1 relationships which are not yet mapped by foreign keys
param
relationFKey Map from RelationshipElement to ForeignKeyElement, indicating which relationships have already been mapped.


        for (Iterator i = deferredRelationships.iterator(); i.hasNext();) {
            DeferredRelationship dr = (DeferredRelationship)i.next();

            RelationshipElement relation = dr.getRelation();
            RelationshipElement inverseRelation = dr.getInverseRelation();

            ForeignKeyElement fKey =
                getMappedForeignKey(relation, inverseRelation, relationFKey);

            // Only map if not already mapped
            if (fKey == null) {

                TableElement sourceTable = dr.getSourceTable();
                TableElement relTable = dr.getRelTable();

                String relationName = dr.getRelationName();
                String inverseRelName = dr.getInverseRelName();

                MappingClassElement mappingClass = dr.getMappingClass();
                MappingClassElement relMappingClass = dr.getRelMappingClass();

                String uniqueId = dr.getUniqueId();
                
                // If this side already has any foreign keys, map it on this
                // side, else on the other side.
                ForeignKeyElement keys[] = sourceTable.getForeignKeys();
                if (null != keys && keys.length > 0) {
                    fKey = createRelationship(
                            sourceTable, relTable, 
                            relationName, inverseRelName,
                            mappingClass, relMappingClass,
                            uniqueId, false);
                    if (logger.isLoggable(Logger.FINE)) {
                        logger.fine(
                                "1-1 deferred relationship (this)" // NOI18N
                                + getTblInfo("sourceTable", sourceTable, relationName) // NOI18N
                                + getTblInfo("relTable", relTable, inverseRelName)); // NOI18N
                    }
                } else {
                    fKey = createRelationship(
                            relTable, sourceTable,
                            inverseRelName, relationName,
                            relMappingClass, mappingClass,
                            uniqueId, false);
                    if (logger.isLoggable(Logger.FINE)) {
                        logger.fine(
                                "1-1 deferred relationship (inverse)" // NOI18N
                                + getTblInfo("sourceTable", sourceTable, relationName) // NOI18N
                                + getTblInfo("relTable", relTable, inverseRelName)); // NOI18N
                    }
                }
                relationFKey.put(relation, fKey);
            }
        }
    
private voidaddInverseMappingRelationship(java.lang.String relationName, MappingClassElement declaringClass, ForeignKeyElement fkey, boolean isJoin)
Create and add MappingRelationship with local inverse column pair (for join table) or inverse column pair (for non join table) for referenced table. It is for column pairs between local table and join table or for column pairs between local table and foreign table (contains foreign key) The column pair for mappingRelationship is inverse order as the column pair from foreign key. Foreign key is not in the passing mapping class but in the inverse mapping class

param
relationName a String for relation name
param
mappingClass mapping class that holds the relationship
param
fkeyForeign holding column pair information for the relationship
throws
ModelException
throws
DBException


        MappingRelationshipElement impl =
                (MappingRelationshipElement) declaringClass.getField(
                        relationName);

        // for join table, need to add two MappingRelationshipElement
        if (null == impl) {
            impl = new MappingRelationshipElementImpl(relationName,
                declaringClass);
            declaringClass.addField(impl);
        }

        TableElement declaringTbl = getPrimaryTable(declaringClass);
        ColumnPairElement [] pairs = fkey.getColumnPairs();

        // Column pair get inverted since adding to referenced table
        for (int i = 0; i < pairs.length; i++) {
            ColumnPairElement pair = pairs[i];
            ColumnPairElement inversePair = DBElementFactory.createColumnPair(
                    pair.getReferencedColumn(), pair.getLocalColumn(), 
                    declaringTbl);

            if (isJoin) {
                impl.addLocalColumn(inversePair);
            } else {
                impl.addColumn(inversePair);
            }
        }
    
private voidaddMappingRelationship(java.lang.String relationName, MappingClassElement declaringClass, ForeignKeyElement fkey)
Create and add mapping relationship with column pairs to mapping class. The column pair for mappingRelationship is same order as the column pair from foreign key. It is used for 1-1 or 1-M relationship It is column pair between local table and foreign table

param
relationName relationship name for the declaring mapping class
param
mappingClass mapping class that holds the relationship
param
foreign key which hold column pair for the relationship
throws
ModelException


        MappingRelationshipElement impl = new MappingRelationshipElementImpl(
                relationName, declaringClass);
        ColumnPairElement [] pairs = fkey.getColumnPairs();

        for (int i = 0; i < pairs.length; i++) {
            ColumnPairElement pair = pairs[i];
            impl.addColumn(pair);
        }
        declaringClass.addField(impl);
    
private voidaddRelationships()
Generate relationships for schema and mapping model from mappingClasses which already have all mapping fields populated.

throws
DBException
throws
ModelExpception

        if (logger.isLoggable(Logger.FINE)) {
            logger.fine("add relationship"); // NOI18N
        }

        Map relationFKey = new HashMap();

        // This is a list of 1-1 relationships that are deferred for
        // processing until all other relationships are processed.  Deferral
        // allows us to concentrate foreign keys on one side of the
        // relationship.
        List deferredRelationships = new ArrayList();

        for (Iterator i = mappingClasses.values().iterator(); i.hasNext();) {
            MappingClassElement mappingClass = (MappingClassElement) i.next();
            String pcClassName = mappingClass.getName();
            PersistenceClassElement pcClass =
                model.getPersistenceClass(pcClassName);
            validateModel(pcClass, "pcClass", pcClassName); // NOI18N
            TableElement sourceTable = getPrimaryTable(mappingClass);
            validateModel(sourceTable, "sourceTable", pcClassName); // NOI18N

            // Create a string that can keep names unique
            String uniqueId = getShortClassName(pcClassName);
            int want = 8; // Ideally, take this many chars from end
            int end = uniqueId.length();
            int start = want > end ? 0 : end - want;
            uniqueId = uniqueId.substring(start, end);

            RelationshipElement [] rels = pcClass.getRelationships();
            if (rels != null) {
                for (int j = 0; j < rels.length; j++) {

                    // relationship
                    RelationshipElement relation = rels[j];
                    String relationName = relation.getName();
                    int upperBound = relation.getUpperBound();

                    // inverseRelationship
                    String inverseRelName =
                            relation.getInverseRelationshipName();
                    validateModel(inverseRelName,
                                  "inverseRelName", relationName); // NOI18N
                    String relClassName = model.getRelatedClass(relation);
                    validateModel(relClassName,
                                  "relClassName", relationName); // NOI18N

                    // get related MappingClass and PersistenceClass
                    MappingClassElement relMappingClass =
                        (MappingClassElement) mappingClasses.get(relClassName);
                    validateModel(relMappingClass,
                                  "relMappingClass", relClassName); // NOI18N
                    PersistenceClassElement relClass =
                            model.getPersistenceClass(relClassName);
                    validateModel(relClass,
                                  "relClass", relClassName); // NOI18N
                    RelationshipElement inverseRelation =
                            relClass.getRelationship(inverseRelName);
                    validateModel(inverseRelation,
                                  "inverseRelation", inverseRelName); // NOI18N
                    TableElement relTable = getPrimaryTable(relMappingClass);
                    validateModel(relTable,
                                  "relTable", relClassName); // NOI18N
                    int relUpperBound = inverseRelation.getUpperBound();

                    if (logger.isLoggable(Logger.FINE)) {
                        logger.fine(
                                "Before adding relationship:" // NOI18N
                                + getTblInfo("sourceTable", sourceTable, relationName) // NOI18N
                                + getTblInfo("relTable", relTable, inverseRelName)); // NOI18N
                    }

                    // XXX Suggest making each block below a separate method.
                    
                    if ((upperBound > 1) && (relUpperBound > 1)) {
                        // M-N relationship, create new table
                        if (logger.isLoggable(Logger.FINE)) {
                            logger.fine("M-N relationship"); // NOI18N
                        }

                        ForeignKeyElement fKey = getMappedForeignKey(
                                relation, inverseRelation, relationFKey);
                        if (fKey == null) {
                            TableElement joinTable =
                                DBElementFactory.createAndAttachTable(
                                        schema,
                                        mappingPolicy.getJoinTableName(
                                                sourceTable.getName().getName(),
                                                relTable.getName().getName()));
                            fKey = createRelationship(
                                    joinTable, sourceTable, relationName,
                                    inverseRelName, mappingClass,
                                    relMappingClass, uniqueId, true);
                            relationFKey.put(relation, fKey);
                            ForeignKeyElement fKey2 = createRelationship(
                                    joinTable, relTable, inverseRelName,
                                    relationName, relMappingClass,
                                    mappingClass, uniqueId, true);
                            relationFKey.put(inverseRelation, fKey2);
                        }

                    } else if ((upperBound > 1) && (relUpperBound == 1)) {
                        // M-1 relationship, add foreign key at upper bound
                        // equal 1 side.  So here, we do nothing: We add
                        // relationships at the 1 side for 1-M relationships,
                        // and the current mapping class is the many side.

                        if (logger.isLoggable(Logger.FINE)) {
                            logger.fine("M-1 relationship: skip"); // NOI18N
                        }

                    } else if ((upperBound == 1) && (relUpperBound >1)) {
                        // 1-M relationship, add foreign key at upperBound =
                        // 1 side

                        if (logger.isLoggable(Logger.FINE)) {
                            logger.fine("1-M relationship"); // NOI18N
                        }

                        ForeignKeyElement fKey = getMappedForeignKey(relation, 
                            inverseRelation, relationFKey);
                        if (fKey == null) {
                            fKey = createRelationship(sourceTable, relTable, 
                                relationName, inverseRelName, mappingClass, 
                                relMappingClass, uniqueId, false);
                            relationFKey.put(relation, fKey);
                        }

                    } else if ((upperBound == 1) && (relUpperBound == 1)) {
                        // 1-1 relationship, add foreign key at either side.
                        // Check existence of foreign key at the other side
                        // before adding one to here.  If there is cascade
                        // delete in this side, add FK.  Otherwise, defer
                        // adding it until all other relationships are added.

                        ForeignKeyElement fKey = getMappedForeignKey(relation, 
                                inverseRelation, relationFKey);
                        if (fKey == null) {
                            if (relation.getDeleteAction() == 
                                RelationshipElement.CASCADE_ACTION) {
                                if (logger.isLoggable(Logger.FINE)) {
                                    logger.fine("1-1 relationship: cascade(this)"); // NOI18N
                                }
                                fKey = createRelationship(
                                        sourceTable, relTable, relationName,
                                        inverseRelName, mappingClass, 
                                        relMappingClass, uniqueId, false);
                                relationFKey.put(relation, fKey);
                            } else if (inverseRelation.getDeleteAction() == 
                                       RelationshipElement.CASCADE_ACTION) {
                                if (logger.isLoggable(Logger.FINE)) {
                                    logger.fine("1-1 relationship: cascade(inverse)"); // NOI18N
                                }
                                fKey = createRelationship(
                                        relTable, sourceTable, 
                                        inverseRelName, relationName, 
                                        relMappingClass, mappingClass,
                                        uniqueId, false);
                                relationFKey.put(inverseRelation, fKey);
                            } else {
                                if (logger.isLoggable(Logger.FINE)) {
                                    logger.fine("1-1 relationship: defer"); // NOI18N
                                }
                                deferredRelationships.add(
                                        new DeferredRelationship(
                                                relation, inverseRelation,
                                                sourceTable, relTable,
                                                relationName, inverseRelName,
                                                mappingClass, relMappingClass,
                                                uniqueId));
                            }
                        }
                    }
                    if (logger.isLoggable(Logger.FINE)) {
                        logger.fine(
                                "After adding relationship:" // NOI18N
                                + getTblInfo("sourceTable", sourceTable, relationName) // NOI18N
                                + getTblInfo("relTable", relTable, inverseRelName)); // NOI18N
                    }
                }
            }
        }
        
        if (deferredRelationships.size() > 0) {
            addDeferredRelationships(deferredRelationships, relationFKey);
        }
    
private MappingFieldElementcreateAndAttachMappingField(java.lang.String fieldName, MappingClassElement mappingClass, ColumnElement column)
Create mapping field and add to mapping class

param
fieldName a String for field name
param
mappingClass mapping class object that field belong to
return
mapping field object
throws
ModelException


        MappingFieldElement mappingField =
                new MappingFieldElementImpl(fieldName, mappingClass);

        mappingClass.addField(mappingField);
        mappingField.addColumn(column);
        if (column.isBlobType()) {
            mappingField.setFetchGroup(MappingFieldElement.GROUP_NONE);
        } else {
            mappingField.setFetchGroup(MappingFieldElement.GROUP_DEFAULT);
        }
        return mappingField;
    
private MappingClassElementcreateMappingClass(PersistenceClassElement pcClass, TableElement table)
Create mapping class and associated table and PC class

param
pcClass PC class that mapping class associated
param
table table element that mapping class associated
return
MappingClassElement associated with table and PC class
throws
ModelException


         MappingClassElement mappingClass =
             new MappingClassElementImpl(pcClass);

         mappingClass.setDatabaseRoot(schema);
         mappingClass.addTable(table);
         return mappingClass;
    
private ForeignKeyElementcreateRelationship(TableElement srcTable, TableElement relTable, java.lang.String relName, java.lang.String inverseRelName, MappingClassElement mappingClass, MappingClassElement relMappingClass, java.lang.String uniqueId, boolean srcIsJoin)
Create and add a relationship.

param
srcTable Source table of the relationship.
param
relTable Related table.
param
relName Name of the relationship.
param
inverseRelName Name of the inverse relationship.
param
mappingClass Mapping information for the source table.
param
relMappingClass Mapping information for the related table.
param
uniqueId Id that can be appened to relName to distinguish it from other relNames in the database.
param
srcIsJoin True if srcTable is a join table
return
ForeignKeyElement representing the relationship.


        ForeignKeyElement fKey = DBElementFactory.createAndAttachForeignKey(
                 srcTable, relTable, relName, mappingPolicy, uniqueId);

        if (srcIsJoin) {
            addInverseMappingRelationship(relName, mappingClass,
                    fKey, true);
            addAssocMappingRelationship(inverseRelName, relMappingClass, fKey);
        } else {
            addMappingRelationship(relName, mappingClass, fKey);
            addInverseMappingRelationship(inverseRelName, relMappingClass,
                    fKey, false);
        }
        return fKey;
    
public static com.sun.jdo.spi.persistence.generator.database.DatabaseGenerator$Resultsgenerate(com.sun.jdo.api.persistence.model.Model model, java.util.List pcClasses, MappingPolicy mappingPolicy, java.lang.String schemaName, java.lang.String classSuffix, boolean generateMappingClasses)
Generate database schema and mapping model from given map of persistence class names. The schema is not put into the SchemaElement cache as a result of this generation. The next call to SchemaElement.forName will result in the schema being placed in the cache if it is not already there. If it is already there, it is the caller's responsibility to remove the old version if desired using SchemaElement's removeFromCache method before a SchemaElement.forName call. The generated schema is saved in outputDir.schemaName.

param
model Holds java type information.
param
pcClasses A List of NameTuple objects containing persistence class name and table names.
param
mappingPolicy Determines how dbvendor and user influence generated schema.
param
schemaName Identifies the generated schema.
param
classSuffix Class name suffix that should be removed when creating table names from class names.
param
generateMappingClasses if it is true, generate mapping classes also. Currently MappingClassElement's are always generated, and the parameter's value is ignored.
return
A DatabaseGenerator.Results instance which holds the results of generation.


        DatabaseGenerator generator = new DatabaseGenerator(
                model, pcClasses, mappingPolicy,
                schemaName, classSuffix);

        Results rc = generator.generate();

        mappingPolicy.resetCounter();

        return rc;
    
private com.sun.jdo.spi.persistence.generator.database.DatabaseGenerator$Resultsgenerate()
Generate database schema and mapping classes. Iterate over all persistence-capable classes, generating a table for each. Within each persistence-capable class, iterate over all fields and make columns for each. Then handle relationships separately, see {@link #addRelationships}.

return
A DatabaseGenerator.Results instance which holds the results of generation.

        for (Iterator i = pcClasses.iterator(); i.hasNext();) {
            NameTuple nameTuple = (NameTuple) i.next();
            String pcClassName = nameTuple.getPersistenceClassName();
            String desiredTableName = nameTuple.getDesiredTableName();

            PersistenceClassElement pcClass =
                    model.getPersistenceClass(pcClassName);

            String tableName = mappingPolicy.getTableName(
                    desiredTableName, getShortClassName(nameTuple.getHashClassName()));
            TableElement table = DBElementFactory.createAndAttachTable(
                    schema, tableName);
            UniqueKeyElement pKey = DBElementFactory.createAndAttachPrimaryKey(
                    table,
                    mappingPolicy.getPrimaryKeyConstraintName(
                            table.getName().getName()));
            MappingClassElement mappingClass = createMappingClass(
                    pcClass, table);

            PersistenceFieldElement[] fields = pcClass.getFields();
            if (fields != null) {
                for (int j = 0; j < fields.length; j++) {
                    PersistenceFieldElement field = fields[j];
                    String fieldName = field.getName();
                    if (!(field instanceof RelationshipElement)) {
                        String columnName = mappingPolicy.getColumnName(
                                desiredTableName, fieldName, tableName);
                        String fieldType = model.getFieldType(
                                pcClassName, fieldName);
                        String fullFieldName =
                                new StringBuffer(desiredTableName)
                                .append(DOT).append(fieldName).toString();
                        JDBCInfo columnType =
                            DBElementFactory.getColumnType(
                                    fullFieldName,
                                    fieldType,
                                    mappingPolicy);
                        if (logger.isLoggable(Logger.FINEST)) {
                            logger.fine(
                                    "DBGenerator.generate: " // NOI18N
                                    + tableName + "." + columnName + ": " // NOI18N
                                    + columnType.toString());
                        }
                        ColumnElement column =
                                DBElementFactory.createAndAttachColumn(
                                        columnName, table, columnType);
                        MappingFieldElement mappingField = 
                                createAndAttachMappingField(
                                        fieldName, mappingClass, column);

                        if (field.isKey()) {
                            column.setNullable(false);
                            pKey.addColumn(column);
                            pKey.getAssociatedIndex().addColumn(column);
                            mappingClass.getTable(tableName).addKeyColumn(
                                    column);
                        }
                    }
                }
            }
            mappingClasses.put(pcClassName, mappingClass);
        }
        addRelationships();

        return new Results(schema, mappingClasses);
    
private ForeignKeyElementgetMappedForeignKey(RelationshipElement relation, RelationshipElement inverseRelation, java.util.Map relationFKey)
Check if the relationship has been visited

param
relation current visiting relationship
param
inverseRelation inverse relationship
param
relationFKey a map to hold relation and foreign key
return
the foreign key element or null


        ForeignKeyElement fkey =
                (ForeignKeyElement) relationFKey.get(relation);

        if (fkey == null) {
            return (ForeignKeyElement) relationFKey.get(inverseRelation);
        } else {
            return fkey;
        }
    
private TableElementgetPrimaryTable(MappingClassElement mappingClass)
Get the primary table element for the mapping class.

param
mappingClass That which is associated with the table.
return
Table that is associated with mapping class.
throws
DBException


        List tables = mappingClass.getTables();

        MappingTableElement tbl = (MappingTableElement) tables.get(0);
        if (tbl != null) {
            DBIdentifier tblName = DBIdentifier.create(tbl.getTable());
            return schema.getTable(tblName);
        } else {
            return null;
        }
    
private java.lang.StringgetShortClassName(java.lang.String className)
Computes the class name (without package) for the supplied class name and trims off the class suffix.

param
className the fully qualified name of the class
return
the class name (without package and class suffix) for the supplied class name

        String shortName = JavaTypeHelper.getShortClassName(className);

        if ((classSuffix != null) && (!shortName.equals(classSuffix))) {
            int index = shortName.lastIndexOf(classSuffix);
            if (index != -1) {
                shortName = shortName.substring(0, index);
            }
        }
        return shortName;
    
private static java.lang.StringgetTblInfo(java.lang.String tblName, TableElement tbl, java.lang.String relName)
Debug support. Returns a string describing the table and it's keys (only the first key is listed).

param
tblName name of the table described
param
tbl table being described
param
relName name of a relationship

        int numFK = tbl.getForeignKeys().length;
        ForeignKeyElement fk = null;
        if (numFK > 0) {
            fk = tbl.getForeignKeys()[0];
        }
        return " " + tblName + "=" + tbl.toString()
            + ", # keys=" + numFK // NOI18N
            + ", 1st key=" + fk // NOI18N
            + "; relationship Name=" + relName; // NOI18N
    
private voidvalidateModel(java.lang.Object o, java.lang.String failedItem, java.lang.String accessor)
Assert that the given object reference is not null.

param
o Object reference that is checked for null.
param
failedItem String that names the item that is being checked.
param
accessor String which names an object that was use to try and get object o.
throws
ModelException If o is null.

        if (null == o) {
            String msg = I18NHelper.getMessage(
                    messages,
                    "EXC_InvalidRelationshipMapping",  // NOI18N
                    failedItem,
                    accessor);
            logger.log(Logger.SEVERE, msg);
            throw new ModelException(msg);
        }