FileDocCategorySizeDatePackage
ManyToManyMapping.javaAPI DocGlassfish v2 API51306Tue May 22 16:54:46 BST 2007oracle.toplink.essentials.mappings

ManyToManyMapping

public class ManyToManyMapping extends CollectionMapping implements RelationalMapping

Purpose: Many to many mappings are used to represent the relationships between a collection of source objects and a collection of target objects. The mapping require the creation of an intermediate table for managing the associations between the source and target records.

author
Sati
since
TOPLink/Java 1.0

Fields Summary
protected static final String
PostInsert
Used for data modification events.
protected static final String
ObjectRemoved
protected static final String
ObjectAdded
protected transient DatabaseTable
relationTable
The intermediate relation table.
protected transient Vector
sourceKeyFields
The field in the source table that corresponds to the key in the relation table
protected transient Vector
targetKeyFields
The field in the target table that corresponds to the key in the relation table
protected transient Vector
sourceRelationKeyFields
The field in the intermediate table that corresponds to the key in the source table
protected transient Vector
targetRelationKeyFields
The field in the intermediate table that corresponds to the key in the target table
protected transient DataModifyQuery
deleteQuery
Query used for single row deletion.
protected transient boolean
hasCustomDeleteQuery
protected transient DataModifyQuery
insertQuery
Used for insertion.
protected transient boolean
hasCustomInsertQuery
Constructors Summary
public ManyToManyMapping()
PUBLIC: Default constructor.

    
            
      
        this.insertQuery = new DataModifyQuery();
        this.deleteQuery = new DataModifyQuery();
        this.sourceRelationKeyFields = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(1);
        this.targetRelationKeyFields = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(1);
        this.sourceKeyFields = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(1);
        this.targetKeyFields = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(1);
        this.hasCustomDeleteQuery = false;
        this.hasCustomInsertQuery = false;
    
Methods Summary
public voidaddSourceRelationKeyField(DatabaseField sourceRelationKeyField, DatabaseField sourcePrimaryKeyField)
PUBLIC: Add the fields in the intermediate table that corresponds to the primary key in the source table. This method is used if the keys are composite.

        getSourceRelationKeyFields().addElement(sourceRelationKeyField);
        getSourceKeyFields().addElement(sourcePrimaryKeyField);
    
public voidaddSourceRelationKeyFieldName(java.lang.String sourceRelationKeyFieldName, java.lang.String sourcePrimaryKeyFieldName)
PUBLIC: Add the fields in the intermediate table that corresponds to the primary key in the source table. This method is used if the keys are composite.

        addSourceRelationKeyField(new DatabaseField(sourceRelationKeyFieldName), new DatabaseField(sourcePrimaryKeyFieldName));
    
public voidaddTargetRelationKeyField(DatabaseField targetRelationKeyField, DatabaseField targetPrimaryKeyField)
PUBLIC: Add the fields in the intermediate table that corresponds to the primary key in the target table. This method is used if the keys are composite.

        getTargetRelationKeyFields().addElement(targetRelationKeyField);
        getTargetKeyFields().addElement(targetPrimaryKeyField);
    
public voidaddTargetRelationKeyFieldName(java.lang.String targetRelationKeyFieldName, java.lang.String targetPrimaryKeyFieldName)
PUBLIC: Add the fields in the intermediate table that corresponds to the primary key in the target table. This method is used if the keys are composite.

        addTargetRelationKeyField(new DatabaseField(targetRelationKeyFieldName), new DatabaseField(targetPrimaryKeyFieldName));
    
public java.lang.Objectclone()
INTERNAL: The mapping clones itself to create deep copy.

        ManyToManyMapping clone = (ManyToManyMapping)super.clone();

        clone.setTargetKeyFields(cloneFields(getTargetKeyFields()));
        clone.setSourceKeyFields(cloneFields(getSourceKeyFields()));
        clone.setTargetRelationKeyFields(cloneFields(getTargetRelationKeyFields()));
        clone.setSourceRelationKeyFields(cloneFields(getSourceRelationKeyFields()));

        return clone;
    
protected java.util.VectorextractKeyFromRelationRow(oracle.toplink.essentials.internal.sessions.AbstractRecord row, oracle.toplink.essentials.internal.sessions.AbstractSession session)
INTERNAL: Extract the source primary key value from the relation row. Used for batch reading, most following same order and fields as in the mapping.

        Vector key = new Vector(getSourceRelationKeyFields().size());

        for (int index = 0; index < getSourceRelationKeyFields().size(); index++) {
            DatabaseField relationField = (DatabaseField)getSourceRelationKeyFields().elementAt(index);
            DatabaseField sourceField = (DatabaseField)getSourceKeyFields().elementAt(index);
            Object value = row.get(relationField);

            // Must ensure the classificatin to get a cache hit.
            try {
                value = session.getDatasourcePlatform().getConversionManager().convertObject(value, getDescriptor().getObjectBuilder().getFieldClassification(sourceField));
            } catch (ConversionException e) {
                throw ConversionException.couldNotBeConverted(this, getDescriptor(), e);
            }

            key.addElement(value);
        }

        return key;
    
