Package org.datanucleus.store.mapped.mapping

Source Code of org.datanucleus.store.mapped.mapping.PersistableMapping

/**********************************************************************
Copyright (c) 2002 Mike Martin (TJDO) and others. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Contributors:
2003 Erik Bengtson - Important changes regarding the application identity
                     support when fetching PreparedStatements. Added an
                     inner class that should be moved away.
2004 Erik Bengtson - added commentary and Localisation
2004 Andy Jefferson - added capability to handle Abstract PC fields for
                      both SingleFieldIdentity and AID
2007 Andy Jefferson - implement RelationMappingCallbacks
    ...
**********************************************************************/
package org.datanucleus.store.mapped.mapping;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.Collection;

import javax.jdo.JDOObjectNotFoundException;
import javax.jdo.spi.PersistenceCapable;

import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.FetchPlan;
import org.datanucleus.NucleusContext;
import org.datanucleus.api.ApiAdapter;
import org.datanucleus.exceptions.NucleusException;
import org.datanucleus.exceptions.NucleusObjectNotFoundException;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.identity.OID;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.ClassMetaData;
import org.datanucleus.metadata.ColumnMetaData;
import org.datanucleus.metadata.FieldMetaData;
import org.datanucleus.metadata.FieldRole;
import org.datanucleus.metadata.IdentityStrategy;
import org.datanucleus.metadata.IdentityType;
import org.datanucleus.metadata.InheritanceStrategy;
import org.datanucleus.metadata.MetaDataManager;
import org.datanucleus.metadata.Relation;
import org.datanucleus.store.ExecutionContext;
import org.datanucleus.store.FieldValues;
import org.datanucleus.store.ObjectProvider;
import org.datanucleus.store.StoreManager;
import org.datanucleus.store.Type;
import org.datanucleus.store.exceptions.NotYetFlushedException;
import org.datanucleus.store.exceptions.ReachableObjectNotCascadedException;
import org.datanucleus.store.fieldmanager.FieldManager;
import org.datanucleus.store.fieldmanager.SingleValueFieldManager;
import org.datanucleus.store.mapped.DatastoreClass;
import org.datanucleus.store.mapped.DatastoreContainerObject;
import org.datanucleus.store.mapped.DatastoreField;
import org.datanucleus.store.mapped.MappedStoreManager;
import org.datanucleus.store.mapped.StatementClassMapping;
import org.datanucleus.store.mapped.StatementMappingIndex;
import org.datanucleus.store.scostore.PersistableRelationStore;
import org.datanucleus.store.types.sco.SCOCollection;
import org.datanucleus.util.ClassUtils;
import org.datanucleus.util.NucleusLogger;
import org.datanucleus.util.StringUtils;

/**
* Maps a java field to a persistable class.
* For persistable classes using datastore-id most of the behaviour is coded in the OIDMapping superclass.
* TODO Split this from OIDMapping since PCMapping may represent app-id instead of datastore-id
* TODO Move column creation for join table collection/map/array of PC to this class
*/
public class PersistableMapping extends OIDMapping implements MappingCallbacks
{
    /** Mappings for all fields necessary to represent the id of the PC object. */
    protected JavaTypeMapping[] javaTypeMappings = new JavaTypeMapping[0];

    //----------------------- convenience fields to improve performance ----------------------//
    /** ClassMetaData for the represented class. Create a new one on each getObject invoke is expensive **/
    protected AbstractClassMetaData cmd;

    /** number of DatastoreField - convenience field to improve performance **/
    private int numberOfDatastoreFields = 0;

    /**
     * Create a new empty PersistenceCapableMapping.
     * The caller must call one of the initialize methods to initialize the instance with the
     * DatastoreAdapter and its type.
     */
    public PersistableMapping()
    {
    }

    /**
     * Initialize this JavaTypeMapping with the given DatastoreAdapter for the given metadata.
     * @param mmd MetaData for the field/property to be mapped (if any)
     * @param container The datastore container storing this mapping (if any)
     * @param clr the ClassLoaderResolver
     */
    public void initialize(AbstractMemberMetaData mmd, DatastoreContainerObject container,
            ClassLoaderResolver clr)
    {
      super.initialize(mmd, container, clr);

      prepareDatastoreMapping(clr);
    }

    /**
     * Add a new JavaTypeMapping
     * @param mapping the JavaTypeMapping
     */
  public void addJavaTypeMapping(JavaTypeMapping mapping)
  {
      if (mapping == null)
      {
          throw new NucleusException("mapping argument in PersistenceCapableMapping.addJavaTypeMapping is null").setFatal();
      }
        JavaTypeMapping[] jtm = javaTypeMappings;
        javaTypeMappings = new JavaTypeMapping[jtm.length+1];
        System.arraycopy(jtm, 0, javaTypeMappings, 0, jtm.length);
        javaTypeMappings[jtm.length] = mapping;
  }
   
    /**
     * Method to prepare the PC mapping and add its associated datastore mappings.
     */
    protected void prepareDatastoreMapping()
    {
        // Does nothing - added to prevent column creation by the method in OIDMapping
        // If we change the inheritance so this doesn't extend OIDMapping, then this can be deleted
    }

