Package org.jpox.store.mapped.mapping

Source Code of org.jpox.store.mapped.mapping.PersistenceCapableMapping

/**********************************************************************
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 - Important changes regarding the application identity
                     support when fetching PreparedStatements. Added an
                     inner class that should be moved away.
2004 Erik Bengtson - added commentary and Localisation
2004 Andy Jefferson - added capability to handle Abstract PC fields for
                      both SingleFieldIdentity and AID
2007 Andy Jefferson - implement RelationMappingCallbacks
    ...
**********************************************************************/
package org.jpox.store.mapped.mapping;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;

import javax.jdo.JDOObjectNotFoundException;
import javax.jdo.spi.PersistenceCapable;

import org.jpox.ClassLoaderResolver;
import org.jpox.FetchPlan;
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.JPOXException;
import org.jpox.exceptions.JPOXObjectNotFoundException;
import org.jpox.exceptions.JPOXUserException;
import org.jpox.identity.OID;
import org.jpox.metadata.AbstractClassMetaData;
import org.jpox.metadata.AbstractMemberMetaData;
import org.jpox.metadata.ClassMetaData;
import org.jpox.metadata.ColumnMetaData;
import org.jpox.metadata.IdentityStrategy;
import org.jpox.metadata.IdentityType;
import org.jpox.metadata.InheritanceStrategy;
import org.jpox.metadata.MetaDataManager;
import org.jpox.metadata.Relation;
import org.jpox.sco.SCOCollection;
import org.jpox.store.FieldValues;
import org.jpox.store.StoreManager;
import org.jpox.store.exceptions.NotYetFlushedException;
import org.jpox.store.exceptions.ReachableObjectNotCascadedException;
import org.jpox.store.mapped.DatastoreAdapter;
import org.jpox.store.mapped.DatastoreClass;
import org.jpox.store.mapped.DatastoreContainerObject;
import org.jpox.store.mapped.DatastoreElementContainer;
import org.jpox.store.mapped.DatastoreField;
import org.jpox.store.mapped.DatastoreIdentifier;
import org.jpox.store.mapped.IdentifierFactory;
import org.jpox.store.mapped.MappedStoreManager;
import org.jpox.store.mapped.StatementExpressionIndex;
import org.jpox.store.mapped.expression.LogicSetExpression;
import org.jpox.store.mapped.expression.ObjectExpression;
import org.jpox.store.mapped.expression.ObjectLiteral;
import org.jpox.store.mapped.expression.QueryExpression;
import org.jpox.store.mapped.expression.ScalarExpression;
import org.jpox.util.ClassUtils;
import org.jpox.util.JPOXLogger;
import org.jpox.util.StringUtils;

/**
* Maps a java field to a PersistenceCapable class.
* For PersistenceCapable classes using datastore identity most of the necessary behaviour
* is coded in the OIDMapping super class.
* TODO Split this from OIDMapping since a PCMapping may represent an application
* identity object instead of a datastore identity object
*
* @version $Revision: 1.154 $
*/
public class PersistenceCapableMapping extends OIDMapping implements MappingCallbacks
{
    /** Mappings for all fields necessary to represent the id of the PC object. */
    protected JavaTypeMapping[] javaTypeMappings = new JavaTypeMapping[0];

    //----------------------- convenience fields to improve performance ----------------------//
    /** ClassMetaData for the represented class. Create a new one on each getObject invoke is expensive **/
    private AbstractClassMetaData cmd;

    /** number of DatastoreField - convenience field to improve performance **/
    private int numberOfDatastoreFields = 0;

    /**
     * Create a new empty PersistenceCapableMapping.
     * The caller must call one of the initialize methods to initialize the instance with the
     * DatastoreAdapter and its type.
     */
    public PersistenceCapableMapping()
    {
    }

    /**
     * Initialize this JavaTypeMapping with the given DatastoreAdapter for
     * the given FieldMetaData.
     * 
     * @param dba The Datastore Adapter that this Mapping should use.
     * @param fmd FieldMetaData for the field to be mapped (if any)
     * @param container The datastore container storing this mapping (if any)
     * @param clr the ClassLoaderResolver
     */
    public void initialize(DatastoreAdapter dba, AbstractMemberMetaData fmd, DatastoreContainerObject container,
            ClassLoaderResolver clr)
    {
      super.initialize(dba, fmd, container, clr);
        prepareDatastoreMapping(clr);
    }

    /**
     * Add a new JavaTypeMapping
     * @param mapping the JavaTypeMapping
     */
  public void addJavaTypeMapping(JavaTypeMapping mapping)
  {
      if (mapping == null)
      {
          throw new JPOXException("mapping argument in PersistenceCapableMapping.addJavaTypeMapping is null").setFatal();
      }
        JavaTypeMapping[] jtm = javaTypeMappings;
        javaTypeMappings = new JavaTypeMapping[jtm.length+1];
        System.arraycopy(jtm, 0, javaTypeMappings, 0, jtm.length);
        javaTypeMappings[jtm.length] = mapping;
  }
   
    /**
     * Method to prepare the PC mapping and add its associated datastore mappings.
     */
    protected void prepareDatastoreMapping()
    {
        // Does nothing - added to prevent column creation by the method in OIDMapping
        // If we change the inheritance so this doesnt extend OIDMapping, then this can be deleted
    }