protected java.util.VectorextractPrimaryKeyFromRow(oracle.toplink.essentials.internal.sessions.AbstractRecord row, oracle.toplink.essentials.internal.sessions.AbstractSession session)
INTERNAL: Extract the primary key value from the source row. Used for batch reading, most following same order and fields as in the mapping.

        Vector key = new Vector(getSourceKeyFields().size());

        for (Enumeration fieldEnum = getSourceKeyFields().elements(); fieldEnum.hasMoreElements();) {
            DatabaseField field = (DatabaseField)fieldEnum.nextElement();
            Object value = row.get(field);

            // Must ensure the classificatin to get a cache hit.
            try {
                value = session.getDatasourcePlatform().getConversionManager().convertObject(value, getDescriptor().getObjectBuilder().getFieldClassification(field));
            } catch (ConversionException e) {
                throw ConversionException.couldNotBeConverted(this, getDescriptor(), e);
            }

            key.addElement(value);
        }

        return key;
    
protected DataModifyQuerygetDeleteQuery()

        return deleteQuery;
    
protected DataModifyQuerygetInsertQuery()

        return insertQuery;
    
public DatabaseTablegetRelationTable()
INTERNAL: Return the relation table associated with the mapping.

        return relationTable;
    
public java.lang.StringgetRelationTableName()
PUBLIC: Return the relation table name associated with the mapping.

        if (relationTable == null) {
            return null;
        }
        return relationTable.getName();
    
public java.lang.StringgetRelationTableQualifiedName()
PUBLIC: Return the relation table qualified name associated with the mapping.

        if (relationTable == null) {
            return null;
        }
        return relationTable.getQualifiedName();
    
public ExpressiongetSelectionCriteria()
INTERNAL: Returns the selection criteria stored in the mapping selection query. This criteria is used to read reference objects from the database.

        return getSelectionQuery().getSelectionCriteria();
    
public ReadQuerygetSelectionQuery()
INTERNAL: Returns the read query assoicated with the mapping.

        return selectionQuery;
    
public java.util.VectorgetSourceKeyFieldNames()
PUBLIC: Return the source key field names associated with the mapping. These are in-order with the sourceRelationKeyFieldNames.

        Vector fieldNames = new Vector(getSourceKeyFields().size());
        for (Enumeration fieldsEnum = getSourceKeyFields().elements();
                 fieldsEnum.hasMoreElements();) {
            fieldNames.addElement(((DatabaseField)fieldsEnum.nextElement()).getQualifiedName());
        }

        return fieldNames;
    
public java.util.VectorgetSourceKeyFields()
INTERNAL: Return all the source key fields associated with the mapping.

        return sourceKeyFields;
    
public java.util.VectorgetSourceRelationKeyFieldNames()
PUBLIC: Return the source relation key field names associated with the mapping. These are in-order with the sourceKeyFieldNames.

        Vector fieldNames = new Vector(getSourceRelationKeyFields().size());
        for (Enumeration fieldsEnum = getSourceRelationKeyFields().elements();
                 fieldsEnum.hasMoreElements();) {
            fieldNames.addElement(((DatabaseField)fieldsEnum.nextElement()).getQualifiedName());
        }

        return fieldNames;
    
public java.util.VectorgetSourceRelationKeyFields()
INTERNAL: Return all the source realtion key fields associated with the mapping.

        return sourceRelationKeyFields;
    
public java.util.VectorgetTargetKeyFieldNames()
PUBLIC: Return the target key field names associated with the mapping. These are in-order with the targetRelationKeyFieldNames.

        Vector fieldNames = new Vector(getTargetKeyFields().size());
        for (Enumeration fieldsEnum = getTargetKeyFields().elements();
                 fieldsEnum.hasMoreElements();) {
            fieldNames.addElement(((DatabaseField)fieldsEnum.nextElement()).getQualifiedName());
        }

        return fieldNames;
    
public java.util.VectorgetTargetKeyFields()
INTERNAL: Return all the target keys associated with the mapping.

        return targetKeyFields;
    
public java.util.VectorgetTargetRelationKeyFieldNames()
PUBLIC: Return the target relation key field names associated with the mapping. These are in-order with the targetKeyFieldNames.

        Vector fieldNames = new Vector(getTargetRelationKeyFields().size());
        for (Enumeration fieldsEnum = getTargetRelationKeyFields().elements();
                 fieldsEnum.hasMoreElements();) {
            fieldNames.addElement(((DatabaseField)fieldsEnum.nextElement()).getQualifiedName());
        }

        return fieldNames;
    
public java.util.VectorgetTargetRelationKeyFields()
INTERNAL: Return all the target relation key fields associated with the mapping.

        return targetRelationKeyFields;
    
protected booleanhasCustomDeleteQuery()

        return hasCustomDeleteQuery;
    
protected booleanhasCustomInsertQuery()

        return hasCustomInsertQuery;
    
public booleanhasDependency()
INTERNAL: The join table is a dependency if not read-only.

        return isPrivateOwned() || (!isReadOnly());
    
public voidinitialize(oracle.toplink.essentials.internal.sessions.AbstractSession session)
INTERNAL: Initialize mappings

        super.initialize(session);
        
        initializeRelationTable(session);
        initializeSourceRelationKeys(session);
        initializeTargetRelationKeys(session);

        if (isSingleSourceRelationKeySpecified()) {
            initializeSourceKeysWithDefaults(session);
        } else {
            initializeSourceKeys(session);
        }

        if (isSingleTargetRelationKeySpecified()) {
            initializeTargetKeysWithDefaults(session);
        } else {
            initializeTargetKeys(session);
        }

        if (shouldInitializeSelectionCriteria()) {
            initializeSelectionCriteria(session);
        }

        if (!getSelectionQuery().hasSessionName()) {
            getSelectionQuery().setSessionName(session.getName());
        }
        initializeDeleteAllQuery(session);
        initializeInsertQuery(session);
        initializeDeleteQuery(session);
    
