Package org.datanucleus.store.mapped.scostore

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

/**********************************************************************
Copyright (c) 2002 Kelly Grizzle (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:
2002 Mike Martin (TJDO)
2003 Andy Jefferson - added comments
2003 Erik Bengtson - removed unused import
2003 Erik Bengtson - fixed bug [832635] Imcompatiblities in casting
                    inherited classes in query
2003 Andy Jefferson - coding standards
2004 Andy Jefferson - updated retrieval of ColumnLists
2004 Marco Schulze  - replaced catch(NotPersistenceCapableException ...) by
                     advance-check via TypeManager.isSupportedType(...)
2004 Andy Jefferson - moved statements to AbstractSetStore
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.HashSet;
import java.util.Iterator;
import java.util.List;

import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.exceptions.NucleusDataStoreException;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.FieldRole;
import org.datanucleus.metadata.MetaDataUtils;
import org.datanucleus.metadata.Relation;
import org.datanucleus.store.ExecutionContext;
import org.datanucleus.store.ObjectProvider;
import org.datanucleus.store.connection.ManagedConnection;
import org.datanucleus.store.mapped.DatastoreClass;
import org.datanucleus.store.mapped.DatastoreContainerObject;
import org.datanucleus.store.mapped.exceptions.MappedDatastoreException;
import org.datanucleus.store.mapped.mapping.JavaTypeMapping;
import org.datanucleus.store.types.sco.SCOMtoN;
import org.datanucleus.util.ClassUtils;
import org.datanucleus.util.NucleusLogger;
import org.datanucleus.util.StringUtils;

/**
* Representation of a JoinTable Set as part of a relationship. This class is
* used where you have a 1-N and the tables are joined via a link table.
* That is one table is the owner, and it has a link table to another table,
* with the link table having 2 columns - the ids of the 2 tables.
* This is in contrast to FKSetStore which represents 1-N relationships
* without using a link table (using an id in the other table).
* <p>
* For sets of primitive types (e.g Date,String etc), the JoinSetStore is used,
* but the 'link' table contains the id of the owner and the field(s)
* representing the primitive type.
*/
public abstract class JoinSetStore extends AbstractSetStore
{
    /**
     * Constructor for the relationship representation.
     * @param mmd Metadata for the owner member
     * @param joinTable The table for the link
     * @param clr The ClassLoaderResolver
     */
    public JoinSetStore(AbstractMemberMetaData mmd, DatastoreContainerObject joinTable,
            ClassLoaderResolver clr, JavaTypeMapping ownerMapping, JavaTypeMapping elementMapping,
            JavaTypeMapping orderMapping, JavaTypeMapping relationDiscriminatorMapping,
            String relationDiscriminatorValue, boolean isEmbeddedElement, boolean isSerialisedElement,
            AbstractSetStoreSpecialization specialization)
    {
        super(joinTable.getStoreManager(), clr, specialization);

        // A Set really needs a SetTable, but we need to cope with the situation
        // where a user declares a field as Collection but is instantiated as a List or a Set
        // so we just accept CollectionTable and rely on it being adequate
        this.containerTable = joinTable;
        setOwner(mmd, clr);

        this.ownerMapping = ownerMapping;
        this.elementMapping = elementMapping;
        this.orderMapping = orderMapping;
        this.relationDiscriminatorMapping = relationDiscriminatorMapping;
        this.relationDiscriminatorValue = relationDiscriminatorValue;

        this.elementType = mmd.getCollection().getElementType();
        this.elementsAreEmbedded = isEmbeddedElement;
        this.elementsAreSerialised = isSerialisedElement;

        if (elementsAreSerialised)
        {
            elementInfo = null;
        }
        else
        {
            Class element_class = clr.classForName(elementType);
            if (ClassUtils.isReferenceType(element_class))
            {
                // Collection of reference types (interfaces/Objects)
                String[] implNames = MetaDataUtils.getInstance().getImplementationNamesForReferenceField(
                    ownerMemberMetaData, FieldRole.ROLE_COLLECTION_ELEMENT, clr, storeMgr.getMetaDataManager());
                elementInfo = new ElementInfo[implNames.length];
                for (int i = 0; i < implNames.length; i++)
                {
                    DatastoreClass table = storeMgr.getDatastoreClass(implNames[i], clr);
                    AbstractClassMetaData cmd =
                        storeMgr.getNucleusContext().getMetaDataManager().getMetaDataForClass(implNames[i], clr);
                    elementInfo[i] = new ElementInfo(cmd, table);
                }
            }
            else
            {
                // Set<PC>, Set<Non-PC>
                // Generate the information for the possible elements
                emd = storeMgr.getNucleusContext().getMetaDataManager().getMetaDataForClass(element_class, clr);
                if (emd != null && !elementsAreEmbedded)
                {
                    elementInfo = getElementInformationForClass();
                }
                else
                {
                    elementInfo = null;
                }
            }
        }
    }