  /**
     * Method to prepare the PC mapping and add its associated datastore mappings.
     * @param clr The ClassLoaderResolver
     */
    protected void prepareDatastoreMapping(ClassLoaderResolver clr)
    {
        if (roleForMember == FieldRole.ROLE_COLLECTION_ELEMENT)
        {
            // TODO Handle creation of columns in join table for collection of PCs
        }
        else if (roleForMember == FieldRole.ROLE_ARRAY_ELEMENT)
        {
            // TODO Handle creation of columns in join table for array of PCs
        }
        else if (roleForMember == FieldRole.ROLE_MAP_KEY)
        {
            // TODO Handle creation of columns in join table for map of PCs as keys
        }
        else if (roleForMember == FieldRole.ROLE_MAP_VALUE)
        {
            // TODO Handle creation of columns in join table for map of PCs as values
        }
        else
        {
            // Either one end of a 1-1 relation, or the N end of a N-1
            AbstractClassMetaData refCmd = storeMgr.getNucleusContext().getMetaDataManager().getMetaDataForClass(mmd.getType(), clr);
            JavaTypeMapping referenceMapping = null;
            if (refCmd.getInheritanceMetaData() != null &&
                    refCmd.getInheritanceMetaData().getStrategy() == InheritanceStrategy.SUBCLASS_TABLE)
            {
                // Find the actual tables storing the other end (can be multiple subclasses)
                AbstractClassMetaData[] cmds = storeMgr.getClassesManagingTableForClass(refCmd, clr);
                if (cmds != null && cmds.length > 0)
                {
                    if (cmds.length > 1)
                    {
                        NucleusLogger.PERSISTENCE.warn("Field " + mmd.getFullFieldName() + " represents either a 1-1 relation, " +
                            "or a N-1 relation where the other end uses \"subclass-table\" inheritance strategy and more " +
                        "than 1 subclasses with a table. This is not fully supported");
                    }
                }
                else
                {
                    // No subclasses of the class using "subclasses-table" so no mapping!
                    // TODO Throw an exception ?
                    return;
                }
                // TODO We need a mapping for each of the possible subclass tables
                referenceMapping = storeMgr.getDatastoreClass(cmds[0].getFullClassName(), clr).getIdMapping();
            }
            else
            {
                referenceMapping = storeMgr.getDatastoreClass(mmd.getType().getName(), clr).getIdMapping();
            }

            // Generate a mapping from the columns of the referenced object to this mapping's ColumnMetaData
            CorrespondentColumnsMapper correspondentColumnsMapping = new CorrespondentColumnsMapper(mmd, referenceMapping, true);

            // Find any related field where this is part of a bidirectional relation
            int relationType = mmd.getRelationType(clr);
            boolean createDatastoreMappings = true;
            if (relationType == Relation.MANY_TO_ONE_BI)
            {
                AbstractMemberMetaData[] relatedMmds = mmd.getRelatedMemberMetaData(clr);
                // TODO Cater for more than 1 related field
                createDatastoreMappings = (relatedMmds[0].getJoinMetaData() == null);
            }
            else if (relationType == Relation.ONE_TO_ONE_BI)
            {
                // Put the FK at the end without "mapped-by"
                createDatastoreMappings = (mmd.getMappedBy() == null);
            }

            if (relationType == Relation.MANY_TO_ONE_UNI)
            {
                // create join table
                storeMgr.newJoinDatastoreContainerObject(mmd, clr);
            }
            else
            {
                // Loop through the datastore fields in the referenced class and create a datastore field for each
                for (int i=0; i<referenceMapping.getNumberOfDatastoreMappings(); i++)
                {
                    DatastoreMapping refDatastoreMapping = referenceMapping.getDatastoreMapping(i);
                    JavaTypeMapping mapping = storeMgr.getMappingManager().getMapping(refDatastoreMapping.getJavaTypeMapping().getJavaType());
                    this.addJavaTypeMapping(mapping);

                    // Create physical datastore columns where we require a FK link to the related table.
                    if (createDatastoreMappings)
                    {
                        // Find the Column MetaData that maps to the referenced datastore field
                        ColumnMetaData colmd = correspondentColumnsMapping.getColumnMetaDataByIdentifier(
                            refDatastoreMapping.getDatastoreField().getIdentifier());
                        if (colmd == null)
                        {
                            throw new NucleusUserException(LOCALISER.msg("041038",
                                refDatastoreMapping.getDatastoreField().getIdentifier(), toString())).setFatal();
                        }

                        // Create a Datastore field to equate to the referenced classes datastore field
                        MappingManager mmgr = storeMgr.getMappingManager();
                        DatastoreField col = mmgr.createDatastoreField(mmd, datastoreContainer, mapping,
                            colmd, refDatastoreMapping.getDatastoreField(), clr);

                        // Add its datastore mapping
                        DatastoreMapping datastoreMapping = mmgr.createDatastoreMapping(mapping, col, refDatastoreMapping.getJavaTypeMapping().getJavaTypeForDatastoreMapping(i));
                        this.addDatastoreMapping(datastoreMapping);
                    }
                    else
                    {
                        mapping.setReferenceMapping(referenceMapping);
                    }
                }
            }
        }
    }

    /**
     * Accessor for the Java type mappings
     * @return The Java type mappings
     */
    public JavaTypeMapping[] getJavaTypeMapping()
    {
        return javaTypeMappings;
    }

    /**
     * Accessor for the number of datastore fields.
     * Zero datastore fields implies that the mapping uses a FK in the associated class
     * to reference back.
     * @return Number of datastore fields for this PC mapping.
     */
    public int getNumberOfDatastoreMappings()
    {
        if (numberOfDatastoreFields == 0)
        {
            for (int i=0; i<javaTypeMappings.length; i++)
            {
                numberOfDatastoreFields += javaTypeMappings[i].getNumberOfDatastoreMappings();
            }
        }
        return numberOfDatastoreFields;
    }

  /**
     * Accessor for a datastore mapping.
     * This method works through the java type mappings, and for each mapping through its datastore mappings
     * incrementing the index with each datastore mapping.
     * @param index The position of the mapping.
     * @return The datastore mapping.
   */
  public DatastoreMapping getDatastoreMapping(int index)
  {
        int currentIndex = 0;
        int numberJavaMappings = javaTypeMappings.length;
        for (int i=0; i<numberJavaMappings; i++)
        {
            int numberDatastoreMappings = javaTypeMappings[i].getNumberOfDatastoreMappings();
            for (int j=0; j<numberDatastoreMappings; j++)
            {
                if (currentIndex == index)
                {
                    return javaTypeMappings[i].getDatastoreMapping(j);
                }
                currentIndex++;
            }
        }
        // TODO Localise this message
        throw new NucleusException("Invalid index " + index + " for DataStoreMapping.").setFatal();
  }

    /**
     * Accessor for the datastore mappings for this java type.
     * Overrides the method in JavaTypeMapping so we can add on datastore mappings of any sub mappings here.
     * @return The datastore mapping(s)
     */
    public DatastoreMapping[] getDatastoreMappings()
    {
        if (datastoreMappings.length == 0)
        {
            datastoreMappings = new DatastoreMapping[getNumberOfDatastoreMappings()];
            int currentIndex = 0;
            int numberJavaMappings = javaTypeMappings.length;
            for (int i=0; i<numberJavaMappings; i++)
            {
                int numberDatastoreMappings = javaTypeMappings[i].getNumberOfDatastoreMappings();
                for (int j=0; j<numberDatastoreMappings; j++)
                {
                    datastoreMappings[currentIndex++] = javaTypeMappings[i].getDatastoreMapping(j);
                }
            }
        }
        return super.getDatastoreMappings();
    }

    /**
     * Method to return the value to be stored in the specified datastore index given the overall
     * value for this java type.
     * @param nucleusCtx Context
     * @param index The datastore index
     * @param value The overall value for this java type
     * @return The value for this datastore index
     */
    public Object getValueForDatastoreMapping(NucleusContext nucleusCtx, int index, Object value)
    {
        ExecutionContext ec = nucleusCtx.getApiAdapter().getExecutionContext(value);
        if (cmd == null)
        {
            cmd = nucleusCtx.getMetaDataManager().getMetaDataForClass(getType(),
                ec != null ? ec.getClassLoaderResolver() : nucleusCtx.getClassLoaderResolver(null));
        }

        if (cmd.getIdentityType() == IdentityType.APPLICATION)
        {
            AbstractMemberMetaData mmd =
                cmd.getMetaDataForManagedMemberAtAbsolutePosition(cmd.getPKMemberPositions()[index]);
            ObjectProvider sm = null;
            if (ec != null)
            {
                sm = ec.findObjectProvider(value);
            }

            if (sm == null)
            {
                // Transient or detached maybe, so use reflection to get PK field values
                if (mmd instanceof FieldMetaData)
                {
                    return ClassUtils.getValueOfFieldByReflection(value, mmd.getName());
                }
                else
                {
                    return ClassUtils.getValueOfMethodByReflection(value,
                        ClassUtils.getJavaBeanGetterName(mmd.getName(), false), null);
                }
            }

            if (!mmd.isPrimaryKey())
            {
                // Make sure the field is loaded
                nucleusCtx.getApiAdapter().isLoaded(sm, mmd.getAbsoluteFieldNumber());
            }
            FieldManager fm = new SingleValueFieldManager();
            sm.provideFields(new int[] {mmd.getAbsoluteFieldNumber()}, fm);
            return fm.fetchObjectField(mmd.getAbsoluteFieldNumber());
        }
        else if (cmd.getIdentityType() == IdentityType.DATASTORE)
        {
            OID oid = (OID)nucleusCtx.getApiAdapter().getIdForObject(value);
            return oid != null ? oid.getKeyValue() : null;
        }
        return null;
    }