protected voidinitializeDeleteAllQuery(oracle.toplink.essentials.internal.sessions.AbstractSession session)
Initialize delete all query. This query is used to all relevant rows from the relation table.

        if (!getDeleteAllQuery().hasSessionName()) {
            getDeleteAllQuery().setSessionName(session.getName());
        }

        if (hasCustomDeleteAllQuery()) {
            return;
        }

        Expression expression = null;
        Expression subExpression;
        Expression builder = new ExpressionBuilder();
        SQLDeleteStatement statement = new SQLDeleteStatement();

        // Construct an expression to delete from the relation table.
        for (int index = 0; index < getSourceRelationKeyFields().size(); index++) {
            DatabaseField sourceRelationKey = (DatabaseField)getSourceRelationKeyFields().elementAt(index);
            DatabaseField sourceKey = (DatabaseField)getSourceKeyFields().elementAt(index);

            subExpression = builder.getField(sourceRelationKey).equal(builder.getParameter(sourceKey));
            expression = subExpression.and(expression);
        }

        // All the enteries are deleted in one shot.
        statement.setWhereClause(expression);
        statement.setTable(getRelationTable());
        getDeleteAllQuery().setSQLStatement(statement);
    
protected voidinitializeDeleteQuery(oracle.toplink.essentials.internal.sessions.AbstractSession session)
INTERNAL: Initialize delete query. This query is used to delete a specific row from the join table in uow, given the objects on both sides of the relation.

        if (!getDeleteQuery().hasSessionName()) {
            getDeleteQuery().setSessionName(session.getName());
        }
        if (hasCustomDeleteQuery()) {
            return;
        }

        // Build where clause expression.
        Expression whereClause = null;
        Expression builder = new ExpressionBuilder();

        Enumeration relationKeyEnum = getSourceRelationKeyFields().elements();
        for (; relationKeyEnum.hasMoreElements();) {
            DatabaseField relationKey = (DatabaseField)relationKeyEnum.nextElement();

            Expression expression = builder.getField(relationKey).equal(builder.getParameter(relationKey));
            whereClause = expression.and(whereClause);
        }

        relationKeyEnum = getTargetRelationKeyFields().elements();
        for (; relationKeyEnum.hasMoreElements();) {
            DatabaseField relationKey = (DatabaseField)relationKeyEnum.nextElement();

            Expression expression = builder.getField(relationKey).equal(builder.getParameter(relationKey));
            whereClause = expression.and(whereClause);
        }

        SQLDeleteStatement statement = new SQLDeleteStatement();
        statement.setTable(getRelationTable());
        statement.setWhereClause(whereClause);
        getDeleteQuery().setSQLStatement(statement);
    
protected voidinitializeInsertQuery(oracle.toplink.essentials.internal.sessions.AbstractSession session)
INTERNAL: Initialize insert query. This query is used to insert the collection of objects into the relation table.

        if (!getInsertQuery().hasSessionName()) {
            getInsertQuery().setSessionName(session.getName());
        }
        if (hasCustomInsertQuery()) {
            return;
        }

        SQLInsertStatement statement = new SQLInsertStatement();
        statement.setTable(getRelationTable());
        AbstractRecord joinRow = new DatabaseRecord();
        for (Enumeration targetEnum = getTargetRelationKeyFields().elements();
                 targetEnum.hasMoreElements();) {
            joinRow.put((DatabaseField)targetEnum.nextElement(), null);
        }
        for (Enumeration sourceEnum = getSourceRelationKeyFields().elements();
                 sourceEnum.hasMoreElements();) {
            joinRow.put((DatabaseField)sourceEnum.nextElement(), null);
        }
        statement.setModifyRow(joinRow);
        getInsertQuery().setSQLStatement(statement);
        getInsertQuery().setModifyRow(joinRow);
    
protected voidinitializeRelationTable(oracle.toplink.essentials.internal.sessions.AbstractSession session)
INTERNAL: Set the table qualifier on the relation table if required

        Platform platform = session.getDatasourcePlatform();

        if ((getRelationTable() == null) || (getRelationTableName().length() == 0)) {
            throw DescriptorException.noRelationTable(this);
        }

        if (platform.getTableQualifier().length() == 0) {
            return;
        }

        if (getRelationTable().getTableQualifier().length() == 0) {
            getRelationTable().setTableQualifier(platform.getTableQualifier());
        }
    
