Package org.jpox.store.rdbms.scostore

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

/**********************************************************************
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 Erik Bengtson - removed unused variable
2003 Andy Jefferson - coding standards
2003 Andy Jefferson - addition of getGetStatement for inherited values
2004 Andy Jefferson - addition of query methods
2004 Marco Schulze  - replaced catch(NotPersistenceCapableException ...) by
                      advance-check via TypeManager.isSupportedType(...)
2004 Andy Jefferson - moved statements to AbstractMapStore
2004 Andy Jefferson - added support for 1-N unidirectional
2007 Andy Jefferson - added support for restricted 1-N bi where key/value class is subclass
    ...
**********************************************************************/
package org.jpox.store.rdbms.scostore;

import java.sql.PreparedStatement;
import java.sql.SQLException;
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.ObjectManagerHelper;
import org.jpox.StateManager;
import org.jpox.api.ApiAdapter;
import org.jpox.exceptions.JPOXDataStoreException;
import org.jpox.exceptions.JPOXUserException;
import org.jpox.metadata.AbstractMemberMetaData;
import org.jpox.metadata.DiscriminatorStrategy;
import org.jpox.metadata.MapMetaData;
import org.jpox.store.FieldValues;
import org.jpox.store.exceptions.ClassDefinitionException;
import org.jpox.store.mapped.DatastoreContainerObject;
import org.jpox.store.mapped.DatastoreIdentifier;
import org.jpox.store.mapped.expression.BooleanExpression;
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.mapped.mapping.SerialisedMapping;
import org.jpox.store.mapped.mapping.SimpleDatastoreRepresentation;
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.store.scostore.SetStore;
import org.jpox.util.ClassUtils;

/**
* Representation of a FK backing for a Map. This class is used where you have a 1-N and the tables
* are not joined via a link table, instead using a FK. There are 2 possible uses here
* <UL>
* <LI><B>bidirectional</B> - where the owner has a Map of (key,value), and the key/value has an owner.
* In this case the key/value class will have an owner field which can be updated directly</LI>
* <LI><B>unidirectional</B> - where the owner has a Map of (key,value), but the key/value knows nothing
* about the owner. In this case the key/value class has no owner field. In this case the column in the
* key/value table has to be updated directly.</LI>
* </UL>
* In both cases the value class will have a field that represents the key, and so the user must specify
* a 'key mapped-by="..."' attribute to denote which field is the key, or a 'value mapped-by="..."' to
* denote which field is the value.
*
* @version $Revision: 1.43 $
**/
public class FKMapStore extends AbstractMapStore
{
    /** Field number of owner link in value class. */
    private final int ownerFieldNumber;

    /** Field number of key in value class (when Key=Non-PC, Value=PC). */
    private int keyFieldNumber = -1;

    /** Field number of value in key class (when Key=PC, value=Non-PC). */
    private int valueFieldNumber = -1;

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

    private SetStore keySetStore = null;
    private SetStore valueSetStore = null;
    private SetStore entrySetStore = null;