  /**
     * Method to prepare the PC mapping and add its associated datastore mappings.
     * @param clr The ClassLoaderResolver
     */
    protected void prepareDatastoreMapping(ClassLoaderResolver clr)
    {
        // Either one end of a 1-1 relation, or the N end of a N-1
        MappedStoreManager storeMgr = datastoreContainer.getStoreManager();
        AbstractClassMetaData refCmd = storeMgr.getOMFContext().getMetaDataManager().getMetaDataForClass(fmd.getType(), clr);
        JavaTypeMapping referenceMapping = null;
        if (refCmd.getInheritanceMetaData().getStrategyValue() == InheritanceStrategy.SUBCLASS_TABLE)
        {
            // Find the actual tables storing the other end (can be multiple subclasses)
            AbstractClassMetaData[] cmds = storeMgr.getClassesManagingTableForClass(refCmd, clr);
            if (cmds != null && cmds.length > 0)
            {
                if (cmds.length > 1)
                {
                    JPOXLogger.PERSISTENCE.warn("Field " + fmd.getFullFieldName() + " represents either a 1-1 relation, " +
                        "or a N-1 relation where the other end uses \"subclass-table\" inheritance strategy and more " +
                        "than 1 subclasses with a table. This is not fully supported by JPOX");
                }
            }
            else
            {
                // No subclasses of the class using "subclasses-table" so no mapping!
                // TODO Throw an exception ?
                return;
            }
            // TODO We need a mapping for each of the possible subclass tables
            referenceMapping = storeMgr.getDatastoreClass(cmds[0].getFullClassName(), clr).getIDMapping();
        }
        else
        {
            referenceMapping = storeMgr.getDatastoreClass(fmd.getType().getName(), clr).getIDMapping();
        }

        // Generate a mapping from the columns of the referenced object to this mapping's ColumnMetaData
        CorrespondentColumnsMapper correspondentColumnsMapping = new CorrespondentColumnsMapper(fmd, referenceMapping, true);

        // Find any related field where this is part of a bidirectional relation
        int relationType = fmd.getRelationType(clr);
        boolean createDatastoreMappings = true;
        if (relationType == Relation.MANY_TO_ONE_BI)
        {
            AbstractMemberMetaData[] relatedMmds = fmd.getRelatedMemberMetaData(clr);
            // TODO Cater for more than 1 related field
            createDatastoreMappings = (relatedMmds[0].getJoinMetaData() == null);
        }
        else if (relationType == Relation.ONE_TO_ONE_BI)
        {
            // Put the FK at the end without "mapped-by"
            createDatastoreMappings = (fmd.getMappedBy() == null);
        }

        // Loop through the datastore fields in the referenced class and create a datastore field for each
        for (int i=0; i<referenceMapping.getNumberOfDatastoreFields(); i++)
        {
            DatastoreMapping refDatastoreMapping = referenceMapping.getDataStoreMapping(i);
            JavaTypeMapping mapping = dba.getMapping(refDatastoreMapping.getJavaTypeMapping().getJavaType(), storeMgr);
            this.addJavaTypeMapping(mapping);

            // Create physical datastore columns where we require a FK link to the related table.
            if (createDatastoreMappings)
            {
                // Find the Column MetaData that maps to the referenced datastore field
                ColumnMetaData colmd = correspondentColumnsMapping.getColumnMetaDataByIdentifier(
                    refDatastoreMapping.getDatastoreField().getIdentifier());
                if (colmd == null)
                {
                    throw new JPOXUserException(LOCALISER.msg("041038",
                        refDatastoreMapping.getDatastoreField().getIdentifier(), toString())).setFatal();
                }

                // Create a Datastore field to equate to the referenced classes datastore field
                MappingManager mmgr = datastoreContainer.getStoreManager().getMappingManager();
                DatastoreField col = mmgr.createDatastoreField(fmd, datastoreContainer, mapping,
                    colmd, refDatastoreMapping.getDatastoreField(), clr);

                // Add its datastore mapping
                DatastoreMapping datastoreMapping = mmgr.createDatastoreMapping(mapping, storeMgr, col,
                    refDatastoreMapping.getJavaTypeMapping().getJavaTypeForDatastoreMapping(i));
                this.addDataStoreMapping(datastoreMapping);
            }
            else
            {
                mapping.setReferenceMapping(referenceMapping);
            }
        }
    }

    /**
     * Accessor for the Java type mappings
     * @return The Java type mappings
     */
    public JavaTypeMapping[] getJavaTypeMapping()
    {
        return javaTypeMappings;
    }

    /**
     * Accessor for the number of datastore fields.
     * Zero datastore fields implies that the mapping uses a FK in the associated class
     * to reference back.
     * @return Number of datastore fields for this PC mapping.
     */
    public int getNumberOfDatastoreFields()
    {
        if (numberOfDatastoreFields == 0)
        {
            for (int i=0; i<javaTypeMappings.length; i++)
            {
                numberOfDatastoreFields += javaTypeMappings[i].getNumberOfDatastoreFields();
            }
        }
        return numberOfDatastoreFields;
    }

  /**
     * Accessor for a datastore mapping.
     * This method works through the java type mappings, and for each mapping through its datastore mappings
     * incrementing the index with each datastore mapping.
     * @param index The position of the mapping.
     * @return The datastore mapping.
   */
  public DatastoreMapping getDataStoreMapping(int index)
  {
        int currentIndex = 0;
        int numberJavaMappings = javaTypeMappings.length;
        for (int i=0; i<numberJavaMappings; i++)
        {
            int numberDatastoreMappings = javaTypeMappings[i].getNumberOfDatastoreFields();
            for (int j=0; j<numberDatastoreMappings; j++)
            {
                if (currentIndex == index)
                {
                    return javaTypeMappings[i].getDataStoreMapping(j);
                }
                currentIndex++;
            }
        }
        // TODO Localise this message
        throw new JPOXException("Invalid index " + index + " for DataStoreMapping.").setFatal();
  }

  /**
   * Method to set an object in the datastore.
   * @param om The ObjectManager
   * @param ps The Prepared Statement
   * @param param The parameter ids in the statement
   * @param value The value to put in the statement at these ids
   * @throws NotYetFlushedException
   */
    public void setObject(ObjectManager om, Object ps, int[] param, Object value)
    {
        setObject(om, ps, param, value, null, -1);
    }

    /**
     * Method to set an object reference (FK) in the datastore.
     * @param om The Object Manager
     * @param ps The Prepared Statement
     * @param param The parameter ids in the statement
     * @param value The value to put in the statement at these ids
     * @param ownerSM StateManager for the owner object
     * @param ownerFieldNumber Field number of this PC object in the owner
     * @throws NotYetFlushedException
     */
    public void setObject(ObjectManager om, Object ps, int[] param, Object value, StateManager ownerSM, int ownerFieldNumber)
    {
        if (value == null)
        {
            setObjectAsNull(om, ps, param);
        }
        else
        {
            setObjectAsValue(om, ps, param, value, ownerSM, ownerFieldNumber);
        }
    }

