Package org.jpox.store.rdbms.scostore

Source Code of org.jpox.store.rdbms.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.jpox.store.rdbms.scostore;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import org.jpox.ClassLoaderResolver;
import org.jpox.ManagedConnection;
import org.jpox.ObjectManager;
import org.jpox.StateManager;
import org.jpox.exceptions.JPOXDataStoreException;
import org.jpox.exceptions.JPOXUserException;
import org.jpox.metadata.AbstractClassMetaData;
import org.jpox.metadata.AbstractMemberMetaData;
import org.jpox.metadata.CollectionMetaData;
import org.jpox.metadata.DiscriminatorStrategy;
import org.jpox.metadata.FieldRole;
import org.jpox.metadata.MetaDataUtils;
import org.jpox.metadata.Relation;
import org.jpox.metadata.OrderMetaData.FieldOrder;
import org.jpox.store.mapped.DatastoreClass;
import org.jpox.store.mapped.DatastoreIdentifier;
import org.jpox.store.mapped.IdentifierFactory;
import org.jpox.store.mapped.expression.LogicSetExpression;
import org.jpox.store.mapped.expression.QueryExpression;
import org.jpox.store.mapped.expression.ScalarExpression;
import org.jpox.store.mapped.expression.UnboundVariable;
import org.jpox.store.mapped.mapping.JavaTypeMapping;
import org.jpox.store.mapped.mapping.ReferenceMapping;
import org.jpox.store.query.IncompatibleQueryElementTypeException;
import org.jpox.store.rdbms.SQLController;
import org.jpox.store.rdbms.mapping.RDBMSMapping;
import org.jpox.store.rdbms.query.DiscriminatorIteratorStatement;
import org.jpox.store.rdbms.query.UnionIteratorStatement;
import org.jpox.store.rdbms.table.CollectionTable;
import org.jpox.util.JPOXLogger;
import org.jpox.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.
*
* @version $Revision: 1.51 $
**/
public class JoinListStore extends AbstractListStore
{
    /**
     * Constructor.
     * @param joinTable The join table
     * @param clr ClassLoader resolver
     */
    public JoinListStore(AbstractMemberMetaData fmd, CollectionTable joinTable, ClassLoaderResolver clr)
    {
        super(joinTable.getStoreManager(), clr);

        // 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;
        setOwnerMemberMetaData(fmd);
        listName = "list";

        ownerMapping = joinTable.getOwnerMapping();
        elementMapping = joinTable.getElementMapping();

        orderMapping = joinTable.getOrderMapping();
        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 JPOXUserException(LOCALISER.msg("056044",
                ownerMemberMetaData.getFullFieldName(), joinTable.toString()));
        }
        relationDiscriminatorMapping = joinTable.getRelationDiscriminatorMapping();
        relationDiscriminatorValue = joinTable.getRelationDiscriminatorValue();

        elementType = fmd.getCollection().getElementType();
        elementsAreEmbedded = joinTable.isEmbeddedElement();
        elementsAreSerialised = joinTable.isSerialisedElement();