    /**
     * Constructor for an Inverse Map.
     * @param fmd Field Meta-Data for the Map field.
     * @param storeMgr The Store Manager we are using.
     * @param clr The ClassLoaderResolver
     **/
    public FKMapStore(AbstractMemberMetaData fmd, RDBMSManager storeMgr, ClassLoaderResolver clr)
    {
        super(storeMgr);

        setOwnerMemberMetaData(fmd);
        String ownerFieldName = fmd.getMappedBy();
        MapMetaData mmd = (MapMetaData)fmd.getContainer();
        if (mmd == null)
        {
            // No <map> specified for this field!
            throw new JPOXUserException(LOCALISER.msg("056002", fmd.getFullFieldName()));
        }

        // Check whether we store the key in the value, or the value in the key
        boolean keyStoredInValue = false;
        if (fmd.getKeyMetaData() != null && fmd.getKeyMetaData().getMappedBy() != null)
        {
            keyStoredInValue = true;
        }
        else if (fmd.getValueMetaData() != null && fmd.getValueMetaData().getMappedBy() == null)
        {
            // No mapped-by specified on either key or value so we dont know what to do with this relation!
            throw new JPOXUserException(LOCALISER.msg("056071", fmd.getFullFieldName()));
        }
        else
        {
            // Should throw an exception since must store key in value or value in key
        }

        // Load the key and value classes
        keyType = mmd.getKeyType();
        valueType = mmd.getValueType();
        Class keyClass = clr.classForName(keyType);
        Class valueClass = clr.classForName(valueType);

        ApiAdapter api = getStoreManager().getApiAdapter();
        if (keyStoredInValue && !api.isPersistable(valueClass))
        {
            // key stored in value but value is not PC!
            throw new JPOXUserException(LOCALISER.msg("056072", fmd.getFullFieldName(), valueType));
        }
        if (!keyStoredInValue && !api.isPersistable(keyClass))
        {
            // value stored in key but key is not PC!
            throw new JPOXUserException(LOCALISER.msg("056073", fmd.getFullFieldName(), keyType));
        }

        if (keyStoredInValue)
        {
            // Key = field in value, Value = PC
            vmd = storeMgr.getOMFContext().getMetaDataManager().getMetaDataForClass(valueClass, clr);
            if (vmd == null)
            {
                // Value has no MetaData!
                throw new JPOXUserException(LOCALISER.msg("056070", valueType, fmd.getFullFieldName()));
            }

            valueTable = storeMgr.getDatastoreClass(valueType, clr);
            valueMapping  = storeMgr.getDatastoreClass(valueType, clr).getIDMapping();
            valuesAreEmbedded = false;
            valuesAreSerialised = false;

            if (fmd.getMappedBy() != null)
            {
                // 1-N bidirectional : The value class has a field for the owner.
                AbstractMemberMetaData vofmd = vmd.getMetaDataForMember(ownerFieldName);
                if (vofmd == null)
                {
                    throw new JPOXUserException(LOCALISER.msg("056067", fmd.getFullFieldName(),
                        ownerFieldName, valueClass.getName()));
                }

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

                ownerFieldNumber = vmd.getAbsolutePositionOfMember(ownerFieldName);
                ownerMapping = valueTable.getFieldMapping(vofmd);
                if (ownerMapping == null)
                {
                    throw new JPOXUserException(LOCALISER.msg("RDBMS.SCO.Map.InverseOwnerMappedByFieldNotPresent",
                        fmd.getAbstractClassMetaData().getFullClassName(), fmd.getName(), valueType, ownerFieldName));
                }
                if (isEmbeddedMapping(ownerMapping))
                {
                    throw new JPOXUserException(LOCALISER.msg("056055",
                        ownerFieldName, valueType, vofmd.getTypeName(), fmd.getClassName()));
                }
            }
            else
            {
                // 1-N Unidirectional : The value class knows nothing about the owner
                ownerFieldNumber = -1;
                ownerMapping = valueTable.getExternalMapping(fmd, MappingConsumer.MAPPING_TYPE_EXTERNAL_FK);
                if (ownerMapping == null)
                {
                    throw new JPOXUserException(LOCALISER.msg("056056",
                        fmd.getAbstractClassMetaData().getFullClassName(), fmd.getName(), valueType));
                }
            }

            AbstractMemberMetaData vkfmd = null;
            if (fmd.getKeyMetaData() == null || fmd.getKeyMetaData().getMappedBy() == null)
            {
                if (vkfmd == null)
                {
                    throw new JPOXUserException(LOCALISER.msg("056050", valueClass.getName()));
                }
            }

            String key_field_name = fmd.getKeyMetaData().getMappedBy();
            if (key_field_name != null)
            {
                // check if key field exists in the ClassMetaData for the element-value type
                vkfmd = storeMgr.getOMFContext().getMetaDataManager().getMetaDataForMember(valueClass, clr, key_field_name);
                if (vkfmd == null)
                {
                    throw new JPOXUserException(LOCALISER.msg("056052", valueClass.getName(), key_field_name));
                }
            }
            if (vkfmd == null)
            {
                throw new ClassDefinitionException(LOCALISER.msg("056050", fmd.getFullFieldName()));
            }

            // Check that the key type is correct for the declared type
            if (!ClassUtils.typesAreCompatible(vkfmd.getType(), keyType, clr))
            {
                throw new JPOXUserException(LOCALISER.msg("056051",
                    fmd.getFullFieldName(), keyType, vkfmd.getType().getName()));
            }

            // Set up key field
            String keyFieldName = vkfmd.getName();
            keyFieldNumber = vmd.getAbsolutePositionOfMember(keyFieldName);
            keyMapping = valueTable.getFieldMapping(vmd.getMetaDataForManagedMemberAtAbsolutePosition(keyFieldNumber));
            if (keyMapping == null)
            {
                throw new JPOXUserException(LOCALISER.msg("056053",
                    fmd.getAbstractClassMetaData().getFullClassName(), fmd.getName(), valueType, keyFieldName));
            }

            if (!SimpleDatastoreRepresentation.class.isAssignableFrom(keyMapping.getClass()))
            {
                // Check the type of the mapping
                throw new JPOXUserException("Invalid field type for map key field: " + fmd.getClassName() + "." + fmd.getName());
            }
            keysAreEmbedded = isEmbeddedMapping(keyMapping);
            keysAreSerialised = isEmbeddedMapping(keyMapping);

            mapTable = valueTable;
            if (fmd.getMappedBy() != null && ownerMapping.getDatastoreContainer() != mapTable)
            {
                // Value and owner don't have consistent tables so use the one with the mapping
                // e.g map value is subclass, yet superclass has the link back to the owner
                mapTable = ownerMapping.getDatastoreContainer();
            }
        }
        else
        {
            // Key = PC, Value = field in key
            kmd = storeMgr.getOMFContext().getMetaDataManager().getMetaDataForClass(keyClass, clr);
            if (kmd == null)
            {
                // Key has no MetaData!
                throw new JPOXUserException(LOCALISER.msg("056069", keyType, fmd.getFullFieldName()));
            }

            // TODO This should be called keyvalueTable or something and not valueTable
            valueTable = storeMgr.getDatastoreClass(keyType, clr);
            keyMapping  = storeMgr.getDatastoreClass(keyType, clr).getIDMapping();
            keysAreEmbedded = false;
            keysAreSerialised = false;

            if (fmd.getMappedBy() != null)
            {
                // 1-N bidirectional : The key class has a field for the owner.
                AbstractMemberMetaData kofmd = kmd.getMetaDataForMember(ownerFieldName);
                if (kofmd == null)
                {
                    throw new JPOXUserException(LOCALISER.msg("056067", fmd.getFullFieldName(),
                        ownerFieldName, keyClass.getName()));
                }

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

                ownerFieldNumber = kmd.getAbsolutePositionOfMember(ownerFieldName);
                ownerMapping = valueTable.getFieldMapping(kofmd);
                if (ownerMapping == null)
                {
                    throw new JPOXUserException(LOCALISER.msg("RDBMS.SCO.Map.InverseOwnerMappedByFieldNotPresent",
                        fmd.getAbstractClassMetaData().getFullClassName(), fmd.getName(), keyType, ownerFieldName));
                }
                if (isEmbeddedMapping(ownerMapping))
                {
                    throw new JPOXUserException(LOCALISER.msg("056055",
                        ownerFieldName, keyType, kofmd.getTypeName(), fmd.getClassName()));
                }
            }
            else
            {
                // 1-N Unidirectional : The key class knows nothing about the owner
                ownerFieldNumber = -1;
                ownerMapping = valueTable.getExternalMapping(fmd, MappingConsumer.MAPPING_TYPE_EXTERNAL_FK);
                if (ownerMapping == null)
                {
                    throw new JPOXUserException(LOCALISER.msg("056056",
                        fmd.getAbstractClassMetaData().getFullClassName(), fmd.getName(), keyType));
                }
            }

            AbstractMemberMetaData vkfmd = null;
            if (fmd.getValueMetaData() == null || fmd.getValueMetaData().getMappedBy() == null)
            {
                if (vkfmd == null)
                {
                    throw new JPOXUserException(LOCALISER.msg("056057", keyClass.getName()));
                }
            }

            String value_field_name = fmd.getValueMetaData().getMappedBy();
            if (value_field_name != null)
            {
                // check if value field exists in the ClassMetaData for the element-value type
                vkfmd = storeMgr.getOMFContext().getMetaDataManager().getMetaDataForMember(keyClass, clr, value_field_name);
                if (vkfmd == null)
                {
                    throw new JPOXUserException(LOCALISER.msg("056059", keyClass.getName(), value_field_name));
                }
            }
            if (vkfmd == null)
            {
                throw new ClassDefinitionException(LOCALISER.msg("056057", fmd.getFullFieldName()));
            }

            // Check that the value type is consistent with the declared type
            if (!ClassUtils.typesAreCompatible(vkfmd.getType(), valueType, clr))
            {
                throw new JPOXUserException(LOCALISER.msg("056058",
                    fmd.getFullFieldName(), valueType, vkfmd.getType().getName()));
            }

            // Set up value field
            String valueFieldName = vkfmd.getName();
            valueFieldNumber = kmd.getAbsolutePositionOfMember(valueFieldName);
            valueMapping = valueTable.getFieldMapping(kmd.getMetaDataForManagedMemberAtAbsolutePosition(valueFieldNumber));
            if (valueMapping == null)
            {
                throw new JPOXUserException(LOCALISER.msg("056054",
                    fmd.getAbstractClassMetaData().getFullClassName(), fmd.getName(), keyType, valueFieldName));
            }

            if (!SimpleDatastoreRepresentation.class.isAssignableFrom(valueMapping.getClass()))
            {
                // Check the type of the mapping
                throw new JPOXUserException("Invalid field type for map value field: " + fmd.getClassName() + "." + fmd.getName());
            }
            valuesAreEmbedded = isEmbeddedMapping(valueMapping);
            valuesAreSerialised = isEmbeddedMapping(valueMapping);

            mapTable = valueTable;
            if (fmd.getMappedBy() != null && ownerMapping.getDatastoreContainer() != mapTable)
            {
                // Key and owner don't have consistent tables so use the one with the mapping
                // e.g map key is subclass, yet superclass has the link back to the owner
                mapTable = ownerMapping.getDatastoreContainer();
            }
        }

        // Generate the statements
        initialiseStatements();
        updateFkStmt = getUpdateFkStmt();
    }