    /**
     * Populates the PreparedStatement with a null value for the mappings of this PersistenceCapableMapping.
     * @param om the Object Manager
     * @param ps the Prepared Statement
     * @param param The parameter ids in the statement
     */
    private void setObjectAsNull(ObjectManager om, Object ps, int[] param)
    {
        // Null out the PC object
        int n=0;
        for (int i=0; i<javaTypeMappings.length; i++)
        {
            JavaTypeMapping mapping = javaTypeMappings[i];
            if (mapping.getNumberOfDatastoreFields() > 0)
            {
                // Only populate the PreparedStatement for the object if it has any datastore mappings
                int[] posMapping = new int[mapping.getNumberOfDatastoreFields()];
                for (int j=0; j<posMapping.length; j++)
                {
                    posMapping[j] = param[n++];
                }
                mapping.setObject(om, ps, posMapping, null);
            }
        }
    }

    /**
     * Check if one of the primary key fields of the PC has value attributed by the datastore
     * @param mdm the {@link MetaDataManager}
     * @param srm the {@link StoreManager}
     * @param clr the {@link ClassLoaderResolver}
     * @return true if one of the primary key fields of the PC has value attributed by the datastore
     */
    private boolean hasDatastoreAttributedPrimaryKeyValues(MetaDataManager mdm, StoreManager srm, ClassLoaderResolver clr)
    {
        boolean hasDatastoreAttributedPrimaryKeyValues = false;
        if (this.fmd != null)
        {
            // Object is associated to a field (i.e not a join table)
            AbstractClassMetaData acmd = mdm.getMetaDataForClass(this.fmd.getType(), clr);
            if (acmd.getIdentityType() == IdentityType.APPLICATION)
            {
                for (int i=0; i<acmd.getPKMemberPositions().length; i++)
                {
                    IdentityStrategy strategy =
                        acmd.getMetaDataForManagedMemberAtAbsolutePosition(acmd.getPKMemberPositions()[i]).getValueStrategy();
                    if (strategy != null)
                    {
                        //if strategy is null, then it's user attributed value
                        hasDatastoreAttributedPrimaryKeyValues |= srm.isStrategyDatastoreAttributed(strategy, false);
                    }
                }
            }
        }
        return hasDatastoreAttributedPrimaryKeyValues;
    }
   
