Package org.datanucleus.store.mapped.scostore

Source Code of org.datanucleus.store.mapped.scostore.FKMapStore

/**********************************************************************
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 - removed unused variable
2003 Andy Jefferson - coding standards
2003 Andy Jefferson - addition of getGetStatement for inherited values
2004 Andy Jefferson - addition of query methods
2004 Marco Schulze  - replaced catch(NotPersistenceCapableException ...) by
                      advance-check via TypeManager.isSupportedType(...)
2004 Andy Jefferson - moved statements to AbstractMapStore
2004 Andy Jefferson - added support for 1-N unidirectional
2007 Andy Jefferson - added support for restricted 1-N bi where key/value class is subclass
    ...
**********************************************************************/
package org.datanucleus.store.mapped.scostore;

import java.util.Iterator;

import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.FetchPlan;
import org.datanucleus.api.ApiAdapter;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.MapMetaData;
import org.datanucleus.store.ExecutionContext;
import org.datanucleus.store.FieldValues;
import org.datanucleus.store.ObjectProvider;
import org.datanucleus.store.exceptions.ClassDefinitionException;
import org.datanucleus.store.mapped.MappedStoreManager;
import org.datanucleus.store.mapped.mapping.JavaTypeMapping;
import org.datanucleus.store.mapped.mapping.MappingConsumer;
import org.datanucleus.store.scostore.SetStore;
import org.datanucleus.util.ClassUtils;

/**
* Representation of a FK backing for a Map. This class is used where you have a 1-N and the tables
* are not joined via a link table, instead using a FK. There are 2 possible uses here
* <UL>
* <LI><B>bidirectional</B> - where the owner has a Map of (key,value), and the key/value has an owner.
* In this case the key/value class will have an owner field which can be updated directly</LI>
* <LI><B>unidirectional</B> - where the owner has a Map of (key,value), but the key/value knows nothing
* about the owner. In this case the key/value class has no owner field. In this case the column in the
* key/value table has to be updated directly.</LI>
* </UL>
* In both cases the value class will have a field that represents the key, and so the user must specify
* a 'key mapped-by="..."' attribute to denote which field is the key, or a 'value mapped-by="..."' to
* denote which field is the value.
*/
public abstract class FKMapStore extends AbstractMapStore
{
    /** Field number of owner link in value class. */
    private final int ownerFieldNumber;

    protected final ClassLoaderResolver clr;

    /** Field number of key in value class (when Key=Non-PC, Value=PC). */
    protected int keyFieldNumber = -1;

    /** Field number of value in key class (when Key=PC, value=Non-PC). */
    private int valueFieldNumber = -1;

    private SetStore keySetStore = null;
    private SetStore valueSetStore = null;
    private SetStore entrySetStore = null;

