Package org.datanucleus.store.mapped.scostore

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

/**********************************************************************
Copyright (c) 2004 Erik Bengtson 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 Andy Jefferson - updated retrieval of ColumnList's
2003 Andy Jefferson - updated to support inherited elements
2003 Andy Jefferson - fixed sizeStmt to ignore records with no index value
2004 Andy Jefferson - merged IteratorStmt and GetStmt into GetRangeStmt.
2004 Andy Jefferson - moved majority statement construction to AbstractListStore.
2004 Andy Jefferson - fixed use of shift statement
2004 Andy Jefferson - added support for inverse 1-N unidirectional
2005 Andy Jefferson - added dependent-element when removed from collection
    ...
**********************************************************************/
package org.datanucleus.store.mapped.scostore;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.ClassNameConstants;
import org.datanucleus.FetchPlan;
import org.datanucleus.exceptions.NucleusDataStoreException;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.CollectionMetaData;
import org.datanucleus.metadata.Relation;
import org.datanucleus.store.ExecutionContext;
import org.datanucleus.store.FieldValues;
import org.datanucleus.store.ObjectProvider;
import org.datanucleus.store.connection.ManagedConnection;
import org.datanucleus.store.mapped.DatastoreClass;
import org.datanucleus.store.mapped.MappedStoreManager;
import org.datanucleus.store.mapped.exceptions.MappedDatastoreException;
import org.datanucleus.store.mapped.mapping.JavaTypeMapping;
import org.datanucleus.store.mapped.mapping.MappingConsumer;
import org.datanucleus.util.ClassUtils;
import org.datanucleus.util.NucleusLogger;
import org.datanucleus.util.StringUtils;

/**
* Representation of a List using a ForeignKey to form a relationship. This class is
* used where you have a 1-N and the tables are not joined via a join table.
* There is an owner table and an element table, and the element table has a
* column being the id of the owner table. This is in contrast to
* NormalListStore which represents 1-N relationships using a join table.
* There are 2 possible uses here
* <UL>
* <LI><B>bidirectional</B> - where the owner has a List of elements, and
* the element has an owner. In this case the element class will have an owner field
* and index which can be updated directly</LI>
* <LI><B>unidirectional</B> - where the owner has a List of elements, but
* the element knows nothing about the owner. In this case the element class has no
* owner field. In this case the owner/index columns in the element table have to be
* updated directly.</LI>
* </UL>
*/
public abstract class FKListStore extends AbstractListStore
{
    /** Field number of owner link in element class. */
    private final int ownerFieldNumber;