    /**
     * Method to set an object reference (FK) in the datastore.
     * @param om The Object Manager
     * @param ps The Prepared Statement
     * @param param The parameter ids in the statement
     * @param value The value to put in the statement at these ids
     * @param ownerSM StateManager for the owner object
     * @param ownerFieldNumber Field number of this PC object in the owner
     * @throws NotYetFlushedException Just put "null" in and throw "NotYetFlushedException",
     *                                to be caught by ParameterSetter and will signal to the PC object being inserted
     *                                that it needs to inform this object when it is inserted.
     */
    private void setObjectAsValue(ObjectManager om, Object ps, int[] param, Object value, StateManager ownerSM,
            int ownerFieldNumber)
    {
        Object id;

        ApiAdapter api = om.getApiAdapter();
        if (!api.isPersistable(value))
        {
            throw new JPOXException(LOCALISER.msg("041016",
                value.getClass(), value)).setFatal();
        }

        StateManager sm = om.findStateManager(value);

        try
        {
            ClassLoaderResolver clr = om.getClassLoaderResolver();

            // Check if the field is attributed in the datastore
            boolean hasDatastoreAttributedPrimaryKeyValues = hasDatastoreAttributedPrimaryKeyValues(
                om.getMetaDataManager(), om.getStoreManager(), clr);

            boolean inserted = false;
            if (ownerFieldNumber >= 0)
            {
                // Field mapping : is this field of the related object present in the datastore?
                inserted = om.isInserted(value, ownerFieldNumber);
            }
            else if (fmd == null)
            {
                // Identity mapping : is the object inserted far enough to be considered of this mapping type?
                inserted = om.isInserted(value, type);
            }

            if (sm != null)
            {
                if (om.getApiAdapter().isDetached(value) && sm.getReferencedPC() != null && ownerSM != null && fmd != null)
                {
                    // 1-1, N-1 mapping
                    // The value is really still detached but has a temporary StateManager whilst attaching so
                    // replace this field reference to be for the newly attached object
                    // Note that we have "fmd != null" here hence omitting any M-N relations where this is a join table
                    // mapping
                    ownerSM.replaceField(ownerFieldNumber, sm.getReferencedPC(), true);
                }

                if (sm.isWaitingToBeFlushedToDatastore())
                {
                    // Related object is not yet flushed to the datastore so flush it so we can set the FK
                    sm.flush();
                }
            }

            // we can execute this block when
            // 1) the pc has been inserted; OR
            // 2) is not in process of being inserted; OR
            // 3) is being inserted yet is inserted enough to use this mapping; OR
            // 4) the PC PK values are not attributed by the database and this mapping is for a PK field (compound identity)
            if (inserted || !om.isInserting(value) ||
                (!hasDatastoreAttributedPrimaryKeyValues && (this.fmd != null && this.fmd.isPrimaryKey())))
            {
                // The PC is either already inserted, or inserted down to the level we need, or not inserted at all,
                // or the field is a PK and identity not attributed by the datastore

                // Object either already exists, or is not yet being inserted.
                id = api.getIdForObject(value);

                // Check if the PersistenceCapable exists in this datastore
                boolean requiresPersisting = false;
                if (om.getApiAdapter().isDetached(value) && ownerSM != null)
                {
                    // Detached object so needs attaching
                    if (ownerSM.isInserting())
                    {
                        // Inserting other object, and this object is detached but if detached from this datastore
                        // we can just return the value now and attach later (in InsertRequest)
                        if (!om.getOMFContext().getPersistenceConfiguration().getBooleanProperty("org.jpox.attachSameDatastore"))
                        {
                            if (om.getObjectFromCache(api.getIdForObject(value)) != null)
                            {
                                // Object is in cache so exists for this datastore, so no point checking
                            }
                            else
                            {
                                try
                                {
                                    Object obj = om.findObject(api.getIdForObject(value), true, false,
                                        value.getClass().getName());
                                    if (obj != null)
                                    {
                                        // Make sure this object is not retained in cache etc
                                        StateManager objSM = om.findStateManager(obj);
                                        if (objSM != null)
                                        {
                                            om.evictFromTransaction(objSM);
                                        }
                                        om.removeObjectFromCache(value, api.getIdForObject(value), true, true);
                                    }
                                }
                                catch (JPOXObjectNotFoundException onfe)
                                {
                                    // Object doesnt yet exist
                                    requiresPersisting = true;
                                }
                            }
                        }
                    }
                    else
                    {
                        requiresPersisting = true;
                    }
                }
                else if (id == null)
                {
                    // Transient object, so we need to persist it
                    requiresPersisting = true;
                }
                else
                {
                    ObjectManager pcPM = ObjectManagerHelper.getObjectManager(value);
                    if (pcPM != null && om != pcPM)
                    {
                        throw new JPOXUserException(LOCALISER.msg("041015"), id);
                    }
                }

                if (requiresPersisting)
                {
                    // PERSISTENCE-BY-REACHABILITY
                    // This PC object needs persisting (new or detached) to do the "set"
                    if (fmd != null && !fmd.isCascadePersist() && !om.getApiAdapter().isDetached(value))
                    {
                        // Related PC object not persistent, but cant do cascade-persist so throw exception
                        if (JPOXLogger.REACHABILITY.isDebugEnabled())
                        {
                            JPOXLogger.REACHABILITY.debug(LOCALISER.msg("007006",
                                fmd.getFullFieldName()));
                        }
                        throw new ReachableObjectNotCascadedException(fmd.getFullFieldName(), value);
                    }

                    if (JPOXLogger.REACHABILITY.isDebugEnabled())
                    {
                        JPOXLogger.REACHABILITY.debug(LOCALISER.msg("007007",
                            fmd != null ? fmd.getFullFieldName() : null));
                    }

                    try
                    {
                        Object pcNew = om.persistObjectInternal(value, null, null, -1, StateManager.PC);
                        if (hasDatastoreAttributedPrimaryKeyValues)
                        {
                            om.flushInternal(false);
                        }
                        id = api.getIdForObject(pcNew);
                        if (om.getApiAdapter().isDetached(value) && ownerSM != null)
                        {
                            // Update any detached reference to refer to the attached variant
                            ownerSM.replaceField(ownerFieldNumber, pcNew, true);
                            int relationType = fmd.getRelationType(clr);
                            if (relationType == Relation.MANY_TO_ONE_BI)
                            {
                                // TODO Update the container to refer to the attached object
                                if (JPOXLogger.PERSISTENCE.isInfoEnabled())
                                {
                                    JPOXLogger.PERSISTENCE.info("PCMapping.setObject : object " + ownerSM.getInternalObjectId() +
                                        " has field " + ownerFieldNumber + " that is 1-N bidirectional." +
                                        " Have just attached the N side so should really update the reference in the 1 side collection" +
                                        " to refer to this attached object. Not yet implemented");
                                }
                            }
                            else if (relationType == Relation.ONE_TO_ONE_BI)
                            {
                                AbstractMemberMetaData[] relatedMmds = fmd.getRelatedMemberMetaData(clr);
                                // TODO Cater for more than 1 related field
                                StateManager relatedSM = om.findStateManager(pcNew);
                                relatedSM.replaceField(relatedMmds[0].getAbsoluteFieldNumber(), ownerSM.getObject(), true);
                            }
                        }
                    }
                    catch (NotYetFlushedException e)
                    {
                        setObjectAsNull(om, ps, param);
                        throw new NotYetFlushedException(value);
                    }
                }

                if (sm != null)
                {
                    sm.setStoringPC();
                }

                // If the field doesnt map to any datastore fields, omit the set process
                if (getNumberOfDatastoreFields() > 0)
                {
                    if (id instanceof OID)
                    {
                        super.setObject(om, ps, param, id);
                    }
                    else
                    {
                        // TODO Factor out this PersistenceCapable reference
                        ((PersistenceCapable)value).jdoCopyKeyFieldsFromObjectId(new AppIDObjectIdFieldConsumer(param, om, ps,
                            javaTypeMappings), id);
                    }
                }
            }
            else
            {
                if (sm != null)
                {
                    sm.setStoringPC();
                }

                if (getNumberOfDatastoreFields() > 0)
                {
                    // Object is in the process of being inserted so we cant use its id currently and we need to store
                    // a foreign key to it (which we cant yet do). Just put "null" in and throw "NotYetFlushedException",
                    // to be caught by ParameterSetter and will signal to the PC object being inserted that it needs
                    // to inform this object when it is inserted.
                    setObjectAsNull(om, ps, param);
                    throw new NotYetFlushedException(value);
                }
            }
        }
        finally
        {
            if (sm != null)
            {
                sm.unsetStoringPC();
            }
        }
    }

    /**
     * Returns a instance of a PersistenceCapable class.
     * Processes a FK field and converts the id stored firstly into an OID/AID
     * and then into the object that the FK id relates to.
     * @param om The Object Manager
     * @param rs The ResultSet
     * @param param Array of parameter ids in the ResultSet to retrieve
     * @return The Persistence Capable object
     */
    public Object getObject(ObjectManager om, final Object rs, int[] param)
    {
        // Check for null FK
        try
        {
            //if the first param is null, then the field is null
            // TODO Factor this out - using RDBMS-specific code
            if (((ResultSet) rs).getObject(param[0]) == null)
            {
                return null;
            }
        }
        catch (SQLException e)
        {
            throw new JPOXDataStoreException(e.getMessage(),e);
        }
      
        if (cmd == null)
        {
            cmd = om.getMetaDataManager().getMetaDataForClass(getType(),om.getClassLoaderResolver());
        }

        // Return the object represented by this mapping
        if (cmd.getIdentityType() == IdentityType.DATASTORE)
        {
            return getObjectForDatastoreIdentity(om,rs,param,cmd);
        }
        else if (cmd.getIdentityType() == IdentityType.APPLICATION)
        {
            return getObjectForApplicationIdentity(om,rs,param,cmd);
        }
        else
        {
            return null;
        }
    }

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