protected voidinitializeSelectionCriteria(oracle.toplink.essentials.internal.sessions.AbstractSession session)
INTERNAL: Selection criteria is created to read target records from the table.

        DatabaseField relationKey;
        DatabaseField sourceKey;
        DatabaseField targetKey;
        Expression exp1;
        Expression exp2;
        Expression expression;
        Expression criteria;
        Expression builder = new ExpressionBuilder();
        Enumeration relationKeyEnum;
        Enumeration sourceKeyEnum;
        Enumeration targetKeyEnum;

        Expression linkTable = null;

        targetKeyEnum = getTargetKeyFields().elements();
        relationKeyEnum = getTargetRelationKeyFields().elements();
        for (; targetKeyEnum.hasMoreElements();) {
            relationKey = (DatabaseField)relationKeyEnum.nextElement();
            targetKey = (DatabaseField)targetKeyEnum.nextElement();
            if (linkTable == null) {// We could just call getTable repeatedly, but it's a waste
                linkTable = builder.getTable(relationKey.getTable());
            }

            exp1 = builder.getField(targetKey);
            exp2 = linkTable.getField(relationKey);
            expression = exp1.equal(exp2);

            if ((criteria = getSelectionCriteria()) == null) {
                criteria = expression;
            } else {
                criteria = expression.and(criteria);
            }

            setSelectionCriteria(criteria);
        }

        relationKeyEnum = getSourceRelationKeyFields().elements();
        sourceKeyEnum = getSourceKeyFields().elements();

        for (; relationKeyEnum.hasMoreElements();) {
            relationKey = (DatabaseField)relationKeyEnum.nextElement();
            sourceKey = (DatabaseField)sourceKeyEnum.nextElement();

            exp1 = linkTable.getField(relationKey);
            exp2 = builder.getParameter(sourceKey);
            expression = exp1.equal(exp2);

            if ((criteria = getSelectionCriteria()) == null) {
                criteria = expression;
            } else {
                criteria = expression.and(criteria);
            }

            setSelectionCriteria(criteria);
        }
    
protected voidinitializeSourceKeys(oracle.toplink.essentials.internal.sessions.AbstractSession session)
INTERNAL: All the source key field names are converted to DatabaseField and stored.

        for (Enumeration sourceEnum = getSourceKeyFields().elements();
                 sourceEnum.hasMoreElements();) {
            getDescriptor().buildField((DatabaseField)sourceEnum.nextElement());
        }
    
protected voidinitializeSourceKeysWithDefaults(oracle.toplink.essentials.internal.sessions.AbstractSession session)
INTERNAL: If a user does not specify the source key then the primary keys of the source table are used.

        List<DatabaseField> primaryKeyFields = getDescriptor().getPrimaryKeyFields();
        for (int index = 0; index < primaryKeyFields.size(); index++) {
            getSourceKeyFields().addElement(primaryKeyFields.get(index));
        }
    
protected voidinitializeSourceRelationKeys(oracle.toplink.essentials.internal.sessions.AbstractSession session)
INTERNAL: All the source relation key field names are converted to DatabaseField and stored.

        if (getSourceRelationKeyFields().size() == 0) {
            throw DescriptorException.noSourceRelationKeysSpecified(this);
        }

        for (Enumeration entry = getSourceRelationKeyFields().elements(); entry.hasMoreElements();) {
            DatabaseField field = (DatabaseField)entry.nextElement();
            if (field.hasTableName() && (!(field.getTableName().equals(getRelationTable().getName())))) {
                throw DescriptorException.relationKeyFieldNotProperlySpecified(field, this);
            }
            field.setTable(getRelationTable());
        }
    
protected voidinitializeTargetKeys(oracle.toplink.essentials.internal.sessions.AbstractSession session)
INTERNAL: All the target key field names are converted to DatabaseField and stored.

        for (Enumeration targetEnum = getTargetKeyFields().elements();
                 targetEnum.hasMoreElements();) {
            getReferenceDescriptor().buildField((DatabaseField)targetEnum.nextElement());
        }
    
protected voidinitializeTargetKeysWithDefaults(oracle.toplink.essentials.internal.sessions.AbstractSession session)
INTERNAL: If a user does not specify the target key then the primary keys of the target table are used.

        List<DatabaseField> primaryKeyFields = getReferenceDescriptor().getPrimaryKeyFields();
        for (int index = 0; index < primaryKeyFields.size(); index++) {
            getTargetKeyFields().addElement(primaryKeyFields.get(index));
        }
    
protected voidinitializeTargetRelationKeys(oracle.toplink.essentials.internal.sessions.AbstractSession session)
INTERNAL: All the target relation key field names are converted to DatabaseField and stored.

        if (getTargetRelationKeyFields().size() == 0) {
            throw DescriptorException.noTargetRelationKeysSpecified(this);
        }

        for (Enumeration targetEnum = getTargetRelationKeyFields().elements();
                 targetEnum.hasMoreElements();) {
            DatabaseField field = (DatabaseField)targetEnum.nextElement();
            if (field.hasTableName() && (!(field.getTableName().equals(getRelationTable().getName())))) {
                throw DescriptorException.relationKeyFieldNotProperlySpecified(field, this);
            }
            field.setTable(getRelationTable());
        }
    
