| /* |
| * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0, which is available at |
| * http://www.eclipse.org/legal/epl-2.0. |
| * |
| * This Source Code may also be made available under the following Secondary |
| * Licenses when the conditions for such availability set forth in the |
| * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, |
| * version 2 with the GNU Classpath Exception, which is available at |
| * https://www.gnu.org/software/classpath/license.html. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 |
| */ |
| |
| /* |
| * DatabaseGenerator.java |
| * |
| * Created on Jan 14, 2003 |
| */ |
| |
| package com.sun.jdo.spi.persistence.generator.database; |
| |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.ResourceBundle; |
| |
| import org.netbeans.modules.dbschema.*; |
| import org.netbeans.modules.dbschema.util.NameUtil; |
| import com.sun.jdo.api.persistence.model.mapping.*; |
| import com.sun.jdo.api.persistence.model.mapping.impl.*; |
| import com.sun.jdo.api.persistence.model.jdo.*; |
| import com.sun.jdo.api.persistence.model.jdo.impl.*; |
| import com.sun.jdo.api.persistence.model.Model; |
| import com.sun.jdo.api.persistence.model.ModelException; |
| import com.sun.jdo.spi.persistence.utility.JavaTypeHelper; |
| |
| import org.glassfish.persistence.common.I18NHelper; |
| import com.sun.jdo.spi.persistence.utility.logging.Logger; |
| |
| |
| /** |
| * This class generates a database schema and a Map of mapping classes from a |
| * set of JDO classes. |
| */ |
| public class DatabaseGenerator { |
| /** @see DatabaseGenerationConstants#DOT */ |
| private static final char DOT = DatabaseGenerationConstants.DOT; |
| |
| /** Holds Java type information */ |
| private final Model model; |
| |
| /** Provides information on how database info should be generated */ |
| private final MappingPolicy mappingPolicy; |
| |
| /** List of NameTuple objects which holds persistence class name, |
| * desired table name and hash class name for database generation. */ |
| private final List pcClasses; |
| |
| /** Map from persistence-capable class names to MappingClassElement's. */ |
| // See also DatabaseGenerator.Results.mappingClasses. |
| private final Map mappingClasses = new HashMap(); |
| |
| /** Generated database schema. */ |
| private final SchemaElement schema; |
| |
| /** |
| * Used to recognize and remove classname suffixes. See {@link |
| * #getShortClassName}. |
| */ |
| private final String classSuffix; |
| |
| /** The logger */ |
| private static final Logger logger = |
| LogHelperDatabaseGenerator.getLogger(); |
| |
| /** I18N message handler */ |
| private static final ResourceBundle messages = |
| I18NHelper.loadBundle(DatabaseGenerator.class); |
| |
| |
| /** |
| * Contains the results of invoking DatabaseGenerator.generate() |
| */ |
| public class Results { |
| /** Generated database schema. */ |
| private final SchemaElement schema; |
| |
| /** Set of generated MappingClassElement's. */ |
| // Notice that this is a Set, while the outer class has a Map. This |
| // is intentional: DatabaseGenerator.addRelationships needs to get a |
| // MappingClassElement for a pc class name, but the clients of the |
| // DatabaseGenerator should only need the mapping classes. |
| private final Set mappingClasses; |
| |
| Results(SchemaElement schema, Map mappingClasses) { |
| this.schema = schema; |
| this.mappingClasses = new HashSet(mappingClasses.values()); |
| } |
| |
| /** @return Generated SchemaElement. */ |
| public SchemaElement getSchema() { |
| return schema; |
| } |
| |
| /** @return Generated mapping classes. */ |
| public Set getMappingClasses() { |
| return mappingClasses; |
| } |
| } |
| |
| |
| /** |
| * This class holds three strings which contain three type of information. |
| * For database generation, for each persistence class, we need to have |
| * persistence class name to look up persistence class, desired table name |
| * for table name and hash class name for unique table name. Depending on |
| * the caller's option, hash class name can be same as persistence class |
| * name or it can be different from persistence class name. |
| */ |
| public static class NameTuple { |
| |
| /** persistence class name */ |
| private final String persistenceClassName; |
| |
| /** desired table name */ |
| private final String desiredTableName; |
| |
| /** hash class name */ |
| private final String hashClassName; |
| |
| /** |
| * An object holds three string objects. |
| * @param persistenceClassName persistence class name for |
| * persistence class look up |
| * @param desiredTableName it can be used for table name |
| * @param hashClassName it can be used for unique table name |
| */ |
| public NameTuple( |
| String persistenceClassName, String desiredTableName, |
| String hashClassName) { |
| |
| this.persistenceClassName = persistenceClassName; |
| this.desiredTableName = desiredTableName; |
| this.hashClassName = (hashClassName != null) ? |
| hashClassName : persistenceClassName; |
| } |
| |
| /** |
| * An object holds three string objects. |
| * @param persistenceClassName persistence class name |
| * @param desiredTableName name for creating table name |
| */ |
| public NameTuple(String persistenceClassName, String desiredTableName) { |
| this(persistenceClassName, desiredTableName, null); |
| } |
| |
| /** @return persistence class name. */ |
| public String getPersistenceClassName() { |
| return persistenceClassName; |
| } |
| |
| /** @return hash class name. */ |
| public String getHashClassName() { |
| return hashClassName; |
| } |
| |
| /** @return desired table name. */ |
| public String getDesiredTableName() { |
| return desiredTableName; |
| } |
| } |
| |
| /** |
| * 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. |
| */ |
| private DatabaseGenerator( |
| Model model, List pcClasses, MappingPolicy mappingPolicy, |
| String schemaName, String classSuffix) |
| throws DBException { |
| |
| this.model = model; |
| this.pcClasses = pcClasses; |
| this.mappingPolicy = mappingPolicy; |
| this.schema = DBElementFactory.createSchema(schemaName); |
| this.classSuffix = classSuffix; |
| } |
| |
| /** |
| * 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 <i>outputDir.schemaName</i>. |
| * @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. <em>Currently MappingClassElement's are <b>always</b> |
| * generated, and the parameter's value is ignored.</em> |
| * @return A DatabaseGenerator.Results instance which holds the results |
| * of generation. |
| */ |
| public static Results generate( |
| Model model, List pcClasses, MappingPolicy mappingPolicy, |
| String schemaName, String classSuffix, |
| boolean generateMappingClasses) |
| throws DBException, IOException, ModelException { |
| |
| DatabaseGenerator generator = new DatabaseGenerator( |
| model, pcClasses, mappingPolicy, |
| schemaName, classSuffix); |
| |
| Results rc = generator.generate(); |
| |
| mappingPolicy.resetCounter(); |
| |
| return rc; |
| } |
| |
| /** |
| * 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. |
| */ |
| private Results generate() throws DBException, ModelException { |
| 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); |
| } |
| |
| /** |
| * 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 |
| */ |
| private TableElement getPrimaryTable(MappingClassElement mappingClass) |
| 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; |
| } |
| } |
| |
| /** |
| * 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 |
| */ |
| private MappingClassElement createMappingClass( |
| PersistenceClassElement pcClass, TableElement table) |
| throws ModelException { |
| |
| MappingClassElement mappingClass = |
| new MappingClassElementImpl(pcClass); |
| |
| mappingClass.setDatabaseRoot(schema); |
| mappingClass.addTable(table); |
| return mappingClass; |
| } |
| |
| /** |
| * 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 |
| */ |
| private MappingFieldElement createAndAttachMappingField( |
| String fieldName, MappingClassElement mappingClass, |
| ColumnElement column) 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; |
| } |
| |
| |
| /** |
| * 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 |
| */ |
| private void addMappingRelationship(String relationName, |
| MappingClassElement declaringClass, ForeignKeyElement fkey) |
| 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); |
| } |
| |
| /** |
| * 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 |
| */ |
| private void addAssocMappingRelationship(String relationName, |
| MappingClassElement declaringClass, ForeignKeyElement fkeyForeign) |
| 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); |
| } |
| } |
| |
| /** |
| * 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 |
| */ |
| private void addInverseMappingRelationship(String relationName, |
| MappingClassElement declaringClass, ForeignKeyElement fkey, |
| boolean isJoin) |
| throws ModelException, 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); |
| } |
| } |
| } |
| |
| /** |
| * 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. |
| */ |
| private ForeignKeyElement createRelationship(TableElement srcTable, |
| TableElement relTable, String relName, String inverseRelName, |
| MappingClassElement mappingClass, |
| MappingClassElement relMappingClass, |
| String uniqueId, boolean srcIsJoin) |
| throws DBException, ModelException { |
| |
| 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; |
| } |
| |
| /** |
| * Generate relationships for schema and mapping model from |
| * mappingClasses which already have all mapping fields populated. |
| * @throws DBException |
| * @throws ModelExpception |
| */ |
| private void addRelationships() |
| throws DBException, ModelException { |
| 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); |
| } |
| } |
| |
| /** |
| * 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. |
| */ |
| private void addDeferredRelationships( |
| List deferredRelationships, Map relationFKey) |
| throws DBException, ModelException { |
| |
| 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); |
| } |
| } |
| } |
| |
| |
| /** |
| * A DeferredRelationship instance represents all the information required |
| * to create a foreign key from the relationship. |
| */ |
| static class DeferredRelationship { |
| private final RelationshipElement relation; |
| private final RelationshipElement inverseRelation; |
| private final TableElement sourceTable; |
| private final TableElement relTable; |
| private final String relationName; |
| private final String inverseRelName; |
| private final MappingClassElement mappingClass; |
| private final MappingClassElement relMappingClass; |
| private final String uniqueId; |
| |
| |
| DeferredRelationship(RelationshipElement relation, |
| RelationshipElement inverseRelation, |
| TableElement sourceTable, |
| TableElement relTable, |
| String relationName, |
| String inverseRelName, |
| MappingClassElement mappingClass, |
| MappingClassElement relMappingClass, |
| String uniqueId) { |
| |
| this.relation = relation; |
| this.inverseRelation = inverseRelation; |
| this.sourceTable = sourceTable; |
| this.relTable = relTable; |
| this.relationName = relationName; |
| this.inverseRelName = inverseRelName; |
| this.mappingClass = mappingClass; |
| this.relMappingClass = relMappingClass; |
| this.uniqueId = uniqueId; |
| } |
| |
| RelationshipElement getRelation() { return relation; } |
| RelationshipElement getInverseRelation() { return inverseRelation; } |
| |
| TableElement getSourceTable() { return sourceTable; } |
| TableElement getRelTable() { return relTable; } |
| |
| String getRelationName() { return relationName; } |
| String getInverseRelName() { return inverseRelName; } |
| |
| MappingClassElement getMappingClass() { return mappingClass; } |
| MappingClassElement getRelMappingClass() { return relMappingClass; } |
| |
| String getUniqueId() { return uniqueId; } |
| } |
| |
| |
| |
| /** |
| * 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 |
| */ |
| private ForeignKeyElement getMappedForeignKey( |
| RelationshipElement relation, RelationshipElement inverseRelation, |
| Map relationFKey) { |
| |
| ForeignKeyElement fkey = |
| (ForeignKeyElement) relationFKey.get(relation); |
| |
| if (fkey == null) { |
| return (ForeignKeyElement) relationFKey.get(inverseRelation); |
| } else { |
| return fkey; |
| } |
| } |
| |
| /** |
| * 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 |
| */ |
| private String getShortClassName(String className) { |
| 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; |
| } |
| |
| /** |
| * Assert that the given object reference is <em>not</em> 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 <code>o</code>. |
| * @throws ModelException If o is null. |
| */ |
| private void validateModel(Object o, |
| String failedItem, |
| String accessor) throws ModelException { |
| if (null == o) { |
| String msg = I18NHelper.getMessage( |
| messages, |
| "EXC_InvalidRelationshipMapping", // NOI18N |
| failedItem, |
| accessor); |
| logger.log(Logger.SEVERE, msg); |
| throw new ModelException(msg); |
| } |
| } |
| |
| /** |
| * 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 |
| */ |
| private static String getTblInfo(String tblName, TableElement tbl, String relName) { |
| 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 |
| } |
| } |