Package org.jpox.store.rdbms.scostore

Source Code of org.jpox.store.rdbms.scostore.FKListStore

/**********************************************************************
Copyright (c) 2004 Erik Bengtson and others. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Contributors:
2003 Andy Jefferson - updated retrieval of ColumnList's
2003 Andy Jefferson - updated to support inherited elements
2003 Andy Jefferson - fixed sizeStmt to ignore records with no index value
2004 Andy Jefferson - merged IteratorStmt and GetStmt into GetRangeStmt.
2004 Andy Jefferson - moved majority statement construction to AbstractListStore.
2004 Andy Jefferson - fixed use of shift statement
2004 Andy Jefferson - added support for inverse 1-N unidirectional
2005 Andy Jefferson - added dependent-element when removed from collection
    ...
**********************************************************************/
package org.jpox.store.rdbms.scostore;

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

import org.jpox.ClassLoaderResolver;
import org.jpox.ClassNameConstants;
import org.jpox.FetchPlan;
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.Relation;
import org.jpox.metadata.OrderMetaData.FieldOrder;
import org.jpox.store.FieldValues;
import org.jpox.store.mapped.DatastoreClass;
import org.jpox.store.mapped.DatastoreContainerObject;
import org.jpox.store.mapped.DatastoreIdentifier;
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.mapping.JavaTypeMapping;
import org.jpox.store.mapped.mapping.MappingConsumer;
import org.jpox.store.mapped.mapping.Mappings;
import org.jpox.store.query.IncompatibleQueryElementTypeException;
import org.jpox.store.rdbms.RDBMSManager;
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.util.JPOXLogger;
import org.jpox.util.StringUtils;

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

    private String unsetStmt;
    private String removeAtNullifyStmt;
    private String clearNullifyStmt;

    /** Statement for updating a foreign key in a 1-N unidirectional */
    private String updateFkStmt;

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

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

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

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

        elementInfo = getElementInformationForClass();
        if (elementInfo != null && elementInfo.length > 1)
        {
            throw new JPOXUserException(LOCALISER.msg("056031",
                ownerMemberMetaData.getFullFieldName()));
        }

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

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

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

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

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

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

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

    /**
     * Generates the statement for setting an item to be at a position.
     * <PRE>
     * UPDATE LISTTABLE SET OWNERCOL=?, INDEXCOL = ? [,DISTINGUISHER=?]
     * WHERE ELEMENTCOL = ?
     * </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()); // TODO Allow for multiple element tables
            stmt.append(" SET ");
            for (int i=0; i<ownerMapping.getNumberOfDatastoreFields(); i++)
            {
                if (i > 0)
                {
                    stmt.append(",");
                }
                stmt.append(ownerMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
                stmt.append(" = ");
                stmt.append(((RDBMSMapping)ownerMapping.getDataStoreMapping(i)).getUpdateInputParameter());
            }

            if (orderMapping != null)
            {
                for (int i=0; i<orderMapping.getNumberOfDatastoreFields(); i++)
                {
                    stmt.append(",");
                    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(",");
                    stmt.append(relationDiscriminatorMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
                    stmt.append(" = ");
                    stmt.append(((RDBMSMapping)relationDiscriminatorMapping.getDataStoreMapping(i)).getUpdateInputParameter());
                }
            }

            stmt.append(" WHERE ");

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

        return setStmt;
    }

    /**
     * Generates the statement for unsetting an item from a list position.
     * <PRE>
     * UPDATE LISTTABLE SET OWNERCOL=NULL, INDEXCOL=-1 [, DISTINGUISHER = NULL]
     * WHERE OWNERCOL = ? AND INDEXCOL = ? [AND DISTINGUISHER = ?]
     * </PRE>
     * @return The Statement for unsetting an item
     */
    protected String getUnsetStmt()
    {
        if (unsetStmt == null)
        {
            StringBuffer stmt = new StringBuffer();
            stmt.append("UPDATE ");
            // TODO Allow for multiple element tables
            stmt.append(containerTable.toString());
            stmt.append(" SET ");
            for (int i=0; i<ownerMapping.getNumberOfDatastoreFields(); i++)
            {
                if (i > 0)
                {
                    stmt.append(",");
                }
                stmt.append(ownerMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
                stmt.append(" = NULL");
            }

            if (orderMapping != null)
            {
                for (int i=0; i<orderMapping.getNumberOfDatastoreFields(); i++)
                {
                    stmt.append(",");
                    stmt.append(orderMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
                    stmt.append("=-1");
                }
            }
            if (relationDiscriminatorMapping != null)
            {
                for (int i=0; i<relationDiscriminatorMapping.getNumberOfDatastoreFields(); i++)
                {
                    stmt.append(",");
                    stmt.append(relationDiscriminatorMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
                    stmt.append(" = NULL");
                }
            }

            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());
            }
            if (orderMapping != null)
            {
                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());
                }
            }
            unsetStmt = stmt.toString();
        }
        return unsetStmt;
    }

    /**
     * Method to set an object in the List at a position.
     * @param sm The state manager
     * @param index The item index
     * @param element What to set it to.
     * @param allowDependentField Whether to enable dependent-field deletes during the set
     * @return The value before setting.
     **/
    public Object set(StateManager sm, int index, Object element, boolean allowDependentField)
    {
        validateElementForWriting(sm, element, -1); // Last argument means dont set the position on any INSERT
        Object o = get(sm, index);
        String setStmt = getSetStmt();
        String unsetStmt = getUnsetStmt();
        try
        {
            ObjectManager om = sm.getObjectManager();
            ManagedConnection mconn = storeMgr.getConnection(om);
            SQLController sqlControl = storeMgr.getSQLController();
            try
            {
                // Unset the existing object from this position
                PreparedStatement ps = sqlControl.getStatementForUpdate(mconn, unsetStmt, false);
                try
                {
                    int jdbcPosition = 1;
                    jdbcPosition = populateOwnerInStatement(sm, om, ps, jdbcPosition);
                    if (orderMapping != null)
                    {
                        jdbcPosition = populateOrderInStatement(om, ps, index, jdbcPosition);
                    }
                    if (relationDiscriminatorMapping != null)
                    {
                        jdbcPosition = populateRelationDiscriminatorInStatement(om, ps, jdbcPosition);
                    }

                    sqlControl.executeStatementUpdate(mconn, unsetStmt, ps, true);
                }
                finally
                {
                    sqlControl.closeStatement(mconn, ps);
                }

                // Set the new object at this position
                PreparedStatement ps2 = sqlControl.getStatementForUpdate(mconn, setStmt, false);
                try
                {
                    int jdbcPosition = 1;
                    jdbcPosition = populateOwnerInStatement(sm, om, ps2, jdbcPosition);
                    if (orderMapping != null)
                    {
                        jdbcPosition = populateOrderInStatement(om, ps2, index, jdbcPosition);
                    }
                    if (relationDiscriminatorMapping != null)
                    {
                        jdbcPosition = populateRelationDiscriminatorInStatement(om, ps2, jdbcPosition);
                    }
                    jdbcPosition = populateElementInStatement(om, ps2, element, jdbcPosition);

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

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

        return o;
    }

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

        boolean retval;
        ObjectManager om = sm.getObjectManager();
        String updateFkStmt = getUpdateFkStmt();
        try
        {
            ManagedConnection mconn = storeMgr.getConnection(om);
            SQLController sqlControl = storeMgr.getSQLController();
            try
            {
                PreparedStatement ps = sqlControl.getStatementForUpdate(mconn, updateFkStmt, false);
                try
                {
                    int jdbcPosition = 1;
                    if (elementInfo.length > 1)
                    {
                        storeMgr.getDatastoreClass(element.getClass().getName(), clr);
                    }
                    if (owner == null)
                    {
                        if (ownerMemberMetaData != null)
                        {
                            ownerMapping.setObject(om, ps, Mappings.getParametersIndex(jdbcPosition, ownerMapping), null);
                        }
                        else
                        {
                            ownerMapping.setObject(om, ps, Mappings.getParametersIndex(jdbcPosition, ownerMapping), null,
                                sm, ownerMemberMetaData.getAbsoluteFieldNumber());
                        }
                        jdbcPosition += ownerMapping.getNumberOfDatastoreFields();
                    }
                    else
                    {
                        jdbcPosition = populateOwnerInStatement(sm, om, ps, jdbcPosition);
                    }
                    if (orderMapping != null)
                    {
                        jdbcPosition = populateOrderInStatement(om, ps, index, jdbcPosition);
                    }
                    if (relationDiscriminatorMapping != null)
                    {
                        jdbcPosition = populateRelationDiscriminatorInStatement(om, ps, jdbcPosition);
                    }
                    jdbcPosition = populateElementInStatement(om, ps, element, jdbcPosition);

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

        return retval;
    }

    /**
     * Generate statement for updating the owner, index columns in an inverse 1-N.
     * Will result in the statement
     * <PRE>
     * UPDATE ELEMENTTABLE SET FK_COL_1 = ?, FK_COL_2 = ?, FK_IDX = ? [,DISTINGUISHER=?]
     * WHERE ELEMENT_ID = ?
     * </PRE>
     * when we have a single element table, and
     * <PRE>
     * UPDATE ? SET FK_COL_1=?, FK_COL_2=?, FK_IDX=? [,DISTINGUISHER=?]
     * WHERE ELEMENT_ID=?
     * </PRE>
     * when we have multiple element tables possible
     * @return Statement for updating the owner/index of an element in an inverse 1-N
     */
    private String getUpdateFkStmt()
    {
        if (updateFkStmt == null)
        {
            StringBuffer stmt = new StringBuffer();
            stmt.append("UPDATE ");
            if (elementInfo.length > 1)
            {
                stmt.append("?");
            }
            else
            {
                // Could use elementInfo[0].getDatastoreClass but need to allow for relation in superclass table
                stmt.append(containerTable.toString());
            }
            stmt.append(" SET ");
            for (int i=0; i<ownerMapping.getNumberOfDatastoreFields(); i++)
            {
                if (i > 0)
                {
                    stmt.append(",");
                }
                stmt.append(ownerMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
                stmt.append(" = ");
                stmt.append(((RDBMSMapping)ownerMapping.getDataStoreMapping(i)).getUpdateInputParameter());
            }
            if (orderMapping != null)
            {
                for (int i=0; i<orderMapping.getNumberOfDatastoreFields(); i++)
                {
                    stmt.append(",");
                    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(",");
                    stmt.append(relationDiscriminatorMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
                    stmt.append(" = ");
                    stmt.append(((RDBMSMapping)relationDiscriminatorMapping.getDataStoreMapping(i)).getUpdateInputParameter());
                }
            }

            stmt.append(" WHERE ");

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

        return updateFkStmt;
    }

    /**
     * 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)
    {
        if (elementInfo == null)
        {
            return null;
        }

        final ClassLoaderResolver clr = ownerSM.getObjectManager().getClassLoaderResolver();
        QueryExpression stmt = null;
        if (elementInfo.length == 1 &&
            elementInfo[0].getDatastoreClass().getDiscriminatorMetaData() != null &&
            elementInfo[0].getDatastoreClass().getDiscriminatorMetaData().getStrategy() != DiscriminatorStrategy.NONE)
        {
            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 i=0; i<clsNames.length; i++)
                {
                    cls[i] = clr.classForName(clsNames[i]);
                }
                stmt = new DiscriminatorIteratorStatement(clr,
                    cls, true, this.storeMgr, true).getQueryStatement(null);
            }
            else
            {
                stmt = new DiscriminatorIteratorStatement(clr, new Class[] {clr.classForName(elementInfo[0].getClassName())},
                    true, this.storeMgr, true).getQueryStatement(null);
            }
            iterateUsingDiscriminator = true;
        }
        else
        {
            for (int i=0;i<elementInfo.length;i++)
            {
                final int elementNo = i;
                Class elementCls = clr.classForName(elementType);
                QueryExpression subStmt = new UnionIteratorStatement(
                    clr, elementCls, true, this.storeMgr,
                    elementCls, elementMapping, elementInfo[elementNo].getDatastoreClass(), false,
                    null, true, false).getQueryStatement(null);
                if (stmt == null)
                {
                    stmt = subStmt;
                }
                else
                {
                    stmt.union(subStmt);
                }
            }
        }

        // 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" so allow restriction on returned indexes
            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
            {
                returning_range = true;

                // Apply filter to retrieve indexes in the range
                // a). start_index (inclusive) constraint
                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 (exclusive) constraint
                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);
                }
            }

            if (returning_range)
            {
                // If returning a range, add ordering by index
                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.getMainTableExpression()).getExpressionList().toArray();
                stmt.setOrdering(exprIndex, descendingOrder);
            }
        }

        return stmt;
    }

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

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

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

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

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

        if (shiftingElements)
        {
            // We need to shift existing elements before positioning the new ones
            try
            {
                ObjectManager om = sm.getObjectManager();
                ManagedConnection mconn = storeMgr.getConnection(om);

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

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

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

        return true;
    }

    /**
     * Generates the statement for removing an item by nulling it out.
     * When there is only a single element table the statement will be
     * <PRE>
     * UPDATE LISTTABLE SET OWNERCOL=NULL, INDEXCOL=-1
     * WHERE OWNERCOL = ?
     * AND INDEXCOL = ?
     * [AND DISTINGUISHER = ?]
     * </PRE>
     * and when there are multiple element tables the statement will be
     * <PRE>
     * UPDATE ? SET OWNERCOL=NULL, INDEXCOL=-1
     * WHERE OWNERCOL=?
     * AND INDEXCOL=?
     * [AND DISTINGUISHER = ?]
     * </PRE>
     * @return The Statement for removing an item from a position
     */
    protected String getRemoveAtNullifyStmt()
    {
        if (removeAtNullifyStmt == null)
        {
            StringBuffer stmt = new StringBuffer();
            stmt.append("UPDATE ");
            if (elementInfo.length > 1)
            {
                stmt.append("?");
            }
            else
            {
                // Could use elementInfo[0].getDatastoreClass but need to allow for relation in superclass table
                stmt.append(containerTable.toString());
            }
            stmt.append(" SET ");
            for (int i=0; i<ownerMapping.getNumberOfDatastoreFields(); i++)
            {
                if (i > 0)
                {
                    stmt.append(", ");
                }
                stmt.append(ownerMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString() + "=NULL");
            }
            if (orderMapping != null)
            {
                for (int i=0; i<orderMapping.getNumberOfDatastoreFields(); i++)
                {
                    stmt.append(", ");
                    stmt.append(orderMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString() + "=-1");
                }
            }

            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());
            }
            if (orderMapping != null)
            {
                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());
                }
            }
            removeAtNullifyStmt = stmt.toString();
        }
        return removeAtNullifyStmt;
    }

    /**
     * Convenience method to remove the specified element from the List.
     * @param sm StateManager of the owner
     * @param element The element
     * @return Whether the List was modified
     */
    protected boolean internalRemove(StateManager sm, Object element, int size)
    {
        if (indexedList)
        {
            // Indexed List
            // The element can be at one position only (no duplicates allowed in FK list)
            int index = indexOf(sm, element);
            if (index == -1)
            {
                return false;
            }
            removeAt(sm, index, size);
        }
        else
        {
            // Ordered List - no index so null the FK (if nullable) or delete the element
            if (ownerMapping.isNullable())
            {
                // Nullify the FK
                StateManager elementSM = sm.getObjectManager().findStateManager(element);
                if (ownerMemberMetaData.getRelationType(clr) == Relation.ONE_TO_MANY_BI)
                {
                    // TODO Log this
                    elementSM.replaceField(ownerMemberMetaData.getRelatedMemberMetaData(clr)[0].getAbsoluteFieldNumber(),
                        null, true);
                    if (sm.getObjectManager().isFlushing())
                    {
                        elementSM.flush();
                    }
                }
                else
                {
                    updateElementFk(sm, element, null, -1);
                }
            }
            else
            {
                // Delete the element
                // TODO Log this
                sm.getObjectManager().deleteObjectInternal(element);
            }
        }

        return true;
    }

    /**
     * Internal method to remove an object at a location in the List.
     * Differs from the JoinTable List in that it nulls out the owner FK.
     * @param sm The state manager.
     * @param index The location
     * @param size Current size of list (if known). -1 if not known
     */
    protected void removeAt(StateManager sm, int index, int size)
    {
        String stmt = null;
        if (ownerMapping.isNullable() && orderMapping != null && orderMapping.isNullable())
        {
            JPOXLogger.DATASTORE.debug(LOCALISER.msg("056043"));
            // TODO When using this statement we need to plug in the element table name, and do it for
            // all possible element tables
            stmt = getRemoveAtNullifyStmt();
        }
        else
        {
            JPOXLogger.DATASTORE.debug(LOCALISER.msg("056042"));
            stmt = getRemoveAtStmt();
        }
        internalRemoveAt(sm, index, stmt, size);
    }

    /**
     * Generates the statement for clearing items by nulling the owner link out.
     * The statement will be
     * <PRE>
     * UPDATE LISTTABLE SET OWNERCOL=NULL, INDEXCOL=-1 [,DISTINGUISHER=NULL]
     * WHERE OWNERCOL=? [AND DISTINGUISHER=?]
     * </PRE>
     * when there is only one element table, and will be
     * <PRE>
     * UPDATE ? SET OWNERCOL=NULL, INDEXCOL=-1 [,DISTINGUISHER=NULL]
     * WHERE OWNERCOL=? [AND DISTINGUISHER=?]
     * </PRE>
     * when there is more than 1 element table.
     * @return The Statement for clearing items for the owner.
     */
    protected String getClearNullifyStmt()
    {
        if (clearNullifyStmt == null)
        {
            StringBuffer stmt = new StringBuffer();
            stmt.append("UPDATE ");
            if (elementInfo.length > 1)
            {
                stmt.append("?");
            }
            else
            {
                // Could use elementInfo[0].getDatastoreClass but need to allow for relation in superclass table
                stmt.append(containerTable.toString());
            }
            stmt.append(" SET ");
            for (int i=0; i<ownerMapping.getNumberOfDatastoreFields(); i++)
            {
                if (i > 0)
                {
                    stmt.append(", ");
                }
                stmt.append(ownerMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString() + "=NULL");
            }
            if (orderMapping != null)
            {
                for (int i=0; i<orderMapping.getNumberOfDatastoreFields(); i++)
                {
                    stmt.append(", ");
                    stmt.append(orderMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString() + "=-1");
                }
            }
            if (relationDiscriminatorMapping != null)
            {
                for (int i=0; i<relationDiscriminatorMapping.getNumberOfDatastoreFields(); i++)
                {
                    stmt.append(", ");
                    stmt.append(relationDiscriminatorMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
                    stmt.append("=NULL");
                }
            }

            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());
            }
            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());
                }
            }
            clearNullifyStmt = stmt.toString();
        }
        return clearNullifyStmt;
    }

    /**
     * Generate statement for getting the size of the container.
     * The order part is only present when an order mapping is used.
     * The discriminator part is only present when the element type has
     * inheritance strategy of "superclass-table" and is Inverse.
     * <PRE>
     * SELECT COUNT(*) FROM CONTAINERTABLE
     * WHERE OWNERCOL=?
     * [AND ORDERCOL IS NOT NULL]
     * [AND (DISCRIMINATOR=? OR DISCRMINATOR=? OR DISCRIMINATOR=?)]
     * </PRE>
     * The discriminator part includes all subclasses of the element type
     * @return The Statement returning the size of the container.
     */
    protected String getSizeStmt()
    {
        // TODO Allow for multiple element tables
        return super.getSizeStmt();
    }

    /**
     * Generate statement for retrieving the contents of the Collection.
     * The discriminator part is only present when the element type has
     * inheritance strategy of "superclass-table" and is Inverse.
     * <PRE>
     * SELECT OWNERCOL FROM COLLECTIONTABLE
     * WHERE OWNERCOL=?
     * AND ELEMENTCOL = ?
     * [AND DISCRIMINATOR = ?]
     * </PRE>
     * @return Statement for retrieving the contents of the Collection.
     */
    protected String getContainsStmt()
    {
        // TODO Allow for multiple element tables
        return super.getContainsStmt();
    }

    /**
     * Generate statement for clearing the container.
     * <PRE>
     * DELETE FROM CONTAINERTABLE
     * WHERE OWNERCOL = ?
     * </PRE>
     * @return Statement for clearing the container.
     */
    protected String getClearStmt()
    {
        // TODO Allow for multiple element tables
        return super.getClearStmt();
    }

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

        if (deleteElements)
        {
            // Find elements present in the datastore and delete them one-by-one
            Iterator elementsIter = iterator(ownerSM);
            if (elementsIter != null)
            {
                while (elementsIter.hasNext())
                {
                    Object element = elementsIter.next();
                    if (om.getApiAdapter().isPersistable(element) && om.getApiAdapter().isDeleted(element))
                    {
                        // Element is waiting to be deleted so flush it (it has the FK)
                        StateManager objSM = om.findStateManager(element);
                        objSM.flush();
                    }
                    else
                    {
                        // Element not yet marked for deletion so go through the normal process
                        om.deleteObjectInternal(element);
                    }
                }
            }
        }
        else
        {
            // TODO If the relation is bidirectional we need to clear the owner in the element
            String clearNullifyStmt = getClearNullifyStmt();
            try
            {
                ManagedConnection mconn = storeMgr.getConnection(om);
                SQLController sqlControl = storeMgr.getSQLController();
                try
                {
                    PreparedStatement ps = sqlControl.getStatementForUpdate(mconn, clearNullifyStmt, false);
                    try
                    {
                        int jdbcPosition = 1;
                        jdbcPosition = populateOwnerInStatement(ownerSM, om, ps, jdbcPosition);
                        if (relationDiscriminatorMapping != null)
                        {
                            jdbcPosition = populateRelationDiscriminatorInStatement(om, ps, jdbcPosition);
                        }
                        sqlControl.executeStatementUpdate(mconn, clearNullifyStmt, ps, true);
                    }
                    finally
                    {
                        sqlControl.closeStatement(mconn, ps);
                    }
                }
                finally
                {
                    mconn.release();
                }
            }
            catch (SQLException e)
            {
                throw new JPOXDataStoreException(LOCALISER.msg("056013",clearNullifyStmt),e);
            }
        }
    }

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

        // Check if element is ok for use in the datastore, specifying any external mappings that may be required
        boolean inserted = super.validateElementForWriting(sm, element, new FieldValues()
        {
            public void fetchFields(StateManager esm)
            {
                boolean isPersistentInterface = storeMgr.getOMFContext().getMetaDataManager().isPersistentInterface(elementType);
                DatastoreClass elementTable = null;
                if (isPersistentInterface)
                {
                    elementTable = storeMgr.getDatastoreClass(
                        storeMgr.getOMFContext().getMetaDataManager().getImplementationNameForPersistentInterface(elementType), clr);
                }
                else
                {
                    elementTable = storeMgr.getDatastoreClass(elementType, clr);
                }
                if (elementTable == null)
                {
                    // "subclass-table", persisted into table of other class
                    AbstractClassMetaData[] managingCmds = storeMgr.getClassesManagingTableForClass(emd, clr);
                    if (managingCmds != null && managingCmds.length == 1)
                    {
                        elementTable = storeMgr.getDatastoreClass(managingCmds[0].getFullClassName(), clr);
                    }
                    else
                    {
                        // Throw exception if managed by more than 1 other class
                        // TODO Localise this
                        throw new JPOXUserException("Field " + ownerMemberMetaData.getFullFieldName() +
                            " is a List with element using subclass-table strategy BUT there are more than 1 subclass with table. This is not yet supported by JPOX");
                    }
                }
                if (elementTable != null)
                {
                    JavaTypeMapping externalFKMapping = elementTable.getExternalMapping(ownerMemberMetaData, MappingConsumer.MAPPING_TYPE_EXTERNAL_FK);
                    if (externalFKMapping != null)
                    {
                        // The element has an external FK mapping so set the value it needs to use in the INSERT
                        esm.setExternalFieldValueForMapping(externalFKMapping, sm.getObject());
                    }
                    if (relationDiscriminatorMapping != null)
                    {
                        esm.setExternalFieldValueForMapping(relationDiscriminatorMapping, relationDiscriminatorValue);
                    }
                    if (orderMapping != null && index >= 0)
                    {
                        if (ownerMemberMetaData.getOrderMetaData() != null && ownerMemberMetaData.getOrderMetaData().getMappedBy() != null)
                        {
                            // Order is stored in a field in the element so update it
                            // We support mapped-by fields of types int/long/Integer/Long currently
                            Object indexValue = null;
                            if (orderMapping.getFieldMetaData().getTypeName().equals(ClassNameConstants.JAVA_LANG_LONG) ||
                                    orderMapping.getFieldMetaData().getTypeName().equals(ClassNameConstants.LONG))
                            {
                                indexValue = new Long(index);
                            }
                            else
                            {
                                indexValue = new Integer(index);
                            }
                            esm.replaceField(orderMapping.getFieldMetaData().getAbsoluteFieldNumber(), indexValue, true);
                        }
                        else
                        {
                            // Order is stored in a surrogate column so save its vaue for the element to use later
                            esm.setExternalFieldValueForMapping(orderMapping, new Integer(index));
                        }
                    }
                }
                if (ownerFieldNumber >= 0 &&
                    sm.getObjectManager().getOMFContext().getPersistenceConfiguration().getBooleanProperty("org.jpox.manageRelationships"))
                {
                    // Managed Relations : 1-N bidir, so make sure owner is correct at persist
                    Object currentOwner = esm.provideField(ownerFieldNumber);
                    if (currentOwner == null)
                    {
                        // No owner, so correct it
                        JPOXLogger.JDO.info(LOCALISER.msg("056037",
                            StringUtils.toJVMIDString(sm.getObject()), ownerMemberMetaData.getFullFieldName(),
                            StringUtils.toJVMIDString(esm.getObject())));
                        esm.replaceField(ownerFieldNumber, newOwner, true);
                    }
                    else if (currentOwner != newOwner && sm.getReferencedPC() == null)
                    {
                        // Owner of the element is neither this container nor is it being attached
                        // Inconsistent owner, so throw exception
                        throw new JPOXUserException(LOCALISER.msg("056038",
                            StringUtils.toJVMIDString(sm.getObject()), ownerMemberMetaData.getFullFieldName(),
                            StringUtils.toJVMIDString(esm.getObject()),
                            StringUtils.toJVMIDString(currentOwner)));
                    }
                }
            }
            public void fetchNonLoadedFields(StateManager sm)
            {
            }
            public FetchPlan getFetchPlanForLoading()
            {
                return null;
            }
        });

        return inserted;
    }

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

    /**
     * Utility method to return a new QueryStatement for retrieval of the elements of this List.
     * @param sm StateManager for this object
     * @param candidateClass Class for the element end of the link.
     * @param candidateAlias Alias for the candidate
     * @return The QueryStatement
     **/
    public QueryExpression newQueryStatement(StateManager sm, String candidateClass,
            DatastoreIdentifier candidateAlias)
    {
        // TODO Use the candidateAlias
        if (!sm.getObjectManager().getClassLoaderResolver().isAssignableFrom(elementType,candidateClass))
        {
            throw new IncompatibleQueryElementTypeException(elementType, candidateClass);
        }

        ClassLoaderResolver clr = sm.getObjectManager().getClassLoaderResolver();
        DatastoreClass candidateTable = storeMgr.getDatastoreClass(candidateClass, clr);

        // QueryStatement for the element table
        QueryExpression stmt = dba.newQueryStatement(candidateTable, clr);

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

        // Select the element table ID mapping
        stmt.select(elementMapping);

        return stmt;
    }

    /**
     * Method used in queries when contains() has been invoked.
     * @param stmt The Query Statement
     * @param parentStmt the parent Query Statement. If there is no parent, "parentStmt" must be equal to "stmt"
     * @param ownerMapping the mapping for the owner.
     * @param ownerTe 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 expression or to the element table.
     * @param listTableAlias The alias for the "List" table
     * @param existsQuery Whether this is joining for an EXISTS query
     * @return expression to the join
     */
    public ScalarExpression joinElementsTo(QueryExpression stmt,
            QueryExpression parentStmt,
            JavaTypeMapping ownerMapping,
            LogicSetExpression ownerTe,
            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());
        }
        DatastoreContainerObject filteredElementTable = storeMgr.getDatastoreClass(filteredElementType.getName(), clr);

        if (stmt.getTableExpression(elementTableAlias) == null)
        {
            // Make sure we have the table of the element object in the statement
            stmt.newTableExpression(filteredElementTable, elementTableAlias);
        }

        DatastoreIdentifier containerRangeVar = listTableAlias;
        if (existsQuery)
        {
            // Part of EXISTS query. Why do we treat this differently ?????????????
            if (stmt.getTableExpression(containerRangeVar) == null)
            {
                // WHY????????????????????????
                containerRangeVar = elementTableAlias;
            }
            if (stmt.getTableExpression(containerRangeVar) == null)
            {
                // Create the container table if not yet present in the statement
                stmt.newTableExpression(containerTable, containerRangeVar);
            }
        }
        else
        {
            if (parentStmt != stmt)
            {
                // Subquery. Why do we treat differently ??????
                if (stmt.getTableExpression(containerRangeVar) == null)
                {
                    // WHY????????????????????????
                    containerRangeVar = elementTableAlias;
                }
                if (stmt.getTableExpression(containerRangeVar) == null)
                {
                    // Create the container table if not yet present in the statement
                    stmt.newTableExpression(containerTable, containerRangeVar);
                }

                // Reverse collection contains query so join back to the owner
                ScalarExpression ownerExpr = ownerMapping.newScalarExpression(stmt, ownerTe);
                ScalarExpression ownerSetExpr = this.ownerMapping.newScalarExpression(stmt,
                    stmt.getTableExpression(containerRangeVar));
                stmt.andCondition(ownerExpr.eq(ownerSetExpr), true);
            }
            else
            {
                if (elementExpr.getLogicSetExpression().getMainTable() == filteredElementTable)
                {
                    // Element expression is of the container table
                    // Simple example is "SELECT FROM MyType WHERE field1.contains(this)"
                    // so candidate is MyType, element type is MyType and the element we are checking
                    // is MyType.
                    if (stmt.getTableExpression(containerRangeVar) == null)
                    {
                        // Why???????????????
                        containerRangeVar = elementTableAlias;
                    }
                    if (stmt.getTableExpression(containerRangeVar) == null)
                    {
                        // Create the container table if not yet present in the statement
                        stmt.newTableExpression(containerTable, containerRangeVar);
                    }
                    ScalarExpression ownerExpr = ownerMapping.newScalarExpression(stmt, ownerTe);
                    ScalarExpression ownerSetExpr = this.ownerMapping.newScalarExpression(stmt,
                        stmt.getTableExpression(containerRangeVar));
                    stmt.andCondition(ownerExpr.eq(ownerSetExpr), true);
                }
                else
                {
                    // Element is of a different table
                    // Simple example is "SELECT FROM MyType WHERE field1.contains(field2)"
                    // so candidate is MyType, element type is MyElement, and the element we are checking
                    // is MyType.field2. This creates an INNER JOIN MYTYPES.ID -> MYELEMENT.FK
                    if (stmt.getTableExpression(containerRangeVar) == null)
                    {
                        // Create the container table if not yet present in the statement
                        stmt.newTableExpression(containerTable, containerRangeVar);
                    }

                    // Add a join from the owner table to the container table
                    ScalarExpression ownerExpr = ownerMapping.newScalarExpression(stmt, ownerTe);
                    ScalarExpression ownerSetExpr = this.ownerMapping.newScalarExpression(stmt,
                        stmt.getTableExpression(containerRangeVar));
                    stmt.innerJoin(ownerExpr, ownerSetExpr, stmt.getTableExpression(containerRangeVar), true, true);
                }
            }
        }

        // Return the expression of the PK of the elements table for the element to be bound to
        JavaTypeMapping elementTableID = filteredElementTable.getIDMapping();
        return elementTableID.newScalarExpression(stmt, stmt.getTableExpression(containerRangeVar));
    }
}
TOP

Related Classes of org.jpox.store.rdbms.scostore.FKListStore

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.