    /**
     * Constructor for an Inverse Map.
     * @param fmd Field Meta-Data for the Map field.
     * @param storeMgr The Store Manager we are using.
     * @param clr The ClassLoaderResolver
     * @param specialization The datastore-specific specialization
     */
    public FKMapStore(AbstractMemberMetaData fmd, MappedStoreManager storeMgr, ClassLoaderResolver clr,
            AbstractMapStoreSpecialization specialization)
    {
        super(storeMgr, specialization);
        this.clr = clr;
        setOwner(fmd, clr);
        MapMetaData mmd = (MapMetaData)fmd.getContainer();
        if (mmd == null)
        {
            // No <map> specified for this field!
            throw new NucleusUserException(LOCALISER.msg("056002", fmd.getFullFieldName()));
        }

        // Check whether we store the key in the value, or the value in the key
        boolean keyStoredInValue = false;
        if (fmd.getKeyMetaData() != null && fmd.getKeyMetaData().getMappedBy() != null)
        {
            keyStoredInValue = true;
        }
        else if (fmd.getValueMetaData() != null && fmd.getValueMetaData().getMappedBy() == null)
        {
            // No mapped-by specified on either key or value so we dont know what to do with this relation!
            throw new NucleusUserException(LOCALISER.msg("056071", fmd.getFullFieldName()));
        }
        else
        {
            // Should throw an exception since must store key in value or value in key
        }

        // Load the key and value classes
        keyType = mmd.getKeyType();
        valueType = mmd.getValueType();
        Class keyClass = clr.classForName(keyType);
        Class valueClass = clr.classForName(valueType);

        ApiAdapter api = getStoreManager().getApiAdapter();
        if (keyStoredInValue && !api.isPersistable(valueClass))
        {
            // key stored in value but value is not PC!
            throw new NucleusUserException(LOCALISER.msg("056072", fmd.getFullFieldName(), valueType));
        }
        if (!keyStoredInValue && !api.isPersistable(keyClass))
        {
            // value stored in key but key is not PC!
            throw new NucleusUserException(LOCALISER.msg("056073", fmd.getFullFieldName(), keyType));
        }

        String ownerFieldName = fmd.getMappedBy();
        if (keyStoredInValue)
        {
            // Key = field in value, Value = PC
            vmd = storeMgr.getNucleusContext().getMetaDataManager().getMetaDataForClass(valueClass, clr);
            if (vmd == null)
            {
                // Value has no MetaData!
                throw new NucleusUserException(LOCALISER.msg("056070", valueType, fmd.getFullFieldName()));
            }

            valueTable = storeMgr.getDatastoreClass(valueType, clr);
            valueMapping  = storeMgr.getDatastoreClass(valueType, clr).getIdMapping();
            valuesAreEmbedded = false;
            valuesAreSerialised = false;

            if (fmd.getMappedBy() != null)
            {
                // 1-N bidirectional : The value class has a field for the owner.
                AbstractMemberMetaData vofmd = vmd.getMetaDataForMember(ownerFieldName);
                if (vofmd == null)
                {
                    throw new NucleusUserException(LOCALISER.msg("056067", fmd.getFullFieldName(),
                        ownerFieldName, valueClass.getName()));
                }

                // Check that the type of the value "mapped-by" field is consistent with the owner type
                if (!clr.isAssignableFrom(vofmd.getType(), fmd.getAbstractClassMetaData().getFullClassName()))
                {
                    throw new NucleusUserException(LOCALISER.msg("056068", fmd.getFullFieldName(),
                        vofmd.getFullFieldName(), vofmd.getTypeName(), fmd.getAbstractClassMetaData().getFullClassName()));
                }

                ownerFieldNumber = vmd.getAbsolutePositionOfMember(ownerFieldName);
                ownerMapping = valueTable.getMemberMapping(vofmd);
                if (ownerMapping == null)
                {
                    throw new NucleusUserException(LOCALISER.msg("RDBMS.SCO.Map.InverseOwnerMappedByFieldNotPresent",
                        fmd.getAbstractClassMetaData().getFullClassName(), fmd.getName(), valueType, ownerFieldName));
                }
                if (isEmbeddedMapping(ownerMapping))
                {
                    throw new NucleusUserException(LOCALISER.msg("056055",
                        ownerFieldName, valueType, vofmd.getTypeName(), fmd.getClassName()));
                }
            }
            else
            {
                // 1-N Unidirectional : The value class knows nothing about the owner
                ownerFieldNumber = -1;
                ownerMapping = valueTable.getExternalMapping(fmd, MappingConsumer.MAPPING_TYPE_EXTERNAL_FK);
                if (ownerMapping == null)
                {
                    throw new NucleusUserException(LOCALISER.msg("056056",
                        fmd.getAbstractClassMetaData().getFullClassName(), fmd.getName(), valueType));
                }
            }

            AbstractMemberMetaData vkfmd = null;
            if (fmd.getKeyMetaData() == null || fmd.getKeyMetaData().getMappedBy() == null)
            {
                if (vkfmd == null)
                {
                    throw new NucleusUserException(LOCALISER.msg("056050", valueClass.getName()));
                }
            }

            String key_field_name = fmd.getKeyMetaData().getMappedBy();
            if (key_field_name != null)
            {
                // check if key field exists in the ClassMetaData for the element-value type
                vkfmd = storeMgr.getNucleusContext().getMetaDataManager().getMetaDataForMember(valueClass, clr, key_field_name);
                if (vkfmd == null)
                {
                    throw new NucleusUserException(LOCALISER.msg("056052", valueClass.getName(), key_field_name));
                }
            }
            if (vkfmd == null)
            {
                throw new ClassDefinitionException(LOCALISER.msg("056050", fmd.getFullFieldName()));
            }

            // Check that the key type is correct for the declared type
            if (!ClassUtils.typesAreCompatible(vkfmd.getType(), keyType, clr))
            {
                throw new NucleusUserException(LOCALISER.msg("056051",
                    fmd.getFullFieldName(), keyType, vkfmd.getType().getName()));
            }

            // Set up key field
            String keyFieldName = vkfmd.getName();
            keyFieldNumber = vmd.getAbsolutePositionOfMember(keyFieldName);
            keyMapping = valueTable.getMemberMapping(vmd.getMetaDataForManagedMemberAtAbsolutePosition(keyFieldNumber));
            if (keyMapping == null)
            {
                throw new NucleusUserException(LOCALISER.msg("056053",
                    fmd.getAbstractClassMetaData().getFullClassName(), fmd.getName(), valueType, keyFieldName));
            }

            if (!keyMapping.hasSimpleDatastoreRepresentation())
            {
                // Check the type of the mapping
                throw new NucleusUserException("Invalid field type for map key field: " + fmd.getClassName() + "." + fmd.getName());
            }
            keysAreEmbedded = isEmbeddedMapping(keyMapping);
            keysAreSerialised = isEmbeddedMapping(keyMapping);

            mapTable = valueTable;
            if (fmd.getMappedBy() != null && ownerMapping.getDatastoreContainer() != mapTable)
            {
                // Value and owner don't have consistent tables so use the one with the mapping
                // e.g map value is subclass, yet superclass has the link back to the owner
                mapTable = ownerMapping.getDatastoreContainer();
            }
        }
        else
        {
            // Key = PC, Value = field in key
            kmd = storeMgr.getNucleusContext().getMetaDataManager().getMetaDataForClass(keyClass, clr);
            if (kmd == null)
            {
                // Key has no MetaData!
                throw new NucleusUserException(LOCALISER.msg("056069", keyType, fmd.getFullFieldName()));
            }

            // TODO This should be called keyvalueTable or something and not valueTable
            valueTable = storeMgr.getDatastoreClass(keyType, clr);
            keyMapping  = storeMgr.getDatastoreClass(keyType, clr).getIdMapping();
            keysAreEmbedded = false;
            keysAreSerialised = false;

            if (fmd.getMappedBy() != null)
            {
                // 1-N bidirectional : The key class has a field for the owner.
                AbstractMemberMetaData kofmd = kmd.getMetaDataForMember(ownerFieldName);
                if (kofmd == null)
                {
                    throw new NucleusUserException(LOCALISER.msg("056067", fmd.getFullFieldName(),
                        ownerFieldName, keyClass.getName()));
                }

                // Check that the type of the key "mapped-by" field is consistent with the owner type
                if (!ClassUtils.typesAreCompatible(kofmd.getType(), fmd.getAbstractClassMetaData().getFullClassName(), clr))
                {
                    throw new NucleusUserException(LOCALISER.msg("056068", fmd.getFullFieldName(),
                        kofmd.getFullFieldName(), kofmd.getTypeName(), fmd.getAbstractClassMetaData().getFullClassName()));
                }

                ownerFieldNumber = kmd.getAbsolutePositionOfMember(ownerFieldName);
                ownerMapping = valueTable.getMemberMapping(kofmd);
                if (ownerMapping == null)
                {
                    throw new NucleusUserException(LOCALISER.msg("RDBMS.SCO.Map.InverseOwnerMappedByFieldNotPresent",
                        fmd.getAbstractClassMetaData().getFullClassName(), fmd.getName(), keyType, ownerFieldName));
                }
                if (isEmbeddedMapping(ownerMapping))
                {
                    throw new NucleusUserException(LOCALISER.msg("056055",
                        ownerFieldName, keyType, kofmd.getTypeName(), fmd.getClassName()));
                }
            }
            else
            {
                // 1-N Unidirectional : The key class knows nothing about the owner
                ownerFieldNumber = -1;
                ownerMapping = valueTable.getExternalMapping(fmd, MappingConsumer.MAPPING_TYPE_EXTERNAL_FK);
                if (ownerMapping == null)
                {
                    throw new NucleusUserException(LOCALISER.msg("056056",
                        fmd.getAbstractClassMetaData().getFullClassName(), fmd.getName(), keyType));
                }
            }

            AbstractMemberMetaData vkfmd = null;
            if (fmd.getValueMetaData() == null || fmd.getValueMetaData().getMappedBy() == null)
            {
                if (vkfmd == null)
                {
                    throw new NucleusUserException(LOCALISER.msg("056057", keyClass.getName()));
                }
            }

            String value_field_name = fmd.getValueMetaData().getMappedBy();
            if (value_field_name != null)
            {
                // check if value field exists in the ClassMetaData for the element-value type
                vkfmd = storeMgr.getNucleusContext().getMetaDataManager().getMetaDataForMember(keyClass, clr, value_field_name);
                if (vkfmd == null)
                {
                    throw new NucleusUserException(LOCALISER.msg("056059", keyClass.getName(), value_field_name));
                }
            }
            if (vkfmd == null)
            {
                throw new ClassDefinitionException(LOCALISER.msg("056057", fmd.getFullFieldName()));
            }

            // Check that the value type is consistent with the declared type
            if (!ClassUtils.typesAreCompatible(vkfmd.getType(), valueType, clr))
            {
                throw new NucleusUserException(LOCALISER.msg("056058",
                    fmd.getFullFieldName(), valueType, vkfmd.getType().getName()));
            }

            // Set up value field
            String valueFieldName = vkfmd.getName();
            valueFieldNumber = kmd.getAbsolutePositionOfMember(valueFieldName);
            valueMapping = valueTable.getMemberMapping(kmd.getMetaDataForManagedMemberAtAbsolutePosition(valueFieldNumber));
            if (valueMapping == null)
            {
                throw new NucleusUserException(LOCALISER.msg("056054",
                    fmd.getAbstractClassMetaData().getFullClassName(), fmd.getName(), keyType, valueFieldName));
            }

            if (!valueMapping.hasSimpleDatastoreRepresentation())
            {
                // Check the type of the mapping
                throw new NucleusUserException("Invalid field type for map value field: " + fmd.getClassName() + "." + fmd.getName());
            }
            valuesAreEmbedded = isEmbeddedMapping(valueMapping);
            valuesAreSerialised = isEmbeddedMapping(valueMapping);

            mapTable = valueTable;
            if (fmd.getMappedBy() != null && ownerMapping.getDatastoreContainer() != mapTable)
            {
                // Key and owner don't have consistent tables so use the one with the mapping
                // e.g map key is subclass, yet superclass has the link back to the owner
                mapTable = ownerMapping.getDatastoreContainer();
            }
        }

        // Generate the statements
        specialization.initialise(this);
        initialise();
    }