    /**
     * Generate statement for updating a Foreign Key from key/value to owner in an inverse 1-N.
     * <PRE>
     * UPDATE MAPTABLE SET FK_COL_1 = ?, FK_COL_2 = ?
     * WHERE ELEMENT_ID = ?
     * </PRE>
     * @return Statement for updating the FK in an inverse 1-N
     */
    private String getUpdateFkStmt()
    {
        StringBuffer stmt = new StringBuffer();
        stmt.append("UPDATE ");
        stmt.append(mapTable.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());
        }
        stmt.append(" WHERE ");
        if (keyFieldNumber >= 0)
        {
            for (int i=0; i<valueMapping.getNumberOfDatastoreFields(); i++)
            {
                if (i > 0)
                {
                    stmt.append(" AND ");
                }
                stmt.append(valueMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
                stmt.append(" = ");
                stmt.append(((RDBMSMapping)valueMapping.getDataStoreMapping(i)).getUpdateInputParameter());
            }
        }
        else
        {
            for (int i=0; i<keyMapping.getNumberOfDatastoreFields(); i++)
            {
                if (i > 0)
                {
                    stmt.append(" AND ");
                }
                stmt.append(keyMapping.getDataStoreMapping(i).getDatastoreField().getIdentifier().toString());
                stmt.append(" = ");
                stmt.append(((RDBMSMapping)keyMapping.getDataStoreMapping(i)).getUpdateInputParameter());
            }
        }

        return stmt.toString();
    }

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