    public ScalarExpression newLiteral(QueryExpression qs, Object value)
    {
        ScalarExpression expr = new ObjectLiteral(qs, this, value,getType());
        return expr;       
    }

    public ScalarExpression newScalarExpression(QueryExpression qs, LogicSetExpression te)
    {
        if (getNumberOfDatastoreFields() > 0)
        {
            return new ObjectExpression(qs, this, te);
        }
        else
        {
            ClassLoaderResolver clr = qs.getClassLoaderResolver();
            MappedStoreManager srm = qs.getStoreManager();
            int relationType = fmd.getRelationType(clr);
            if (relationType == Relation.ONE_TO_ONE_BI)
            {
                // Create an expression joining to the related field in the related table
                DatastoreClass targetTable = srm.getDatastoreClass(fmd.getTypeName(), clr);
                AbstractMemberMetaData[] relatedMmds = fmd.getRelatedMemberMetaData(clr);
                // TODO Cater for more than one related field
                JavaTypeMapping refMapping = targetTable.getFieldMapping(relatedMmds[0]);
                JavaTypeMapping selectMapping = targetTable.getIDMapping();
                DatastoreIdentifier targetTableIdentifier =
                    srm.getIdentifierFactory().newIdentifier(IdentifierFactory.TABLE, "RELATED" + fmd.getAbsoluteFieldNumber());
                LogicSetExpression targetTe = qs.newTableExpression(targetTable, targetTableIdentifier);
                return new ObjectExpression(qs, this, te, refMapping, targetTe, selectMapping);
            }
            else if (relationType == Relation.MANY_TO_ONE_BI)
            {
                AbstractMemberMetaData[] relatedMmds = fmd.getRelatedMemberMetaData(clr);
                // TODO Cater for more than one related field
                if (fmd.getJoinMetaData() != null || relatedMmds[0].getJoinMetaData() != null)
                {
                    // Join table relation - only allows for Collection/Array
                    // Create an expression this table to the join table on the element id, selecting the owner id
                    DatastoreContainerObject targetTable = srm.getDatastoreContainerObject(relatedMmds[0]);
                    JavaTypeMapping refMapping = null;
                    JavaTypeMapping selectMapping = null;
                    DatastoreElementContainer elementTable = (DatastoreElementContainer)targetTable;
                    refMapping = elementTable.getElementMapping();
                    selectMapping = elementTable.getOwnerMapping();
                    DatastoreIdentifier targetTableIdentifier =
                        srm.getIdentifierFactory().newIdentifier(IdentifierFactory.TABLE, "JOINTABLE" + fmd.getAbsoluteFieldNumber());
                    LogicSetExpression targetTe = qs.newTableExpression(targetTable, targetTableIdentifier);
                    return new ObjectExpression(qs, this, te, refMapping, targetTe, selectMapping);
                }
            }
        }

        // TODO Throw an exception since should be impossible
        return null;
    }

    // ------------------------------------- Utility Methods ------------------------------------------

    /**
     * Get the object instance for a class using datastore identity
     * @param om the ObjectManager
     * @param rs the ResultSet
     * @param param the parameters
     * @param cmd the AbstractClassMetaData
     * @return the id
     */
    private Object getObjectForDatastoreIdentity(ObjectManager om, final Object rs, int[] param, AbstractClassMetaData cmd)
    {
        // Datastore Identity - retrieve the OID for the class.
        // Note that this is a temporary OID that is simply formed from the type of base class in the relationship
        // and the id stored in the FK. The real OID for the object may be of a different class.
        // For that reason we get the object by checking the inheritance (final param in getObjectById())
        Object oid = super.getObject(om, rs, param);
        ApiAdapter api = om.getApiAdapter();
        if (api.isPersistable(oid)) //why check this?
        {
          return oid;
        }
        return oid == null ? null : om.findObject(oid, false, true, null);
    }

    /**
     * Create a SingleFieldIdentity instance
     * @param om the ObjectManager
     * @param rs the ResultSet
     * @param param the parameters
     * @param cmd the AbstractClassMetaData
     * @param objectIdClass the object id class
     * @param pcClass the PersistenceCapable class
     * @return the id
     */
    private Object createSingleFieldIdentity(ObjectManager om, final Object rs, int[] param, AbstractClassMetaData cmd,
            Class objectIdClass, Class pcClass)
    {
        // SingleFieldIdentity
        int paramNumber = param[0];
        try
        {
            // TODO Factor this out - using RDBMS-specific code
            Object idObj = ((ResultSet)rs).getObject(paramNumber);
            if (idObj == null)
            {
                throw new JPOXException(LOCALISER.msg("041039")).setFatal();
            }
            else
            {
                // Make sure the key type is correct for the type of SingleFieldIdentity
                Class keyType = om.getApiAdapter().getKeyTypeForSingleFieldIdentityType(objectIdClass);
                idObj = ClassUtils.convertValue(idObj, keyType);
            }
            return om.getApiAdapter().getNewSingleFieldIdentity(objectIdClass, pcClass, idObj);
        }
        catch (Exception e)
        {
            JPOXLogger.PERSISTENCE.error(LOCALISER.msg("041036", cmd.getObjectidClass(),
                e));
            return null;
        }
    }