    protected abstract void initialise();

    protected abstract boolean updateKeyFkInternal(ObjectProvider sm, Object key, Object owner);
    protected abstract boolean updateValueFkInternal(ObjectProvider sm, Object value, Object owner);

    protected abstract MapKeySetStore newMapKeySetStore();
    protected abstract MapValueSetStore newMapValueSetStore();
    protected abstract MapEntrySetStore newMapEntrySetStore();

    /**
     * Utility to update a foreign-key in the value in the case of
     * a unidirectional 1-N relationship.
     * @param sm StateManager for the owner
     * @param value The value to update
     * @param owner The owner object to set in the FK
     * @return Whether it was performed successfully
     */
    private boolean updateValueFk(ObjectProvider sm, Object value, Object owner)
    {
        if (value == null)
        {
            return false;
        }
        validateValueForWriting(sm, value);
        return updateValueFkInternal(sm, value, owner);
    }

    /**
     * Utility to update a foreign-key in the key in the case of
     * a unidirectional 1-N relationship.
     * @param sm StateManager for the owner
     * @param key The key to update
     * @param owner The owner object to set in the FK
     * @return Whether it was performed successfully
     */
    private boolean updateKeyFk(ObjectProvider sm, Object key, Object owner)
    {
        if (key == null)
        {
            return false;
        }
        validateKeyForWriting(sm, key);
        return updateKeyFkInternal(sm, key, owner);
    }