    /**
   * Method to set an object in the datastore.
   * @param ec The ObjectManager
   * @param ps The Prepared Statement
   * @param param The parameter ids in the statement
   * @param value The value to put in the statement at these ids
   * @throws NotYetFlushedException
   */
    public void setObject(ExecutionContext ec, Object ps, int[] param, Object value)
    {
        setObject(ec, ps, param, value, null, -1);
    }

    /**
     * Method to set an object reference (FK) in the datastore.
     * @param ec The Object Manager
     * @param ps The Prepared Statement
     * @param param The parameter ids in the statement
     * @param value The value to put in the statement at these ids
     * @param ownerSM StateManager for the owner object
     * @param ownerFieldNumber Field number of this PC object in the owner
     * @throws NotYetFlushedException
     */
    public void setObject(ExecutionContext ec, Object ps, int[] param, Object value, ObjectProvider ownerSM, int ownerFieldNumber)
    {
        if (value == null)
        {
            setObjectAsNull(ec, ps, param);
        }
        else
        {
            setObjectAsValue(ec, ps, param, value, ownerSM, ownerFieldNumber);
        }
    }

    /**
     * Populates the PreparedStatement with a null value for the mappings of this PersistenceCapableMapping.
     * @param ec ExecutionContext
     * @param ps the Prepared Statement
     * @param param The parameter ids in the statement
     */
    private void setObjectAsNull(ExecutionContext ec, Object ps, int[] param)
    {
        // Null out the PC object
        int n=0;
        for (int i=0; i<javaTypeMappings.length; i++)
        {
            JavaTypeMapping mapping = javaTypeMappings[i];
            if (mapping.getNumberOfDatastoreMappings() > 0)
            {
                // Only populate the PreparedStatement for the object if it has any datastore mappings
                int[] posMapping = new int[mapping.getNumberOfDatastoreMappings()];
                for (int j=0; j<posMapping.length; j++)
                {
                    posMapping[j] = param[n++];
                }
                mapping.setObject(ec, ps, posMapping, null);
            }
        }
    }

    /**
     * Check if one of the primary key fields of the PC has value attributed by the datastore
     * @param mdm the {@link MetaDataManager}
     * @param srm the {@link StoreManager}
     * @param clr the {@link ClassLoaderResolver}
     * @return true if one of the primary key fields of the PC has value attributed by the datastore
     */
    private boolean hasDatastoreAttributedPrimaryKeyValues(MetaDataManager mdm, StoreManager srm, ClassLoaderResolver clr)
    {
        boolean hasDatastoreAttributedPrimaryKeyValues = false;
        if (this.mmd != null)
        {
            if (roleForMember != FieldRole.ROLE_ARRAY_ELEMENT &&
                roleForMember != FieldRole.ROLE_COLLECTION_ELEMENT &&
                roleForMember != FieldRole.ROLE_MAP_KEY &&
                roleForMember != FieldRole.ROLE_MAP_VALUE)
            {
                // Object is associated to a field (i.e not a join table)
                AbstractClassMetaData acmd = mdm.getMetaDataForClass(this.mmd.getType(), clr);
                if (acmd.getIdentityType() == IdentityType.APPLICATION)
                {
                    for (int i=0; i<acmd.getPKMemberPositions().length; i++)
                    {
                        IdentityStrategy strategy =
                            acmd.getMetaDataForManagedMemberAtAbsolutePosition(acmd.getPKMemberPositions()[i]).getValueStrategy();
                        if (strategy != null)
                        {
                            //if strategy is null, then it's user attributed value
                            hasDatastoreAttributedPrimaryKeyValues |= srm.isStrategyDatastoreAttributed(strategy, false);
                        }
                    }
                }
            }
        }
        return hasDatastoreAttributedPrimaryKeyValues;
    }
   