    /**
     * Constructor.
     * @param fmd Field MetaData for the field that this represents
     * @param storeMgr The Store Manager in use
     * @param clr The ClassLoaderResolver
     * @param specialization The datastore-specific specialization
     */
    public FKListStore(AbstractMemberMetaData fmd, MappedStoreManager storeMgr, ClassLoaderResolver clr,
            FKListStoreSpecialization specialization)
    {
        super(storeMgr, clr, specialization);

        setOwner(fmd, clr);
        CollectionMetaData colmd = fmd.getCollection();
        if (colmd == null)
        {
            throw new NucleusUserException(LOCALISER.msg("056001", fmd.getFullFieldName()));
        }

        // Load the element class
        elementType = colmd.getElementType();
        Class element_class = clr.classForName(elementType);

        if (ClassUtils.isReferenceType(element_class))
        {
            // Take the metadata for the first implementation of the reference type
            emd = storeMgr.getNucleusContext().getMetaDataManager().getMetaDataForImplementationOfReference(element_class,null,clr);
            if (emd != null)
            {
                // Pretend we have a relationship with this one implementation
                elementType = emd.getFullClassName();
            }
        }
        else
        {
            // Check that the element class has MetaData
            emd = storeMgr.getNucleusContext().getMetaDataManager().getMetaDataForClass(element_class, clr);
        }
        if (emd == null)
        {
            throw new NucleusUserException(LOCALISER.msg("056003", element_class.getName(), fmd.getFullFieldName()));
        }

        elementInfo = getElementInformationForClass();
        if (elementInfo != null && elementInfo.length > 1)
        {
            throw new NucleusUserException(LOCALISER.msg("056031", ownerMemberMetaData.getFullFieldName()));
        }
        else if (elementInfo == null || elementInfo.length == 0)
        {
            throw new NucleusUserException(LOCALISER.msg("056075", ownerMemberMetaData.getFullFieldName(), elementType));
        }

        elementMapping = elementInfo[0].getDatastoreClass().getIdMapping(); // Just use the first element type as the guide for the element mapping
        elementsAreEmbedded = false; // Can't embed element when using FK relation
        elementsAreSerialised = false; // Can't serialise element when using FK relation

        // Get the field in the element table (if any)
        String mappedByFieldName = fmd.getMappedBy();
        if (mappedByFieldName != null)
        {
            // 1-N FK bidirectional
            // The element class has a field for the owner.
            AbstractMemberMetaData eofmd = emd.getMetaDataForMember(mappedByFieldName);
            if (eofmd == null)
            {
                throw new NucleusUserException(LOCALISER.msg("056024", fmd.getFullFieldName(),
                    mappedByFieldName, element_class.getName()));
            }

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

            String ownerFieldName = eofmd.getName();
            ownerFieldNumber = emd.getAbsolutePositionOfMember(ownerFieldName);
            ownerMapping = elementInfo[0].getDatastoreClass().getMemberMapping(eofmd);
            if (ownerMapping == null)
            {
                throw new NucleusUserException(LOCALISER.msg("056029",
                    fmd.getAbstractClassMetaData().getFullClassName(), fmd.getName(), elementType, ownerFieldName));
            }
            if (isEmbeddedMapping(ownerMapping))
            {
                throw new NucleusUserException(LOCALISER.msg("056026",
                    ownerFieldName, elementType, eofmd.getTypeName(), fmd.getClassName()));
            }
        }
        else
        {
            // 1-N FK unidirectional
            // The element class knows nothing about the owner (but its table has external mappings)
            ownerFieldNumber = -1;
            ownerMapping = elementInfo[0].getDatastoreClass().getExternalMapping(fmd, MappingConsumer.MAPPING_TYPE_EXTERNAL_FK);
            if (ownerMapping == null)
            {
                throw new NucleusUserException(LOCALISER.msg("056030",
                    fmd.getAbstractClassMetaData().getFullClassName(), fmd.getName(), elementType));
            }
        }

        orderMapping = elementInfo[0].getDatastoreClass().getExternalMapping(fmd, MappingConsumer.MAPPING_TYPE_EXTERNAL_INDEX);
        if (fmd.getOrderMetaData() != null && !fmd.getOrderMetaData().isIndexedList())
        {
            indexedList = false;
        }
        if (orderMapping == null && indexedList)
        {
            // "Indexed List" but no order mapping present!
            throw new NucleusUserException(LOCALISER.msg("056041",
                fmd.getAbstractClassMetaData().getFullClassName(), fmd.getName(), elementType));
        }

        relationDiscriminatorMapping =
            elementInfo[0].getDatastoreClass().getExternalMapping(fmd, MappingConsumer.MAPPING_TYPE_EXTERNAL_FK_DISCRIM);
        if (relationDiscriminatorMapping != null)
        {
            relationDiscriminatorValue = fmd.getValueForExtension("relation-discriminator-value");
            if (relationDiscriminatorValue == null)
            {
                // No value defined so just use the field name
                relationDiscriminatorValue = fmd.getFullFieldName();
            }
        }

        // TODO Cater for multiple element tables
        containerTable = elementInfo[0].getDatastoreClass();
        if (fmd.getMappedBy() != null && ownerMapping.getDatastoreContainer() != containerTable)
        {
            // Element and owner don't have consistent tables so use the one with the mapping
            // e.g collection is of subclass, yet superclass has the link back to the owner
            containerTable = ownerMapping.getDatastoreContainer();
        }
    }