    // -------------------- Overridden SetStore methods ------------------------

    /**
     * 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;
        }

        if (ownerMemberMetaData.getCollection().isSerializedElement() || ownerMemberMetaData.getCollection().isEmbeddedElement())
        {
            // Serialized/Embedded elements so just clear and add again
            clear(sm);
            addAll(sm, coll, 0);
            return;
        }

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

        if (existing.size() != coll.size())
        {
            // Add any elements that aren't already present
            Iterator iter = coll.iterator();
            while (iter.hasNext())
            {
                Object elem = iter.next();
                if (!existing.contains(elem))
                {
                    add(sm, elem, 0);
                }
            }
        }
    }

    /**
     * Remove all elements from a collection from the association owner vs elements.
     * @param sm State Manager for the container
     * @param elements Collection of elements to remove
     * @return Whether the database was updated
     */
    public boolean removeAll(ObjectProvider sm, Collection elements, int size)
    {
        if (elements == null || elements.size() == 0)
        {
            return false;
        }

        boolean modified = removeAllInternal(sm, elements, size);
        boolean dependent = ownerMemberMetaData.getCollection().isDependentElement();
        if (ownerMemberMetaData.isCascadeRemoveOrphans())
        {
            dependent = true;
        }
        if (dependent)
        {
            // "delete-dependent" : delete elements if the collection is marked as dependent
            // TODO What if the collection contains elements that are not in the Set ? should not delete them
            sm.getExecutionContext().deleteObjects(elements.toArray());
        }

        return modified;
    }

    protected abstract boolean removeAllInternal(ObjectProvider sm, Collection elements, int size);

    /**
     * Convenience method to check if an element already refers to the owner in an M-N relation.
     * @param ownerSM State Manager of the owner
     * @param element The element
     * @return Whether the element contains the owner
     */
    private boolean elementAlreadyContainsOwnerInMtoN(ObjectProvider ownerSM, Object element)
    {
        ExecutionContext ec = ownerSM.getExecutionContext();
        ObjectProvider elementSM = ec.findObjectProvider(element);
        AbstractMemberMetaData[] relatedMmds = ownerMemberMetaData.getRelatedMemberMetaData(ec.getClassLoaderResolver());
        Object elementSCO = elementSM.provideField(relatedMmds[0].getAbsoluteFieldNumber());
        if (elementSCO instanceof SCOMtoN)
        {
            // The field is already a SCO wrapper so just query it
            if (contains(ownerSM, element))
            {
                NucleusLogger.DATASTORE.info(LOCALISER.msg("056040", ownerMemberMetaData.getFullFieldName(), element));
                return true;
            }
        }
        else
        {
            // The element is not a SCO wrapper so query the datastore directly
            // TODO Fix this. It is inefficient to go off to the datastore to check whether a record exists.
            // This should be changed in line with TCK test AllRelationships since that tries to load up
            // many relationships, and CollectionMapping.postInsert calls addAll with these hence we don't
            // have SCO's to use contains() on
            if (locate(ownerSM, element))
            {
                NucleusLogger.DATASTORE.info(LOCALISER.msg("056040", ownerMemberMetaData.getFullFieldName(), element));
                return true;
            }
        }
        return false;
    }

    /**
     * Method to check for the existence in the datastore of an owner-element relation.
     * @param sm State Manager for the owner
     * @param element The element
     * @return Whether the relation exists in the datastore
     */
    public abstract boolean locate(ObjectProvider sm, Object element);