    /**
     * Utility to validate the type of a value for storing in the Map.
     * @param value The value to check.
     * @param clr The ClassLoaderResolver
     **/
    protected void validateValueType(ClassLoaderResolver clr, Object value)
    {
        if (value == null)
        {
            throw new NullPointerException(LOCALISER.msg("056063"));
        }

        super.validateValueType(clr, value);
    }

    /**
     * Method to put an item in the Map.
     * @param sm State Manager for the map.
     * @param newKey The key to store the value against
     * @param newValue The value to store.
     * @return The value stored.
     **/
    public Object put(final ObjectProvider sm, final Object newKey, Object newValue)
    {
        if (keyFieldNumber >= 0)
        {
            validateKeyForWriting(sm, newKey);
            validateValueType(sm.getExecutionContext().getClassLoaderResolver(), newValue);
        }
        else
        {
            validateKeyType(sm.getExecutionContext().getClassLoaderResolver(), newKey);
            validateValueForWriting(sm, newValue);
        }

        // Check if there is an existing value for this key
        Object oldValue = get(sm, newKey);
        if (oldValue != newValue)
        {
            if (vmd != null)
            {
                if (oldValue != null && !oldValue.equals(newValue))
                {
                    // Key is stored in the value and the value has changed so remove the old value
                    removeValue(sm, newKey, oldValue);
                }

                ExecutionContext ec = sm.getExecutionContext();
                final Object newOwner = sm.getObject();

                if (ec.getApiAdapter().isPersistent(newValue))
                {
                    /*
                     * The new value is already persistent.
                     *
                     * "Put" the new value in the map by updating its owner and key
                     * fields to the appropriate values.  This is done with the same
                     * methods the PC itself would use if the application code
                     * modified the fields.  It should result in no actual database
                     * activity if the fields were already set to the right values.
                     */
                    if (ec != ec.getApiAdapter().getExecutionContext(newValue))
                    {
                        throw new NucleusUserException(LOCALISER.msg("RDBMS.SCO.Map.WriteValueInvalidWithDifferentPM"), ec.getApiAdapter().getIdForObject(newValue));
                    }

                    ObjectProvider vsm = ec.findObjectProvider(newValue);
                   
                    // Ensure the current owner field is loaded, and replace with new value
                    if (ownerFieldNumber >= 0)
                    {
                        ec.getApiAdapter().isLoaded(vsm, ownerFieldNumber);
                        Object oldOwner = vsm.provideField(ownerFieldNumber);
                        vsm.replaceFieldMakeDirty(ownerFieldNumber, newOwner);
                        if (ec.getManageRelations())
                        {
                            ec.getRelationshipManager(vsm).relationChange(ownerFieldNumber, oldOwner, newOwner);
                        }
                    }
                    else
                    {
                        updateValueFk(sm, newValue, newOwner);
                    }

                    // Ensure the current key field is loaded, and replace with new value
                    ec.getApiAdapter().isLoaded(vsm, keyFieldNumber);
                    Object oldKey = vsm.provideField(keyFieldNumber);
                    vsm.replaceFieldMakeDirty(keyFieldNumber, newKey);
                    if (ec.getManageRelations())
                    {
                        ec.getRelationshipManager(vsm).relationChange(keyFieldNumber, oldKey, newKey);
                    }
                }
                else
                {                 
                    /*
                     * The new value is not yet persistent.
                     *
                     * Update its owner and key fields to the appropriate values and
                     * *then* make it persistent.  Making the changes before DB
                     * insertion avoids an unnecessary UPDATE allows the owner
                     * and/or key fields to be non-nullable.
                     */
                    ec.persistObjectInternal(newValue, new FieldValues()
                        {
                        public void fetchFields(ObjectProvider vsm)
                        {
                            if (ownerFieldNumber >= 0)
                            {
                                vsm.replaceFieldMakeDirty(ownerFieldNumber, newOwner);
                            }
                            vsm.replaceFieldMakeDirty(keyFieldNumber, newKey);

                            JavaTypeMapping externalFKMapping = valueTable.getExternalMapping(ownerMemberMetaData, MappingConsumer.MAPPING_TYPE_EXTERNAL_FK);
                            if (externalFKMapping != null)
                            {
                                // Set the owner in the value object where appropriate
                                vsm.setAssociatedValue(externalFKMapping, sm.getObject());
                            }
                        }
                        public void fetchNonLoadedFields(ObjectProvider sm)
                        {
                        }
                        public FetchPlan getFetchPlanForLoading()
                        {
                            return null;
                        }
                        }, ObjectProvider.PC);

      /*              // TODO Think about removing this since we set the associated value here
                    if (ownerFieldNumber < 0)
                    {
                        updateValueFk(sm, newValue, newOwner);
                    }*/
                }
            }
            else
            {
                // Value is stored in the key
                ExecutionContext ec = sm.getExecutionContext();
                final Object newOwner = sm.getObject();

                if (ec.getApiAdapter().isPersistent(newKey))
                {
                    /*
                     * The new key is already persistent.
                     *
                     * "Put" the new key in the map by updating its owner and value
                     * fields to the appropriate values. This is done with the same
                     * methods the PC itself would use if the application code
                     * modified the fields. It should result in no actual database
                     * activity if the fields were already set to the right values.
                     */
                    if (ec != ec.getApiAdapter().getExecutionContext(newKey))
                    {
                        throw new NucleusUserException(LOCALISER.msg("056060"),
                            ec.getApiAdapter().getIdForObject(newKey));
                    }

                    ObjectProvider vsm = ec.findObjectProvider(newKey);

                    // Ensure the current owner field is loaded, and replace with new key
                    if (ownerFieldNumber >= 0)
                    {
                        ec.getApiAdapter().isLoaded(vsm, ownerFieldNumber);
                        Object oldOwner = vsm.provideField(ownerFieldNumber);
                        vsm.replaceFieldMakeDirty(ownerFieldNumber, newOwner);
                        if (ec.getManageRelations())
                        {
                            ec.getRelationshipManager(vsm).relationChange(ownerFieldNumber, oldOwner, newOwner);
                        }
                    }
                    else
                    {
                        updateKeyFk(sm, newKey, newOwner);
                    }

                    // Ensure the current value field is loaded, and replace with new value
                    ec.getApiAdapter().isLoaded(vsm, valueFieldNumber);
                    oldValue = vsm.provideField(valueFieldNumber); // TODO Should we update the local variable ?
                    vsm.replaceFieldMakeDirty(valueFieldNumber, newValue);
                    if (ec.getManageRelations())
                    {
                        ec.getRelationshipManager(vsm).relationChange(valueFieldNumber, oldValue, newValue);
                    }
                }
                else
                {
                    /*
                     * The new key is not yet persistent.
                     *
                     * Update its owner and key fields to the appropriate values and
                     * *then* make it persistent.  Making the changes before DB
                     * insertion avoids an unnecessary UPDATE allows the owner
                     * and/or key fields to be non-nullable.
                     */
                    final Object newValueObj = newValue;
                    ec.persistObjectInternal(newKey, new FieldValues()
                        {
                        public void fetchFields(ObjectProvider vsm)
                        {
                            if (ownerFieldNumber >= 0)
                            {
                                vsm.replaceFieldMakeDirty(ownerFieldNumber, newOwner);
                            }
                            vsm.replaceFieldMakeDirty(valueFieldNumber, newValueObj);

                            JavaTypeMapping externalFKMapping =
                                valueTable.getExternalMapping(ownerMemberMetaData, MappingConsumer.MAPPING_TYPE_EXTERNAL_FK);
                            if (externalFKMapping != null)
                            {
                                // Set the owner in the value object where appropriate
                                vsm.setAssociatedValue(externalFKMapping, sm.getObject());
                            }
                        }
                        public void fetchNonLoadedFields(ObjectProvider sm)
                        {
                        }
                        public FetchPlan getFetchPlanForLoading()
                        {
                            return null;
                        }
                        }, ObjectProvider.PC
                    );

                    /*if (ownerFieldNumber < 0)
                    {
                        // TODO Think about removing this since we set the associated owner here
                        updateKeyFk(sm, newKey, newOwner);
                    }*/
                }
            }
        }

        // TODO Cater for key being PC and having delete-dependent
        if (ownerMemberMetaData.getMap().isDependentValue() && oldValue != null)
        {
            // Delete the old value if it is no longer contained and is dependent
            if (!containsValue(sm, oldValue))
            {
                sm.getExecutionContext().deleteObjectInternal(oldValue);
            }
        }

        return oldValue;
    }