    /**
     * Method to set an object reference (FK) in the datastore.
     * @param ec The ExecutionContext
     * @param ps The Prepared Statement
     * @param param The parameter ids in the statement
     * @param value The value to put in the statement at these ids
     * @param ownerSM StateManager for the owner object
     * @param ownerFieldNumber Field number of this PC object in the owner
     * @throws NotYetFlushedException Just put "null" in and throw "NotYetFlushedException",
     *                                to be caught by ParameterSetter and will signal to the PC object being inserted
     *                                that it needs to inform this object when it is inserted.
     */
    private void setObjectAsValue(ExecutionContext ec, Object ps, int[] param, Object value, ObjectProvider ownerSM,
            int ownerFieldNumber)
    {
        Object id;

        ApiAdapter api = ec.getApiAdapter();
        if (!api.isPersistable(value))
        {
            throw new NucleusException(LOCALISER.msg("041016", value.getClass(), value)).setFatal();
        }

        ObjectProvider valueSM = ec.findObjectProvider(value);

        try
        {
            ClassLoaderResolver clr = ec.getClassLoaderResolver();
            MappedStoreManager storeMgr = (MappedStoreManager)ec.getStoreManager();

            // Check if the field is attributed in the datastore
            boolean hasDatastoreAttributedPrimaryKeyValues = hasDatastoreAttributedPrimaryKeyValues(
                ec.getMetaDataManager(), storeMgr, clr);

            boolean inserted = false;
            if (ownerFieldNumber >= 0)
            {
                // Field mapping : is this field of the related object present in the datastore?
                inserted = storeMgr.isObjectInserted(valueSM, ownerFieldNumber);
            }
            else if (mmd == null)
            {
                // Identity mapping : is the object inserted far enough to be considered of this mapping type?
                inserted = storeMgr.isObjectInserted(valueSM, type);
            }

            if (valueSM != null)
            {
                if (ec.getApiAdapter().isDetached(value) && valueSM.getReferencedPC() != null && ownerSM != null && mmd != null)
                {
                    // Still detached but started attaching so replace the field with what will be the attached
                    // Note that we have "fmd != null" here hence omitting any M-N relations where this is a join table
                    // mapping
                    ownerSM.replaceFieldMakeDirty(ownerFieldNumber, valueSM.getReferencedPC());
                }

                if (valueSM.isWaitingToBeFlushedToDatastore())
                {
                    // Related object is not yet flushed to the datastore so flush it so we can set the FK
                    valueSM.flush();
                }
            }
            else
            {
                if (ec.getApiAdapter().isDetached(value))
                {
                    // Field value is detached and not yet started attaching, so attach
                    Object attachedValue = ec.persistObjectInternal(value, null, -1, ObjectProvider.PC);
                    if (attachedValue != value && ownerSM != null)
                    {
                        // Replace the field value if using copy-on-attach
                        ownerSM.replaceFieldMakeDirty(ownerFieldNumber, attachedValue);
                    }
                }
            }

            // we can execute this block when
            // 1) the pc has been inserted; OR
            // 2) is not in process of being inserted; OR
            // 3) is being inserted yet is inserted enough to use this mapping; OR
            // 4) the PC PK values are not attributed by the database and this mapping is for a PK field (compound identity)
            // 5) the value is the same object as we are inserting anyway and has its identity set
            if (inserted || !ec.isInserting(value) ||
                (!hasDatastoreAttributedPrimaryKeyValues && (this.mmd != null && this.mmd.isPrimaryKey())) ||
                (!hasDatastoreAttributedPrimaryKeyValues && ownerSM == valueSM && api.getIdForObject(value) != null))
            {
                // The PC is either already inserted, or inserted down to the level we need, or not inserted at all,
                // or the field is a PK and identity not attributed by the datastore

                // Object either already exists, or is not yet being inserted.
                id = api.getIdForObject(value);

                // Check if the PersistenceCapable exists in this datastore
                boolean requiresPersisting = false;
                if (ec.getApiAdapter().isDetached(value) && ownerSM != null)
                {
                    // Detached object so needs attaching
                    if (ownerSM.isInserting())
                    {
                        // Inserting other object, and this object is detached but if detached from this datastore
                        // we can just return the value now and attach later (in InsertRequest)
                        if (!ec.getNucleusContext().getPersistenceConfiguration().getBooleanProperty("datanucleus.attachSameDatastore"))
                        {
                            if (ec.getObjectFromCache(api.getIdForObject(value)) != null)
                            {
                                // Object is in cache so exists for this datastore, so no point checking
                            }
                            else
                            {
                                try
                                {
                                    Object obj = ec.findObject(api.getIdForObject(value), true, false,
                                        value.getClass().getName());
                                    if (obj != null)
                                    {
                                        // Make sure this object is not retained in cache etc
                                        ObjectProvider objSM = ec.findObjectProvider(obj);
                                        if (objSM != null)
                                        {
                                            ec.evictFromTransaction(objSM);
                                        }
                                        ec.removeObjectFromCache(api.getIdForObject(value));
                                    }
                                }
                                catch (NucleusObjectNotFoundException onfe)
                                {
                                    // Object doesnt yet exist
                                    requiresPersisting = true;
                                }
                            }
                        }
                    }
                    else
                    {
                        requiresPersisting = true;
                    }
                }
                else if (id == null)
                {
                    // Transient object, so we need to persist it
                    requiresPersisting = true;
                }
                else
                {
                    ExecutionContext pcEC = ec.getApiAdapter().getExecutionContext(value);
                    if (pcEC != null && ec != pcEC)
                    {
                        throw new NucleusUserException(LOCALISER.msg("041015"), id);
                    }
                }

                if (requiresPersisting)
                {
                    // PERSISTENCE-BY-REACHABILITY
                    // This PC object needs persisting (new or detached) to do the "set"
                    if (mmd != null && !mmd.isCascadePersist() && !ec.getApiAdapter().isDetached(value))
                    {
                        // Related PC object not persistent, but cant do cascade-persist so throw exception
                        if (NucleusLogger.PERSISTENCE.isDebugEnabled())
                        {
                            NucleusLogger.PERSISTENCE.debug(LOCALISER.msg("007006",
                                mmd.getFullFieldName()));
                        }
                        throw new ReachableObjectNotCascadedException(mmd.getFullFieldName(), value);
                    }

                    if (NucleusLogger.PERSISTENCE.isDebugEnabled())
                    {
                        NucleusLogger.PERSISTENCE.debug(LOCALISER.msg("007007",
                            mmd != null ? mmd.getFullFieldName() : null));
                    }

                    try
                    {
                        Object pcNew = ec.persistObjectInternal(value, null, -1, ObjectProvider.PC);
                        if (hasDatastoreAttributedPrimaryKeyValues)
                        {
                            ec.flushInternal(false);
                        }
                        id = api.getIdForObject(pcNew);
                        if (ec.getApiAdapter().isDetached(value) && ownerSM != null)
                        {
                            // Update any detached reference to refer to the attached variant
                            ownerSM.replaceFieldMakeDirty(ownerFieldNumber, pcNew);
                            int relationType = mmd.getRelationType(clr);
                            if (relationType == Relation.MANY_TO_ONE_BI)
                            {
                                // TODO Update the container to refer to the attached object
                                if (NucleusLogger.PERSISTENCE.isInfoEnabled())
                                {
                                    NucleusLogger.PERSISTENCE.info("PCMapping.setObject : object " + ownerSM.getInternalObjectId() +
                                        " has field " + ownerFieldNumber + " that is 1-N bidirectional." +
                                        " Have just attached the N side so should really update the reference in the 1 side collection" +
                                        " to refer to this attached object. Not yet implemented");
                                }
                            }
                            else if (relationType == Relation.ONE_TO_ONE_BI)
                            {
                                AbstractMemberMetaData[] relatedMmds = mmd.getRelatedMemberMetaData(clr);
                                // TODO Cater for more than 1 related field
                                ObjectProvider relatedSM = ec.findObjectProvider(pcNew);
                                relatedSM.replaceFieldMakeDirty(relatedMmds[0].getAbsoluteFieldNumber(), ownerSM.getObject());
                            }
                        }
                    }
                    catch (NotYetFlushedException e)
                    {
                        setObjectAsNull(ec, ps, param);
                        throw new NotYetFlushedException(value);
                    }
                }

                if (valueSM != null)
                {
                    valueSM.setStoringPC();
                }

                // If the field doesn't map to any datastore fields, omit the set process
                if (getNumberOfDatastoreMappings() > 0)
                {
                    if (id instanceof OID)
                    {
                        super.setObject(ec, ps, param, id);
                    }
                    else
                    {
                        // TODO Factor out this PersistenceCapable reference
                        ((PersistenceCapable)value).jdoCopyKeyFieldsFromObjectId(
                            new AppIDObjectIdFieldConsumer(param, ec, ps, javaTypeMappings), id);
                    }
                }
            }
            else
            {
                if (valueSM != null)
                {
                    valueSM.setStoringPC();
                }

                if (getNumberOfDatastoreMappings() > 0)
                {
                    // Object is in the process of being inserted so we cant use its id currently and we need to store
                    // a foreign key to it (which we cant yet do). Just put "null" in and throw "NotYetFlushedException",
                    // to be caught by ParameterSetter and will signal to the PC object being inserted that it needs
                    // to inform this object when it is inserted.
                    setObjectAsNull(ec, ps, param);
                    throw new NotYetFlushedException(value);
                }
            }
        }
        finally
        {
            if (valueSM != null)
            {
                valueSM.unsetStoringPC();
            }
        }
    }