    /**
     * Adds one element to the association owner vs elements.
     * @param sm State Manager for the container.
     * @param element Element to add
     * @return Whether it was successful
     */
    public boolean add(ObjectProvider sm, Object element, int size)
    {
        // Check that the object is valid for writing
        validateElementForWriting(sm, element, null);

        if (relationType == Relation.ONE_TO_MANY_BI)
        {
            // TODO This is ManagedRelations - move into RelationshipManager
            // Managed Relations : make sure we have consistency of relation
            ObjectProvider elementSM = sm.getExecutionContext().findObjectProvider(element);
            if (elementSM != null)
            {
                AbstractMemberMetaData[] relatedMmds = ownerMemberMetaData.getRelatedMemberMetaData(clr);
                Object elementOwner = elementSM.provideField(relatedMmds[0].getAbsoluteFieldNumber());
                if (elementOwner == null)
                {
                    // No owner, so correct it
                    NucleusLogger.PERSISTENCE.info(LOCALISER.msg("056037", sm.toPrintableID(), ownerMemberMetaData
                            .getFullFieldName(), StringUtils.toJVMIDString(elementSM.getObject())));
                    elementSM.replaceField(relatedMmds[0].getAbsoluteFieldNumber(), sm.getObject());
                }
                else if (elementOwner != sm.getObject() && sm.getReferencedPC() == null)
                {
                    // Owner of the element is neither this container nor being attached
                    // Inconsistent owner, so throw exception
                    throw new NucleusUserException(LOCALISER.msg("056038", sm.toPrintableID(), ownerMemberMetaData
                            .getFullFieldName(), StringUtils.toJVMIDString(elementSM.getObject()), StringUtils.toJVMIDString(elementOwner)));
                }
            }
        }

        boolean modified = false;

        boolean toBeInserted = true;
        if (relationType == Relation.MANY_TO_MANY_BI)
        {
            // This is an M-N relation so we need to check if the element already has us in its collection
            // to avoid duplicate join table entries
            toBeInserted = !elementAlreadyContainsOwnerInMtoN(sm, element);
        }

        if (toBeInserted)
        {
            try
            {
                ExecutionContext ec = sm.getExecutionContext();
                ManagedConnection mconn = storeMgr.getConnection(ec);
                try
                {
                    // Add a row to the join table
                    int orderID = -1;
                    if (orderMapping != null)
                    {
                        orderID = getNextIDForOrderColumn(sm);
                    }
                    int[] returnCode = internalAdd(sm, element, mconn, false, orderID, true);
                    if (returnCode[0] > 0)
                    {
                        modified = true;
                    }
                }
                finally
                {
                    mconn.release();
                }
            }
            catch (MappedDatastoreException e)
            {
                NucleusLogger.DATASTORE.error(e);
                String msg = LOCALISER.msg("056009", e.getMessage());
                NucleusLogger.DATASTORE.error(msg);
                throw new NucleusDataStoreException(msg, e);
            }
        }

        return modified;
    }

