Package org.jpox.store.rdbms.scostore

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

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

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

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

Contributors:
2002 Mike Martin (TJDO)
2003 Andy Jefferson - added comments
2003 Erik Bengtson - removed unused import
2003 Erik Bengtson - fixed bug [832635] Imcompatiblities in casting
                    inherited classes in query
2003 Andy Jefferson - coding standards
2004 Andy Jefferson - updated retrieval of ColumnLists
2004 Marco Schulze  - replaced catch(NotPersistenceCapableException ...) by
                     advance-check via TypeManager.isSupportedType(...)
2004 Andy Jefferson - moved statements to AbstractSetStore
2005 Andy Jefferson - added dependent-element when removed from collection
    ...
**********************************************************************/
package org.jpox.store.rdbms.scostore;

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

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.DiscriminatorStrategy;
import org.jpox.metadata.FieldRole;
import org.jpox.metadata.MetaDataUtils;
import org.jpox.metadata.Relation;
import org.jpox.sco.SCOMtoN;
import org.jpox.store.exceptions.NotYetFlushedException;
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.SQLWarnings;
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 a Normal Set as part of a relationship. This class is
* used where you have a 1-N and the tables are joined via a link table.
* That is one table is the owner, and it has a link table to another table,
* with the link table having 2 columns - the ids of the 2 tables.
* This is in contrast to InverseSetStore which represents 1-N relationships
* without using a link table (using an id in the other table).
* <p>
* For sets of primitive types (eg Date,String etc), the NormalSetStore is used,
* but the 'link' table contains the id of the owner and the field(s)
* representing the primitive type.
*
* @version $Revision: 1.53 $
**/
public class JoinSetStore extends AbstractSetStore
{
    /** Statement to check the existence of an owner-element relation. */
    protected String locateStmt;

    /** Statement to get the maximum order column id so we can set the next insert value. */
    protected String maxOrderColumnIdStmt;

    /** Whether this relation is one end of an M-N relation. */
    protected final boolean m2n;