    /**
     * Returns a instance of a PersistenceCapable class.
     * Processes a FK field and converts the id stored firstly into an OID/AID
     * and then into the object that the FK id relates to.
     * @param ec execution context
     * @param rs The ResultSet
     * @param param Array of parameter ids in the ResultSet to retrieve
     * @return The Persistence Capable object
     */
    public Object getObject(ExecutionContext ec, final Object rs, int[] param)
    {
        // Check for null FK
        MappedStoreManager storeMgr = (MappedStoreManager)ec.getStoreManager();
        if (storeMgr.getResultValueAtPosition(rs, this, param[0]) == null)
        {
            // if the first param is null, then the field is null
            return null;
        }

        if (cmd == null)
        {
            cmd = ec.getMetaDataManager().getMetaDataForClass(getType(),ec.getClassLoaderResolver());
        }

        // Return the object represented by this mapping
        if (cmd.getIdentityType() == IdentityType.DATASTORE)
        {
            return getObjectForDatastoreIdentity(ec,rs,param,cmd);
        }
        else if (cmd.getIdentityType() == IdentityType.APPLICATION)
        {
            return getObjectForApplicationIdentity(ec,rs,param,cmd);
        }
        else
        {
            return null;
        }
    }

    // ------------------------------------- Utility Methods ------------------------------------------

    /**
     * Get the object instance for a class using datastore identity
     * @param ec the ObjectManager
     * @param rs the ResultSet
     * @param param the parameters
     * @param cmd the AbstractClassMetaData
     * @return the id
     */
    private Object getObjectForDatastoreIdentity(ExecutionContext ec, final Object rs, int[] param, AbstractClassMetaData cmd)
    {
        // Datastore Identity - retrieve the OID for the class.
        // Note that this is a temporary OID that is simply formed from the type of base class in the relationship
        // and the id stored in the FK. The real OID for the object may be of a different class.
        // For that reason we get the object by checking the inheritance (final param in getObjectById())
        Object oid = super.getObject(ec, rs, param);
        ApiAdapter api = ec.getApiAdapter();
        if (api.isPersistable(oid)) //why check this?
        {
          return oid;
        }
        return oid == null ? null : ec.findObject(oid, false, true, null);
    }

    /**
     * Create a SingleFieldIdentity instance
     * @param ec the ObjectManager
     * @param rs the ResultSet
     * @param param the parameters
     * @param cmd the AbstractClassMetaData
     * @param objectIdClass the object id class
     * @param pcClass the PersistenceCapable class
     * @return the id
     */
    private Object createSingleFieldIdentity(ExecutionContext ec, final Object rs, int[] param, AbstractClassMetaData cmd,
            Class objectIdClass, Class pcClass)
    {
        // SingleFieldIdentity
        int paramNumber = param[0];
        try
        {
            MappedStoreManager storeMgr = (MappedStoreManager)ec.getStoreManager();
            Object idObj = storeMgr.getResultValueAtPosition(rs, this, paramNumber);
            if (idObj == null)
            {
                throw new NucleusException(LOCALISER.msg("041039")).setFatal();
            }
            else
            {
                // Make sure the key type is correct for the type of SingleFieldIdentity
                Class keyType = ec.getApiAdapter().getKeyTypeForSingleFieldIdentityType(objectIdClass);
                idObj = ClassUtils.convertValue(idObj, keyType);
            }
            return ec.getApiAdapter().getNewSingleFieldIdentity(objectIdClass, pcClass, idObj);
        }
        catch (Exception e)
        {
            NucleusLogger.PERSISTENCE.error(LOCALISER.msg("041036", cmd.getObjectidClass(),
                e));
            return null;
        }
    }

    /**
     * Create an object id instance and fill the fields using reflection
     * @param ec the ObjectManager
     * @param rs the ResultSet
     * @param param the parameters
     * @param cmd the AbstractClassMetaData
     * @param objectIdClass the object id class
     * @return the id
     */
    private Object createObjectIdInstanceReflection(ExecutionContext ec, final Object rs, int[] param,
            AbstractClassMetaData cmd, Class objectIdClass)
    {
        // Users own AID
        Object fieldValue = null;
        try
        {
            // Create an AID
            Object id = objectIdClass.newInstance();

            // Set the fields of the AID
            int paramIndex = 0;
            int[] pkFieldNums = cmd.getPKMemberPositions();
            for (int i=0; i<pkFieldNums.length; ++i)
            {
                AbstractMemberMetaData fmd = cmd.getMetaDataForManagedMemberAtAbsolutePosition(pkFieldNums[i]);
                Field field = objectIdClass.getField(fmd.getName());

                MappedStoreManager storeMgr = (MappedStoreManager)ec.getStoreManager();
                JavaTypeMapping m = storeMgr.getDatastoreClass(cmd.getFullClassName(), ec.getClassLoaderResolver()).getMemberMapping(fmd);
                // NOTE This assumes that each field has one datastore column.
                for (int j = 0; j < m.getNumberOfDatastoreMappings(); j++)
                {
                    Object obj = storeMgr.getResultValueAtPosition(rs, this, param[paramIndex++]);
                    if ((obj instanceof BigDecimal))
                    {
                        BigDecimal bigDecimal = (BigDecimal) obj;
                        // Oracle 10g returns BigDecimal for NUMBER columns,
                        // resulting in IllegalArgumentException when reflective
                        // setter is invoked for incompatible field type
                        // (see http://www.jpox.org/servlet/jira/browse/CORE-2624)
                        Class keyType = ec.getApiAdapter().getKeyTypeForSingleFieldIdentityType(field.getType());
                        obj = ClassUtils.convertValue(bigDecimal, keyType);
                        if (!bigDecimal.subtract(new BigDecimal("" + obj)).equals(new BigDecimal("0")))
                        {
                            throw new NucleusException("Cannot convert retrieved BigInteger value to field of object id class!").setFatal();
                        }
                    }
                    // field with multiple columns should have values returned from db merged here
                    fieldValue = obj;
                }
                field.set(id, fieldValue);
            }
            return id;
        }
        catch (Exception e)
        {
            NucleusLogger.PERSISTENCE.error(LOCALISER.msg("041037",
                cmd.getObjectidClass(), mmd == null ? null : mmd.getName(), fieldValue, e));
            return null;
        }
    }