        boolean retval;
        ObjectManager om = sm.getObjectManager();
        try
        {
            ManagedConnection mconn = storeMgr.getConnection(om);
            SQLController sqlControl = storeMgr.getSQLController();
            try
            {
                PreparedStatement ps = sqlControl.getStatementForUpdate(mconn, updateFkStmt, false);
                try
                {
                    int jdbcPosition = 1;
                    if (owner == null)
                    {
                        if (ownerMemberMetaData != null)
                        {
                            ownerMapping.setObject(om, ps, Mappings.getParametersIndex(1,ownerMapping), null,
                                sm, ownerMemberMetaData.getAbsoluteFieldNumber());
                        }
                        else
                        {
                            ownerMapping.setObject(om, ps, Mappings.getParametersIndex(1,ownerMapping), null);
                        }
                        jdbcPosition += ownerMapping.getNumberOfDatastoreFields();
                    }
                    else
                    {
                        jdbcPosition = populateOwnerInStatement(sm, om, ps, jdbcPosition);
                    }
                    jdbcPosition = populateValueInStatement(om, ps, value, jdbcPosition);

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

        return retval;
    }

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

        boolean retval;
        ObjectManager om = sm.getObjectManager();
        try
        {
            ManagedConnection mconn = storeMgr.getConnection(om);
            SQLController sqlControl = storeMgr.getSQLController();
            try
            {
                PreparedStatement ps = sqlControl.getStatementForUpdate(mconn, updateFkStmt, false);
                try
                {
                    int jdbcPosition = 1;
                    if (owner == null)
                    {
                        if (ownerMemberMetaData != null)
                        {
                            ownerMapping.setObject(om, ps, Mappings.getParametersIndex(1,ownerMapping), null,
                                sm, ownerMemberMetaData.getAbsoluteFieldNumber());
                        }
                        else
                        {
                            ownerMapping.setObject(om, ps, Mappings.getParametersIndex(1,ownerMapping), null);
                        }
                        jdbcPosition += ownerMapping.getNumberOfDatastoreFields();
                    }
                    else
                    {
                        jdbcPosition = populateOwnerInStatement(sm, om, ps, jdbcPosition);
                    }
                    jdbcPosition = populateKeyInStatement(om, ps, key, jdbcPosition);

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

        return retval;
    }

    // ---------------------------- Statement Methods ------------------------------

    /**
     * Accessor for the Get statement to retrieve a value from the Map.
     * @param ownerSm The owner StateManager
     * @param key The search key
     * @return The QueryStatement.
     **/
    protected QueryExpression getGetStatement(StateManager ownerSm, Object key)
    {
        final ClassLoaderResolver clr = ownerSm.getObjectManager().getClassLoaderResolver();
        final Class valueCls = clr.classForName(valueType);
        QueryExpression stmt = null;
        if (valueTable.getDiscriminatorMetaData() != null && valueTable.getDiscriminatorMetaData().getStrategy() != DiscriminatorStrategy.NONE)
        {
            if (storeMgr.getOMFContext().getTypeManager().isReferenceType(clr.classForName(ownerMemberMetaData.getMap().getValueType())))
            {               
                // Take the metadata for the first implementation of the reference type
                String[] clsNames = storeMgr.getOMFContext().getMetaDataManager().getClassesImplementingInterface(ownerMemberMetaData.getMap().getValueType(), 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[] {valueCls},
                    true, this.storeMgr, true).getQueryStatement(null);
            }
            iterateUsingDiscriminator = true;
        }
        else
        {
            stmt = new UnionIteratorStatement(clr, valueCls, true, this.storeMgr,
                valueCls, valueMapping, valueTable, false, null, true, false).getQueryStatement(null);
        }

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

        //TODO
        //this is a database hack. :-)
        //if the keyMapping contains a BLOB column (or any other column not supported by the database
        //as primary key), uses like instead of the operator OP_EQ (=)
        //in future do not check if the keyMapping is of ObjectMapping, but use the database
        //adapter to check the data types not supported as primary key
        //
        //if object mapping (BLOB) use like

        if (keyMapping instanceof SerialisedMapping)
        {
            // Apply condition on join-table owner field to filter by owner
            ScalarExpression keyExpr = keyMapping.newScalarExpression(stmt, stmt.getMainTableExpression());
            ScalarExpression keyVal = keyMapping.newLiteral(stmt, key).add(dba.getMapping(String.class, storeMgr).newLiteral(stmt,"%"));
            stmt.andCondition(new BooleanExpression(keyExpr,ScalarExpression.OP_LIKE,keyVal), true);            
        }
        else
        {
            // Apply condition on join-table owner field to filter by owner
            ScalarExpression keyExpr = keyMapping.newScalarExpression(stmt, stmt.getMainTableExpression());
            ScalarExpression keyVal = keyMapping.newLiteral(stmt, key);
            stmt.andCondition(keyExpr.eq(keyVal), true);
        }
        return stmt;
    }

    /**
     * Utility to validate the type of a value for storing in the Map.
     * @param value The value to check.
     * @param clr The ClassLoaderResolver
     **/
    protected void validateValueType(ClassLoaderResolver clr, Object value)
    {
        if (value == null)
        {
            throw new NullPointerException(LOCALISER.msg("056063"));
        }

        super.validateValueType(clr, value);
    }

    /**
     * Method to put an item in the Map.
     * @param sm State Manager for the map.
     * @param newKey The key to store the value against
     * @param newValue The value to store.
     * @return The value stored.
     **/
    public Object put(StateManager sm, final Object newKey, Object newValue)
    {
        if (keyFieldNumber >= 0)
        {
            validateKeyForWriting(sm, newKey);
            validateValueType(sm.getObjectManager().getClassLoaderResolver(), newValue);
        }
        else
        {
            validateKeyType(sm.getObjectManager().getClassLoaderResolver(), newKey);
            validateValueForWriting(sm, newValue);
        }

        // Check if there's an existing value having the key.
        // If so, remove that object from the map by nulling out its key field.
        Object oldValue = get(sm, newKey);
        if (oldValue != newValue)
        {
            if (vmd != null)
            {
                // Key is stored in the value and the value has changed so remove the old value
                if (oldValue != null)
                {
                    removeValue(sm, newKey, oldValue);
                }

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

                if (om.getApiAdapter().isPersistent(newValue))
                {
                    /*
                     * The new value is already persistent.
                     *
                     * "Put" the new value in the map by updating its owner and key
                     * fields to the appropriate values.  This is done with the same
                     * methods the PC itself would use if the application code
                     * modified the fields.  It should result in no actual database
                     * activity if the fields were already set to the right values.
                     */
                    PersistenceCapable newValuePC = (PersistenceCapable)newValue;
                    if (om != ObjectManagerHelper.getObjectManager(newValue))
                    {
                        throw new JPOXUserException(LOCALISER.msg("RDBMS.SCO.Map.WriteValudInvalidWithDifferentPM"), newValuePC.jdoGetObjectId());
                    }

                    StateManager vsm = om.findStateManager(newValue);
                   
                    // Ensure the current owner field is loaded, and replace with new value
                    if (ownerFieldNumber >= 0)
                    {
                        om.getApiAdapter().isLoaded(vsm, ownerFieldNumber);
                        Object oldOwner = vsm.provideField(ownerFieldNumber);
                        vsm.setObjectField(newValuePC, ownerFieldNumber, oldOwner, newOwner);
                    }
                    else
                    {
                        updateValueFk(sm, newValue, newOwner);
                    }
                   
                    // Ensure the current key field is loaded, and replace with new value
                    om.getApiAdapter().isLoaded(vsm, keyFieldNumber);
                    Object oldKey = vsm.provideField(keyFieldNumber);
                    vsm.setObjectField(newValuePC, keyFieldNumber, oldKey, newKey);
                }
                else
                {                 
                    /*
                     * The new value is not yet persistent.
                     *
                     * Update its owner and key fields to the appropriate values and
                     * *then* make it persistent.  Making the changes before DB
                     * insertion avoids an unnecessary UPDATE allows the owner
                     * and/or key fields to be non-nullable.
                     */
                    om.persistObjectInternal(newValue, new FieldValues()
                        {
                        public void fetchFields(StateManager vsm)
                        {
                            if (ownerFieldNumber >= 0)
                            {
                                vsm.replaceField(ownerFieldNumber, newOwner, true);
                            }
                            vsm.replaceField(keyFieldNumber, newKey, true);
                        }
                        public void fetchNonLoadedFields(StateManager sm)
                        {
                        }
                        public FetchPlan getFetchPlanForLoading()
                        {
                            return null;
                        }
                        }, null, -1, StateManager.PC);
                    if (ownerFieldNumber < 0)
                    {
                        updateValueFk(sm, newValue, newOwner);
                    }
                }
            }
            else
            {
                // Value is stored in the key
                ObjectManager om = sm.getObjectManager();
                PersistenceCapable pcNewKey = (PersistenceCapable)newKey;
                final Object newOwner = sm.getObject();

                if (om.getApiAdapter().isPersistent(pcNewKey))
                {
                    /*
                     * The new key is already persistent.
                     *
                     * "Put" the new key in the map by updating its owner and value
                     * fields to the appropriate values. This is done with the same
                     * methods the PC itself would use if the application code
                     * modified the fields. It should result in no actual database
                     * activity if the fields were already set to the right values.
                     */
                    if (om != ObjectManagerHelper.getObjectManager(pcNewKey))
                    {
                        throw new JPOXUserException(LOCALISER.msg("056060"),
                            om.getApiAdapter().getIdForObject(pcNewKey));
                    }

                    StateManager vsm = om.findStateManager(pcNewKey);

                    // Ensure the current owner field is loaded, and replace with new key
                    if (ownerFieldNumber >= 0)
                    {
                        om.getApiAdapter().isLoaded(vsm, ownerFieldNumber);
                        Object oldOwner = vsm.provideField(ownerFieldNumber);
                        vsm.setObjectField(pcNewKey, ownerFieldNumber, oldOwner, newOwner);
                    }
                    else
                    {
                        updateKeyFk(sm, pcNewKey, newOwner);
                    }

                    // Ensure the current value field is loaded, and replace with new value
                    om.getApiAdapter().isLoaded(vsm, valueFieldNumber);
                    oldValue = vsm.provideField(valueFieldNumber); // TODO Should we update the local variable ?
                    vsm.setObjectField(pcNewKey, valueFieldNumber, oldValue, newValue);
                }
                else
                {
                    /*
                     * The new key is not yet persistent.
                     *
                     * Update its owner and key fields to the appropriate values and
                     * *then* make it persistent.  Making the changes before DB
                     * insertion avoids an unnecessary UPDATE allows the owner
                     * and/or key fields to be non-nullable.
                     */
                    final Object newValueObj = newValue;
                    om.persistObjectInternal(newKey, new FieldValues()
                        {
                        public void fetchFields(StateManager vsm)
                        {
                            if (ownerFieldNumber >= 0)
                            {
                                vsm.replaceField(ownerFieldNumber, newOwner, true);
                            }
                            vsm.replaceField(valueFieldNumber, newValueObj, true);
                        }
                        public void fetchNonLoadedFields(StateManager sm)
                        {
                        }
                        public FetchPlan getFetchPlanForLoading()
                        {
                            return null;
                        }
                        }, null, -1, StateManager.PC
                    );
                   
                    if (ownerFieldNumber < 0)
                    {
                        updateKeyFk(sm, pcNewKey, newOwner);
                    }
                }
            }
        }

        // TODO Cater for key being PC and having delete-dependent
        if (ownerMemberMetaData.getMap().isDependentValue() && oldValue != null)
        {
            // Delete the old value if it is no longer contained and is dependent
            if (!containsValue(sm, oldValue))
            {
                sm.getObjectManager().deleteObjectInternal(oldValue);
            }
        }

        return oldValue;
    }

    /**
     * Method to remove an item from the map.
     * @param sm State Manager for the map.
     * @param key Key of the item to remove.
     * @return The value that was removed.
     **/
    public Object remove(StateManager sm, Object key)
    {
        Object oldValue = get(sm, key);
        if (keyFieldNumber >= 0)
        {
            if (oldValue != null)
            {
                removeValue(sm, key, oldValue);
                sm.getObjectManager().flushInternal(false); // Make sure it is in the datastore
            }
        }
        else
        {
            sm.getObjectManager().deleteObjectInternal(key);
        }

        if (ownerMemberMetaData.getMap().isDependentKey())
        {
            // Delete the key if it is dependent
            sm.getObjectManager().deleteObjectInternal(key);
        }

        if (ownerMemberMetaData.getMap().isDependentValue())
        {
            // Delete the value if it is dependent
            sm.getObjectManager().deleteObjectInternal(oldValue);
        }

        return oldValue;
    }

    /**
     * Utility to remove a value from the Map.
     * @param sm State Manager for the map.
     * @param key Key of the object
     * @param oldValue Value to remove
     **/
    private void removeValue(StateManager sm, Object key, Object oldValue)
    {
        ObjectManager om = sm.getObjectManager();
       
        // Null out the key and owner fields if they are nullable
        if (keyMapping.isNullable())
        {
            PersistenceCapable pcOldValue = (PersistenceCapable)oldValue;
            StateManager vsm = om.findStateManager(pcOldValue);
           
            // Null the key field
            vsm.setObjectField(pcOldValue, keyFieldNumber, key, null);
            vsm.replaceField(keyFieldNumber, null, true);
           
            // Null the owner field
            if (ownerFieldNumber >= 0)
            {
                Object oldOwner = vsm.provideField(ownerFieldNumber);
                vsm.setObjectField(pcOldValue, ownerFieldNumber, oldOwner, null);
                vsm.replaceField(ownerFieldNumber, null, true);
            }
            else
            {
                updateValueFk(sm, pcOldValue, null);
            }
        }
        // otherwise just delete the item
        else
        {
            om.deleteObjectInternal(oldValue);
        }
    }

    /**
     * Method to clear the map of all values.
     * @param sm State Manager for the map.
     **/
    public void clear(StateManager sm)
    {
        // TODO Fix this. Should not be retrieving objects only to remove them since they
        // may be cached in the org.jpox.sco object. But we need to utilise delete-dependent correctly too
        Iterator iter = keySetStore(sm.getObjectManager().getClassLoaderResolver()).iterator(sm);
        while (iter.hasNext())
        {
            remove(sm, iter.next());
        }
    }

    /**
     * Utility to clear the key of a value from the Map.
     * If the key is non nullable, delete the value.
     * @param sm State Manager for the map.
     * @param key Key of the object
     * @param oldValue Value to remove
     **/
    public void clearKeyOfValue(StateManager sm, Object key, Object oldValue)
    {
        ObjectManager om = sm.getObjectManager();

        // Null out the key and owner fields if they are nullable
        if (keyMapping.isNullable())
        {
            StateManager vsm = om.findStateManager(oldValue);

            // Check that the value hasn't already been deleted due to being removed from the map
            PersistenceCapable oldValuePC = (PersistenceCapable)oldValue;
            if (!om.getApiAdapter().isDeleted(oldValuePC))
            {
                // Null the key field
                vsm.setObjectField(oldValuePC, keyFieldNumber, key, null);
                vsm.replaceField(keyFieldNumber, null, true);
                vsm.makeDirty(keyFieldNumber);
            }
        }
        // otherwise just delete the item
        else
        {
            om.deleteObjectInternal(oldValue);
        }
    }

    /**
     * Accessor for the keys in the Map.
     * @param clr The ClassLoaderResolver
     * @return The keys
     **/
    public synchronized SetStore keySetStore(ClassLoaderResolver clr)
    {
        return (keySetStore == null) ?
            new MapKeySetStore(valueTable, ownerMapping, keyMapping, clr, ownerMemberMetaData) : keySetStore;
    }

    /**
     * Accessor for the values in the Map.
     * @param clr The ClassLoaderResolver
     * @return The values.
     **/
    public synchronized SetStore valueSetStore(ClassLoaderResolver clr)
    {
        return (valueSetStore == null) ?
            new MapValueSetStore(valueTable, ownerMapping, valueMapping, this, clr, ownerMemberMetaData) : valueSetStore;
    }

    /**
     * Accessor for the map entries in the Map.
     * @return The map entries.
     **/
    public synchronized SetStore entrySetStore()
    {
        return (entrySetStore == null) ?
            new MapEntrySetStore(valueTable, ownerMapping, keyMapping, valueMapping, this) : entrySetStore;
    }

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

     /**
     * Utility method to return a new QueryStatement for retrieval of the values of this Map.
     * @param sm StateManager for this object
     * @param candidateClass Class for the value 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
        if (!sm.getObjectManager().getClassLoaderResolver().isAssignableFrom(valueType, candidateClass))
        {
            throw new IncompatibleQueryElementTypeException(valueType,candidateClass);
        }

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

        // QueryStatement for the value 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 value table ID mapping
        stmt.select(valueMapping);

        return stmt;
    }

    /**
     * Utility to create a join for keys to be used in a containsKey() query.
     * @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 Mapping for the owner
     * @param te Table Expression for the owner
     * @param mapTableAlias Alias for the "Map" table.
     * @param filteredKeyType The Class Type for the filtered key
     * @param keyTableAlias The SQL alias to assign to the expression or to the key table.
     * @param kExpr the expression to the key field                   
     * @return QueryColumnList with the columns from the key mapping
     **/
    public ScalarExpression joinKeysTo(
                                QueryExpression stmt,
                                QueryExpression parentStmt,
                      JavaTypeMapping ownerMapping,
                      LogicSetExpression te,
                                DatastoreIdentifier mapTableAlias,
                                Class filteredKeyType,
                                ScalarExpression kExpr,
                                DatastoreIdentifier keyTableAlias)
    {
        ClassLoaderResolver clr = stmt.getClassLoaderResolver();
        if (!clr.isAssignableFrom(keyType, filteredKeyType) &&
            !clr.isAssignableFrom(filteredKeyType, keyType))
        {
            throw new IncompatibleQueryElementTypeException(keyType, filteredKeyType.getName());
        }

        ScalarExpression ownerExpr = ownerMapping.newScalarExpression(stmt,te);
        DatastoreIdentifier containerRangeVar = mapTableAlias;

        if (stmt.getTableExpression(containerRangeVar) == null)
        {
            containerRangeVar = keyTableAlias;
        }

        // Join the map table on the owner ID column
        stmt.newTableExpression(mapTable, containerRangeVar);
        ScalarExpression ownerMapExpr = this.ownerMapping.newScalarExpression(stmt,stmt.getTableExpression(containerRangeVar));
        stmt.andCondition(ownerExpr.eq(ownerMapExpr),true);       
        // This depends on whether we have a separate key table or not
        if (!storeMgr.getOMFContext().getTypeManager().isSupportedType(filteredKeyType.getName()))
        {
            // Join the key table on the key ID column (if theres a key table)
            DatastoreContainerObject filteredKeyTable = storeMgr.getDatastoreClass(filteredKeyType.getName(), stmt.getClassLoaderResolver());
            JavaTypeMapping keyTableID = filteredKeyTable.getIDMapping();
            stmt.newTableExpression(filteredKeyTable, keyTableAlias);

            ScalarExpression keyMapExpr = keyMapping.newScalarExpression(stmt,stmt.getTableExpression(containerRangeVar));
            ScalarExpression keyExpr = keyTableID.newScalarExpression(stmt, stmt.getTableExpression(keyTableAlias));
            stmt.innerJoin(keyExpr,keyMapExpr,stmt.getTableExpression(keyTableAlias), true, true);

            return keyTableID.newScalarExpression(stmt, stmt.getTableExpression(keyTableAlias));
        }
        else
        {
            // Key is embedded in the map table
            return keyMapping.newScalarExpression(stmt, stmt.getTableExpression(containerRangeVar));
        }
    }

    /**
     * Used as part of the Querying of Maps where a containsValue() is used.
     * @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 Mapping for the owner
     * @param ownerTe Table Expression for the owner
     * @param mapRangeVar   The range variable for the "Map" table.
     * @param filteredValueType The Class Type for the filtered value
     * @param valExpr Table Expression for the value
     * @param valueRangeVar The SQL alias, or "range variable", to assign to the
     *                      expression or to the value table.
     * @return QueryColumnList with the columns from the value mapping
     **/
    public ScalarExpression joinValuesTo(
                                QueryExpression stmt,
                                QueryExpression parentStmt,
                                JavaTypeMapping ownerMapping,
                                LogicSetExpression ownerTe,
                                DatastoreIdentifier mapRangeVar,
                                Class filteredValueType,
                                ScalarExpression valExpr,
                                DatastoreIdentifier valueRangeVar)
    {
        ClassLoaderResolver clr = stmt.getClassLoaderResolver();
        if (!clr.isAssignableFrom(valueType,filteredValueType) &&
            !clr.isAssignableFrom(filteredValueType,valueType))
        {
            throw new IncompatibleQueryElementTypeException(valueType,filteredValueType.getName());
        }

        // Join the value table on the owner ID column.
        DatastoreContainerObject filteredValueTable = storeMgr.getDatastoreClass(filteredValueType.getName(), stmt.getClassLoaderResolver());
        stmt.newTableExpression(filteredValueTable, valueRangeVar);

        ScalarExpression ownerExpr = ownerMapping.newScalarExpression(stmt,ownerTe);
        DatastoreIdentifier containerRangeVar = mapRangeVar;
        if( stmt.getTableExpression(containerRangeVar) == null )
        {
            containerRangeVar = valueRangeVar;
        }
        ScalarExpression ownerValueExpr = this.ownerMapping.newScalarExpression(stmt,stmt.getTableExpression(containerRangeVar));
        stmt.newTableExpression(valueTable, containerRangeVar);
       
        stmt.andCondition(ownerExpr.eq(ownerValueExpr),true);

        JavaTypeMapping valueTableID = filteredValueTable.getIDMapping();
        return valueTableID.newScalarExpression(stmt,stmt.getTableExpression(containerRangeVar));
    }
   
    /**
     * Utility to create a join for keys and values to be used in ai
     * containsEntry() query.
     * @param stmt The Query Statement to apply the join
     * @param parentStmt the parent Query Statement. If there is no parent, <code>parentStmt</code> must be equals to <code>stmt</code>
     * @param ownerMapping Mapping for the owner
     * @param ownerTe Table Expression for the owner
     * @param mapRangeVar  The SQL alias, or "range variable", to assign to the
     *                     expression or to the main table.
     * @param filteredKeyType The Class Type for the filtered key
     * @param filteredValueType The Class Type for the filtered value
     * @param keyExpr the expression to the key field                   
     * @param valExpr Table Expression for the value
     * @param keyRangeVar  The SQL alias, or "range variable", to assign to the
     *                     expression or to the key table.
     * @param valueRangeVar The SQL alias, or "range variable", to assign to the
     *                      expression or to the value table.
     * @return an array with 2 elements of QueryColumnList. The first element
     * contains the columns from the key mapping and the second element the
     * columns from the value mapping
     */
    public ScalarExpression[] joinKeysValuesTo(
                                QueryExpression stmt,
                                QueryExpression parentStmt,
                      JavaTypeMapping ownerMapping,
                      LogicSetExpression ownerTe,
                                DatastoreIdentifier mapRangeVar,
                                Class filteredKeyType,
                                Class filteredValueType,
                                ScalarExpression keyExpr,
                                ScalarExpression valExpr,
                                DatastoreIdentifier keyRangeVar,
                                DatastoreIdentifier valueRangeVar)
    {
        ScalarExpression[] qclKeyValues = new ScalarExpression[2];       

        qclKeyValues[0] = joinKeysTo(stmt, parentStmt, ownerMapping, ownerTe, mapRangeVar, filteredKeyType, keyExpr, keyRangeVar);
        qclKeyValues[1] = joinValuesTo(stmt, parentStmt, ownerMapping, ownerTe, mapRangeVar, filteredValueType, valExpr, valueRangeVar);
        return qclKeyValues;
    }
}
TOP

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

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.