protected voidinsertAddedObjectEntry(ObjectLevelModifyQuery query, java.lang.Object objectAdded)
INTERNAL: An object was added to the collection during an update, insert it.

        //cr 3819 added the line below to fix the translationtable to ensure that it
        // contains the required values
        prepareTranslationRow(query.getTranslationRow(), query.getObject(), query.getSession());
        AbstractRecord databaseRow = new DatabaseRecord();

        // Extract primary key and value from the source.
        for (int index = 0; index < getSourceRelationKeyFields().size(); index++) {
            DatabaseField sourceRelationKey = (DatabaseField)getSourceRelationKeyFields().elementAt(index);
            DatabaseField sourceKey = (DatabaseField)getSourceKeyFields().elementAt(index);
            Object sourceKeyValue = query.getTranslationRow().get(sourceKey);
            databaseRow.put(sourceRelationKey, sourceKeyValue);
        }

        // Extract target field and its value. Construct insert statement and execute it
        for (int index = 0; index < getTargetRelationKeyFields().size(); index++) {
            DatabaseField targetRelationKey = (DatabaseField)getTargetRelationKeyFields().elementAt(index);
            DatabaseField targetKey = (DatabaseField)getTargetKeyFields().elementAt(index);
            Object targetKeyValue = getReferenceDescriptor().getObjectBuilder().extractValueFromObjectForField(objectAdded, targetKey, query.getSession());
            databaseRow.put(targetRelationKey, targetKeyValue);
        }

        query.getSession().executeQuery(getInsertQuery(), databaseRow);
    
public voidinsertIntoRelationTable(WriteObjectQuery query)
INTERNAL: Insert into relation table. This follows following steps.

- Extract primary key and its value from the source object.

- Extract target key and its value from the target object.

- Construct a insert statement with above fields and values for relation table.

- execute the statement.

- Repeat above three statements until all the target objects are done.

        if (isReadOnly()) {
            return;
        }

        ContainerPolicy cp = getContainerPolicy();
        Object objects = getRealCollectionAttributeValueFromObject(query.getObject(), query.getSession());
        if (cp.isEmpty(objects)) {
            return;
        }

        prepareTranslationRow(query.getTranslationRow(), query.getObject(), query.getSession());
        AbstractRecord databaseRow = new DatabaseRecord();

        // Extract primary key and value from the source.
        for (int index = 0; index < getSourceRelationKeyFields().size(); index++) {
            DatabaseField sourceRelationKey = (DatabaseField)getSourceRelationKeyFields().elementAt(index);
            DatabaseField sourceKey = (DatabaseField)getSourceKeyFields().elementAt(index);
            Object sourceKeyValue = query.getTranslationRow().get(sourceKey);
            databaseRow.put(sourceRelationKey, sourceKeyValue);
        }

        // Extract target field and its value. Construct insert statement and execute it
        for (Object iter = cp.iteratorFor(objects); cp.hasNext(iter);) {
            Object object = cp.next(iter, query.getSession());
            for (int index = 0; index < getTargetRelationKeyFields().size(); index++) {
                DatabaseField targetRelationKey = (DatabaseField)getTargetRelationKeyFields().elementAt(index);
                DatabaseField targetKey = (DatabaseField)getTargetKeyFields().elementAt(index);
                Object targetKeyValue = getReferenceDescriptor().getObjectBuilder().extractValueFromObjectForField(object, targetKey, query.getSession());
                databaseRow.put(targetRelationKey, targetKeyValue);
            }

            query.getSession().executeQuery(getInsertQuery(), databaseRow);
        }
    
public voidinsertTargetObjects(WriteObjectQuery query)
INTERNAL: Write the target objects if the cascade policy requires them to be written first. They must be written within a unit of work to ensure that they exist.

        if (!shouldObjectModifyCascadeToParts(query)) {
            return;
        }

        // Only cascade dependents writes in uow.
        if (query.shouldCascadeOnlyDependentParts()) {
            return;
        }

        Object objects = getRealCollectionAttributeValueFromObject(query.getObject(), query.getSession());

        ContainerPolicy cp = getContainerPolicy();
        if (cp.isEmpty(objects)) {
            return;
        }

        // Write each of the target objects
        for (Object objectsIterator = cp.iteratorFor(objects); cp.hasNext(objectsIterator);) {
            Object object = cp.next(objectsIterator, query.getSession());
            if (isPrivateOwned()) {
                // no need to set changeset as insert is a straight copy anyway
                InsertObjectQuery insertQuery = new InsertObjectQuery();
                insertQuery.setObject(object);
                insertQuery.setCascadePolicy(query.getCascadePolicy());
                query.getSession().executeQuery(insertQuery);
            } else {
                ObjectChangeSet changeSet = null;
                UnitOfWorkChangeSet uowChangeSet = null;
                if (query.getSession().isUnitOfWork() && (((UnitOfWorkImpl)query.getSession()).getUnitOfWorkChangeSet() != null)) {
                    uowChangeSet = (UnitOfWorkChangeSet)((UnitOfWorkImpl)query.getSession()).getUnitOfWorkChangeSet();
                    changeSet = (ObjectChangeSet)uowChangeSet.getObjectChangeSetForClone(object);
                }
                WriteObjectQuery writeQuery = new WriteObjectQuery();
                writeQuery.setObject(object);
                writeQuery.setObjectChangeSet(changeSet);
                writeQuery.setCascadePolicy(query.getCascadePolicy());
                query.getSession().executeQuery(writeQuery);
            }
        }
    
public booleanisJoiningSupported()
INTERNAL: Return if this mapping support joining.

        return true;
    
public booleanisManyToManyMapping()
INTERNAL:

        return true;
    
public booleanisRelationalMapping()
INTERNAL:

        return true;
    
protected booleanisSingleSourceRelationKeySpecified()
INTERNAL: Checks if a single source key was specified.

        return getSourceKeyFields().isEmpty();
    