        if (elementsAreSerialised)
        {
            elementInfo = null;
        }
        else
        {
            Class element_class = clr.classForName(elementType);
            if (storeMgr.getOMFContext().getTypeManager().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;
                }
            }
        }
    }

    /**
     * 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 (ownerMemberMetaData.getRelationType(clr) == Relation.ONE_TO_MANY_BI &&
                sm.getObjectManager().getOMFContext().getPersistenceConfiguration().getBooleanProperty("org.jpox.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
                        JPOXLogger.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 JPOXUserException(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;
        }

        String addStmt = getAddStmt();
        try
        {
            ObjectManager om = sm.getObjectManager();
            ManagedConnection mconn = storeMgr.getConnection(om);
            SQLController sqlControl = storeMgr.getSQLController();
            try
            {
                // Shift any existing elements so that we can insert the new element(s) at their position
                if (!atEnd && start != currentListSize)
                {
                    boolean batched = currentListSize-start > 0;

                    for (int i = currentListSize - 1; i >= start; i--)
                    {
                        // Shift the index for this row by "shift"
                        internalShift(sm, mconn, batched, i, shift, (i == start));
                    }
                }
                else
                {
                    start = currentListSize;
                }

                // Insert the elements at their required location
                int jdbcPosition = 1;
                boolean batched = (c.size() > 1);

                iter = c.iterator();
                while (iter.hasNext())
                {
                    Object element = iter.next();
                    PreparedStatement ps = sqlControl.getStatementForUpdate(mconn, addStmt, batched);
                    try
                    {
                        jdbcPosition = 1;
                        jdbcPosition = populateOwnerInStatement(sm, om, ps, jdbcPosition);
                        jdbcPosition = populateElementInStatement(om, ps, element, jdbcPosition);
                        if (orderMapping != null)
                        {
                            jdbcPosition = populateOrderInStatement(om, ps, start, jdbcPosition);
                        }
                        if (relationDiscriminatorMapping != null)
                        {
                            jdbcPosition = populateRelationDiscriminatorInStatement(om, ps, jdbcPosition);
                        }
                        start++;

                        // Execute the statement
                        sqlControl.executeStatementUpdate(mconn, addStmt, ps, !iter.hasNext());
                    }
                    finally
                    {
                        sqlControl.closeStatement(mconn, ps);
                    }
                }
            }
            finally
            {
                mconn.release();
            }
        }
        catch (SQLException e)
        {
            throw new JPOXDataStoreException(LOCALISER.msg("056009", addStmt), e);
        }
        return true;
    }

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

        String setStmt = getSetStmt();
        try
        {
            ObjectManager om = sm.getObjectManager();
            ManagedConnection mconn = storeMgr.getConnection(om);
            SQLController sqlControl = storeMgr.getSQLController();
            try
            {
                PreparedStatement ps = sqlControl.getStatementForUpdate(mconn, setStmt, false);
                try
                {
                    int jdbcPosition = 1;
                    jdbcPosition = populateElementInStatement(om, ps, element, jdbcPosition);
                    jdbcPosition = populateOwnerInStatement(sm, om, ps, jdbcPosition);
                    jdbcPosition = populateOrderInStatement(om, ps, index, jdbcPosition);
                    if (relationDiscriminatorMapping != null)
                    {
                        jdbcPosition = populateRelationDiscriminatorInStatement(om, ps, jdbcPosition);
                    }

                    sqlControl.executeStatementUpdate(mconn, setStmt, ps, true);
                }
                finally
                {
                    sqlControl.closeStatement(mconn, ps);
                }
            }
            finally
            {
                mconn.release();
            }
        }
        catch (SQLException e)
        {
            throw new JPOXDataStoreException(LOCALISER.msg("056015",setStmt),e);
        }

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

    /**
     * Generates the statement for setting an item.
     * <PRE>
     * UPDATE LISTTABLE SET [ELEMENTCOL = ?]
     * [EMBEDDEDFIELD1=?, EMBEDDEDFIELD2=?, ...]
     * WHERE OWNERCOL = ?
     * AND INDEXCOL = ?
     * [AND DISTINGUISHER=?]
     * </PRE>
     * @return The Statement for setting an item
     */
    protected String getSetStmt()
    {
        if (setStmt == null)
        {
            StringBuffer stmt = new StringBuffer();
            stmt.append("UPDATE ");
            stmt.append(containerTable.toString());
            stmt.append(" SET ");
            for (int i=0; i<elementMapping.getNumberOfDatastoreFields(); i++)
            {
                if (i > 0)
                {
                    stmt.append(",");
                }
                stmt.append(elementMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
                stmt.append(" = ");
                stmt.append(((RDBMSMapping)elementMapping.getDataStoreMapping(i)).getUpdateInputParameter());
            }

            stmt.append(" WHERE ");
            for (int i=0; i<ownerMapping.getNumberOfDatastoreFields(); i++)
            {
                if (i > 0)
                {
                    stmt.append(" AND ");
                }
                stmt.append(ownerMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
                stmt.append(" = ");
                stmt.append(((RDBMSMapping)ownerMapping.getDataStoreMapping(i)).getUpdateInputParameter());
            }

            for (int i=0; i<orderMapping.getNumberOfDatastoreFields(); i++)
            {
                stmt.append(" AND ");
                stmt.append(orderMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
                stmt.append(" = ");
                stmt.append(((RDBMSMapping)orderMapping.getDataStoreMapping(i)).getUpdateInputParameter());
            }
            if (relationDiscriminatorMapping != null)
            {
                for (int i=0; i<relationDiscriminatorMapping.getNumberOfDatastoreFields(); i++)
                {
                    stmt.append(" AND ");
                    stmt.append(relationDiscriminatorMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
                    stmt.append(" = ");
                    stmt.append(((RDBMSMapping)relationDiscriminatorMapping.getDataStoreMapping(i)).getUpdateInputParameter());
                }
            }
            setStmt = stmt.toString();
        }

        return setStmt;
    }

    /**
     * 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 = internalRemove(ownerSM, mconn, false, element, true);
                if (rcs != null)
                {
                    if (rcs[0] > 0)
                    {
                        modified = true;
                    }
                }
            }
            catch (SQLException sqe)
            {
                String msg = LOCALISER.msg("056012", getRemoveStmt());
                JPOXLogger.DATASTORE.error(msg, sqe);
                throw new JPOXDataStoreException(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;
        }

        boolean modified = 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);

        // Remove the specified elements from the join table
        String removeAllStmt = getRemoveAllStmt(elements);
        SQLController sqlControl = storeMgr.getSQLController();
        try
        {
            ObjectManager om = sm.getObjectManager();
            ManagedConnection mconn = storeMgr.getConnection(om);
            try
            {
                PreparedStatement ps = sqlControl.getStatementForUpdate(mconn, removeAllStmt, false);
                try
                {
                    int jdbcPosition = 1;
                    Iterator iter = elements.iterator();
                    while (iter.hasNext())
                    {
                        Object element = iter.next();
                        jdbcPosition = populateOwnerInStatement(sm, om, ps, jdbcPosition);
                        jdbcPosition = populateElementInStatement(om, ps, element, jdbcPosition);
                        if (relationDiscriminatorMapping != null)
                        {
                            jdbcPosition = populateRelationDiscriminatorInStatement(om, ps, jdbcPosition);
                        }
                    }

                    int[] number = sqlControl.executeStatementUpdate(mconn, removeAllStmt, ps, true);
                    if (number[0] > 0)
                    {
                        modified = true;
                    }
                }
                finally
                {
                    sqlControl.closeStatement(mconn, ps);
                }
            }
            finally
            {
                mconn.release();
            }
        }
        catch (SQLException e)
        {
            JPOXLogger.DATASTORE.error(e);
            throw new JPOXDataStoreException(LOCALISER.msg("056012", removeAllStmt), e);
        }

        try
        {
            // Shift the remaining indices to remove the holes in ordering
            boolean batched = allowsBatching();

            ObjectManager om = sm.getObjectManager();
            ManagedConnection mconn = storeMgr.getConnection(om);
            try
            {
                for (int i=0;i<currentListSize;i++)
                {
                    // Find the number of deleted indexes above this index
                    int shift = 0;
                    boolean removed = false;
                    for (int j=0;j<indices.length;j++)
                    {
                        if (indices[j] == i)
                        {
                            removed = true;
                            break;
                        }
                        if (indices[j] < i)
                        {
                            shift++;
                        }
                    }
                    if (!removed && shift > 0)
                    {
                        internalShift(sm, mconn, batched, i, -1*shift, (i == currentListSize-1));
                    }
                }
            }
            finally
            {
                mconn.release();
            }
        }
        catch (SQLException e)
        {
            JPOXLogger.DATASTORE.error(e);
            throw new JPOXDataStoreException(LOCALISER.msg("056012", removeAllStmt), e);
        }

        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 List ? should not delete them
            sm.getObjectManager().deleteObjects(elements.toArray());
        }

        return modified;
    }

    /**
     * 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)
    {
        internalRemoveAt(sm, index, getRemoveAtStmt(), size);
    }

    /**
     * Generate statement for removing a collection of items from the List.
     * <PRE>
     * DELETE FROM LISTTABLE
     * WHERE (OWNERCOL=? AND ELEMENTCOL=?) OR
     *      (OWNERCOL=? AND ELEMENTCOL=?) OR
     *      (OWNERCOL=? AND ELEMENTCOL=?)
     * </PRE>
     * @param elements Collection of elements to remove
     * @return Statement for deleting items from the List.
     **/
    protected String getRemoveAllStmt(Collection elements)
    {
        if (elements == null || elements.size() == 0)
        {
            return null;
        }

        StringBuffer stmt = new StringBuffer();
        stmt.append("DELETE FROM ");
        stmt.append(containerTable.toString());
        stmt.append(" WHERE ");
        Iterator elementsIter = elements.iterator();
        boolean first = true;
        while (elementsIter.hasNext())
        {
            elementsIter.next(); // Not really used at the moment except to size the Collection
            if (first)
            {
                stmt.append("(");
            }
            else
            {
                stmt.append(" OR (");
            }
            for (int i=0; i<ownerMapping.getNumberOfDatastoreFields(); i++)
            {
                if (i > 0)
                {
                    stmt.append(" AND ");
                }
                stmt.append(ownerMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
                stmt.append(" = ");
                stmt.append(((RDBMSMapping)ownerMapping.getDataStoreMapping(i)).getUpdateInputParameter());
            }
            for (int i=0; i<elementMapping.getNumberOfDatastoreFields(); i++)
            {
                stmt.append(" AND ");
                stmt.append(elementMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
                if (elementsAreSerialised)
                {
                    // Can't directly compare serialised element fields
                    stmt.append(" LIKE ");
                }
                else
                {
                    stmt.append(" = ");
                }
                stmt.append(((RDBMSMapping)elementMapping.getDataStoreMapping(i)).getUpdateInputParameter());
            }

            if (relationDiscriminatorMapping != null)
            {
                for (int i=0; i<relationDiscriminatorMapping.getNumberOfDatastoreFields(); i++)
                {
                    stmt.append(" AND ");
                    stmt.append(relationDiscriminatorMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
                    stmt.append(" = ");
                    stmt.append(((RDBMSMapping)relationDiscriminatorMapping.getDataStoreMapping(i)).getUpdateInputParameter());
                }
            }

            stmt.append(")");
            first = false;
        }

        return stmt.toString();
    }

    /**
     * Accessor for the iterator statement to retrieve element(s) in a range
     * from the List. This has 5 modes
     * <UL>
     * <LI>start and end have the same value and &ge;0 then get that
     * element</LI>
     * <LI>start and end have values &ge;0 and are different then get a
     * range</LI>
     * <LI>start is &ge;0 and end is -1 the get all from start (inclusive).</LI>
     * <LI>start is -1 and end is &ge;0 the get all up to end(exclusive).</LI>
     * <LI>start and end values are both -1 then get all elements</LI>
     * </UL>
     * @param ownerSM The StateManager
     * @param start_index The start position in the List.
     * @param end_index The end position in the List.
     * @return The QueryStatement.
     */
    protected QueryExpression getIteratorStatement(StateManager ownerSM, int start_index, int end_index)
    {
        QueryExpression stmt = null;
        final ClassLoaderResolver clr = ownerSM.getObjectManager().getClassLoaderResolver();
        if (elementsAreEmbedded || elementsAreSerialised)
        {
            // Element = embedded/serialised
            // Just select the join table since we're going to return the embedded/serialised columns from it
            stmt = dba.newQueryStatement(containerTable, clr);
            stmt.select(elementMapping);
        }
        else if (elementMapping instanceof ReferenceMapping)
        {
            // Element = Reference type (interface/Object)
            // Just select the join table since we're going to return the implementation id columns only
            stmt = dba.newQueryStatement(containerTable, clr);
        }
        else if (elementInfo != null)
        {
            // Element = PC
            // Join to the element table(s)
            for (int i=0;i<elementInfo.length;i++)
            {
                // TODO This will only work if all element types have a discriminator
                final int elementNo = i;
                final Class elementCls = clr.classForName(elementInfo[elementNo].getClassName());
                QueryExpression elementStmt = null;
                if (elementInfo[elementNo].getDiscriminatorStrategy() != null &&
                    elementInfo[elementNo].getDiscriminatorStrategy() != DiscriminatorStrategy.NONE)
                {
                    // The element uses a discriminator so just use that in the SELECT
                    if (storeMgr.getOMFContext().getTypeManager().isReferenceType(clr.classForName(ownerMemberMetaData.getCollection().getElementType())))
                    {               
                        // Take the metadata for the first implementation of the reference type
                        String[] clsNames = storeMgr.getOMFContext().getMetaDataManager().getClassesImplementingInterface(ownerMemberMetaData.getCollection().getElementType(), clr);
                        Class[] cls = new Class[clsNames.length];
                        for (int j=0; j<clsNames.length; j++)
                        {
                            cls[j] = clr.classForName(clsNames[j]);
                        }
                        elementStmt = new DiscriminatorIteratorStatement(clr, cls,
                            true, this.storeMgr, true, allowsNull,
                            containerTable, elementMapping, elmIdentifier).getQueryStatement(null);
                    }
                    else
                    {
                        elementStmt = new DiscriminatorIteratorStatement(clr, new Class[] {elementCls},
                            true, this.storeMgr, true, allowsNull,
                            containerTable, elementMapping, elmIdentifier).getQueryStatement(null);
                    }
                    iterateUsingDiscriminator = true;
                }
                else
                {
                    // The element has potential subclasses and no discrim so use UNIONs
                    elementStmt = new UnionIteratorStatement(clr, elementCls, true, this.storeMgr,
                        elementCls, elementMapping, containerTable, false, Boolean.TRUE,
                        true, allowsNull).getQueryStatement(null);
                }
               
                if (stmt == null)
                {
                    stmt = elementStmt;
                }
                else
                {
                    stmt.union(elementStmt);
                }
            }
        }
        else
        {
            // Should be impossible
            throw new JPOXUserException("Attempt to get iterator for List when insufficient information is available to perform the operation.");
        }

        // Apply condition on join-table owner field to filter by owner
        ScalarExpression ownerExpr = ownerMapping.newScalarExpression(stmt, stmt.getMainTableExpression());
        ScalarExpression ownerVal = ownerMapping.newLiteral(stmt, ownerSM.getObject());
        stmt.andCondition(ownerExpr.eq(ownerVal), true);

        // Apply condition on distinguisher field to filter by distinguisher (when present)
        if (relationDiscriminatorMapping != null)
        {
            ScalarExpression distinguisherExpr = relationDiscriminatorMapping.newScalarExpression(stmt, stmt.getMainTableExpression());
            ScalarExpression distinguisherVal = relationDiscriminatorMapping.newLiteral(stmt, relationDiscriminatorValue);
            stmt.andCondition(distinguisherExpr.eq(distinguisherVal), true);
        }

        // Add restriction(s) on index values (where required)
        if (indexedList)
        {
            // "Indexed List"
            boolean returning_range=false;
            if (start_index == -1 && end_index == -1)
            {
                // Add no more constraints. We want all elements.
                returning_range = true;
            }
            else if (start_index == end_index && start_index >= 0)
            {
                // Apply condition on join-table owner field to filter by index
                ScalarExpression indexExpr = orderMapping.newScalarExpression(stmt, stmt.getMainTableExpression());
                ScalarExpression indexVal = orderMapping.newLiteral(stmt, new Integer(start_index));
                stmt.andCondition(indexExpr.eq(indexVal), true);
            }
            else
            {
                // Apply filter to retrieve indexes in the range
                // a). >= start_index
                if (start_index >= 0)
                {
                    // Apply condition on join-table index field to filter by index
                    ScalarExpression indexExpr = orderMapping.newScalarExpression(stmt, stmt.getMainTableExpression());
                    ScalarExpression indexVal = orderMapping.newLiteral(stmt, new Integer(start_index));
                    stmt.andCondition(indexExpr.gteq(indexVal), true);
                }
                // b). < end_index
                if (end_index >= 0)
                {
                    // Apply condition on join-table index field to filter by index
                    ScalarExpression indexExpr = orderMapping.newScalarExpression(stmt, stmt.getMainTableExpression());
                    ScalarExpression indexVal = orderMapping.newLiteral(stmt, new Integer(end_index));
                    stmt.andCondition(indexExpr.lt(indexVal), true);
                }
                returning_range = true;
            }

            // If returning a range, add ordering by index
            if (returning_range)
            {
                ScalarExpression exprIndex[] = new ScalarExpression[orderMapping.getNumberOfDatastoreFields()];
                boolean descendingOrder[] = new boolean[orderMapping.getNumberOfDatastoreFields()];
                exprIndex = orderMapping.newScalarExpression(stmt, stmt.getMainTableExpression()).getExpressionList().toArray();
                stmt.setOrdering(exprIndex, descendingOrder);
            }
        }
        else
        {
            // Apply ordering defined by <order-by>
            DatastoreClass elementTbl = elementInfo[0].getDatastoreClass();
            FieldOrder[] orderComponents = ownerMemberMetaData.getOrderMetaData().getFieldOrders();
            for (int i=0;i<orderComponents.length;i++)
            {
                String fieldName = orderComponents[i].getFieldName();
                JavaTypeMapping fieldMapping = elementTbl.getFieldMapping(elementInfo[0].getAbstractClassMetaData().getMetaDataForMember(fieldName));
                ScalarExpression exprIndex[] = new ScalarExpression[fieldMapping.getNumberOfDatastoreFields()];
                boolean descendingOrder[] = new boolean[fieldMapping.getNumberOfDatastoreFields()];
                for (int j=0;j<descendingOrder.length;j++)
                {
                    descendingOrder[j] = !orderComponents[i].isForward();
                }
                exprIndex = fieldMapping.newScalarExpression(stmt, stmt.getTableExpression(elmIdentifier)).getExpressionList().toArray();
                stmt.setOrdering(exprIndex, descendingOrder);
            }
        }
        return stmt;
    }

    // ---------------------------- Query Methods ------------------------------
    /**
     * Utility to return a new QueryStatement.
     * @param sm The state manager
     * @param candidateClass The base class
     * @param candidateAlias Alias for the candidate
     * @return The QueryStatement
     **/
    public QueryExpression newQueryStatement(StateManager sm, String candidateClass, DatastoreIdentifier candidateAlias)
    {
        // TODO Use candidate alias instead of "LIST"
        // TODO Parametrise out "LIST_ELEMENTS"
        if (elementsAreEmbedded || elementsAreSerialised)
        {
            throw new JPOXUserException(LOCALISER.msg("056021"));
        }
        if (!clr.isAssignableFrom(elementType, candidateClass))
        {
            throw new IncompatibleQueryElementTypeException(elementType, candidateClass);
        }

        ClassLoaderResolver clr = sm.getObjectManager().getClassLoaderResolver();
        DatastoreIdentifier listTableAlias = storeMgr.getIdentifierFactory().newIdentifier(
            IdentifierFactory.TABLE, listName);

        // QueryStatement for the join table
        QueryExpression stmt = dba.newQueryStatement(containerTable, listTableAlias, clr);

        // Join to the owner
        ScalarExpression ownerExpr = ownerMapping.newScalarExpression(stmt, stmt.getTableExpression(listTableAlias));
        ScalarExpression ownerVal = ownerMapping.newLiteral(stmt, sm.getObject());
        stmt.andCondition(ownerExpr.eq(ownerVal), true);

        if (storeMgr.getOMFContext().getTypeManager().isSupportedType(candidateClass))
        {
            // Non-PC(embedded) - select the join table element
            stmt.select(listTableAlias, elementMapping);
        }
        else
        {
            // PC
            DatastoreClass candidateTable = storeMgr.getDatastoreClass(candidateClass, clr);

            // Add the element table to the query, called "LIST_ELEMENTS"
            DatastoreIdentifier elementTblAlias = storeMgr.getIdentifierFactory().newIdentifier(
                IdentifierFactory.TABLE, "LIST_ELEMENTS");
            LogicSetExpression elementTblExpr = stmt.newTableExpression(candidateTable, elementTblAlias);

            // Inner Join from the ID of the element to the element mapping of the join table
            JavaTypeMapping elementTableID = candidateTable.getIDMapping();
            ScalarExpression elmListExpr = elementMapping.newScalarExpression(stmt,
                stmt.getTableExpression(listTableAlias));
            ScalarExpression elmExpr = elementTableID.newScalarExpression(stmt, elementTblExpr);
            stmt.innerJoin(elmExpr, elmListExpr, elementTblExpr, true, true);

            // Select the ID of the element table
            stmt.selectScalarExpression(elementTableID.newScalarExpression(stmt, elementTblExpr));
        }

        return stmt;
    }

    /**
     * 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.getOMFContext().getTypeManager().isSupportedType(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.jpox.store.rdbms.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.