    /**
     * Create an object id instance and fill the fields using reflection
     * @param om the ObjectManager
     * @param rs the ResultSet
     * @param param the parameters
     * @param cmd the AbstractClassMetaData
     * @param objectIdClass the object id class
     * @return the id
     */
    private Object createObjectIdInstanceReflection(ObjectManager om, final Object rs, int[] param,
            AbstractClassMetaData cmd, Class objectIdClass)
    {
        // Users own AID
        Object fieldValue = null;
        try
        {
            // Create an AID
            Object id = objectIdClass.newInstance();

            // Set the fields of the AID
            int paramIndex = 0;
            for (int i=0; i<cmd.getPKMemberPositions().length; ++i)
            {
                AbstractMemberMetaData fmd = cmd.getMetaDataForManagedMemberAtAbsolutePosition(cmd.getPKMemberPositions()[i]);
                Field field = objectIdClass.getField(fmd.getName());

                MappedStoreManager storeMgr = (MappedStoreManager)om.getStoreManager();
                JavaTypeMapping m = storeMgr.getDatastoreClass(cmd.getFullClassName(), om.getClassLoaderResolver()).getFieldMapping(fmd);
                // NOTE This assumes that each field has one datastore column.
                for (int j = 0; j < m.getNumberOfDatastoreFields(); j++)
                {
                    // TODO Factor this out - using RDBMS-specific code
                    Object obj = ((ResultSet)rs).getObject(param[paramIndex++]);
                    if ((obj instanceof BigDecimal))
                    {
                        BigDecimal bigDecimal = (BigDecimal) obj;
                        // Oracle 10g returns BigDecimal for NUMBER columns,
                        // resulting in IllegalArgumentException when reflective
                        // setter is invoked for incompatible field type
                        // (see http://www.jpox.org/servlet/jira/browse/CORE-2624)
                        Class keyType = om.getApiAdapter().getKeyTypeForSingleFieldIdentityType(field.getType());
                        obj = ClassUtils.convertValue(bigDecimal, keyType);
                        if (!bigDecimal.subtract(new BigDecimal("" + obj)).equals(new BigDecimal("0")))
                        {
                            throw new JPOXException("Cannot convert retrieved BigInteger value to field of object id class!").setFatal();
                        }
                    }
                    // field with multiple columns should have values returned from db merged here
                    fieldValue = obj;
                }
                field.set(id, fieldValue);
            }
            return id;
        }
        catch (Exception e)
        {
            JPOXLogger.PERSISTENCE.error(LOCALISER.msg("041037",
                cmd.getObjectidClass(), fmd == null ? null : fmd.getName(), fieldValue, e));
            return null;
        }
    }

    /**
     * Create an object id instance and fill the fields using reflection
     * @param om the ObjectManager
     * @param rs the ResultSet
     * @param param the parameters
     * @param cmd the AbstractClassMetaData
     * @return the id
     */
    private Object getObjectForAbstractClass(ObjectManager om, final Object rs, int[] param, AbstractClassMetaData cmd)
    {
        ClassLoaderResolver clr = om.getClassLoaderResolver();

        // Abstract class, so we need to generate an AID before proceeding
        Class objectIdClass = clr.classForName(cmd.getObjectidClass());
        Class pcClass = clr.classForName(cmd.getFullClassName());
        Object id;
        if (cmd.usesSingleFieldIdentityClass())
        {
            id = createSingleFieldIdentity(om, rs, param, cmd, objectIdClass, pcClass);
        }
        else
        {
            id = createObjectIdInstanceReflection(om, rs, param, cmd, objectIdClass);
        }
        return om.findObject(id, false, true, null);
    }

    /**
     * Get the object instance for a class using application identity
     * @param om the ObjectManager
     * @param rs the ResultSet
     * @param param the parameters
     * @param cmd the AbstractClassMetaData
     * @return the id
     */
    private Object getObjectForApplicationIdentity(ObjectManager om, final Object rs, int[] param, AbstractClassMetaData cmd)
    {
        ClassLoaderResolver clr = om.getClassLoaderResolver();

        // Abstract class
        if (((ClassMetaData)cmd).isAbstractPersistenceCapable() && cmd.getObjectidClass() != null)
        {
            return getObjectForAbstractClass(om,rs,param,cmd);
        }
       
       
        // Concrete class
        int totalFieldCount = cmd.getNoOfManagedMembers() + cmd.getNoOfInheritedManagedMembers();
        final StatementExpressionIndex[] statementExpressionIndex = new StatementExpressionIndex[totalFieldCount];
        int paramIndex = 0;

        final MappedStoreManager storeMgr = (MappedStoreManager)om.getStoreManager();
        DatastoreClass datastoreClass = storeMgr.getDatastoreClass(cmd.getFullClassName(), clr);
        final int[] pkFieldNumbers = cmd.getPKMemberPositions();

        for (int i=0; i<pkFieldNumbers.length; ++i)
        {
            AbstractMemberMetaData fmd = cmd.getMetaDataForManagedMemberAtAbsolutePosition(pkFieldNumbers[i]);
            JavaTypeMapping m = datastoreClass.getFieldMapping(fmd);
            statementExpressionIndex[fmd.getAbsoluteFieldNumber()] = new StatementExpressionIndex();
            statementExpressionIndex[fmd.getAbsoluteFieldNumber()].setMapping(m);
            int expressionsIndex[] = new int[m.getNumberOfDatastoreFields()];
            for (int j = 0; j < expressionsIndex.length; j++)
            {
                expressionsIndex[j] = param[paramIndex++];
            }
            statementExpressionIndex[fmd.getAbsoluteFieldNumber()].setExpressionIndex(expressionsIndex);
        }

        return om.findObjectUsingAID(clr.classForName(cmd.getFullClassName()),
            new FieldValues()
            {
            // StateManager calls the fetchFields method
            public void fetchFields(StateManager sm)
            {
                sm.replaceFields(pkFieldNumbers,
                    storeMgr.getFieldManagerForResultProcessing(sm, rs, statementExpressionIndex));
            }
            public void fetchNonLoadedFields(StateManager sm)
            {
                sm.replaceNonLoadedFields(pkFieldNumbers,
                    storeMgr.getFieldManagerForResultProcessing(sm, rs, statementExpressionIndex));
            }
            public FetchPlan getFetchPlanForLoading()
            {
                return null;
            }
            }, false, true);
    }

    // ----------------------- Implementation of MappingCallbacks --------------------------

    /**
     * Method executed just after a fetch of the owning object, allowing any necessary action
     * to this field and the object stored in it.
     * @param sm StateManager for the owner.
     */
    public void postFetch(StateManager sm)
    {
    }