protected booleanisSingleTargetRelationKeySpecified()
INTERNAL: Checks if a single target key was specified.

        return getTargetKeyFields().isEmpty();
    
protected voidobjectAddedDuringUpdate(ObjectLevelModifyQuery query, java.lang.Object objectAdded, ObjectChangeSet changeSet)
INTERNAL: An object was added to the collection during an update, insert it if private.

        // First insert/update object.
        super.objectAddedDuringUpdate(query, objectAdded, changeSet);

        // In the uow data queries are cached until the end of the commit.
        if (query.shouldCascadeOnlyDependentParts()) {
            // Hey I might actually want to use an inner class here... ok array for now.
            Object[] event = new Object[3];
            event[0] = ObjectAdded;
            event[1] = query;
            event[2] = objectAdded;
            query.getSession().getCommitManager().addDataModificationEvent(this, event);
        } else {
            insertAddedObjectEntry(query, objectAdded);
        }
    
protected voidobjectRemovedDuringUpdate(ObjectLevelModifyQuery query, java.lang.Object objectDeleted)
INTERNAL: An object was removed to the collection during an update, delete it if private.

        AbstractRecord databaseRow = new DatabaseRecord();

        // Extract primary key and value from the source.
        for (int index = 0; index < getSourceRelationKeyFields().size(); index++) {
            DatabaseField sourceRelationKey = (DatabaseField)getSourceRelationKeyFields().elementAt(index);
            DatabaseField sourceKey = (DatabaseField)getSourceKeyFields().elementAt(index);
            Object sourceKeyValue = query.getTranslationRow().get(sourceKey);
            databaseRow.put(sourceRelationKey, sourceKeyValue);
        }

        // Extract target field and its value. Construct insert statement and execute it
        for (int index = 0; index < getTargetRelationKeyFields().size(); index++) {
            DatabaseField targetRelationKey = (DatabaseField)getTargetRelationKeyFields().elementAt(index);
            DatabaseField targetKey = (DatabaseField)getTargetKeyFields().elementAt(index);
            Object targetKeyValue = getReferenceDescriptor().getObjectBuilder().extractValueFromObjectForField(objectDeleted, targetKey, query.getSession());
            databaseRow.put(targetRelationKey, targetKeyValue);
        }

        // In the uow data queries are cached until the end of the commit.
        if (query.shouldCascadeOnlyDependentParts()) {
            // Hey I might actually want to use an inner class here... ok array for now.
            Object[] event = new Object[3];
            event[0] = ObjectRemoved;
            event[1] = getDeleteQuery();
            event[2] = databaseRow;
            query.getSession().getCommitManager().addDataModificationEvent(this, event);
        } else {
            query.getSession().executeQuery(getDeleteQuery(), databaseRow);
        }

        // Delete object after join entry is delete if private.
        super.objectRemovedDuringUpdate(query, objectDeleted);
    
public voidperformDataModificationEvent(java.lang.Object[] event, oracle.toplink.essentials.internal.sessions.AbstractSession session)
INTERNAL: Perform the commit event. This is used in the uow to delay data modifications.

        // Hey I might actually want to use an inner class here... ok array for now.
        if (event[0] == PostInsert) {
            insertIntoRelationTable((WriteObjectQuery)event[1]);
        } else if (event[0] == ObjectRemoved) {
            session.executeQuery((DataModifyQuery)event[1], (AbstractRecord)event[(2)]);
        } else if (event[0] == ObjectAdded) {
            insertAddedObjectEntry((WriteObjectQuery)event[1], event[2]);
        } else {
            throw DescriptorException.invalidDataModificationEventCode(event[0], this);
        }
    
public voidpostInsert(WriteObjectQuery query)
INTERNAL: Insert into relation table. This follows following steps.

- Extract primary key and its value from the source object.

- Extract target key and its value from the target object.

- Construct a insert statement with above fields and values for relation table.

- execute the statement.

- Repeat above three statements until all the target objects are done.

        insertTargetObjects(query);
        // Batch data modification in the uow
        if (query.shouldCascadeOnlyDependentParts()) {
            // Hey I might actually want to use an inner class here... ok array for now.
            Object[] event = new Object[2];
            event[0] = PostInsert;
            event[1] = query;
            query.getSession().getCommitManager().addDataModificationEvent(this, event);
        } else {
            insertIntoRelationTable(query);
        }
    
public voidpostUpdate(WriteObjectQuery query)
INTERNAL: Update the relation table with the entries related to this mapping. Delete entries removed, insert entries added. If private also insert/delete/update target objects.

        if (isReadOnly()) {
            return;
        }

        // If objects are not instantiated that means they are not changed.
        if (!isAttributeValueInstantiated(query.getObject())) {
            return;
        }

        Object objectsInMemoryModel = getRealCollectionAttributeValueFromObject(query.getObject(), query.getSession());

        // This accesses the backup in uow otherwise goes to database (may be better of to delete all in non uow case).
        Object currentObjectsInDB = readPrivateOwnedForObject(query);
        if (currentObjectsInDB == null) {
            currentObjectsInDB = getContainerPolicy().containerInstance(1);
        }
        compareObjectsAndWrite(currentObjectsInDB, objectsInMemoryModel, query);
    