    /**
     * Method to remove an item from the map.
     * @param sm State Manager for the map.
     * @param key Key of the item to remove.
     * @return The value that was removed.
     */
    public Object remove(ObjectProvider sm, Object key)
    {
        Object oldValue = get(sm, key);
        if (keyFieldNumber >= 0)
        {
            if (oldValue != null)
            {
                removeValue(sm, key, oldValue);
                sm.getExecutionContext().flushInternal(false); // Make sure it is in the datastore
            }
        }
        else
        {
            sm.getExecutionContext().deleteObjectInternal(key);
        }

        if (ownerMemberMetaData.getMap().isDependentKey())
        {
            // Delete the key if it is dependent
            sm.getExecutionContext().deleteObjectInternal(key);
        }

        if (ownerMemberMetaData.getMap().isDependentValue())
        {
            // Delete the value if it is dependent
            sm.getExecutionContext().deleteObjectInternal(oldValue);
        }

        return oldValue;
    }

    /**
     * Utility to remove a value from the Map.
     * @param sm State Manager for the map.
     * @param key Key of the object
     * @param oldValue Value to remove
     */
    private void removeValue(ObjectProvider sm, Object key, Object oldValue)
    {
        ExecutionContext ec = sm.getExecutionContext();
       
        // Null out the key and owner fields if they are nullable
        if (keyMapping.isNullable())
        {
            ObjectProvider vsm = ec.findObjectProvider(oldValue);
           
            // Null the key field
            vsm.replaceFieldMakeDirty(keyFieldNumber, null);
            if (ec.getManageRelations())
            {
                ec.getRelationshipManager(vsm).relationChange(keyFieldNumber, key, null);
            }
           
            // Null the owner field
            if (ownerFieldNumber >= 0)
            {
                Object oldOwner = vsm.provideField(ownerFieldNumber);
                vsm.replaceFieldMakeDirty(ownerFieldNumber, null);
                if (ec.getManageRelations())
                {
                    ec.getRelationshipManager(vsm).relationChange(ownerFieldNumber, oldOwner, null);
                }
            }
            else
            {
                updateValueFk(sm, oldValue, null);
            }
        }
        // otherwise just delete the item
        else
        {
            ec.deleteObjectInternal(oldValue);
        }
    }