    /**
     * Create an object id instance and fill the fields using reflection
     * @param ec the ObjectManager
     * @param rs the ResultSet
     * @param param the parameters
     * @param cmd the AbstractClassMetaData
     * @return the id
     */
    private Object getObjectForAbstractClass(ExecutionContext ec, final Object rs, int[] param, AbstractClassMetaData cmd)
    {
        ClassLoaderResolver clr = ec.getClassLoaderResolver();

        // Abstract class, so we need to generate an AID before proceeding
        Class objectIdClass = clr.classForName(cmd.getObjectidClass());
        Class pcClass = clr.classForName(cmd.getFullClassName());
        Object id;
        if (cmd.usesSingleFieldIdentityClass())
        {
            id = createSingleFieldIdentity(ec, rs, param, cmd, objectIdClass, pcClass);
        }
        else
        {
            id = createObjectIdInstanceReflection(ec, rs, param, cmd, objectIdClass);
        }
        return ec.findObject(id, false, true, null);
    }

    /**
     * Get the object instance for a class using application identity
     * @param ec the ObjectManager
     * @param rs the ResultSet
     * @param param the parameters
     * @param cmd the AbstractClassMetaData
     * @return the id
     */
    private Object getObjectForApplicationIdentity(ExecutionContext ec, final Object rs, int[] param, AbstractClassMetaData cmd)
    {
        ClassLoaderResolver clr = ec.getClassLoaderResolver();

        // Abstract class
        if (((ClassMetaData)cmd).isAbstract() && cmd.getObjectidClass() != null)
        {
            return getObjectForAbstractClass(ec,rs,param,cmd);
        }

        int totalFieldCount = cmd.getNoOfManagedMembers() + cmd.getNoOfInheritedManagedMembers();
        final StatementMappingIndex[] statementExpressionIndex = new StatementMappingIndex[totalFieldCount];
        int paramIndex = 0;

        final MappedStoreManager storeMgr = (MappedStoreManager)ec.getStoreManager();
        DatastoreClass datastoreClass = storeMgr.getDatastoreClass(cmd.getFullClassName(), clr);
        final int[] pkFieldNumbers = cmd.getPKMemberPositions();

        for (int i=0; i<pkFieldNumbers.length; ++i)
        {
            AbstractMemberMetaData fmd = cmd.getMetaDataForManagedMemberAtAbsolutePosition(pkFieldNumbers[i]);
            JavaTypeMapping m = datastoreClass.getMemberMapping(fmd);
            statementExpressionIndex[fmd.getAbsoluteFieldNumber()] = new StatementMappingIndex(m);
            int expressionsIndex[] = new int[m.getNumberOfDatastoreMappings()];
            for (int j = 0; j < expressionsIndex.length; j++)
            {
                expressionsIndex[j] = param[paramIndex++];
            }
            statementExpressionIndex[fmd.getAbsoluteFieldNumber()].setColumnPositions(expressionsIndex);
        }

        final StatementClassMapping resultMappings = new StatementClassMapping();
        for (int i=0;i<pkFieldNumbers.length;i++)
        {
            resultMappings.addMappingForMember(pkFieldNumbers[i], statementExpressionIndex[pkFieldNumbers[i]]);
        }
        return ec.findObjectUsingAID(new Type(clr.classForName(cmd.getFullClassName())),
            new FieldValues()
            {
                // StateManager calls the fetchFields method
                public void fetchFields(ObjectProvider sm)
                {
                    sm.replaceFields(pkFieldNumbers,
                        storeMgr.getFieldManagerForResultProcessing(sm, rs, resultMappings));
                }
                public void fetchNonLoadedFields(ObjectProvider sm)
                {
                    sm.replaceNonLoadedFields(pkFieldNumbers,
                        storeMgr.getFieldManagerForResultProcessing(sm, rs, resultMappings));
                }
                public FetchPlan getFetchPlanForLoading()
                {
                    return null;
                }
            }, false, true);
    }

    // ----------------------- Implementation of MappingCallbacks --------------------------

    /**
     * Method executed just after a fetch of the owning object, allowing any necessary action
     * to this field and the object stored in it.
     * @param sm StateManager for the owner.
     */
    public void postFetch(ObjectProvider sm)
    {
    }

    public void insertPostProcessing(ObjectProvider op)
    {
    }

    /**
     * Method executed just after the insert of the owning object, allowing any necessary action
     * to this field and the object stored in it.
     * @param sm StateManager for the owner
     */
    public void postInsert(ObjectProvider sm)
    {
        Object pc = sm.provideField(mmd.getAbsoluteFieldNumber());
        if (pc == null)
        {
            // Has been set to null so nothing to do
            return;
        }

        ClassLoaderResolver clr = sm.getExecutionContext().getClassLoaderResolver();
        AbstractMemberMetaData[] relatedMmds = mmd.getRelatedMemberMetaData(clr);
        int relationType = mmd.getRelationType(clr);
        if (pc != null)
        {
            if (relationType == Relation.ONE_TO_ONE_BI)
            {
                ObjectProvider otherSM = sm.getExecutionContext().findObjectProvider(pc);
                AbstractMemberMetaData relatedMmd = mmd.getRelatedMemberMetaDataForObject(clr, sm.getObject(), pc);
                Object relatedValue = otherSM.provideField(relatedMmd.getAbsoluteFieldNumber());
                if (relatedValue == null)
                {
                    // Managed Relations : Other side not set so update it in memory
                    if (NucleusLogger.PERSISTENCE.isDebugEnabled())
                    {
                        NucleusLogger.PERSISTENCE.debug(LOCALISER.msg("041018",
                            sm.toPrintableID(), mmd.getFullFieldName(),
                            StringUtils.toJVMIDString(pc), relatedMmd.getFullFieldName()));
                    }
                    otherSM.replaceField(relatedMmd.getAbsoluteFieldNumber(), sm.getObject());
                }
                else if (relatedValue != sm.getObject())
                {
                    // Managed Relations : Other side is inconsistent so throw exception
                    throw new NucleusUserException(
                        LOCALISER.msg("041020",
                            sm.toPrintableID(), mmd.getFullFieldName(),
                            StringUtils.toJVMIDString(pc),
                            StringUtils.toJVMIDString(relatedValue)));
                }
            }
            else if (relationType == Relation.MANY_TO_ONE_BI && relatedMmds[0].hasCollection())
            {
                // TODO Make sure we have this PC in the collection at the other side
                ObjectProvider otherSM = sm.getExecutionContext().findObjectProvider(pc);
                if (otherSM != null)
                {
                    // Managed Relations : add to the collection on the other side
                    Collection relatedColl = (Collection)otherSM.provideField(relatedMmds[0].getAbsoluteFieldNumber());
                    if (relatedColl != null && !(relatedColl instanceof SCOCollection))
                    {
                        // TODO Make sure the collection is a wrapper
                        boolean contained = relatedColl.contains(sm.getObject());
                        if (!contained)
                        {
                            NucleusLogger.PERSISTENCE.info(
                                LOCALISER.msg("041022",
                                sm.toPrintableID(), mmd.getFullFieldName(),
                                StringUtils.toJVMIDString(pc), relatedMmds[0].getFullFieldName()));
                            // TODO Enable this. CUrrently causes issues with
                            // PMImplTest, InheritanceStrategyTest, TCK "inheritance1.conf"
                            /*relatedColl.add(sm.getObject());*/
                        }
                    }
                }
            }
            else if (relationType == Relation.MANY_TO_ONE_UNI)
            {
                ObjectProvider otherSM = sm.getExecutionContext().findObjectProvider(pc);
                if (otherSM == null)
                {
                    // Related object is not yet persisted so persist it
                    Object other = sm.getExecutionContext().persistObjectInternal(pc, null, -1, ObjectProvider.PC);
                    otherSM = sm.getExecutionContext().findObjectProvider(other);
                }

                // Add join table entry
                PersistableRelationStore store =
                    (PersistableRelationStore) storeMgr.getBackingStoreForField(
                        sm.getExecutionContext().getClassLoaderResolver(), mmd, mmd.getType());
                store.add(sm, otherSM);
            }
        }
    }