    /**
     * Constructor for the relationship representation.
     * @param joinTable The table for the link
     * @param clr The ClassLoaderResolver
     **/
    public JoinSetStore(AbstractMemberMetaData fmd, CollectionTable joinTable, ClassLoaderResolver clr)
    {
        super(joinTable.getStoreManager(), clr);

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

        // Check if this is an M-N relation (only possible with NormalSetStore)
        int relationType = ownerMemberMetaData.getRelationType(clr);
        m2n = (relationType == Relation.MANY_TO_MANY_BI);

        ownerMapping = joinTable.getOwnerMapping();
        elementMapping = joinTable.getElementMapping();
        orderMapping = joinTable.getOrderMapping();
        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;
                }
            }
        }
    }

    /**
     * Generate statement for obtaining the maximum id for the order column.
     * <PRE>
     * SELECT MAX(SCOID) FROM SETTABLE
     * WHERE OWNERCOL=?
     * [AND RELATION_DISCRIM=?]
     * </PRE>
     * @return The Statement returning the higher id
     */
    private String getMaxOrderColumnIdStmt()
    {
        if (maxOrderColumnIdStmt == null)
        {
            StringBuffer stmt = new StringBuffer();
            stmt.append("SELECT MAX(" + orderMapping.getDataStoreMapping(0).getDatastoreField().getIdentifier().toString() + ")");
            stmt.append(" FROM ");
            stmt.append(containerTable.toString());
            stmt.append(" WHERE ");
            stmt.append(ownerMapping.getDataStoreMapping(0).getDatastoreField().getIdentifier().toString());
            stmt.append(" = ");
            stmt.append(((RDBMSMapping)ownerMapping.getDataStoreMapping(0)).getUpdateInputParameter());
            for (int i=1; i<ownerMapping.getNumberOfDatastoreFields(); i++)
            {
                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=1; i<relationDiscriminatorMapping.getNumberOfDatastoreFields(); i++)
                {
                    stmt.append(" AND ");
                    stmt.append(relationDiscriminatorMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
                    stmt.append(" = ");
                    stmt.append(((RDBMSMapping)relationDiscriminatorMapping.getDataStoreMapping(i)).getUpdateInputParameter());
                }
            }
            maxOrderColumnIdStmt = stmt.toString();
        }

        return maxOrderColumnIdStmt;
    }

    /**
     * Generate statement for checking the existence of an owner-element relation (used for M-N).
     * <PRE>
     * SELECT 1 FROM SETTABLE WHERE OWNERCOL = ? AND ELEMENTCOL = ?
     * </PRE>
     * @return Statement for locating an owner-element relation
     */
    private String getLocateStmt()
    {
        if (locateStmt == null)
        {
            StringBuffer stmt = new StringBuffer();
            stmt.append("SELECT 1 FROM ");
            stmt.append(containerTable.toString());
            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)).getInsertionInputParameter());
            }
            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)).getInsertionInputParameter());
            }

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

        return locateStmt;
    }

    /**
     * Generate statement for deleting items from the Set.
     * The EMBEDDEDFIELDX is only present when the elements are PC(embedded).
     * <PRE>
     * DELETE FROM SETTABLE
     * WHERE OWNERCOL=?
     * AND ELEMENTCOL = ?
     * [AND EMBEDDEDFIELD1 = ? AND EMBEDDEDFIELD2 = ? AND EMBEDDEDFIELD3 = ?]
     * [AND RELATION_DISCRIM = ?]
     * </PRE>
     * @return Statement for deleting items from the Set.
     */
    protected String getRemoveStmt()
    {
        StringBuffer stmt = new StringBuffer();
        stmt.append("DELETE FROM ");
        stmt.append(containerTable.toString());
        stmt.append(" WHERE ");
        stmt.append(ownerMapping.getDataStoreMapping(0).getDatastoreField().getIdentifier().toString());
        stmt.append(" = ");
        stmt.append(((RDBMSMapping)ownerMapping.getDataStoreMapping(0)).getUpdateInputParameter());
        for (int i=1; i<ownerMapping.getNumberOfDatastoreFields(); i++)
        {
            stmt.append(" AND ");
            stmt.append(ownerMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
            stmt.append(" = ");
            stmt.append(((RDBMSMapping)ownerMapping.getDataStoreMapping(i)).getUpdateInputParameter());
        }

        // TODO This doesnt allow for any of the element fields being "null" since in that situation we
        // would want the SQL to have "IS NULL" instead of "= NULL"
        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());
            }
        }

        return stmt.toString();
    }

  /**
     * Generate statement for removing a collection of items from the Set.
     * <PRE>
     * DELETE FROM SETTABLE
     * WHERE (OWNERCOL=? AND ELEMENTCOL=?) OR
     *      (OWNERCOL=? AND ELEMENTCOL=?) OR
     *      (OWNERCOL=? AND ELEMENTCOL=?)
     * </PRE>
     * @param ownerSm The owner StateManager
     * @param elements Collection of elements to remove
     * @return Statement for deleting items from the Set.
     **/
    protected String getRemoveAllStmt(StateManager ownerSm, 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();
    }

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

        boolean modified = false;

        String removeAllStmt = getRemoveAllStmt(sm, elements);
        try
        {
            ObjectManager om = sm.getObjectManager();
            ManagedConnection mconn = storeMgr.getConnection(om);
            SQLController sqlControl = storeMgr.getSQLController();
            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);
        }

        if (ownerMemberMetaData.getCollection().isDependentElement())
        {
            // "delete-dependent" : delete elements if the collection is marked as dependent
            // TODO What if the collection contains elements that are not in the Set ? should not delete them
            sm.getObjectManager().deleteObjects(elements.toArray());
        }

        return modified;
    }

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

    /**
     * Method to check for the existence in the datastore of an owner-element relation.
     * @param sm State Manager for the owner
     * @param element The element
     * @return Whether the relation exists in the datastore
     */
    public boolean locate(StateManager sm, Object element)
    {
        boolean exists = true;
        String stmt = getLocateStmt();
        try
        {
            ObjectManager om = sm.getObjectManager();
            ManagedConnection mconn = storeMgr.getConnection(om);
            SQLController sqlControl = storeMgr.getSQLController();
            try
            {
                PreparedStatement ps = sqlControl.getStatementForQuery(mconn, stmt);
                try
                {
                    int jdbcPosition = 1;
                    jdbcPosition = populateOwnerInStatement(sm, om, ps, jdbcPosition);
                    jdbcPosition = populateElementInStatement(om, ps, element, jdbcPosition);
                    if (relationDiscriminatorMapping != null)
                    {
                        jdbcPosition = populateRelationDiscriminatorInStatement(om, ps, jdbcPosition);
                    }

                    ResultSet rs = sqlControl.executeStatementQuery(mconn, stmt, ps);
                    try
                    {
                        if (!rs.next())
                        {
                            exists = false;
                        }
                    }
                    catch (SQLException sqle)
                    {
                        rs.close();
                    }
                }
                finally
                {
                    sqlControl.closeStatement(mconn, ps);
                }
            }
            finally
            {
                mconn.release();
            }
        }
        catch (SQLException e)
        {
            JPOXLogger.DATASTORE.error(e);
            throw new JPOXDataStoreException(LOCALISER.msg("RDBMS.SCO.LocateRequestFailed", stmt), e);
        }
        return exists;
    }

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

        if (ownerMemberMetaData.getRelationType(clr) == Relation.ONE_TO_MANY_BI &&
            sm.getObjectManager().getOMFContext().getPersistenceConfiguration().getBooleanProperty("org.jpox.manageRelationships"))
        {
            // Managed Relations : make sure we have consistency of relation
            StateManager elementSM = sm.getObjectManager().findStateManager(element);
            if (elementSM != null)
            {
                AbstractMemberMetaData[] relatedMmds = ownerMemberMetaData.getRelatedMemberMetaData(clr);
                Object elementOwner = elementSM.provideField(relatedMmds[0].getAbsoluteFieldNumber());
                if (elementOwner == null)
                {
                    // No owner, so correct it
                    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)));
                }
            }
        }

        boolean modified = false;

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

        if (toBeInserted)
        {
            try
            {
                ObjectManager om = sm.getObjectManager();
                ManagedConnection mconn = storeMgr.getConnection(om);
                try
                {
                    // Add a row to the join table
                    int orderID = -1;
                    if (orderMapping != null)
                    {
                        orderID = getNextIDForOrderColumn(sm);
                    }
                    int[] returnCode = internalAdd(sm, element, mconn, false, orderID, true);
                    if (returnCode[0] > 0)
                    {
                        modified = true;
                    }
                }
                finally
                {
                    mconn.release();
                }
            }
            catch (SQLException e)
            {
                JPOXLogger.DATASTORE.error(e);
                String msg = LOCALISER.msg("056009", getAddStmt());
                JPOXLogger.DATASTORE.error(msg);
                throw new JPOXDataStoreException(msg, e);
            }
        }

        return modified;
    }

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

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

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

            if (ownerMemberMetaData.getRelationType(clr) == Relation.ONE_TO_MANY_BI &&
                sm.getObjectManager().getOMFContext().getPersistenceConfiguration().getBooleanProperty("org.jpox.manageRelationships"))
            {
                // Managed Relations : make sure we have consistency of relation
                StateManager elementSM = sm.getObjectManager().findStateManager(element);
                if (elementSM != null)
                {
                    AbstractMemberMetaData[] relatedMmds = ownerMemberMetaData.getRelatedMemberMetaData(clr);
                    Object elementOwner = elementSM.provideField(relatedMmds[0].getAbsoluteFieldNumber());
                    if (elementOwner == null)
                    {
                        // No owner, so correct it
                        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 its referenced object
                        // Inconsistent owner, so throw exception
                        throw new JPOXUserException(LOCALISER.msg("056038",
                            StringUtils.toJVMIDString(sm.getObject()), ownerMemberMetaData.getFullFieldName(),
                            StringUtils.toJVMIDString(elementSM.getObject()),
                            StringUtils.toJVMIDString(elementOwner)));
                    }
                }
            }
        }

        try
        {
            ObjectManager om = sm.getObjectManager();
            ManagedConnection mconn = storeMgr.getConnection(om);
            SQLController sqlControl = storeMgr.getSQLController();
            sqlControl.processStatementsForConnection(mconn); // Process all waiting batched statements before we start our work

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

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

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

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

        return modified;
    }

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

        if (toBeInserted)
        {
            String addStmt = getAddStmt();
            boolean notYetFlushedError = false;
            ObjectManager om = sm.getObjectManager();
            SQLController sqlControl = storeMgr.getSQLController();
            PreparedStatement ps = sqlControl.getStatementForUpdate(conn, addStmt, batched);
            try
            {
                // Insert the join table row
                int jdbcPosition = 1;
                jdbcPosition = populateOwnerInStatement(sm, om, ps, jdbcPosition);
                jdbcPosition = populateElementInStatement(om, ps, element, jdbcPosition);
                if (orderMapping != null)
                {
                    jdbcPosition = populateOrderInStatement(om, ps, orderId, jdbcPosition);
                }
                if (relationDiscriminatorMapping != null)
                {
                    jdbcPosition = populateRelationDiscriminatorInStatement(om, ps, jdbcPosition);
                }

                // Execute the statement
                return sqlControl.executeStatementUpdate(conn, addStmt, ps, executeNow);
            }
            catch (NotYetFlushedException nfe)
            {
                notYetFlushedError = true;
                throw nfe;
            }
            finally
            {
                if (notYetFlushedError)
                {
                    sqlControl.abortStatementForConnection(conn, ps);
                }
                else
                {
                    sqlControl.closeStatement(conn, ps);
                }
            }
        }
        return null;
    }

    /**
     * Accessor for the next id when elements primary key can't be part of
     * the primary key by datastore limitations like BLOB types can't be
     * primary keys. Also for where the user wants to allow duplicates.
     * @param sm The State Manager
     * @return the next id value
     */
    private int getNextIDForOrderColumn(StateManager sm)
    {
        int nextID;
        ObjectManager om = sm.getObjectManager();

        String stmt = getMaxOrderColumnIdStmt();
        try
        {
            ManagedConnection mconn = storeMgr.getConnection(om);
            SQLController sqlControl = storeMgr.getSQLController();

            try
            {
                PreparedStatement ps = sqlControl.getStatementForQuery(mconn, stmt);
                try
                {
                    int jdbcPosition = 1;
                    jdbcPosition = populateOwnerInStatement(sm, om, ps, jdbcPosition);
                    if (relationDiscriminatorMapping != null)
                    {
                        jdbcPosition = populateRelationDiscriminatorInStatement(om, ps, jdbcPosition);
                    }

                    ResultSet rs = sqlControl.executeStatementQuery(mconn, stmt, ps);
                    try
                    {
                        if (!rs.next())
                        {
                            nextID = 1;
                        }
                        else
                        {
                            nextID = rs.getInt(1)+1;
                        }

                        SQLWarnings.log(rs);
                    }
                    finally
                    {
                        rs.close();
                    }
                }
                finally
                {
                    sqlControl.closeStatement(mconn, ps);
                }
            }
            finally
            {
                mconn.release();
            }
        }
        catch (SQLException e)
        {
            throw new JPOXDataStoreException(LOCALISER.msg("056020", stmt), e);
        }

        return nextID;
    }

    /**
     * Accessor for the statement for the iterator.
     * @param ownerSM the owner StateManager
     * @return The iterator Query Statement.
     */
    protected QueryExpression getIteratorStatement(StateManager ownerSM)
    {
        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 = 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 Set 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);
        }

        if (orderMapping != null)
        {
            // Order by the ordering column, when present
            ScalarExpression exprIndex[] = new ScalarExpression[orderMapping.getNumberOfDatastoreFields()];
            boolean descendingOrder[] = new boolean[orderMapping.getNumberOfDatastoreFields()];
            exprIndex = orderMapping.newScalarExpression(stmt, stmt.getMainTableExpression()).getExpressionList().toArray();
            stmt.setOrdering(exprIndex, descendingOrder);
        }

        return stmt;
    }

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

    /**
     * Utility method to return a new QueryStatement for retrieval of the elements of this Set.
     * @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 candidateAlias instead of "SET"
        // TODO Parametrise out "SET_ELEMENTS"
        if (elementsAreEmbedded || elementsAreSerialised)
        {
            throw new JPOXUserException(LOCALISER.msg("056021"));
        }

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

        QueryExpression stmt = dba.newQueryStatement(containerTable, setTableAlias, clr);

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

        if (!clr.isAssignableFrom(elementType, candidateClass))
        {
            throw new IncompatibleQueryElementTypeException(elementType, candidateClass);
        }

        /*
         * Try to join to the element table and select the element ID.
         *
         * If the element class does not have a mapping to a table (e.g. -
         * the Set has the element defined as Object or some other super
         * class or interface that is not PersistenceCapable, but has
         * PersistenceCapable implementations), just select the element
         * ID from the join table.  The actual element table will be joined
         * in later if it is casted to in the Query.
         * Because the ClassNotPersistenceCapableException is not thrown anymore
         * with types that are natively supported by TypeManager, we check for
         * it here.
         */
        if (storeMgr.getOMFContext().getTypeManager().isSupportedType(candidateClass))
        {
            // Non-PC(embedded) - select the join table element
            stmt.select(setTableAlias, elementMapping);
        }
        else
        {
            // PC
            DatastoreClass candidateTable = storeMgr.getDatastoreClass(candidateClass, clr);

            // Add the element table to the query, called "SET_ELEMENTS"
            DatastoreIdentifier elementTblAlias = storeMgr.getIdentifierFactory().newIdentifier(
                IdentifierFactory.TABLE, "SET_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 elmSetExpr = elementMapping.newScalarExpression(stmt,
                stmt.getTableExpression(setTableAlias));
            ScalarExpression elmExpr = elementTableID.newScalarExpression(stmt, elementTblExpr);
            stmt.innerJoin(elmExpr, elmSetExpr, elementTblExpr, true, true);

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

        return stmt;
    }

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

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

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

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

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

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

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

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

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