    /**
     * Method executed just after the insert of the owning object, allowing any necessary action
     * to this field and the object stored in it.
     * @param sm StateManager for the owner
     */
    public void postInsert(StateManager sm)
    {
        Object pc = sm.provideField(fmd.getAbsoluteFieldNumber());
        if (pc == null)
        {
            // Has been set to null so nothing to do
            return;
        }

        ClassLoaderResolver clr = sm.getObjectManager().getClassLoaderResolver();
        AbstractMemberMetaData[] relatedMmds = fmd.getRelatedMemberMetaData(clr);
        int relationType = fmd.getRelationType(clr);
        if (pc != null)
        {
            if (relationType == Relation.ONE_TO_ONE_BI)
            {
                StateManager otherSM = sm.getObjectManager().findStateManager(pc);
                AbstractMemberMetaData relatedMmd = fmd.getRelatedMemberMetaDataForObject(clr, sm.getObject(), pc);
                Object relatedValue = otherSM.provideField(relatedMmd.getAbsoluteFieldNumber());
                if (relatedValue == null)
                {
                    // Managed Relations : Other side not set so update it in memory
                    if (JPOXLogger.PERSISTENCE.isDebugEnabled())
                    {
                        JPOXLogger.PERSISTENCE.debug(LOCALISER.msg("041018",
                            StringUtils.toJVMIDString(sm.getObject()), fmd.getFullFieldName(),
                            StringUtils.toJVMIDString(pc), relatedMmd.getFullFieldName()));
                    }
                    otherSM.replaceField(relatedMmd.getAbsoluteFieldNumber(), sm.getObject(), false);
                }
                else if (relatedValue != sm.getObject())
                {
                    // Managed Relations : Other side is inconsistent so throw exception
                    throw new JPOXUserException(
                        LOCALISER.msg("041020",
                            StringUtils.toJVMIDString(sm.getObject()), fmd.getFullFieldName(),
                            StringUtils.toJVMIDString(pc),
                            StringUtils.toJVMIDString(relatedValue)));
                }
            }
            else if (relationType == Relation.MANY_TO_ONE_BI && relatedMmds[0].hasCollection())
            {
                // TODO Make sure we have this PC in the collection at the other side
                StateManager otherSM = sm.getObjectManager().findStateManager(pc);
                if (otherSM != null)
                {
                    // Managed Relations : add to the collection on the other side
                    Collection relatedColl = (Collection)otherSM.provideField(relatedMmds[0].getAbsoluteFieldNumber());
                    if (relatedColl != null && !(relatedColl instanceof SCOCollection))
                    {
                        // TODO Make sure the collection is a wrapper
                        boolean contained = relatedColl.contains(sm.getObject());
                        if (!contained)
                        {
                            JPOXLogger.PERSISTENCE.info(
                                LOCALISER.msg("041022",
                                StringUtils.toJVMIDString(sm.getObject()), fmd.getFullFieldName(),
                                StringUtils.toJVMIDString(pc), relatedMmds[0].getFullFieldName()));
                            // TODO Enable this. CUrrently causes issues with
                            // PMImplTest, InheritanceStrategyTest, TCK "inheritance1.conf"
                            /*relatedColl.add(sm.getObject());*/
                        }
                    }
                }
            }
        }
    }

    /**
     * Method executed just afer any update of the owning object, allowing any necessary action
     * to this field and the object stored in it.
     * @param sm StateManager for the owner
     */
    public void postUpdate(StateManager sm)
    {
        Object pc = sm.provideField(fmd.getAbsoluteFieldNumber());
        if (pc == null)
        {
            // Has been set to null so nothing to do
            return;
        }

        if (pc != null)
        {
            StateManager otherSM = sm.getObjectManager().findStateManager(pc);
            if (otherSM == null)
            {
                ClassLoaderResolver clr = sm.getObjectManager().getClassLoaderResolver();
                int relationType = fmd.getRelationType(clr);
                if (relationType == Relation.ONE_TO_ONE_BI || relationType == Relation.MANY_TO_ONE_BI)
                {
                    // Related object is not yet persisted (e.g 1-1 with FK at other side) so persist it
                    sm.getObjectManager().persistObjectInternal(pc, null, null, -1, StateManager.PC);
                }
            }
        }
    }

