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 org.datanucleus.ClassLoaderResolver;
import org.datanucleus.ManagedConnection;
import org.datanucleus.ObjectManager;
import org.datanucleus.StateManager;
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.sco.SCOMtoN;
import org.datanucleus.store.mapped.DatastoreClass;
import org.datanucleus.store.mapped.DatastoreContainerObject;
import org.datanucleus.store.mapped.DatastoreIdentifier;
import org.datanucleus.store.mapped.exceptions.MappedDatastoreException;
import org.datanucleus.store.mapped.expression.LogicSetExpression;
import org.datanucleus.store.mapped.expression.QueryExpression;
import org.datanucleus.store.mapped.expression.ScalarExpression;
import org.datanucleus.store.mapped.expression.UnboundVariable;
import org.datanucleus.store.mapped.mapping.JavaTypeMapping;
import org.datanucleus.store.mapped.query.IncompatibleQueryElementTypeException;
import org.datanucleus.util.ClassUtils;
import org.datanucleus.util.NucleusLogger;
import org.datanucleus.util.StringUtils;

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

/**
* 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);
                elementInfo = new ElementInfo[implNames.length];
                for (int i = 0; i < implNames.length; i++)
                {
                    DatastoreClass table = storeMgr.getDatastoreClass(implNames[i], clr);
                    AbstractClassMetaData cmd =
                        storeMgr.getOMFContext().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.getOMFContext().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(StateManager 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(StateManager sm, Collection elements, int size)
    {
        if (elements == null || elements.size() == 0)
        {
            return false;
        }

        boolean modified = removeAllInternal(sm, elements, size);
        if (ownerMemberMetaData.getCollection().isDependentElement())
        {
            // "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.getObjectManager().deleteObjects(elements.toArray());
        }

        return modified;
    }

    protected abstract boolean removeAllInternal(StateManager 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(StateManager ownerSM, Object element)
    {
        ObjectManager om = ownerSM.getObjectManager();
        StateManager elementSM = om.findStateManager(element);
        AbstractMemberMetaData[] relatedMmds = ownerMemberMetaData.getRelatedMemberMetaData(om.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(StateManager 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(StateManager sm, Object element, int size)
    {
        // Check that the object is valid for writing
        validateElementForWriting(sm, element, null);

        if (relationType == Relation.ONE_TO_MANY_BI && sm.getObjectManager().getOMFContext()
                .getPersistenceConfiguration().getBooleanProperty("datanucleus.manageRelationships"))
        {
            // Managed Relations : make sure we have consistency of relation
            StateManager elementSM = sm.getObjectManager().findStateManager(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.JDO.info(LOCALISER.msg("056037", StringUtils.toJVMIDString(sm.getObject()), ownerMemberMetaData
                            .getFullFieldName(), StringUtils.toJVMIDString(elementSM.getObject())));
                    elementSM.replaceField(relatedMmds[0].getAbsoluteFieldNumber(), sm.getObject(), false);
                }
                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", StringUtils.toJVMIDString(sm.getObject()), 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
            {
                ObjectManager om = sm.getObjectManager();
                ManagedConnection mconn = storeMgr.getConnection(om);
                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(StateManager 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 && sm.getObjectManager().getOMFContext()
                    .getPersistenceConfiguration().getBooleanProperty("datanucleus.manageRelationships"))
            {
                // Managed Relations : make sure we have consistency of relation
                StateManager elementSM = sm.getObjectManager().findStateManager(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.JDO.info(LOCALISER.msg("056037", StringUtils.toJVMIDString(sm.getObject()), ownerMemberMetaData
                                .getFullFieldName(), StringUtils.toJVMIDString(elementSM.getObject())));
                        elementSM.replaceField(relatedMmds[0].getAbsoluteFieldNumber(), sm.getObject(), false);
                    }
                    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", StringUtils.toJVMIDString(sm.getObject()),
                            ownerMemberMetaData.getFullFieldName(), StringUtils.toJVMIDString(elementSM.getObject()), StringUtils
                                    .toJVMIDString(elementOwner)));
                    }
                }
            }
        }

        try
        {
            ObjectManager om = sm.getObjectManager();
            ManagedConnection mconn = storeMgr.getConnection(om);
            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)
                    {
                        mde.printStackTrace();
                        exceptions.add(mde);
                        NucleusLogger.DATASTORE.error(mde);
                    }
                }
            }
            finally
            {
                mconn.release();
            }
        }
        catch (MappedDatastoreException e)
        {
            e.printStackTrace();
            exceptions.add(e);
            NucleusLogger.DATASTORE.error(e);
        }

        if (!exceptions.isEmpty())
        {
            // Throw all exceptions received as the cause of a JPOXDataStoreException 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(StateManager 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(StateManager 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(StateManager sm) throws MappedDatastoreException;

    // ---------------------------- Query Methods ------------------------------

    /**
     * Utility for use in building a query, joining the element table and the owner table.
     * @param stmt The Query Statement
     * @param parentStmt the parent Query Statement. If no parent, "parentStmt" must be equal to "stmt"
     * @param ownerMapping the mapping for the owner
     * @param ownerTblExpr Table Expression for the owner
     * @param filteredElementType The Class Type for the filtered element
     * @param elementExpr The Expression for the element
     * @param elementTableAlias The SQL alias to assign to the element table expression
     * @param setTableAlias The alias for the "Set" table
     * @param existsQuery Whether this is joining for an EXISTS query
     * @return Expression for the join
     */
    public ScalarExpression joinElementsTo(QueryExpression stmt, QueryExpression parentStmt, JavaTypeMapping ownerMapping,
            LogicSetExpression ownerTblExpr, DatastoreIdentifier setTableAlias, Class filteredElementType, ScalarExpression elementExpr,
            DatastoreIdentifier elementTableAlias, boolean existsQuery)
    {
        ClassLoaderResolver clr = stmt.getClassLoaderResolver();
        if (!clr.isAssignableFrom(elementType, filteredElementType) && !clr.isAssignableFrom(filteredElementType, elementType))
        {
            throw new IncompatibleQueryElementTypeException(elementType, filteredElementType.getName());
        }

        if (!existsQuery)
        {
            // Not part of an EXISTS subquery
            LogicSetExpression ownTblExpr = stmt.newTableExpression(containerTable, setTableAlias);
            if (!parentStmt.hasCrossJoin(ownTblExpr) && !stmt.getMainTableExpression().equals(ownTblExpr))
            {
                // Parent doesnt have the collection table, and not the candidate here so cross join to it
                stmt.crossJoin(ownTblExpr, true);
            }

            // Reverse collection contains query so join back to the owner
            ScalarExpression ownerExpr = ownerMapping.newScalarExpression(stmt, ownerTblExpr);
            ScalarExpression ownerSetExpr = this.ownerMapping.newScalarExpression(stmt, stmt.getTableExpression(setTableAlias));
            stmt.andCondition(ownerExpr.eq(ownerSetExpr), true);
        }

        if (storeMgr.getMappedTypeManager().isSupportedMappedType(filteredElementType.getName()))
        {
            // Element = Non-PC(embedded)
            return elementMapping.newScalarExpression(stmt, stmt.getTableExpression(setTableAlias));
        }
        else if (elementsAreEmbedded || elementsAreSerialised)
        {
            // Element = PC(embedded), PC(serialised)
            return elementMapping.newScalarExpression(stmt, stmt.getTableExpression(setTableAlias));
        }
        else
        {
            // Element = PC
            // Join to element table on id column(s) of element
            DatastoreClass elementTable = storeMgr.getDatastoreClass(filteredElementType.getName(), clr);
            DatastoreClass joiningClass = (elementExpr.getLogicSetExpression() == null ? elementTable : (DatastoreClass) elementExpr
                    .getLogicSetExpression().getMainTable());
            JavaTypeMapping elementTableID = joiningClass.getIDMapping();

            // Get expression for the element table, allowing for it existing in this query or the parent query
            LogicSetExpression elmTblExpr = stmt.getTableExpression(elementTableAlias);
            if (elmTblExpr == null)
            {
                // Note : we only use the parentStmt if not an unbound variable. Unbound variables may be registered
                // with the parent but not yet bound so ignore the parent for those
                if (!(elementExpr instanceof UnboundVariable) && parentStmt != stmt)
                {
                    elmTblExpr = parentStmt.getTableExpression(elementTableAlias);
                }
                if (elmTblExpr == null)
                {
                    // Table not present in subquery or parent query so add table
                    elmTblExpr = stmt.newTableExpression(elementTable, elementTableAlias);
                }
            }

            if (!parentStmt.getMainTableExpression().equals(elmTblExpr) && !parentStmt.hasCrossJoin(elmTblExpr))
            {
                // Element table not present in parent query so add cross join to it in the subquery
                stmt.crossJoin(elmTblExpr, true);
            }

            ScalarExpression elmSetExpr = elementMapping.newScalarExpression(stmt, stmt.getTableExpression(setTableAlias));
            if (elementExpr.getLogicSetExpression() != null && !elementTable.equals(elementExpr.getLogicSetExpression().getMainTable()))
            {
                // elementExpr might express a FK in another to the ELEMENT table
                if (existsQuery)
                {
                    // Exists subquery so apply "and" condition direct rather than returning it
                    stmt.andCondition(elmSetExpr.eq(elementExpr), true);
                    return elmSetExpr;
                }
                else
                {
                    // Return the expression to join the element to
                    return elmSetExpr;
                }
            }
            else
            {
                // elementExpr might be a PK of the ELEMENT table
                if (existsQuery)
                {
                    // Exists subquery so apply "and" condition direct rather than returning it
                    ScalarExpression elementIdExpr = elementTableID.newScalarExpression(stmt, elmTblExpr);
                    stmt.andCondition(elmSetExpr.eq(elementIdExpr), true);
                    return elementIdExpr;
                }
                else
                {
                    // Return the expression to join the element to
                    return elmSetExpr;
                }
            }
        }
    }
}
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.