Package org.jpox.store.rdbms.scostore

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

/**********************************************************************
Copyright (c) 2002 Mike Martin (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:
2003 Andy Jefferson - added comments
2003 Erik Bengtson - removed unused variable and 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 Andy Jefferson - addition of removeAll()
2004 Andy Jefferson - moved statements to AbstractSetStore
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 javax.jdo.spi.PersistenceCapable;

import org.jpox.ClassLoaderResolver;
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.AbstractMemberMetaData;
import org.jpox.metadata.CollectionMetaData;
import org.jpox.metadata.DiscriminatorStrategy;
import org.jpox.metadata.Relation;
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 an Inverse Set as part of a relationship. This class is
* used where you have a 1-N and the tables are not joined via a link table.
* That is there is an owner table, and a collection table, and the collection
* table has a column being the id of the owner table. This is in contrast to
* NormalSetStore which represents 1-N relationships using a link table.
* There are 2 possible uses here
* <UL>
* <LI><B>bidirectional</B> - where the owner has a Collection of elements, and
* the element has an owner. In this case the element class will have an owner field
* which can be updated directly</LI>
* <LI><B>unidirectional</B> - where the owner has a Collection of elements, but
* the element knows nothing about the owner. In this case the element class has no
* owner field. In this case the column in the element table has to be updated
* directly.</LI>
* </UL>
*
* @version $Revision: 1.64 $
**/
public class FKSetStore extends AbstractSetStore
{
    /** Field number of owner link in element class. */
    private final int ownerFieldNumber;

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

    /** Statement for nullifying a FK in the element. */
    private String clearNullifyStmt;