    /**
     * Adds all elements from a collection to the association container.
     * @param sm State Manager for the container.
     * @param elements Collection of elements to add
     * @return Whether it was successful
     */
    public boolean addAll(ObjectProvider sm, Collection elements, int size)
    {
        if (elements == null || elements.size() == 0)
        {
            return false;
        }

        boolean modified = false;
        List exceptions = new ArrayList();
        boolean batched = (elements.size() > 1);

        // Validate all elements for writing
        Iterator iter = elements.iterator();
        while (iter.hasNext())
        {
            Object element = iter.next();
            validateElementForWriting(sm, element, null);

            if (relationType == Relation.ONE_TO_MANY_BI)
            {
                // TODO This is ManagedRelations - move into RelationshipManager
                // Managed Relations : make sure we have consistency of relation
                ObjectProvider elementSM = sm.getExecutionContext().findObjectProvider(element);
                if (elementSM != null)
                {
                    AbstractMemberMetaData[] relatedMmds = ownerMemberMetaData.getRelatedMemberMetaData(clr);
                    Object elementOwner = elementSM.provideField(relatedMmds[0].getAbsoluteFieldNumber());
                    if (elementOwner == null)
                    {
                        // No owner, so correct it
                        NucleusLogger.PERSISTENCE.info(LOCALISER.msg("056037", sm.toPrintableID(), ownerMemberMetaData
                                .getFullFieldName(), StringUtils.toJVMIDString(elementSM.getObject())));
                        elementSM.replaceField(relatedMmds[0].getAbsoluteFieldNumber(), sm.getObject());
                    }
                    else if (elementOwner != sm.getObject() && sm.getReferencedPC() == null)
                    {
                        // Owner of the element is neither this container nor its referenced object
                        // Inconsistent owner, so throw exception
                        throw new NucleusUserException(LOCALISER.msg("056038", sm.toPrintableID(),
                            ownerMemberMetaData.getFullFieldName(), StringUtils.toJVMIDString(elementSM.getObject()), StringUtils
                                    .toJVMIDString(elementOwner)));
                    }
                }
            }
        }

        try
        {
            ExecutionContext ec = sm.getExecutionContext();
            ManagedConnection mconn = storeMgr.getConnection(ec);
            try
            {
                preGetNextIDForOrderColumn(mconn);

                int nextOrderID = 0;
                if (orderMapping != null)
                {
                    // Get the order id for the first item
                    nextOrderID = getNextIDForOrderColumn(sm);
                }

                // Loop through all elements to be added
                iter = elements.iterator();
                Object element = null;
                while (iter.hasNext())
                {
                    element = iter.next();

                    try
                    {
                        // Add the row to the join table
                        int[] rc = internalAdd(sm, element, mconn, batched, nextOrderID, !batched || (batched && !iter.hasNext()));
                        if (rc != null)
                        {
                            for (int i = 0; i < rc.length; i++)
                            {
                                if (rc[i] > 0)
                                {
                                    // At least one record was inserted
                                    modified = true;
                                }
                            }
                        }
                        nextOrderID++;
                    }
                    catch (MappedDatastoreException mde)
                    {
                        exceptions.add(mde);
                        NucleusLogger.DATASTORE.error("Exception thrown", mde);
                    }
                }
            }
            finally
            {
                mconn.release();
            }
        }
        catch (MappedDatastoreException e)
        {
            exceptions.add(e);
            NucleusLogger.DATASTORE.error("Exception thrown", e);
        }

        if (!exceptions.isEmpty())
        {
            // Throw all exceptions received as the cause of a NucleusDataStoreException so the user can see which
            // record(s) didn't persist
            String msg = LOCALISER.msg("056009", ((Exception) exceptions.get(0)).getMessage());
            NucleusLogger.DATASTORE.error(msg);
            throw new NucleusDataStoreException(msg, (Throwable[]) exceptions.toArray(new Throwable[exceptions.size()]), sm.getObject());
        }

        return modified;
    }

    protected abstract void preGetNextIDForOrderColumn(ManagedConnection mconn) throws MappedDatastoreException;

    /**
     * Method to add a row to the join table. Used by add() and addAll() to add a row to the join table.
     * @param sm StateManager for the owner of the collection
     * @param element The element to add the relation to
     * @param conn Connection to use
     * @param batched Whether we are batching
     * @param orderId The order id to use for this element relation (if ordering is used)
     * @param executeNow Whether to execute the statement now (or leave til later)
     * @return The return code(s) for any records added. There may be multiple if using batched
     * @throws MappedDatastoreException Thrown if an error occurs
     */
    private int[] internalAdd(ObjectProvider sm, Object element, ManagedConnection conn, boolean batched, int orderId, boolean executeNow)
        throws MappedDatastoreException
    {
        boolean toBeInserted = true;
        if (relationType == Relation.MANY_TO_MANY_BI)
        {
            // This is an M-N relation so we need to check if the element already has us
            // in its collection to avoid duplicate join table entries
            // TODO Find a better way of doing this
            toBeInserted = !elementAlreadyContainsOwnerInMtoN(sm, element);
        }

        if (toBeInserted)
        {
            return doInternalAdd(sm, element, conn, batched, orderId, executeNow);
        }
        return null;
    }

    protected abstract int[] doInternalAdd(ObjectProvider sm, Object element, ManagedConnection conn,
            boolean batched, int orderId, boolean executeNow) throws MappedDatastoreException;

    /**
     * Accessor for the next id when elements primary key can't be part of the primary key by datastore limitations like
     * BLOB types can't be primary keys. Also for where the user wants to allow duplicates.
     * @param sm The State Manager
     * @return the next id value
     */
    protected abstract int getNextIDForOrderColumn(ObjectProvider sm) throws MappedDatastoreException;
}
TOP

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

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.