public voidpreDelete(WriteObjectQuery query)
INTERNAL: Delete entries related to this mapping from the relation table.

        Object objectsIterator = null;
        ContainerPolicy containerPolicy = getContainerPolicy();

        Object objects = getRealCollectionAttributeValueFromObject(query.getObject(), query.getSession());

        if (shouldObjectModifyCascadeToPartsForPreDelete(query)) {
            //this must be done up here because the select must be done before the entry in the relation table is deleted.
            objectsIterator = containerPolicy.iteratorFor(objects);
        }
        
        if (!isReadOnly()) {
            prepareTranslationRow(query.getTranslationRow(), query.getObject(), query.getSession());
            query.getSession().executeQuery(getDeleteAllQuery(), query.getTranslationRow());
        }

        // If privately owned delete the objects, this does not handle removed objects (i.e. verify delete, not req in uow).
        // Does not try to optimize delete all like 1-m, (rarely used and hard to do).
        if (shouldObjectModifyCascadeToPartsForPreDelete(query)) {
            if (objects != null) {
                //objectsIterator will not be null because cascade check will still return true.
                while (containerPolicy.hasNext(objectsIterator)) {
                    Object object = containerPolicy.next(objectsIterator, query.getSession());
                    // Do not cascade to objects that are currently being processed
                    if (!query.getSession().getCommitManager().isCommitInPreModify(object) &&
                            shouldObjectDeleteCascadeToPart(query, object)) {
                        DeleteObjectQuery deleteQuery = new DeleteObjectQuery();
                        deleteQuery.setObject(object);
                        deleteQuery.setCascadePolicy(query.getCascadePolicy());
                        query.getSession().executeQuery(deleteQuery);
                    }
                }
            }
        }
    
protected voidprepareTranslationRow(oracle.toplink.essentials.internal.sessions.AbstractRecord translationRow, java.lang.Object object, oracle.toplink.essentials.internal.sessions.AbstractSession session)
INTERNAL: The translation row may require additional fields than the primary key if the mapping in not on the primary key.

        // Make sure that each source key field is in the translation row.
        for (Enumeration sourceFieldsEnum = getSourceKeyFields().elements();
                 sourceFieldsEnum.hasMoreElements();) {
            DatabaseField sourceKey = (DatabaseField)sourceFieldsEnum.nextElement();
            if (!translationRow.containsKey(sourceKey)) {
                Object value = getDescriptor().getObjectBuilder().extractValueFromObjectForField(object, sourceKey, session);
                translationRow.put(sourceKey, value);
            }
        }
    
public voidsetCustomDeleteQuery(DataModifyQuery query)
PUBLIC: The default delete query for mapping can be overridden by specifying the new query. This query must delete the row from the M-M join table.

        setDeleteQuery(query);
        setHasCustomDeleteQuery(true);
    
public voidsetCustomInsertQuery(DataModifyQuery query)
PUBLIC: The default insert query for mapping can be overridden by specifying the new query. This query must insert the row into the M-M join table.

        setInsertQuery(query);
        setHasCustomInsertQuery(true);
    
public voidsetDeleteCall(Call call)
PUBLIC: Set the receiver's delete Call. This allows the user to override the SQL generated by TOPLink, with there own SQL or procedure call. The arguments are translated from the fields of the source row. This is used to delete a single entry from the M-M join table. Example, 'new SQLCall("delete from PROJ_EMP where PROJ_ID = #PROJ_ID AND EMP_ID = #EMP_ID")'.

        DataModifyQuery query = new DataModifyQuery();
        query.setCall(call);
        setCustomDeleteQuery(query);
    
protected voidsetDeleteQuery(DataModifyQuery deleteQuery)

        this.deleteQuery = deleteQuery;
    
public voidsetDeleteSQLString(java.lang.String sqlString)
PUBLIC: Set the receiver's delete SQL string. This allows the user to override the SQL generated by TOPLink, with there own SQL or procedure call. The arguments are translated from the fields of the source row, through replacing the field names marked by '#' with the values for those fields. This is used to delete a single entry from the M-M join table. Example, 'delete from PROJ_EMP where PROJ_ID = #PROJ_ID AND EMP_ID = #EMP_ID'.

        DataModifyQuery query = new DataModifyQuery();
        query.setSQLString(sqlString);
        setCustomDeleteQuery(query);
    
protected voidsetHasCustomDeleteQuery(boolean hasCustomDeleteQuery)

        this.hasCustomDeleteQuery = hasCustomDeleteQuery;
    
protected voidsetHasCustomInsertQuery(boolean bool)

        hasCustomInsertQuery = bool;
    
public voidsetInsertCall(Call call)
PUBLIC: Set the receiver's insert Call. This allows the user to override the SQL generated by TOPLink, with there own SQL or procedure call. The arguments are translated from the fields of the source row. This is used to insert an entry into the M-M join table. Example, 'new SQLCall("insert into PROJ_EMP (EMP_ID, PROJ_ID) values (#EMP_ID, #PROJ_ID)")'.

        DataModifyQuery query = new DataModifyQuery();
        query.setCall(call);
        setCustomInsertQuery(query);
    
protected voidsetInsertQuery(DataModifyQuery insertQuery)

        this.insertQuery = insertQuery;
    