    /**
     * Constructor for the relationship representation.
     * @param fmd The MetaData for the field that this represents
     * @param storeMgr The RDBMSManager managing the associated datastore.
     * @param clr The ClassLoaderResolver
     **/
    public FKSetStore(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))
        {
            elementIsPersistentInterface = storeMgr.getOMFContext().getMetaDataManager().isPersistentInterface(element_class.getName());
            if (elementIsPersistentInterface)
            {
                emd = storeMgr.getOMFContext().getMetaDataManager().getMetaDataForInterface(element_class,clr);
            }
            else
            {
                // 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",
                ownerFieldMetaData.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)
        if (fmd.getMappedBy() != null)
        {
            // 1-N FK bidirectional
            // The element class has a field for the owner.
            AbstractMemberMetaData eofmd = emd.getMetaDataForMember(fmd.getMappedBy());
            if (eofmd == null)
            {
                throw new JPOXUserException(LOCALISER.msg("056024", fmd.getFullFieldName(),
                    fmd.getMappedBy(), element_class.getName()));
            }

            // Check that the type of the element "mapped-by" field is consistent with the owner type
            //TODO check does not work if mapped by has relation to super class. Enable this
            //and run the PersistentInterfacesTest to reproduce the issue
            //TODO there is equivalent code in FKList and FKMap that was
            //not commented out. When fixing, add tests for all types of Inverses
            /*
            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));
            }
        }

        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 Remove use of containerTable - just use elementTable[0] or equivalent
        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();
        }
        setName = "inverseSet";
    }

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

    /**
     * Generates the statement for clearing items by nulling the owner link out.
     * The statement will be
     * <PRE>
     * UPDATE LISTTABLE SET OWNERCOL=NULL [,DISTINGUISHER=NULL]
     * WHERE OWNERCOL=?
     * </PRE>
     * when there is only one element table, and will be
     * <PRE>
     * UPDATE ? SET OWNERCOL=NULL [,DISTINGUISHER=NULL]
     * WHERE OWNERCOL=?
     * </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)
            {
                //CANNOT USE ? to replace. JDBC drivers does not accept, so we replace
                //the <TABLE NAME> right before firing the statement to the database
                stmt.append("<TABLE NAME>");
            }
            else
            {
                // Could use elementInfo[0].getDatastoreClass but need to allow for relation being 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 (relationDiscriminatorMapping != null)
            {
                for (int i=0; i<relationDiscriminatorMapping.getNumberOfDatastoreFields(); i++)
                {
                    stmt.append(", ");
                    stmt.append(relationDiscriminatorMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString() + " = 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());
            }
            clearNullifyStmt = stmt.toString();
        }
        return clearNullifyStmt;
    }

    /**
     * Generate statement for updating a Foreign Key in an inverse 1-N.
     * The statement generated will be
     * <PRE>
     * UPDATE ELEMENTTABLE SET FK_COL_1=?, FK_COL_2=?, [DISTINGUISHER=?]
     * WHERE ELEMENT_ID = ?
     * </PRE>
     * where there is a single element table, and
     * <PRE>
     * UPDATE ? SET FK_COL_1=?, FK_COL_2=?, [DISTINGUISHER=?]
     * WHERE ELEMENT_ID=?
     * </PRE>
     * where there are more than 1 element tables
     * @return Statement for updating the FK in an inverse 1-N
     */
    private String getUpdateFkStmt()
    {
        if (updateFkStmt == null)
        {
            StringBuffer stmt = new StringBuffer();
            stmt.append("UPDATE ");
            if (elementInfo.length > 1)
            {
                //CANNOT USE ? to replace. JDBC drivers does not accept, so we replace
                //the <TABLE NAME> right before firing the statement to the database
                stmt.append("<TABLE NAME>");
            }
            else
            {
                // Could use elementInfo[0].getDatastoreClass but need to allow for relation being 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 (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;
    }

    /**
     * Utility to update a foreign-key (and distinguisher) 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
     * @return Whether it was performed successfully
     */
    private boolean updateElementFk(StateManager sm, Object element, Object owner)
    {
        if (element == null)
        {
            return false;
        }
        validateElementForWriting(sm, element, null);

        boolean retval;
        ObjectManager om = sm.getObjectManager();
        String stmt = getUpdateFkStmt();
        try
        {
            ManagedConnection mconn = storeMgr.getConnection(om);
            SQLController sqlControl = storeMgr.getSQLController();
            try
            {
                int jdbcPosition = 1;
                if (elementInfo.length > 1)
                {
                    DatastoreClass table = storeMgr.getDatastoreClass(element.getClass().getName(), clr);
                    if (table != null)
                    {
                        stmt = StringUtils.replaceAll(stmt, "<TABLE NAME>", table.toString());
                    }
                    else
                    {
                        JPOXLogger.PERSISTENCE.warn("FKSetStore.updateElementFK : need to set table in statement but dont know table where to store " + element);
                    }
                }
                PreparedStatement ps = sqlControl.getStatementForUpdate(mconn, stmt, false);
                try
                {
                    if (owner == null)
                    {
                        if (ownerMemberMetaData != null)
                        {
                            ownerMapping.setObject(om, ps, Mappings.getParametersIndex(jdbcPosition, ownerMapping),
                                null, sm, ownerMemberMetaData.getAbsoluteFieldNumber());
                        }
                        else
                        {
                            ownerMapping.setObject(om, ps, Mappings.getParametersIndex(jdbcPosition, ownerMapping),
                                null);
                        }
                    }
                    else
                    {
                        if (ownerMemberMetaData != null)
                        {
                            ownerMapping.setObject(om, ps, Mappings.getParametersIndex(jdbcPosition, ownerMapping),
                                sm.getObject(), sm, ownerMemberMetaData.getAbsoluteFieldNumber());
                        }
                        else
                        {
                            ownerMapping.setObject(om, ps, Mappings.getParametersIndex(jdbcPosition, ownerMapping),
                                sm.getObject());
                        }
                    }
                    jdbcPosition += ownerMapping.getNumberOfDatastoreFields();

                    if (relationDiscriminatorMapping != null)
                    {
                        jdbcPosition = populateRelationDiscriminatorInStatement(om, ps, jdbcPosition);
                    }
                    elementMapping.setObject(om, ps, Mappings.getParametersIndex(jdbcPosition, elementMapping), element);
                    jdbcPosition += elementMapping.getNumberOfDatastoreFields();

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

        return retval;
    }

    /**
     * This seems to return the field number in the element of the relation when it is a bidirectional
     * relation.
     * @param sm StateManager of the owner
     * @return The field number in the element for this relation
     */
    protected int getFieldNumberInElementForBidirectional(StateManager sm)
    {
        if (ownerFieldNumber < 0)
        {
            // Unidirectional
            return -1;
        }
        // This gives a different result when using persistent interfaces.
        // For example with the JDO2 TCK, org.apache.jdo.tck.pc.company.PIDepartmentImpl.employees will
        // return 3, yet the ownerMemberMetaData.getRelatedMetaData returns 8 since the generated implementation
        // will have all fields in a single MetaData (numbering from 0), whereas in a normal inheritance
        // tree there will be multiple MetaData (the root starting from 0)
        return sm.getClassMetaData().getAbsolutePositionOfMember(ownerMemberMetaData.getMappedBy());
    }

    /**
     * Method to add an object to the relationship at the collection end.
     * @param sm StateManager of the owner of the Set
     * @param element Element to be added
     * @return Success indicator
     */
    public boolean add(final StateManager sm, Object element, int size)
    {
        if (element == null)
        {
            // Sets allow no duplicates
            throw new JPOXUserException(LOCALISER.msg("056039"));
        }

        final Object newOwner = sm.getObject();
        ObjectManager om = sm.getObjectManager();

        // Make sure that the element is persisted in the datastore (reachability)
        boolean inserted = 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)
                {
                    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)
                    {
                        // Element type has a shared FK so set the discriminator value for this relation
                        esm.setExternalFieldValueForMapping(relationDiscriminatorMapping, relationDiscriminatorValue);
                    }
                }
                if (getFieldNumberInElementForBidirectional(esm) >= 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(getFieldNumberInElementForBidirectional(esm));
                    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(getFieldNumberInElementForBidirectional(esm), newOwner, true);
                    }
                    else if (currentOwner != newOwner && sm.getReferencedPC() == null)
                    {
                        // Owner of the element is neither this container and not 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;
            }
        });

        if (inserted)
        {
            // Element has just been persisted so the FK will be set
            return true;
        }
        else
        {
            // Element was already persistent so make sure the FK is in place
            StateManager elementSM = om.findStateManager(element);
            if (getFieldNumberInElementForBidirectional(elementSM) >= 0 &&
                om.getOMFContext().getPersistenceConfiguration().getBooleanProperty("org.jpox.manageRelationships"))
            {
                // Managed Relations : 1-N bidir, so update the owner of the element
                om.getApiAdapter().isLoaded(elementSM, getFieldNumberInElementForBidirectional(elementSM)); // Ensure is loaded
                Object oldOwner = elementSM.provideField(getFieldNumberInElementForBidirectional(elementSM));
                if (oldOwner != newOwner)
                {
                    if (JPOXLogger.PERSISTENCE.isDebugEnabled())
                    {
                        JPOXLogger.PERSISTENCE.debug(
                            LOCALISER.msg("055009",
                            StringUtils.toJVMIDString(sm.getObject()),
                            ownerMemberMetaData.getFullFieldName(),
                            StringUtils.toJVMIDString(element)));
                    }
                    PersistenceCapable pcElement = (PersistenceCapable) element;
                    elementSM.setObjectField(pcElement, getFieldNumberInElementForBidirectional(elementSM), oldOwner, newOwner);
                    if (om.isFlushing())
                    {
                        elementSM.flush();
                    }
                }
                return oldOwner != newOwner;
            }
            else
            {
                // 1-N unidir so update the FK if not set to be contained in the set
                boolean contained = contains(sm, element);
                return (contained ? false : updateElementFk(sm, element, newOwner));
            }
        }
    }
    /**
     * Method to add a collection of object to the relationship at the
     * collection end.
     * @param sm StateManager of the Set
     * @param elements Elements to be added
     * @return Success indicator
     **/
    public boolean addAll(StateManager sm, Collection elements, int size)
    {
        if (elements == null || elements.size() == 0)
        {
            return false;
        }

        boolean success = false;

        Iterator iter = elements.iterator();
        while (iter.hasNext())
        {
            if (add(sm, iter.next(), -1))
            {
                success = true;
            }
        }

        return success;
    }

    /**
     * Method to remove the link to the collection object specified.
     * Depending on the column characteristics in the collection table, the id
     * of the owner field may be NULLed, or the record may be deleted
     * completely (as per cascade-delete in EJB).
     *
     * @param sm The StateManager of the Set
     * @param element The element of the collection to be deleted.
     * @param allowDependentField Whether to allow any cascade deletes caused by this removal
     * @return A success indicator.
     **/
    public boolean remove(StateManager sm, Object element, int size, boolean allowDependentField)
    {
        if (element == null)
        {
            return false;
        }
        if (!validateElementForReading(sm, element))
        {
            return false;
        }

        ObjectManager om = sm.getObjectManager();
        StateManager esm = om.findStateManager(element);

        Object oldOwner = null;
        if (ownerFieldNumber >= 0)
        {
            if (!om.getApiAdapter().isDeleted(element))
            {
                // Find the existing owner if the record hasn't already been deleted
                om.getApiAdapter().isLoaded(esm, ownerFieldNumber);
                oldOwner = esm.provideField(ownerFieldNumber);
            }
        }
        else
        {
            // TODO Check if the element is managed by a different owner now
        }

        // Owner of the element has been changed
        if (ownerFieldNumber >= 0 && oldOwner != sm.getObject())
        {
            return false;
        }

        if (allowDependentField && ownerMemberMetaData.getCollection().isDependentElement())
        {
            // Delete the element since it is dependent
            om.deleteObjectInternal(element);
        }
        else if (ownerMapping.isNullable())
        {
            // Bidirectional 1-N, so nullify the owner
            PersistenceCapable pcElement = (PersistenceCapable) element;
            if (ownerFieldNumber >= 0)
            {
                esm.setObjectField(pcElement, ownerFieldNumber, oldOwner, null);
            }
            // Unidirectional 1-N, so nullify the owner
            else
            {
                updateElementFk(sm, pcElement, null);
            }
        }
        else
        {
            // otherwise remove the element since we can't null its FK
            om.deleteObjectInternal(element);
        }

        return true;
    }

    /**
     * Method to remove the links to a collection of elements specified.
     * Depending on the column characteristics in the collection table, the id
     * of the owner fields may be NULLed, or the records may be deleted
     * completely.
     *
     * @param sm The StateManager of the Set
     * @param elements The elements of the collection to be deleted.
     * @return A success indicator.
     **/
    public boolean removeAll(StateManager sm, Collection elements, int size)
    {
        if (elements == null || elements.size() == 0)
        {
            return false;
        }

        // Check the first element for whether we can null the column or
        // whether we have to delete
        boolean success = true;

        Iterator iter=elements.iterator();
        while (iter.hasNext())
        {
            if (remove(sm, iter.next(), -1, true))
            {
                success = false;
            }
        }

        return success;
    }

    /**
     * Method to allow the Set relationship to be cleared out.
     * 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 StateManager of the Set
     */
    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())
            {
                // Field is not dependent, but is nullable so we null the FK
                JPOXLogger.DATASTORE.debug(LOCALISER.msg("056036"));
                deleteElements = false;
            }
            else
            {
                // Field is not dependent, and is 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 elementSM = om.findStateManager(element);
                        elementSM.flush();
                    }
                    else
                    {
                        // Element not yet marked for deletion so go through the normal process
                        om.deleteObjectInternal(element);
                    }
                }
            }
        }
        else
        {
            int relationType = ownerMemberMetaData.getRelationType(clr);
            if (relationType == Relation.ONE_TO_MANY_BI &&
                om.getOMFContext().getPersistenceConfiguration().getBooleanProperty("org.jpox.manageRelationships"))
            {
                // Managed Relations : 1-N bidir so null the owner on the elements
                om.getApiAdapter().isLoaded(ownerSM, ownerMemberMetaData.getAbsoluteFieldNumber()); // Make sure the field is loaded
                Collection value = (Collection) ownerSM.provideField(ownerMemberMetaData.getAbsoluteFieldNumber());
                Iterator elementsIter = null;
                if (value != null && !value.isEmpty())
                {
                    elementsIter = value.iterator();
                }
                else
                {
                    // Maybe deleting the owner with optimistic transactions so the elements are no longer cached
                    elementsIter = iterator(ownerSM);
                }
                if (elementsIter != null)
                {
                    while (elementsIter.hasNext())
                    {
                        Object element = elementsIter.next();
                        if (!om.getApiAdapter().isDeleted(element))
                        {
                            StateManager elementSM = om.findStateManager(element);
                            if (elementSM != null)
                            {
                                // Null the owner of the element
                                if (JPOXLogger.PERSISTENCE.isDebugEnabled())
                                {
                                    JPOXLogger.PERSISTENCE.debug(
                                        LOCALISER.msg("055010",
                                        StringUtils.toJVMIDString(ownerSM.getObject()),
                                        ownerMemberMetaData.getFullFieldName(),
                                        StringUtils.toJVMIDString(element)));
                                }

                                elementSM.replaceField(getFieldNumberInElementForBidirectional(elementSM), null, true);
                                if (om.isFlushing())
                                {
                                    // Make sure this change gets flushed
                                    elementSM.flush();
                                }
                            }
                        }
                    }
                }
            }

            // Clear the FKs in the datastore
            // TODO This is likely not necessary in the 1-N bidir case since we've just set the owner FK to null above
            String stmt = getClearNullifyStmt();
            try
            {
                if (elementInfo.length > 1)
                {
                    // TODO we should loop over all tables that has elements stored into and clear all them
                    DatastoreClass table = storeMgr.getDatastoreClass(elementInfo[0].getClassName(), clr);
                    if (table != null)
                    {
                        stmt = StringUtils.replaceAll(stmt, "<TABLE NAME>", table.toString());
                    }
                    else
                    {
                        JPOXLogger.PERSISTENCE.warn("FKSetStore.updateElementFK : " +
                            "need to set table in statement but dont know table where to store " +
                            elementInfo[0].getClassName());
                    }
                }

                ManagedConnection mconn = storeMgr.getConnection(om);
                SQLController sqlControl = storeMgr.getSQLController();
                try
                {
                    PreparedStatement ps = sqlControl.getStatementForUpdate(mconn, stmt, false);
                    try
                    {
                        int jdbcPosition = 1;
                        jdbcPosition = populateOwnerInStatement(ownerSM, om, ps, jdbcPosition);
                        sqlControl.executeStatementUpdate(mconn, stmt, ps, true);
                    }
                    finally
                    {
                        sqlControl.closeStatement(mconn, ps);
                    }
                }
                finally
                {
                    mconn.release();
                }
            }
            catch (SQLException e)
            {
                throw new JPOXDataStoreException(LOCALISER.msg("056013",stmt),e);
            }
        }
    }

    /**
     * Accessor for a QueryStatement to retrieve the elements of the set.
     * @param ownerSM the owner StateManager
     * @return The QueryStatement to retrieve the elements
     **/   
    protected QueryExpression getIteratorStatement(StateManager ownerSM)
    {
        if (elementInfo == null || elementInfo.length == 0)
        {
            return null;
        }

        final ClassLoaderResolver clr = ownerSM.getObjectManager().getClassLoaderResolver();
        QueryExpression stmt = null;
        if (elementInfo[0].getDatastoreClass().getDiscriminatorMetaData() != null &&
            elementInfo[0].getDatastoreClass().getDiscriminatorMetaData().getStrategy() != DiscriminatorStrategy.NONE)
        {
            //when one uses discriminator, all must have
            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 Class elementCls = clr.classForName(this.elementInfo[i].getClassName());
                final int elementNo = i;
                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);
        }

        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 the candidateAlias
        if (!sm.getObjectManager().getClassLoaderResolver().isAssignableFrom(elementType, candidateClass))
        {
            throw new IncompatibleQueryElementTypeException(elementType, candidateClass);
        }

        ClassLoaderResolver clr = sm.getObjectManager().getClassLoaderResolver();
        DatastoreContainerObject 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;
    }

    /**
     * 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 there is no parent, <code>parentStmt</code> must be equals to <code>stmt</code>
     * @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 setTableAlias The alias for the "Set" table
     * @param existsQuery Whether this is joining for an EXISTS query
     * @return Expression for the id of the elements table
     */
    public ScalarExpression joinElementsTo(QueryExpression stmt,
            QueryExpression parentStmt,
            JavaTypeMapping ownerMapping,
            LogicSetExpression ownerTe,
            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());
        }
        DatastoreContainerObject filteredElementTable = storeMgr.getDatastoreClass(filteredElementType.getName(), clr);

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

        DatastoreIdentifier containerRangeVar = setTableAlias;
        if (existsQuery)
        {
            // Part of EXISTS query. Why do we treat this differently ?????????????
            if (stmt.getTableExpression(containerRangeVar) == null)
            {
                // WHY????????????????????????
            }
            if (stmt.getTableExpression(containerRangeVar) == null)
            {
                // Create the container table if not yet present in the statement
                containerRangeVar = elementTableAlias;
                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.FKSetStore

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.