Package org.datanucleus.store.mapped.scostore

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

/**********************************************************************
Copyright (c) 2003 David Jencks 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 import
2003 Erik Bengtson  - fixed bug [832635] Imcompatiblities in casting
                     inherited classes in query
2003 Andy Jefferson - coding standards
2003 Andy Jefferson - added getSizeStmt
2003 Andy Jefferson - updated retrieval of ColumnList's
2004 Andy Jefferson - updated to support inherited objects
2004 Marco Schulze  - replaced catch(NotPersistenceCapableException ...)
                     by advance-check via TypeManager.isSupportedType(...)
2004 Andy Jefferson - merged IteratorStmt and GetStmt into GetRangeStmt.
2004 Andy Jefferson - added removeAll
2004 Andy Jefferson - moved all statement construction to AbstractListStore
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.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.CollectionMetaData;
import org.datanucleus.metadata.FieldRole;
import org.datanucleus.metadata.MetaDataUtils;
import org.datanucleus.metadata.Relation;
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;

/**
* Representation of the backing store for a join-table List.
* Uses a Join table, so we use 3 tables - owner table, join table and element table.
*/
public abstract class JoinListStore extends AbstractListStore
{
    /**
     * Constructor.
     * @param clr ClassLoader resolver
     */
    public JoinListStore(AbstractMemberMetaData fmd, ClassLoaderResolver clr, DatastoreContainerObject joinTable,
            JavaTypeMapping ownerMapping, JavaTypeMapping elementMapping, JavaTypeMapping orderMapping,
            JavaTypeMapping relationDiscriminatorMapping, String relationDiscriminatorValue,
            boolean elementsAreEmbedded, boolean elementsAreSerialised,
            JoinListStoreSpecialization specialization)
    {
        super(joinTable.getStoreManager(), clr, specialization);

        // A List really needs a ListTable, 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(fmd, clr);

        this.ownerMapping = ownerMapping;
        this.elementMapping = elementMapping;

        this.orderMapping = orderMapping;
        if (ownerMemberMetaData.getOrderMetaData() != null && !ownerMemberMetaData.getOrderMetaData().isIndexedList())
        {
            indexedList = false;
        }
        if (orderMapping == null && indexedList)
        {
            // If the user declares a field as java.util.Collection JPOX will use SetTable to generate the join table
            // If they then instantiate it as a List type it will come through here, so we need to ensure the order column exists
            throw new NucleusUserException(LOCALISER.msg("056044",
                ownerMemberMetaData.getFullFieldName(), joinTable.toString()));
        }
        this.relationDiscriminatorMapping = relationDiscriminatorMapping;
        this.relationDiscriminatorValue = relationDiscriminatorValue;

        elementType = fmd.getCollection().getElementType();
        this.elementsAreEmbedded = elementsAreEmbedded;
        this.elementsAreSerialised = elementsAreSerialised;

        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
            {
                // Collection of PC or non-PC
                // Generate the information for the possible elements
                emd = storeMgr.getOMFContext().getMetaDataManager().getMetaDataForClass(element_class, clr);
                if (emd != null)
                {
                    if (!elementsAreEmbedded)
                    {
                        elementInfo = getElementInformationForClass();
                        /*if (elementInfo != null && elementInfo.length > 1)
                        {
                            throw new JPOXUserException(LOCALISER.msg("056031",
                                ownerFieldMetaData.getFullFieldName()));
                        }*/
                    }
                    else
                    {
                        elementInfo = null;
                    }
                }
                else
                {
                    elementInfo = null;
                }
            }
        }
    }

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

    /**
     * Internal method to add element(s) to the List.
     * Performs the add in 2 steps.
     * <ol>
     * <li>Shift all existing elements into their new positions so we can insert.</li>
     * <li>Insert all new elements directly at their desired positions>/li>
     * </ol>
     * Both steps can be batched (separately).
     * @param sm The state manager
     * @param start The start location (if required)
     * @param atEnd Whether to add the element at the end
     * @param c The collection of objects to add.
     * @param size Current size of list if known. -1 if not known
     * @return Whether it was successful
     */
    protected boolean internalAdd(StateManager sm, int start, boolean atEnd, Collection c, int size)
    {
        if (c == null || c.size() == 0)
        {
            return true;
        }

        // 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();

        // check all elements are valid for persisting and exist (persistence-by-reachability)
        Iterator iter = c.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"))
            {
                StateManager elementSM = sm.getObjectManager().findStateManager(element);
                if (elementSM != null)
                {
                    AbstractMemberMetaData[] relatedMmds = ownerMemberMetaData.getRelatedMemberMetaData(clr);
                    // TODO Cater for more than 1 related field
                    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)));
                    }
                }
            }

        }

        // 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;
        }
        return getSpecialization().internalAdd(sm, this, start, atEnd, c, currentListSize, shift);
    }

    /**
     * Method to set an object in the List.
     * @param sm The state manager
     * @param index The item index
     * @param element What to set it to.
     * @param allowDependentField Whether to allow dependent field deletes
     * @return The value before setting.
     */
    public Object set(StateManager sm, int index, Object element, boolean allowDependentField)
    {
        validateElementForWriting(sm, element, null);
        Object o = get(sm, index);

        getSpecialization().set(element, index, sm, this);

        CollectionMetaData collmd = ownerMemberMetaData.getCollection();
        if (collmd.isDependentElement() && !collmd.isEmbeddedElement() && allowDependentField)
        {
            if (o != null && !contains(sm, o))
            {
                // Delete the element if it is dependent and doesnt have a duplicate entry in the list
                sm.getObjectManager().deleteObjectInternal(o);
            }
        }

        return o;
    }

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

    /**
     * Convenience method to remove the specified element from the List.
     * @param element The element
     * @param ownerSM StateManager of the owner
     * @param size Current size of list if known. -1 if not known
     * @return Whether the List was modified
     */
    protected boolean internalRemove(StateManager ownerSM, Object element, int size)
    {
        boolean modified = false;
        if (indexedList)
        {
            // Indexed List, so retrieve the index of the element and remove the object
            // Get the indices of the elements to remove in reverse order (highest first)
            // This is done because the element could be duplicated in the list.
            Collection elements = new ArrayList();
            elements.add(element);
            int[] indices = getIndicesOf(ownerSM, elements);

            // Remove each element in turn, doing the shifting of indexes each time
            // TODO : Change this to remove all in one go and then shift once
            for (int i=0;i<indices.length;i++)
            {
                removeAt(ownerSM, indices[i], size);
                modified = true;
            }
        }
        else
        {
            // Ordered List - just remove the list item since no indexing present
            ObjectManager om = ownerSM.getObjectManager();
            ManagedConnection mconn = storeMgr.getConnection(om);
            try
            {
                int[] rcs = getSpecialization().internalRemove(ownerSM, mconn, false, element, true, this);
                if (rcs != null)
                {
                    if (rcs[0] > 0)
                    {
                        modified = true;
                    }
                }
            }
            catch (MappedDatastoreException sqe)
            {
                String msg = LOCALISER.msg("056012", sqe.getMessage());
                NucleusLogger.DATASTORE.error(msg, sqe.getCause());
                throw new NucleusDataStoreException(msg, sqe, ownerSM.getObject());
            }
            finally
            {
                mconn.release();
            }
        }

        return modified;
    }

    /**
     * Remove all elements from a collection from the association owner vs
     * elements. Performs the removal in 3 steps. The first gets the indices
     * that will be removed (and the highest index present). The second step
     * removes these elements from the list. The third step updates the indices
     * of the remaining indices to fill the holes created.
     * @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;
        }

        // Get the current size of the list (and hence maximum index size)
        int currentListSize = size(sm);

        // Get the indices of the elements we are going to remove (highest first)
        int[] indices = getIndicesOf(sm, elements);

        return getSpecialization().removeAll(currentListSize, indices, elements, sm, this);
    }

    /**
     * Method to remove an element from the specified position
     * @param sm The State Manager for the list
     * @param index The index of the element
     * @param size Current size of list (if known). -1 if not known
     */
    protected void removeAt(StateManager 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");
        }

        getSpecialization().removeAt(sm, index, size, this);
    }

    // ---------------------------- Query Methods ------------------------------
    /**
     * Method used in queries when contains() has been invoked to join the collection table to the element 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 Expression for the element
     * @param elementTableAlias The SQL alias to assign to the element table expression
     * @param listTableAlias The alias for the "List" 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 listTableAlias,
            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, listTableAlias);
            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(listTableAlias));
            stmt.andCondition(ownerExpr.eq(ownerSetExpr),true);
        }

        if (storeMgr.getMappedTypeManager().isSupportedMappedType(filteredElementType.getName()))
        {
            // Element = Non-PC(embedded)
            return elementMapping.newScalarExpression(stmt, stmt.getTableExpression(listTableAlias));
        }
        else if (elementsAreEmbedded || elementsAreSerialised)
        {
            // Element = PC(embedded), PC(serialised)
            return elementMapping.newScalarExpression(stmt, stmt.getTableExpression(listTableAlias));
        }
        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 elmListExpr = elementMapping.newScalarExpression(stmt,
                stmt.getTableExpression(listTableAlias));
            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(elmListExpr.eq(elementExpr), true);
                    return elmListExpr;
                }
                else
                {
                    // Return the expression to join the element to
                    return elmListExpr;
                }
            }
            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(elmListExpr.eq(elementIdExpr), true);
                    return elementIdExpr;
                }
                else
                {
                    // Return the expression to join the element to
                    return elmListExpr;
                }
            }
        }
    }
}
TOP

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

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.