    /**
     * Method to clear the map of all values.
     * @param sm State Manager for the map.
     */
    public void clear(ObjectProvider sm)
    {
        // TODO Fix this. Should not be retrieving objects only to remove them since they
        // may be cached in the org.datanucleus.sco object. But we need to utilise delete-dependent correctly too
        Iterator iter = keySetStore().iterator(sm);
        while (iter.hasNext())
        {
            remove(sm, iter.next());
        }
    }

    /**
     * Utility to clear the key of a value from the Map.
     * If the key is non nullable, delete the value.
     * @param sm State Manager for the map.
     * @param key Key of the object
     * @param oldValue Value to remove
     */
    public void clearKeyOfValue(ObjectProvider sm, Object key, Object oldValue)
    {
        ExecutionContext ec = sm.getExecutionContext();

        // Null out the key and owner fields if they are nullable
        if (keyMapping.isNullable())
        {
            ObjectProvider vsm = ec.findObjectProvider(oldValue);

            // Check that the value hasn't already been deleted due to being removed from the map
            if (!ec.getApiAdapter().isDeleted(oldValue))
            {
                // Null the key field
                vsm.replaceFieldMakeDirty(keyFieldNumber, null);
                if (ec.getManageRelations())
                {
                    ec.getRelationshipManager(vsm).relationChange(keyFieldNumber, key, null);
                }
            }
        }
        // otherwise just delete the item
        else
        {
            ec.deleteObjectInternal(oldValue);
        }
    }

    /**
     * Accessor for the keys in the Map.
     * @return The keys
     */
    public synchronized SetStore keySetStore()
    {
        return (keySetStore == null) ? newMapKeySetStore() : keySetStore;
    }

    /**
     * Accessor for the values in the Map.
     * @return The values.
     */
    public synchronized SetStore valueSetStore()
    {
        return (valueSetStore == null) ? newMapValueSetStore() : valueSetStore;
    }

    /**
     * Accessor for the map entries in the Map.
     * @return The map entries.
     */
    public synchronized SetStore entrySetStore()
    {
        return (entrySetStore == null) ? newMapEntrySetStore() : entrySetStore;
    }
}
TOP

Related Classes of org.datanucleus.store.mapped.scostore.FKMapStore

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.