public voidsetInsertSQLString(java.lang.String sqlString)
PUBLIC: Set the receiver's insert SQL string. This allows the user to override the SQL generated by TOPLink, with there own SQL or procedure call. The arguments are translated from the fields of the source row, through replacing the field names marked by '#' with the values for those fields. This is used to insert an entry into the M-M join table. Example, 'insert into PROJ_EMP (EMP_ID, PROJ_ID) values (#EMP_ID, #PROJ_ID)'.

        DataModifyQuery query = new DataModifyQuery();
        query.setSQLString(sqlString);
        setCustomInsertQuery(query);
    
public voidsetRelationTable(DatabaseTable relationTable)
PUBLIC: Set the relational table. This is the join table that store both the source and target primary keys.

        this.relationTable = relationTable;
    
public voidsetRelationTableName(java.lang.String tableName)
PUBLIC: Set the name of the relational table. This is the join table that store both the source and target primary keys.

        relationTable = new DatabaseTable(tableName);
    
public voidsetSessionName(java.lang.String name)
PUBLIC: Set the name of the session to execute the mapping's queries under. This can be used by the session broker to override the default session to be used for the target class.

        super.setSessionName(name);
        getInsertQuery().setSessionName(name);
        getDeleteQuery().setSessionName(name);
    
public voidsetSourceKeyFieldNames(java.util.Vector fieldNames)
PUBLIC: Set the source key field names associated with the mapping. These must be in-order with the sourceRelationKeyFieldNames.

        Vector fields = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(fieldNames.size());
        for (Enumeration fieldNamesEnum = fieldNames.elements(); fieldNamesEnum.hasMoreElements();) {
            fields.addElement(new DatabaseField((String)fieldNamesEnum.nextElement()));
        }

        setSourceKeyFields(fields);
    
public voidsetSourceKeyFields(java.util.Vector sourceKeyFields)
INTERNAL: Set the source fields.

        this.sourceKeyFields = sourceKeyFields;
    
public voidsetSourceRelationKeyFieldName(java.lang.String sourceRelationKeyFieldName)
PUBLIC: Set the source key field in the relation table. This is the name of the foreign key in the relation table to the source's primary key field. This method is used if the source primary key is a singleton only.

        getSourceRelationKeyFields().addElement(new DatabaseField(sourceRelationKeyFieldName));
    
public voidsetSourceRelationKeyFieldNames(java.util.Vector fieldNames)
PUBLIC: Set the source relation key field names associated with the mapping. These must be in-order with the sourceKeyFieldNames.

        Vector fields = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(fieldNames.size());
        for (Enumeration fieldNamesEnum = fieldNames.elements(); fieldNamesEnum.hasMoreElements();) {
            fields.addElement(new DatabaseField((String)fieldNamesEnum.nextElement()));
        }

        setSourceRelationKeyFields(fields);
    
public voidsetSourceRelationKeyFields(java.util.Vector sourceRelationKeyFields)
INTERNAL: Set the source fields.

        this.sourceRelationKeyFields = sourceRelationKeyFields;
    
public voidsetTargetKeyFieldNames(java.util.Vector fieldNames)
INTERNAL: Set the target key field names associated with the mapping. These must be in-order with the targetRelationKeyFieldNames.

        Vector fields = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(fieldNames.size());
        for (Enumeration fieldNamesEnum = fieldNames.elements(); fieldNamesEnum.hasMoreElements();) {
            fields.addElement(new DatabaseField((String)fieldNamesEnum.nextElement()));
        }

        setTargetKeyFields(fields);
    
public voidsetTargetKeyFields(java.util.Vector targetKeyFields)
INTERNAL: Set the target fields.

        this.targetKeyFields = targetKeyFields;
    
public voidsetTargetRelationKeyFieldName(java.lang.String targetRelationKeyFieldName)
PUBLIC: Set the target key field in the relation table. This is the name of the foreign key in the relation table to the target's primary key field. This method is used if the target's primary key is a singleton only.

        getTargetRelationKeyFields().addElement(new DatabaseField(targetRelationKeyFieldName));
    
public voidsetTargetRelationKeyFieldNames(java.util.Vector fieldNames)
INTERNAL: Set the target relation key field names associated with the mapping. These must be in-order with the targetKeyFieldNames.

        Vector fields = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(fieldNames.size());
        for (Enumeration fieldNamesEnum = fieldNames.elements(); fieldNamesEnum.hasMoreElements();) {
            fields.addElement(new DatabaseField((String)fieldNamesEnum.nextElement()));
        }

        setTargetRelationKeyFields(fields);
    
public voidsetTargetRelationKeyFields(java.util.Vector targetRelationKeyFields)
INTERNAL: Set the target fields.

        this.targetRelationKeyFields = targetRelationKeyFields;
    
protected booleanshouldObjectModifyCascadeToPartsForPreDelete(ObjectLevelModifyQuery query)
Return whether any process leading to object deletion should also affect its parts. Used in preDelete. Note that dependencies are reversed for deletes: Join table entries must be removed before the related objects can be deleted. So the owning side must be deleted before the non-owning side is processed.

        // Always cascade for privately-owned parts
        if (isPrivateOwned()) {
            return true;
        }

        // The non-owning side is read-only for join table mappings.
        if (query.shouldCascadeOnlyDependentParts()) {
            return isReadOnly();
        } 
        
        return query.shouldCascadeAllParts();