    /**
     * Method executed just afer any update of the owning object, allowing any necessary action
     * to this field and the object stored in it.
     * @param sm StateManager for the owner
     */
    public void postUpdate(ObjectProvider sm)
    {
        Object pc = sm.provideField(mmd.getAbsoluteFieldNumber());
        ClassLoaderResolver clr = sm.getExecutionContext().getClassLoaderResolver();
        int relationType = mmd.getRelationType(clr);
        if (pc == null)
        {
            if (relationType == Relation.MANY_TO_ONE_UNI)
            {
                // Update join table entry
                PersistableRelationStore store =
                    (PersistableRelationStore) storeMgr.getBackingStoreForField(
                        sm.getExecutionContext().getClassLoaderResolver(), mmd, mmd.getType());
                store.remove(sm);
            }

            return;
        }
        else
        {
            ObjectProvider otherSM = sm.getExecutionContext().findObjectProvider(pc);
            if (otherSM == null)
            {
                if (relationType == Relation.ONE_TO_ONE_BI || relationType == Relation.MANY_TO_ONE_BI ||
                    relationType == Relation.MANY_TO_ONE_UNI)
                {
                    // Related object is not yet persisted (e.g 1-1 with FK at other side) so persist it
                    Object other = sm.getExecutionContext().persistObjectInternal(pc, null, -1, ObjectProvider.PC);
                    otherSM = sm.getExecutionContext().findObjectProvider(other);
                }
            }

            if (relationType == Relation.MANY_TO_ONE_UNI)
            {
                // Update join table entry
                PersistableRelationStore store =
                    (PersistableRelationStore) storeMgr.getBackingStoreForField(
                        sm.getExecutionContext().getClassLoaderResolver(), mmd, mmd.getType());
                store.update(sm, otherSM);
            }
        }
    }