    /**
     * Method executed just before the owning object is deleted, allowing tidying up of any
     * relation information.
     * @param sm StateManager for the owner
     */
    public void preDelete(StateManager sm)
    {
        // makes sure field is loaded
        int fieldNumber = fmd.getAbsoluteFieldNumber();
        try
        {
            sm.getObjectManager().getApiAdapter().isLoaded(sm, fieldNumber);
        }
        catch (JDOObjectNotFoundException onfe)
        {
            // Already deleted so just return
            return;
        }

        Object pc = sm.provideField(fieldNumber);
        if (pc == null)
        {
            // Null value so nothing to do
            return;
        }

        // Check if the field has a FK defined
        ClassLoaderResolver clr = sm.getObjectManager().getClassLoaderResolver();
        MappedStoreManager storeMgr = (MappedStoreManager)sm.getObjectManager().getStoreManager();
        AbstractMemberMetaData[] relatedMmds = fmd.getRelatedMemberMetaData(clr);
        // TODO Cater for more than 1 related field
        boolean dependent = fmd.isDependent();
        boolean hasFK = false;
        if (!dependent)
        {
            // Not dependent, so check if the datastore has a FK and will take care of it for us
            if (fmd.getForeignKeyMetaData() != null)
            {
                hasFK = true;
            }
            if (relatedMmds != null && relatedMmds[0].getForeignKeyMetaData() != null)
            {
                hasFK = true;
            }
            if (sm.getObjectManager().getOMFContext().getPersistenceConfiguration().getStringProperty("org.jpox.deletionPolicy").equals("JDO2"))
            {
                // JDO2 doesnt currently (2.0 spec) take note of foreign-key
                hasFK = false;
            }
        }

        // Basic rules for the following :-
        // 1. If it is dependent then we delete it (maybe after nulling).
        // 2. If it is not dependent and they have defined no FK then null it, else delete it
        // 3. If it is not dependent and they have a FK, let the datastore handle the delete
        // There may be some corner cases that this code doesnt yet cater for
        int relationType = fmd.getRelationType(clr);
        if (pc != null)
        {
            if (relationType == Relation.ONE_TO_ONE_UNI ||
                (relationType == Relation.ONE_TO_ONE_BI && fmd.getMappedBy() == null))
            {
                // 1-1 with FK at this side (owner of the relation)
                if (dependent)
                {
                    boolean relatedObjectDeleted = sm.getObjectManager().getApiAdapter().isDeleted(pc);
                    if (isNullable() && !relatedObjectDeleted)
                    {
                        // Other object not yet deleted - just null out the FK
                        // TODO Not doing this would cause errors in 1-1 uni relations (e.g AttachDetachTest)
                        // TODO Log this since it affects the resultant objects
                        sm.replaceField(fieldNumber, null, true);
                        sm.getStoreManager().getPersistenceHandler().updateObject(sm, new int[]{fieldNumber});
                    }
                    if (!relatedObjectDeleted)
                    {
                        // Mark the other object for deletion since not yet tagged
                        sm.getObjectManager().deleteObjectInternal(pc);
                    }
                }
                else
                {
                    // We're deleting the FK at this side so shouldnt be an issue
                    AbstractMemberMetaData relatedMmd = fmd.getRelatedMemberMetaDataForObject(clr, sm.getObject(), pc);
                    if (relatedMmd != null)
                    {
                        StateManager otherSM = sm.getObjectManager().findStateManager(pc);
                        if (otherSM != null)
                        {
                            // Managed Relations : 1-1 bidir, so null out the object at the other
                            if (JPOXLogger.PERSISTENCE.isDebugEnabled())
                            {
                                JPOXLogger.PERSISTENCE.debug(LOCALISER.msg("041019",
                                    StringUtils.toJVMIDString(pc), relatedMmd.getFullFieldName(),
                                    StringUtils.toJVMIDString(sm.getObject())));
                            }
                            otherSM.replaceField(relatedMmd.getAbsoluteFieldNumber(), null, true);
                        }
                    }
                }
            }
            else if (relationType == Relation.ONE_TO_ONE_BI && fmd.getMappedBy() != null)
            {
                // 1-1 with FK at other side
                DatastoreClass relatedTable = storeMgr.getDatastoreClass(relatedMmds[0].getClassName(), clr);
                JavaTypeMapping relatedMapping = relatedTable.getFieldMapping(relatedMmds[0]);
                boolean isNullable = relatedMapping.isNullable();
                StateManager otherSM = sm.getObjectManager().findStateManager(pc);
                if (dependent)
                {
                    if (isNullable)
                    {
                        // Null out the FK in the datastore using a direct update (since we are deleting)
                        otherSM.replaceField(relatedMmds[0].getAbsoluteFieldNumber(), null, true);
                        otherSM.getStoreManager().getPersistenceHandler().updateObject(otherSM, new int[]{relatedMmds[0].getAbsoluteFieldNumber()});
                    }
                    // Mark the other object for deletion
                    sm.getObjectManager().deleteObjectInternal(pc);
                }
                else if (!hasFK)
                {
                    if (isNullable())
                    {
                        // Null out the FK in the datastore using a direct update (since we are deleting)
                        otherSM.replaceField(relatedMmds[0].getAbsoluteFieldNumber(), null, true);
                        otherSM.getStoreManager().getPersistenceHandler().updateObject(otherSM, new int[]{relatedMmds[0].getAbsoluteFieldNumber()});
                    }
                    else
                    {
                        // TODO Remove it
                    }
                }
                else
                {
                    // User has a FK defined (in MetaData) so let the datastore take care of it
                }
            }
            else if (relationType == Relation.MANY_TO_ONE_BI)
            {
                StateManager otherSM = sm.getObjectManager().findStateManager(pc);
                if (relatedMmds[0].getJoinMetaData() == null)
                {
                    // N-1 with FK at this side
                    if (otherSM.isDeleting())
                    {
                        // Other object is being deleted too but this side has the FK so just delete this object
                    }
                    else
                    {
                        // Other object is not being deleted so delete it if necessary
                        if (dependent)
                        {
                            if (isNullable())
                            {
                                // TODO Datastore nullability info can be unreliable so try to avoid this call
                                // Null out the FK in the datastore using a direct update (since we are deleting)
                                sm.replaceField(fieldNumber, null, true);
                                sm.getStoreManager().getPersistenceHandler().updateObject(sm, new int[]{fieldNumber});
                            }

                            if (sm.getObjectManager().getApiAdapter().isDeleted(pc))
                            {
                                // Object is already tagged for deletion but we're deleting the FK so leave til flush()
                            }
                            else
                            {
                                // Mark the other object for deletion
                                sm.getObjectManager().deleteObjectInternal(pc);
                            }
                        }
                        else
                        {
                            // Managed Relations : remove element from collection/map
                            if (relatedMmds[0].hasCollection())
                            {
                                Collection otherColl = (Collection)otherSM.provideField(relatedMmds[0].getAbsoluteFieldNumber());
                                if (otherColl != null)
                                {
                                    // TODO Remove from any cached SCO collection
                                }
                            }
                            else if (relatedMmds[0].hasMap())
                            {
                                // TODO Cater for maps, but what is the key/value pair ?
                            }
                        }
                    }
                }
                else
                {
                    // N-1 with join table so no FK here so need to remove from Collection/Map first? (managed relations)
                    if (dependent)
                    {
                        // Mark the other object for deletion
                        sm.getObjectManager().deleteObjectInternal(pc);
                    }
                    else
                    {
                        // Managed Relations : remove element from collection/map
                        if (relatedMmds[0].hasCollection())
                        {
                            Collection otherColl = (Collection)otherSM.provideField(relatedMmds[0].getAbsoluteFieldNumber());
                            if (otherColl != null)
                            {
                                // TODO Add log message
                                otherColl.remove(sm.getObject());
                            }
                        }
                        else if (relatedMmds[0].hasMap())
                        {
                            // TODO Cater for maps, but what is the key/value pair ?
                        }
                    }
                }
            }
            else
            {
                // No relation so what is this field ?
            }
        }
    }
}
TOP

Related Classes of org.jpox.store.mapped.mapping.PersistenceCapableMapping

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.