    private FKListStoreSpecialization getSpecialization()
    {
        return (FKListStoreSpecialization) specialization;
    }

    /**
     * Method to set an object in the List at a position.
     * @param sm The state manager
     * @param index The item index
     * @param element What to set it to.
     * @param allowDependentField Whether to enable dependent-field deletes during the set
     * @return The value before setting.
     */
    public Object set(ObjectProvider sm, int index, Object element, boolean allowDependentField)
    {
        validateElementForWriting(sm, element, -1); // Last argument means dont set the position on any INSERT
        Object o = get(sm, index);
        return getSpecialization().set(sm, index, element, allowDependentField, this, o);
    }

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

        ExecutionContext ec = sm.getExecutionContext();
        return getSpecialization().updateElementFk(sm, element, owner, index, ec, this);
    }

    /**
     * Method to update the collection to be the supplied collection of elements.
     * @param sm StateManager of the object
     * @param coll The collection to use
     */
    public void update(ObjectProvider sm, Collection coll)
    {
        if (coll == null || coll.isEmpty())
        {
            clear(sm);
            return;
        }

        // Find existing elements, and remove any that are no longer present
        Collection existing = new ArrayList();
        Iterator elemIter = iterator(sm);
        while (elemIter.hasNext())
        {
            Object elem = elemIter.next();
            if (!coll.contains(elem))
            {
                remove(sm, elem, -1, true);
            }
            else
            {
                existing.add(elem);
            }
        }

        if (existing.equals(coll))
        {
            // Existing (after any removals) is same as the specified so job done
            return;
        }

        // TODO Improve this - need to allow for list element position changes etc
        clear(sm);
        addAll(sm, coll, 0);
    }

    /**
     * Internal method for adding an item to the List.
     * @param sm The state manager
     * @param startAt The start position
     * @param atEnd Whether to add at the end
     * @param c The Collection of elements to add.
     * @param size Current size of list (if known). -1 if not known
     * @return Whether it was successful
     */
    protected boolean internalAdd(ObjectProvider sm, int startAt, boolean atEnd, Collection c, int size)
    {
        if (c == null || c.size() == 0)
        {
            return true;
        }

        // Check what we have persistent already
        int currentListSize = 0;
        if (size < 0)
        {
            // Get the current size from the datastore
            currentListSize = size(sm);
        }
        else
        {
            currentListSize = size;
        }

        boolean shiftingElements = true;
        if (atEnd || startAt == currentListSize)
        {
            shiftingElements = false;
            startAt = currentListSize; // Not shifting so we insert from the end
        }

        boolean elementsNeedPositioning = false;
        int position = startAt;
        Iterator elementIter = c.iterator();
        while (elementIter.hasNext())
        {
            // Persist any non-persistent objects optionally at their final list position (persistence-by-reachability)
            if (shiftingElements)
            {
                // We have to shift things so dont bother with positioning
                position = -1;
            }

            boolean inserted = validateElementForWriting(sm, elementIter.next(), position);
            if (!inserted || shiftingElements)
            {
                // This element wasnt positioned in the validate so we need to set the positions later
                elementsNeedPositioning = true;
            }
            if (!shiftingElements)
            {
                position++;
            }
        }

        if (shiftingElements)
        {
            // We need to shift existing elements before positioning the new ones
            try
            {
                // Calculate the amount we need to shift any existing elements by
                // This is used where inserting between existing elements and have to shift down all elements after the start point
                int shift = c.size();

                ExecutionContext ec = sm.getExecutionContext();
                ManagedConnection mconn = storeMgr.getConnection(ec);
                try
                {
                    // shift up existing elements after start position by "shift"
                    for (int i=currentListSize-1; i>=startAt; i--)
                    {
                        // Shift the index of this row by "shift"
                        getSpecialization().internalShift(sm, mconn, false, i, shift, true, this
                        );
                    }
                }
                finally
                {
                    mconn.release();
                }
            }
            catch (MappedDatastoreException e)
            {
                // An error was encountered during the shift process so abort here
                throw new NucleusDataStoreException(LOCALISER.msg("056009", e.getMessage()), e.getCause());
            }
        }

        if (shiftingElements || elementsNeedPositioning)
        {
            // Some elements have been shifted so the new elements need positioning now, or we already had some
            // of the new elements persistent and so they need their positions setting now
            elementIter = c.iterator();
            while (elementIter.hasNext())
            {
                Object element = elementIter.next();
                updateElementFk(sm, element, sm.getObject(), startAt);
                startAt++;
            }
        }

        return true;
    }


    /**
     * Convenience method to remove the specified element from the List.
     * @param sm StateManager of the owner
     * @param element The element
     * @return Whether the List was modified
     */
    protected boolean internalRemove(ObjectProvider sm, Object element, int size)
    {
        if (indexedList)
        {
            // Indexed List
            // The element can be at one position only (no duplicates allowed in FK list)
            int index = indexOf(sm, element);
            if (index == -1)
            {
                return false;
            }
            removeAt(sm, index, size);
        }
        else
        {
            // Ordered List - no index so null the FK (if nullable) or delete the element
            if (ownerMapping.isNullable())
            {
                // Nullify the FK
                ExecutionContext ec = sm.getExecutionContext();
                ObjectProvider elementSM = ec.findObjectProvider(element);
                if (relationType == Relation.ONE_TO_MANY_BI)
                {
                    // TODO This is ManagedRelations - move into RelationshipManager
                    elementSM.replaceFieldMakeDirty(ownerMemberMetaData.getRelatedMemberMetaData(clr)[0].getAbsoluteFieldNumber(),
                        null);
                    if (sm.getExecutionContext().isFlushing())
                    {
                        elementSM.flush();
                    }
                }
                // TODO Shouldn't we always null the FK in the datastore, not just when unidirectional?
                else
                {
                    updateElementFk(sm, element, null, -1);
                }
            }
            else
            {
                // Delete the element
                // TODO Log this
                sm.getExecutionContext().deleteObjectInternal(element);
            }
        }

        return true;
    }

    /**
     * Convenience method to manage the removal of an element from the collection, performing
     * any necessary "managed relationship" updates when the field is bidirectional.
     * @param ownerSM StateManager for the collection owner
     * @param element The element
     */
    protected void manageRemovalOfElement(ObjectProvider ownerSM, Object element)
    {
        // TODO Complete this
        /*ObjectManager om = ownerSM.getObjectManager();
        if (relationType == Relation.ONE_TO_MANY_BI && om.getManageRelations())
        {
            // Managed Relations : 1-N bidirectional so null the owner on the elements
            if (!om.getApiAdapter().isDeleted(element))
            {
                StateManager elementSM = om.findStateManager(element);
                if (elementSM != null)
                {
                    // Null the owner of the element
                    if (NucleusLogger.PERSISTENCE.isDebugEnabled())
                    {
                        NucleusLogger.PERSISTENCE.debug(
                            LOCALISER.msg("055010",
                                ownerSM.toPrintableID(),
                                ownerMemberMetaData.getFullFieldName(),
                                StringUtils.toJVMIDString(element)));
                    }

                    elementSM.replaceField(getFieldNumberInElementForBidirectional(elementSM), null, true);
                    if (om.isFlushing())
                    {
                        // Make sure this change gets flushed
                        elementSM.flush();
                    }
                }
            }
        }*/
    }

    /**
     * Internal method to remove an object at a location in the List.
     * Differs from the JoinTable List in that it nulls out the owner FK.
     * @param sm The state manager.
     * @param index The location
     * @param size Current size of list (if known). -1 if not known
     */
    protected void removeAt(ObjectProvider sm, int index, int size)
    {
        if (!indexedList)
        {
            throw new NucleusUserException("Cannot remove an element from a particular position with an ordered list since no indexes exist");
        }

        boolean nullify = false;
        if (ownerMapping.isNullable() && orderMapping != null && orderMapping.isNullable())
        {
            NucleusLogger.DATASTORE.debug(LOCALISER.msg("056043"));
            nullify = true;
        }
        else
        {
            NucleusLogger.DATASTORE.debug(LOCALISER.msg("056042"));
        }
        getSpecialization().removeAt(sm, index, size, nullify, this);
    }

    /**
     * Method to clear the List.
     * This is called by the List.clear() method, or when the container object is being deleted
     * and the elements are to be removed (maybe for dependent field), or also when updating a Collection
     * and removing all existing prior to adding all new.
     * @param ownerSM The state manager
     */
    public void clear(ObjectProvider ownerSM)
    {
        boolean deleteElements = false;
        ExecutionContext ec = ownerSM.getExecutionContext();
        boolean dependent = ownerMemberMetaData.getCollection().isDependentElement();
        if (ownerMemberMetaData.isCascadeRemoveOrphans())
        {
            dependent = true;
        }
        if (dependent)
        {
            // Elements are dependent and can't exist on their own, so delete them all
            NucleusLogger.DATASTORE.debug(LOCALISER.msg("056034"));
            deleteElements = true;
        }
        else
        {
            if (ownerMapping.isNullable() && orderMapping == null)
            {
                // Field is not dependent, and nullable so we null the FK
                NucleusLogger.DATASTORE.debug(LOCALISER.msg("056036"));
                deleteElements = false;
            }
            else if (ownerMapping.isNullable() && orderMapping != null && orderMapping.isNullable())
            {
                // Field is not dependent, and nullable so we null the FK
                NucleusLogger.DATASTORE.debug(LOCALISER.msg("056036"));
                deleteElements = false;
            }
            else
            {
                // Field is not dependent, and not nullable so we just delete the elements
                NucleusLogger.DATASTORE.debug(LOCALISER.msg("056035"));
                deleteElements = true;
            }
        }

        if (deleteElements)
        {
            // Find elements present in the datastore and delete them one-by-one
            Iterator elementsIter = iterator(ownerSM);
            if (elementsIter != null)
            {
                while (elementsIter.hasNext())
                {
                    Object element = elementsIter.next();
                    if (ec.getApiAdapter().isPersistable(element) && ec.getApiAdapter().isDeleted(element))
                    {
                        // Element is waiting to be deleted so flush it (it has the FK)
                        ObjectProvider objSM = ec.findObjectProvider(element);
                        objSM.flush();
                    }
                    else
                    {
                        // Element not yet marked for deletion so go through the normal process
                        ec.deleteObjectInternal(element);
                    }
                }
            }
        }
        else
        {
            getSpecialization().clearWithoutDelete(ec, ownerSM, this);
        }
    }

    /**
     * Method to validate that an element is valid for writing to the datastore.
     * TODO Minimise differences to super.validateElementForWriting()
     * @param sm StateManager for the List
     * @param element The element to validate
     * @param index The position that the element is being stored at in the list
     * @return Whether the element was inserted
     */
    protected boolean validateElementForWriting(final ObjectProvider sm, Object element, final int index)
    {
        final Object newOwner = sm.getObject();

        // Check if element is ok for use in the datastore, specifying any external mappings that may be required
        boolean inserted = super.validateElementForWriting(sm, element, new FieldValues()
        {
            public void fetchFields(ObjectProvider esm)
            {
                // Find the (element) table storing the FK back to the owner
                boolean isPersistentInterface = storeMgr.getNucleusContext().getMetaDataManager().isPersistentInterface(elementType);
                DatastoreClass elementTable = null;
                if (isPersistentInterface)
                {
                    elementTable = storeMgr.getDatastoreClass(
                        storeMgr.getNucleusContext().getMetaDataManager().getImplementationNameForPersistentInterface(elementType), clr);
                }
                else
                {
                    elementTable = storeMgr.getDatastoreClass(elementType, clr);
                }
                if (elementTable == null)
                {
                    // "subclass-table", persisted into table of other class
                    AbstractClassMetaData[] managingCmds = storeMgr.getClassesManagingTableForClass(emd, clr);
                    if (managingCmds != null && managingCmds.length > 0)
                    {
                        // Find which of these subclasses is appropriate for this element
                        for (int i=0;i<managingCmds.length;i++)
                        {
                            Class tblCls = clr.classForName(managingCmds[i].getFullClassName());
                            if (tblCls.isAssignableFrom(esm.getObject().getClass()))
                            {
                                elementTable = storeMgr.getDatastoreClass(managingCmds[i].getFullClassName(), clr);
                                break;
                            }
                        }
                    }
                }

                if (elementTable != null)
                {
                    JavaTypeMapping externalFKMapping = elementTable.getExternalMapping(ownerMemberMetaData, MappingConsumer.MAPPING_TYPE_EXTERNAL_FK);
                    if (externalFKMapping != null)
                    {
                        // The element has an external FK mapping so set the value it needs to use in the INSERT
                        esm.setAssociatedValue(externalFKMapping, sm.getObject());
                    }
                    if (relationDiscriminatorMapping != null)
                    {
                        esm.setAssociatedValue(relationDiscriminatorMapping, relationDiscriminatorValue);
                    }
                    if (orderMapping != null && index >= 0)
                    {
                        if (ownerMemberMetaData.getOrderMetaData() != null && ownerMemberMetaData.getOrderMetaData().getMappedBy() != null)
                        {
                            // Order is stored in a field in the element so update it
                            // We support mapped-by fields of types int/long/Integer/Long currently
                            Object indexValue = null;
                            if (orderMapping.getMemberMetaData().getTypeName().equals(ClassNameConstants.JAVA_LANG_LONG) ||
                                    orderMapping.getMemberMetaData().getTypeName().equals(ClassNameConstants.LONG))
                            {
                                indexValue = Long.valueOf(index);
                            }
                            else
                            {
                                indexValue = Integer.valueOf(index);
                            }
                            esm.replaceFieldMakeDirty(orderMapping.getMemberMetaData().getAbsoluteFieldNumber(), indexValue);
                        }
                        else
                        {
                            // Order is stored in a surrogate column so save its vaue for the element to use later
                            esm.setAssociatedValue(orderMapping, Integer.valueOf(index));
                        }
                    }
                }
                if (ownerFieldNumber >= 0)
                {
                    // TODO This is ManagedRelations - move into RelationshipManager
                    // Managed Relations : 1-N bidir, so make sure owner is correct at persist
                    Object currentOwner = esm.provideField(ownerFieldNumber);
                    if (currentOwner == null)
                    {
                        // No owner, so correct it
                        NucleusLogger.PERSISTENCE.info(LOCALISER.msg("056037",
                            sm.toPrintableID(), ownerMemberMetaData.getFullFieldName(),
                            StringUtils.toJVMIDString(esm.getObject())));
                        esm.replaceFieldMakeDirty(ownerFieldNumber, newOwner);
                    }
                    else if (currentOwner != newOwner && sm.getReferencedPC() == null)
                    {
                        // Owner of the element is neither this container nor is it being attached
                        // Inconsistent owner, so throw exception
                        throw new NucleusUserException(LOCALISER.msg("056038",
                            sm.toPrintableID(), ownerMemberMetaData.getFullFieldName(),
                            StringUtils.toJVMIDString(esm.getObject()),
                            StringUtils.toJVMIDString(currentOwner)));
                    }
                }
            }
            public void fetchNonLoadedFields(ObjectProvider sm)
            {
            }
            public FetchPlan getFetchPlanForLoading()
            {
                return null;
            }
        });

        return inserted;
    }
}
TOP

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

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.