    /**
     * Method executed just before the owning object is deleted, allowing tidying up of any
     * relation information.
     * @param sm StateManager for the owner
     */
    public void preDelete(ObjectProvider sm)
    {
        ExecutionContext ec = sm.getExecutionContext();

        // makes sure field is loaded
        int fieldNumber = mmd.getAbsoluteFieldNumber();
        try
        {
            ec.getApiAdapter().isLoaded(sm, fieldNumber);
        }
        catch (JDOObjectNotFoundException onfe) // TODO Use different method that throws NucleusObjectNotFoundException
        {
            // Already deleted so just return
            return;
        }

        Object pc = sm.provideField(fieldNumber);
        if (pc == null)
        {
            // Null value so nothing to do
            return;
        }

        // N-1 Uni, so delete join table entry
        ClassLoaderResolver clr = sm.getExecutionContext().getClassLoaderResolver();
        int relationType = mmd.getRelationType(clr);
        if (relationType == Relation.MANY_TO_ONE_UNI)
        {
            // Update join table entry
            PersistableRelationStore store =
                (PersistableRelationStore) storeMgr.getBackingStoreForField(
                    sm.getExecutionContext().getClassLoaderResolver(), mmd, mmd.getType());
            store.remove(sm);
        }

        // Check if we should delete the related object when this object is deleted
        boolean dependent = mmd.isDependent();
        if (mmd.isCascadeRemoveOrphans())
        {
            // JPA allows "orphan removal" to define deletion of the other side
            dependent = true;
        }

        // Check if the field has a FK defined
        MappedStoreManager storeMgr = (MappedStoreManager)ec.getStoreManager();
        AbstractMemberMetaData[] relatedMmds = mmd.getRelatedMemberMetaData(clr);
        // TODO Cater for more than 1 related field

        boolean hasFK = false;
        if (!dependent)
        {
            // Not dependent, so check if the datastore has a FK and will take care of it for us
            if (mmd.getForeignKeyMetaData() != null)
            {
                hasFK = true;
            }
            if (relatedMmds != null && relatedMmds[0].getForeignKeyMetaData() != null)
            {
                hasFK = true;
            }
            if (ec.getNucleusContext().getPersistenceConfiguration().getStringProperty("datanucleus.deletionPolicy").equals("JDO2"))
            {
                // JDO2 doesnt currently (2.0 spec) take note of foreign-key
                hasFK = false;
            }
        }

        // Basic rules for the following :-
        // 1. If it is dependent then we delete it (maybe after nulling).
        // 2. If it is not dependent and they have defined no FK then null it, else delete it
        // 3. If it is not dependent and they have a FK, let the datastore handle the delete
        // There may be some corner cases that this code doesn't yet cater for
        if (relationType == Relation.ONE_TO_ONE_UNI ||
            (relationType == Relation.ONE_TO_ONE_BI && mmd.getMappedBy() == null))
        {
            // 1-1 with FK at this side (owner of the relation)
            if (dependent)
            {
                boolean relatedObjectDeleted = ec.getApiAdapter().isDeleted(pc);
                if (isNullable() && !relatedObjectDeleted)
                {
                    // Other object not yet deleted - just null out the FK
                    // TODO Not doing this would cause errors in 1-1 uni relations (e.g AttachDetachTest)
                    // TODO Log this since it affects the resultant objects
                    sm.replaceFieldMakeDirty(fieldNumber, null);
                    sm.getExecutionContext().getStoreManager().getPersistenceHandler().updateObject(sm, new int[]{fieldNumber});
                    if (!relatedObjectDeleted)
                    {
                        // Mark the other object for deletion since not yet tagged
                        ec.deleteObjectInternal(pc);
                    }
                }
                else
                {
                    // Can't just delete the other object since that would cause a FK constraint violation
                    // Do nothing - handled by DeleteRequest
                    NucleusLogger.DATASTORE_PERSIST.warn("Delete of " + StringUtils.toJVMIDString(sm.getObject()) +
                        " needs delete of related object at " + mmd.getFullFieldName() + " but cannot delete it direct since FK is here");
                }
            }
            else
            {
                // We're deleting the FK at this side so shouldnt be an issue
                AbstractMemberMetaData relatedMmd = mmd.getRelatedMemberMetaDataForObject(clr, sm.getObject(), pc);
                if (relatedMmd != null)
                {
                    ObjectProvider otherSM = ec.findObjectProvider(pc);
                    if (otherSM != null)
                    {
                        // Managed Relations : 1-1 bidir, so null out the object at the other
                        Object currentValue = otherSM.provideField(relatedMmd.getAbsoluteFieldNumber());
                        if (currentValue != null)
                        {
                            if (NucleusLogger.PERSISTENCE.isDebugEnabled())
                            {
                                NucleusLogger.PERSISTENCE.debug(LOCALISER.msg("041019",
                                    StringUtils.toJVMIDString(pc), relatedMmd.getFullFieldName(),
                                    sm.toPrintableID()));
                            }
                            otherSM.replaceFieldMakeDirty(relatedMmd.getAbsoluteFieldNumber(), null);

                            if (ec.getManageRelations())
                            {
                                otherSM.getExecutionContext().getRelationshipManager(otherSM).relationChange(
                                    relatedMmd.getAbsoluteFieldNumber(), sm.getObject(), null);
                            }
                        }
                    }
                }
            }
        }
        else if (relationType == Relation.ONE_TO_ONE_BI && mmd.getMappedBy() != null)
        {
            // 1-1 with FK at other side
            DatastoreClass relatedTable = storeMgr.getDatastoreClass(relatedMmds[0].getClassName(), clr);
            JavaTypeMapping relatedMapping = relatedTable.getMemberMapping(relatedMmds[0]);
            boolean isNullable = relatedMapping.isNullable();
            ObjectProvider otherSM = ec.findObjectProvider(pc);
            if (dependent)
            {
                if (isNullable)
                {
                    // Null out the FK in the datastore using a direct update (since we are deleting)
                    otherSM.replaceFieldMakeDirty(relatedMmds[0].getAbsoluteFieldNumber(), null);
                    otherSM.getExecutionContext().getStoreManager().getPersistenceHandler().updateObject(
                        otherSM, new int[]{relatedMmds[0].getAbsoluteFieldNumber()});
                }
                // Mark the other object for deletion
                ec.deleteObjectInternal(pc);
            }
            else if (!hasFK)
            {
                if (isNullable())
                {
                    Object currentRelatedValue = otherSM.provideField(relatedMmds[0].getAbsoluteFieldNumber());
                    if (currentRelatedValue != null)
                    {
                        // Null out the FK in the datastore using a direct update (since we are deleting)
                        otherSM.replaceFieldMakeDirty(relatedMmds[0].getAbsoluteFieldNumber(), null);
                        ec.getStoreManager().getPersistenceHandler().updateObject(
                            otherSM, new int[]{relatedMmds[0].getAbsoluteFieldNumber()});

                        // Managed Relations : 1-1 bidir, so null out the object at the other
                        if (ec.getManageRelations())
                        {
                            otherSM.getExecutionContext().getRelationshipManager(otherSM).relationChange(
                                relatedMmds[0].getAbsoluteFieldNumber(), sm.getObject(), null);
                        }
                    }
                }
                else
                {
                    // TODO Remove it
                }
            }
            else
            {
                // User has a FK defined (in MetaData) so let the datastore take care of it
            }
        }
        else if (relationType == Relation.MANY_TO_ONE_BI)
        {
            ObjectProvider otherSM = ec.findObjectProvider(pc);
            if (relatedMmds[0].getJoinMetaData() == null)
            {
                // N-1 with FK at this side
                if (otherSM.isDeleting())
                {
                    // Other object is being deleted too but this side has the FK so just delete this object
                }
                else
                {
                    // Other object is not being deleted so delete it if necessary
                    if (dependent)
                    {
                        if (isNullable())
                        {
                            // TODO Datastore nullability info can be unreliable so try to avoid this call
                            // Null out the FK in the datastore using a direct update (since we are deleting)
                            sm.replaceFieldMakeDirty(fieldNumber, null);
                            sm.getExecutionContext().getStoreManager().getPersistenceHandler().updateObject(sm, new int[]{fieldNumber});
                        }

                        if (ec.getApiAdapter().isDeleted(pc))
                        {
                            // Object is already tagged for deletion but we're deleting the FK so leave til flush()
                        }
                        else
                        {
                            // Mark the other object for deletion
                            ec.deleteObjectInternal(pc);
                        }
                    }
                    else
                    {
                        // Managed Relations : remove element from collection/map
                        if (relatedMmds[0].hasCollection())
                        {
                            // Only update the other side if not already being deleted
                            if (!ec.getApiAdapter().isDeleted(otherSM.getObject()) && !otherSM.isDeleting())
                            {
                                // Make sure the other object is updated in any caches
                                ec.markDirty(otherSM, false);
                                Collection otherColl = (Collection)otherSM.provideField(relatedMmds[0].getAbsoluteFieldNumber());
                                if (otherColl != null)
                                {
                                    if (ec.getManageRelations())
                                    {
                                        otherSM.getExecutionContext().getRelationshipManager(otherSM).relationRemove(
                                            relatedMmds[0].getAbsoluteFieldNumber(), sm.getObject());
                                    }
                                    // TODO Localise this message
                                    NucleusLogger.PERSISTENCE.debug("ManagedRelationships : delete of object causes removal from collection at " + relatedMmds[0].getFullFieldName());
                                    otherColl.remove(sm.getObject());
                                }
                            }
                        }
                        else if (relatedMmds[0].hasMap())
                        {
                            // TODO Cater for maps, but what is the key/value pair ?
                        }
                    }
                }
            }
            else
            {
                // N-1 with join table so no FK here so need to remove from Collection/Map first? (managed relations)
                if (dependent)
                {
                    // Mark the other object for deletion
                    ec.deleteObjectInternal(pc);
                }
                else
                {
                    // Managed Relations : remove element from collection/map
                    if (relatedMmds[0].hasCollection())
                    {
                        // Only update the other side if not already being deleted
                        if (!ec.getApiAdapter().isDeleted(otherSM.getObject()) && !otherSM.isDeleting())
                        {
                            // Make sure the other object is updated in any caches
                            ec.markDirty(otherSM, false);

                            // Make sure the other object has the collection loaded so does this change
                            otherSM.isLoaded(relatedMmds[0].getAbsoluteFieldNumber());
                            Collection otherColl = (Collection)otherSM.provideField(relatedMmds[0].getAbsoluteFieldNumber());
                            if (otherColl != null)
                            {
                                // TODO Localise this
                                NucleusLogger.PERSISTENCE.debug("ManagedRelationships : delete of object causes removal from collection at " + relatedMmds[0].getFullFieldName());
                                otherColl.remove(sm.getObject());
                            }
                        }
                    }
                    else if (relatedMmds[0].hasMap())
                    {
                        // TODO Cater for maps, but what is the key/value pair ?
                    }
                }
            }
        }
        else if (relationType == Relation.MANY_TO_ONE_UNI)
        {
            // N-1 uni with join table
            if (dependent)
            {
                // Mark the other object for deletion
                ec.deleteObjectInternal(pc);
            }
        }
        else
        {
            // No relation so what is this field ?
        }
    }
}
TOP

Related Classes of org.datanucleus.store.mapped.mapping.PersistableMapping

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.