All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.jdo.spi.persistence.support.sqlstore.SQLStateManager Maven / Gradle / Ivy

There is a newer version: 6.2024.7
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
// Portions Copyright [2018] [Payara Foundation and/or its affiliates]

/*
 * SQLStateManager.java
 *
 * Created on March 3, 2000
 */

package com.sun.jdo.spi.persistence.support.sqlstore;

import com.sun.jdo.api.persistence.support.*;
import com.sun.jdo.spi.persistence.support.sqlstore.ejb.EJBHelper;
import com.sun.jdo.spi.persistence.support.sqlstore.model.*;
import com.sun.jdo.spi.persistence.support.sqlstore.query.jqlc.QueryValueFetcher;
import com.sun.jdo.spi.persistence.support.sqlstore.sco.SqlTimestamp;
import com.sun.jdo.spi.persistence.support.sqlstore.sql.UpdateObjectDescImpl;
import com.sun.jdo.spi.persistence.support.sqlstore.state.LifeCycleState;
import com.sun.jdo.spi.persistence.support.sqlstore.state.PersistentNonTransactional;
import com.sun.jdo.spi.persistence.support.sqlstore.state.PersistentClean;
import com.sun.jdo.spi.persistence.support.sqlstore.state.Hollow;
import com.sun.jdo.spi.persistence.utility.NullSemaphore;
import com.sun.jdo.spi.persistence.utility.Semaphore;
import com.sun.jdo.spi.persistence.utility.SemaphoreImpl;
import com.sun.jdo.spi.persistence.utility.logging.Logger;
import org.glassfish.persistence.common.I18NHelper;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Timestamp;
import java.util.*;


/**
 *
 */
public class SQLStateManager implements Cloneable, StateManager, TestStateManager {

    private static final int PRESENCE_MASK = 0;

    private static final int SET_MASK = 1;

    private static final int MAX_MASKS = 2;

    private BitSet fieldMasks;

    /** Array of Object. */
    public ArrayList hiddenValues;

    private ClassDesc persistenceConfig;

    private PersistenceManager persistenceManager;

    private PersistenceStore store;

    private SQLStateManager beforeImage;

    private Object persistentObject;

    private Object objectId;

    private LifeCycleState state;

    /** This flag is used to disable updates due to dependency management. */
    private static final short ST_UPDATE_DISABLED = 0x1;

    private static final short ST_REGISTERED = 0x2;

    private static final short ST_VISITED = 0x4;

    private static final short ST_PREPARED_PHASE_II = 0x8;

    private static final short ST_FIELD_TRACKING_INPROGRESS = 0x10;

    private static final short ST_DELETE_INPROGRESS = 0x20;

    private static final short ST_VALIDATION_FAILED = 0x40;

    private short stateFlags;

    // This instance is a replacement for a deleted instance with the same
    // ObjectId.
    private boolean isReplacementInstance = false;

    // This instance needs to be registered with the global (weak) cache at
    // rollback if it transitions to persistent (HOLLOW or P_NONTX) state.
    private boolean needsRegisterAtRollback = false;

    // This instance needs to be to be verified at the time it is removed
    // from the global (weak) cache at rollback if it transitions to transient state.
    private boolean needsVerifyAtDeregister = false;

    // This flag is initially set to false and changed to true when the first
    // operation (e.g. makePersistent, loadForRead, or getObjectById) succeeds.
    private boolean valid = false;

    /** Stores the updates to the associated object. */
    private UpdateObjectDescImpl updateDesc;

    /** Contains state managers depending on this object. */
    private HashSet updatedForeignReferences;

    /** Counts the foreign state managers this state manager depends on. */
    private int referenceCount;

    /** Serializes access to this StateManager. */
    private final Semaphore lock;

    /** The logger. */
    private static Logger logger = LogHelperStateManager.getLogger();

    /** I18N message handler. */
    private final static ResourceBundle messages = I18NHelper.loadBundle(
            SQLStateManager.class);

    /** Name of the USE_BATCH property. */
    public static final String USE_BATCH_PROPERTY =
        "com.sun.jdo.spi.persistence.support.sqlstore.USE_BATCH"; // NOI18N

    /**
     * Property to swich on/off batching. Note, the default is true, meaning we
     * try to do batching if the property is not specified.
     */
    private static final boolean USE_BATCH = Boolean.valueOf(
        System.getProperty(USE_BATCH_PROPERTY, "true")).booleanValue(); // NOI18N

    /**
     * Construct a new SQLStateManager so that it locks or does not lock as
     * per whether or not it is used in a managed environment.
     */
    public SQLStateManager(PersistenceStore store, ClassDesc persistenceConfig) {
        this.store = store;
        this.persistenceConfig = persistenceConfig;

        if (EJBHelper.isManaged()) {
            this.lock = new NullSemaphore("SQLStateManager");  // NOI18N
        } else {
            this.lock = new SemaphoreImpl("SQLStateManager");  // NOI18N
        }
    }

    public synchronized void initialize(boolean persistentInDB) {
        boolean xactActive = persistenceManager.isActiveTransaction();
        boolean optimistic = persistenceManager.isOptimisticTransaction();
        boolean nontransactionalRead = persistenceManager.isNontransactionalRead();
        LifeCycleState oldstate = state;

        if (state == null) {
            if (persistentInDB == false) {
                // Hollow object aquired by PM.getObjectByOid() does not require
                // to be persistent in DB
                state = LifeCycleState.getLifeCycleState(LifeCycleState.HOLLOW);
                persistenceManager.setFlags(persistentObject, LOAD_REQUIRED);
            } else {
                if (xactActive && !optimistic) {
                    state = LifeCycleState.getLifeCycleState(LifeCycleState.P_CLEAN);
                    persistenceManager.setFlags(persistentObject, READ_OK);
                } else {
                    state = LifeCycleState.getLifeCycleState(LifeCycleState.P_NON_TX);
                    persistenceManager.setFlags(persistentObject, LOAD_REQUIRED);
                }
                valid = true;
            }
        } else if (state.needMerge()) {
            state = state.transitionReadField(optimistic, nontransactionalRead, xactActive);

            // If we are in a state that requires the instance to be reloaded
            // we need to set the jdoFlags to LOAD_REQUIRED to enable field mediation.
            if (state.needsReload(optimistic, nontransactionalRead, xactActive)) {
                persistenceManager.setFlags(persistentObject, LOAD_REQUIRED);
            } else {
                if (persistenceManager.getFlags(persistentObject) == LOAD_REQUIRED) {
                    persistenceManager.setFlags(persistentObject, READ_OK);
                }
            }
        }

        registerInstance(false, null, oldstate);
    }

    private void registerInstance(boolean throwDuplicateException,
        ArrayList newlyRegisteredSMs, LifeCycleState oldstate) {

        if ((stateFlags & ST_REGISTERED) == 0 || // not registered or
            (oldstate != state &&  // state changed from clean to dirty or transactional type.
                (oldstate == null || oldstate.isDirty() != state.isDirty() ||
                    oldstate.isTransactional() != state.isTransactional()))) {

            persistenceManager.registerInstance(this, getObjectId(), throwDuplicateException, false);
            stateFlags |= ST_REGISTERED;
            if (newlyRegisteredSMs != null) {
                if (!newlyRegisteredSMs.contains(this))
                    newlyRegisteredSMs.add(this);
            }
        }
    }

    public void setPersistenceManager(com.sun.jdo.api.persistence.support.PersistenceManager pm) {
        this.persistenceManager = (PersistenceManager) pm;
    }

    public void setPersistent(Object pc) {
        this.persistentObject = pc;
    }

    public PersistenceStore getStore() {
        return store;
    }

    public Object getPersistent() {
        return persistentObject;
    }

    public PersistenceConfig getPersistenceConfig() {
        return persistenceConfig;
    }

    private UpdateObjectDescImpl getUpdateDesc() {
        if (updateDesc == null) {
            updateDesc = (UpdateObjectDescImpl) store.getUpdateObjectDesc(
                    persistenceConfig.getPersistenceCapableClass());
        }

        if (updateDesc.getConcurrency() == null) {
            boolean optimistic = persistenceManager.isOptimisticTransaction();
            updateDesc.setConcurrency(persistenceConfig.getConcurrency(optimistic));
        }

        return updateDesc;
    }

    private void unsetMaskBit(int index, int mask) {
        if (fieldMasks == null) {
            newFieldMasks();
        } else {
            if (index >= 0) {
                fieldMasks.clear(index + mask * persistenceConfig.maxFields);
            } else {
                fieldMasks.clear(-(index + 1) + persistenceConfig.maxVisibleFields +
                        mask * persistenceConfig.maxFields);
            }
        }
    }

    private void clearMask(int mask) {
        if (fieldMasks != null) {
            fieldMasks.clear(mask * persistenceConfig.maxFields,
                    (mask+1) * persistenceConfig.maxFields);
        }
    }

    private void setVisibleMaskBits(int mask) {
        if (fieldMasks == null) {
            newFieldMasks();
        }

        int offset = mask * persistenceConfig.maxFields;
        fieldMasks.set(offset, offset + persistenceConfig.maxVisibleFields);
    }

    private BitSet getVisibleMaskBits(int mask) {
        if (fieldMasks == null) {
            newFieldMasks();
        }

        int offset = mask * persistenceConfig.maxFields;
        return fieldMasks.get(offset, offset + persistenceConfig.maxVisibleFields);
    }

    private void newFieldMasks() {
        this.fieldMasks = new BitSet(MAX_MASKS * persistenceConfig.maxFields);
    }

    public void setPresenceMaskBit(int index) {
        if (fieldMasks == null) {
            newFieldMasks();
        }

        if (index >= 0) {
            fieldMasks.set(index + PRESENCE_MASK * persistenceConfig.maxFields);
        } else {
            fieldMasks.set(-(index + 1) + persistenceConfig.maxVisibleFields +
                    PRESENCE_MASK * persistenceConfig.maxFields);
        }
    }

    private void setSetMaskBit(int index) {
        if (fieldMasks == null) {
            newFieldMasks();
        }

        if (index >= 0) {
            fieldMasks.set(index + SET_MASK * persistenceConfig.maxFields);
        } else {
            fieldMasks.set(-(index + 1) + persistenceConfig.maxVisibleFields +
                    SET_MASK * persistenceConfig.maxFields);
        }
    }

    public boolean getPresenceMaskBit(int index) {
        if (fieldMasks == null) {
            newFieldMasks();
        }

        if (index >= 0) {
            return fieldMasks.get(index + PRESENCE_MASK * persistenceConfig.maxFields);
        } else {
            return fieldMasks.get(-(index + 1) + persistenceConfig.maxVisibleFields +
                    PRESENCE_MASK * persistenceConfig.maxFields);
        }
    }

    public boolean getSetMaskBit(int index) {
        if (fieldMasks == null) {
            newFieldMasks();
        }

        if (index >= 0) {
            return fieldMasks.get(index + SET_MASK * persistenceConfig.maxFields);
        } else {
            return fieldMasks.get(-(index + 1) + persistenceConfig.maxVisibleFields +
                    SET_MASK * persistenceConfig.maxFields);
        }
    }

    public Object getHiddenValue(int index) {
        // This method expects index to be negative for hidden fields.
        if (index >= 0) {
            throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
                    "core.statemanager.poshiddenindex", "" + index)); // NOI18N
        }

        int realIndex = -(index + 1);

        if ((hiddenValues != null) && (realIndex < hiddenValues.size())) {
            return hiddenValues.get(realIndex);
        }

        return null;
    }

    public void setHiddenValue(int index, Object value) {
        // This method expects index to be negative for hidden fields.
        if (index >= 0) {
            throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
                    "core.statemanager.poshiddenindex", "" + index)); // NOI18N
        }

        int realIndex = -(index + 1);

        if (hiddenValues == null) {
            hiddenValues = new ArrayList();
        }

        for (int i = hiddenValues.size(); i <= realIndex; i++) {
            hiddenValues.add(null);
        }

        hiddenValues.set(realIndex, value);
    }

    public synchronized void replaceObjectField(String fieldName, Object o) {
        boolean debug = logger.isLoggable();

        if (debug) {
            Object[] items = new Object[] {fieldName, o.getClass().getName()};
            logger.fine("sqlstore.sqlstatemanager.replaceobjectfield", items); // NOI18N
        }

        FieldDesc fieldDesc = persistenceConfig.getField(fieldName);
        Object oldo = prepareSetField(fieldDesc, o);

        if ((oldo instanceof SCO) && oldo != o) {
            if (debug)
                logger.fine("sqlstore.sqlstatemanager.replaceobjectfield.unsetsco"); // NOI18N
            ((SCO) oldo).unsetOwner();
        }
    }

    public synchronized void makeDirty(String fieldName) {
        boolean debug = logger.isLoggable();

        if (debug) {
            logger.fine("sqlstore.sqlstatemanager.makedirty", fieldName); // NOI18N
        }

        FieldDesc fieldDesc = persistenceConfig.getField(fieldName);

        // Save current value: if it is SCO object we need to replace it with the
        // new value instead of new instance
        Object oldo = fieldDesc.getValue(this);

        prepareUpdateField(fieldDesc, null);

        // Now adjust SCO instance
        Object newo = fieldDesc.getValue(this);

        if ((newo instanceof SCO) && oldo != newo) {
            if (oldo instanceof SCOCollection) {
                if (debug) {
                    logger.fine("sqlstore.sqlstatemanager.makedirty.fixscocollection"); // NOI18N
                }

                ((SCOCollection) oldo).clearInternal();
                ((SCOCollection) oldo).addAllInternal((Collection) newo);
            }

            else if (oldo instanceof SCODate) {
                if (debug) {
                    logger.fine("sqlstore.sqlstatemanager.makedirty.fixscodate"); // NOI18N
                }

                long l = ((java.util.Date) newo).getTime();
                // Adjust nanoseconds if necessary:
                int n = 0;

                if (newo instanceof Timestamp) {
                    n = ((Timestamp) newo).getNanos();
                } else {
                    n = (int) ((l % 1000) * 1000000);
                }

                if (oldo instanceof SqlTimestamp) {
                    ((SCODate) oldo).setTimeInternal(l);
                    ((SqlTimestamp) oldo).setNanosInternal(n);

                } else if (newo instanceof Timestamp) {
                    ((SCODate) oldo).setTimeInternal(l + (n / 1000000));
                } else {
                    ((SCODate) oldo).setTimeInternal(l);
                }
            }

            updateTrackedFields(fieldDesc, oldo, null);
            fieldDesc.setValue(this, oldo);

            // disconnect temp SCO instance
            if (newo instanceof SCO)
                ((SCO) newo).unsetOwner();
        }
    }

    /**
     * This method is central to record changes to SCOCollections.
     */
    public void applyUpdates(String fieldName, SCOCollection c) {
        boolean debug = logger.isLoggable();

        if (debug) {
            logger.fine("sqlstore.sqlstatemanager.applyupdates", fieldName); // NOI18N
        }

        FieldDesc fieldDesc = persistenceConfig.getField(fieldName);
        if (fieldDesc instanceof ForeignFieldDesc) {
            ArrayList removed = new ArrayList(c.getRemoved());
            ArrayList added = new ArrayList(c.getAdded());

            // We reset the collection to clear the added and removed list before calling
            // processCollectionUpdates() which can throw an exception.
            c.reset();
            processCollectionUpdates((ForeignFieldDesc) fieldDesc, removed, added, null, true, false);
        }
        // else it is an ERROR?

        if (debug) {
            logger.fine("sqlstore.sqlstatemanager.applyupdates.exit"); // NOI18N
        }
    }

    public void makePresent(String fieldName, Object value) {
        boolean debug = logger.isLoggable();

        if (debug) {
            logger.fine("sqlstore.sqlstatemanager.makepresent", fieldName); // NOI18N
        }

        FieldDesc fieldDesc = persistenceConfig.getField(fieldName);
        fieldDesc.setValue(this, value);
        setPresenceMaskBit(fieldDesc.absoluteID);
    }

    public void setObjectId(Object objectId) {
        // RESOLVE: do we allow to replace existing?
        this.objectId = objectId;
    }

    public Object getObjectId() {
        // Note: PM.getObjectId() makes copy of the actual object id.
        if (objectId == null) {
            Class oidClass = persistenceConfig.getOidClass();
            Object oid = null;

            try {
                oid = oidClass.newInstance();
            } catch (Exception e) {
                throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
                        "core.statemanager.cantnewoid", oidClass.getName()), e); // NOI18N
            }

            Field keyFields[] = persistenceConfig.getKeyFields();
            String keyFieldNames[] = persistenceConfig.getKeyFieldNames();
            for (int i = 0; i < keyFields.length; i++) {
                Field keyField = keyFields[i];
                try {
                    FieldDesc fd = persistenceConfig.getField(keyFieldNames[i]);

                    if (fd != null) {
                        keyField.set(oid, fd.getValue(this));
                    }

                } catch (IllegalAccessException e) {
                    throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
                            "core.statemanager.cantsetkeyfield", keyField.getName()), e); // NOI18N
                }
            }
            objectId = oid;
        }

        return objectId;
    }

    private void makeAutoPersistent(Object pc) {
        persistenceManager.makePersistent(pc);
        SQLStateManager sm = (SQLStateManager) persistenceManager.getStateManager(pc);

        sm.state = LifeCycleState.getLifeCycleState(LifeCycleState.AP_NEW);
    }

    /**
     * Prepares the associated object to be stored in the datastore.
     * This method is called by PersistenceManager.makePersistent().
     * Thread synchronization is done in the persistence manager.
     */
    public void makePersistent(PersistenceManager pm, Object pc) {

        boolean debug = logger.isLoggable();

        if (debug) {
            logger.fine("sqlstore.sqlstatemanager.makepersistence", // NOI18N
            persistenceConfig.getPersistenceCapableClass().getName());
        }

        // If the instance is autopersistent, we simply transition it to persistent_new.
        if (state != null) {
            if (state.isAutoPersistent()) {
                state = state.transitionMakePersistent();
            }
            return;
        }

        this.persistenceManager = pm;
        this.persistentObject = pc;

        // Mark all the visible fields as present to prevent navigation and
        // to allow us to create a before image that contains all the fields.
        setVisibleMaskBits(PRESENCE_MASK);
        getBeforeImage();

        state = LifeCycleState.getLifeCycleState(LifeCycleState.P_NEW);

        try {
            registerInstance(true, null, null);
        } catch (JDOException e) {
            this.release();

            throw e;
        }

        // We set the statemanager for the pc now so the instance is considered
        // persistent. We need to do this in order for persistent-by-reachability to
        // work properly in the case of self-referencing relationship.
        pm.setStateManager(pc, this);
        valid = true;

        // Now that the state manager has been set in the pc, we need to
        // synchronize it so other threads can't modify this instance while
        // we perform the persistence-by-reachability algorithm.
        try {
            getLock();

            // Make sure all the fields have been marked dirty.
            Object obj = null;
            ArrayList fields = persistenceConfig.fields;
            for (int i = 0; i < fields.size(); i++) {
                FieldDesc f = (FieldDesc) fields.get(i);

                // In case of makePersistent, we skip all secondary tracked fields
                // and use the primary to propagate changes. In addition, we take
                // the policy that a tracked relationship field takes precedence
                // over its primitive counterpart. In other words, we skip all
                // primitive fields that also tracks relationship fields.
                if ((f.sqlProperties & FieldDesc.PROP_SECONDARY_TRACKED_FIELD) > 0) {
                    continue;
                }

                obj = f.getValue(this);

                if (f instanceof ForeignFieldDesc) {
                    ForeignFieldDesc ff = (ForeignFieldDesc) f;
                    ArrayList trackedFields = null;

                    if (debug) {
                        logger.fine("sqlstore.sqlstatemanager.processforeign", ff.getName()); // NOI18N
                    }

                    if ((ff.sqlProperties & FieldDesc.PROP_PRIMARY_TRACKED_FIELD) > 0) {
                        trackedFields = ff.getTrackedFields();
                        Object theValue = obj;

                        for (int j = 0; j < trackedFields.size(); j++) {
                            FieldDesc tf = (FieldDesc) trackedFields.get(j);
                            Object value = tf.getValue(this);

                            if ((theValue != null) && (value != null) && (theValue != value)) {
                                if (needsVerifyAtDeregister) {
                                    persistenceManager.deregisterInstance(getObjectId(), this);
                                    needsVerifyAtDeregister = false;
                                } else {
                                    persistenceManager.deregisterInstance(getObjectId());
                                }
                                this.release();
                                throw new JDOUserException(I18NHelper.getMessage(messages,
                                        "core.statemanager.conflictingvalues", ff.getName(), tf.getName())); // NOI18N
                            } else if ((theValue == null) && (value != null)) {
                                theValue = value;
                            }
                        }

                        if (theValue != obj) {
                            obj = theValue;
                            ff.setValue(this, obj);
                        }
                    }

                    if (obj != null) {
                        if (obj instanceof Collection) {
                            if (((Collection) obj).size() > 0) {
                                ArrayList removed = null;
                                ArrayList added = new ArrayList((Collection) obj);
                                processCollectionUpdates(ff, removed, added, null, true, false);
                            }
                        } else {
                            // null out this field to pretend we are setting this field for the first time
                            ff.setValue(this, null);

                            updateObjectField(ff, obj, true, false);

                            // now restore the value
                            ff.setValue(this, obj);
                        }
                    } else {
                        // For a null managed collection relationship field, we replace it
                        // with an empty SCOCollection
                        if ((ff.getInverseRelationshipField() != null) && (ff.cardinalityUPB > 1)) {
                            replaceCollection(ff, null);
                        }
                    }

                    updateTrackedFields(ff, ff.getValue(this), null);
                } else {
                    // We ignore primitive fields that also tracks relationship field
                    if ((f.sqlProperties & FieldDesc.PROP_TRACK_RELATIONSHIP_FIELD) > 0) {
                        ArrayList trackedFields = f.getTrackedFields();
                        boolean found = false;

                        for (int j = trackedFields.size() - 1; j >= 0; j--) {
                            FieldDesc tf = (FieldDesc) trackedFields.get(j);

                            if (tf instanceof ForeignFieldDesc) {
                                if (tf.getValue(this) != null) {
                                    found = true;
                                    break;
                                }
                            } else {
                                break;
                            }
                        }

                        if (!found) {
                            //f.setValue(this, null);
                            updateTrackedFields(f, obj, null);
                            //f.setValue(this, obj);
                        }
                    } else {
                        updateTrackedFields(f, obj, null);
                    }

                    if ((f.sqlProperties & FieldDesc.PROP_RECORD_ON_UPDATE) > 0) {
                        getUpdateDesc().recordUpdatedField((LocalFieldDesc) f);
                    }
                }

                if (debug) {
                    logger.fine("sqlstore.sqlstatemanager.makedirtyfield", f.getName()); // NOI18N
                }

                setSetMaskBit(f.absoluteID);
            }
        } finally {
            releaseLock();
        }
    }

    /**
     * Prepares the associated object for delete. This method is
     * called by PersistenceManager.deletePersistent(). After
     * nullifying the relationship fields, the instance transitions to
     * deleted state.
     */
    public void deletePersistent() {
        if (logger.isLoggable()) {
             logger.fine("sqlstore.sqlstatemanager.deletepersistence", // NOI18N
             persistenceConfig.getPersistenceCapableClass().getName());

        }

        // Why try try?  The difference is in whether you try and then
        // acquire/get, or acquire/get and then try.
        // Prior to having acquireFieldUpdateLock, this code synchronized on
        // a field, following that with the try for {get,release}Lock.  That
        // pattern of usage calls for that order.
        // {acquire,release}FieldUpdateLock calls for the other order.  We
        // are close to FCS, so this strange situation persists for now
        // (i.e., it ain't broken).

        persistenceManager.acquireFieldUpdateLock();
        try {
            try {
                getLock();

                if (state.isDeleted()) {
                    return;
                }

                deleteRelationships();

                LifeCycleState oldstate = state;
                state = state.transitionDeletePersistent();
                persistenceManager.setFlags(persistentObject, LOAD_REQUIRED);
                registerInstance(false, null, oldstate);
            } finally {
                releaseLock();
            }
        } finally {
            persistenceManager.releaseFieldUpdateLock();
        }
    }

    /**
     * Prepares the current instance for delete by nullifying all
     * relationships. The deletion is propagated to relationship
     * fields marked for cascade delete.
     */
    private void deleteRelationships() {
        ArrayList foreignFields = persistenceConfig.foreignFields;
        int size = foreignFields.size();
        stateFlags |= ST_DELETE_INPROGRESS;

        for (int i = 0; i < size; i++) {
            ForeignFieldDesc ff = (ForeignFieldDesc) foreignFields.get(i);
            ForeignFieldDesc irf = ff.getInverseRelationshipField();

            // Skip this field if it is secondary.
            if ((ff.sqlProperties & FieldDesc.PROP_SECONDARY_TRACKED_FIELD) > 0) {
                continue;
            }

            // Skip this field if it is not managed nor marked for cascade delete.
            if ((ff.deleteAction != ForeignFieldDesc.ACT_CASCADE) && (irf == null)) {
                continue;
            }

            prepareUpdateField(ff, null);

            if (ff.cardinalityUPB > 1) {
                Collection c = (Collection) ff.getValue(this);

                if (c != null) {
                    ArrayList removed = new ArrayList(c);

                    // For managed relationship or cascade delete, we need to call
                    // processCollectionUpdates() to set up the dependency. In case of
                    // managed relationship, the inverse relationship field should also
                    // be set to null.
                    processCollectionUpdates(ff, removed, null, null, true, false);

                    if (c instanceof SCOCollection) {
                        ((SCOCollection) c).clearInternal();
                    } else {
                        c.clear();
                    }

                    if (ff.deleteAction == ForeignFieldDesc.ACT_CASCADE) {
                        Iterator iter = removed.iterator();

                        while (iter.hasNext()) {
                            Object obj = iter.next();

                            if (obj != null) {
                                SQLStateManager sm = (SQLStateManager)
                                        persistenceManager.getStateManager(obj);

                                // Ignore if this sm is in the process of being cascade
                                // deleted. This is to prevent infinite recursive in case
                                // of self-referencing relationship.
                                if ((sm != null) && !sm.isDeleted() &&
                                        ((sm.stateFlags & ST_DELETE_INPROGRESS) == 0)) {
                                    try {
                                        persistenceManager.deletePersistent(obj);
                                    } catch (Throwable e) {
                                    }
                                }
                            }
                        }
                    }
                }
            } else {
                Object obj = ff.getValue(this);

                if (obj != null) {
                    updateObjectField(ff, null, true, false);
                    ff.setValue(this, null);

                    if (ff.deleteAction == ForeignFieldDesc.ACT_CASCADE) {
                        SQLStateManager sm = (SQLStateManager)
                                persistenceManager.getStateManager(obj);

                        // Ignore if this sm is in the process of being cascade
                        // deleted. This is to prevent infinite recursive in case
                        // of self-referencing relationships.
                        if ((sm != null) && !sm.isDeleted() &&
                                ((sm.stateFlags & ST_DELETE_INPROGRESS) == 0)) {
                            try {
                                persistenceManager.deletePersistent(obj);
                            } catch (Throwable e) {
                            }
                        }
                    }
                }
            }
        }

        stateFlags &= ~ST_DELETE_INPROGRESS;
    }

    /**
     * Stores the associated object in the datastore. This method is
     * called by {@link PersistenceManager#beforeCompletion} on
     * flush/commit.  The specified state manager argument is used to
     * determine whether the actual instance should be flushed
     * immediately or whether batch update is possible.
     *
     * @param next Next state manager in the transaction cache.
     */
    public void updatePersistent(StateManager next) {
        boolean debug = logger.isLoggable();

        if ((stateFlags & ST_UPDATE_DISABLED) > 0) {
            if (debug) {
                Object[] items = new Object[] {persistenceConfig.getPersistenceCapableClass().getName(),
                                               persistentObject};
                logger.fine("sqlstore.sqlstatemanager.updatepersistent.skipped", items); // NOI18N
            }
            return;
        }

        try {
            if (debug) {
                 logger.fine("sqlstore.sqlstatemanager.updatepersistent", // NOI18N
                 persistenceConfig.getPersistenceCapableClass().getName());
            }

            ArrayList actions = new ArrayList();

            // Get a list of actions to perform.
            getUpdateActions(actions);

            if (actions.size() == 1 && useBatch()) {
                // Batch update only if actions consists of a single action
                UpdateObjectDesc updateDesc = (UpdateObjectDesc)actions.get(0);
                boolean immediateFlush = requiresImmediateFlush((SQLStateManager)next);

                if (debug && immediateFlush) {
                    Object[] items = new Object[] {getPersistent(), (next != null) ? next.getPersistent() : null};
                    logger.fine("sqlstore.sqlstatemanager.updatepersistent.immediateflush", items); // NOI18N
                }

                store.executeBatch(persistenceManager, updateDesc, immediateFlush);
            } else if (actions.size() > 0) {
                store.execute(persistenceManager, actions);
            }

            incrementVersion(actions);

            if (debug) {
                logger.fine("sqlstore.sqlstatemanager.updatepersistent.exit"); // NOI18N
            }
        } catch (JDOException e) {
            e.addFailedObject(persistentObject);
            throw e;
        } catch (Exception e) {
            logger.throwing("sqlstore.SQLStateManager", "updatePersistent", e); // NOI18N
            throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
                    "core.generic.unknownexception"), e); // NOI18N
        }
    }

    /**
     * Increments the version for all state managers in
     * actions registered for version consistency.
     *
     * @param actions List of updated state managers.
     */
    static private void incrementVersion(List actions) {

        for (Iterator iter = actions.iterator(); iter.hasNext(); ) {
            ((UpdateObjectDescImpl) iter.next()).incrementVersion();
        }
    }

    /**
     * Increments the version fields for this state manager. Instances
     * mapped to multiple tables have got a version field for each table.
     */
    public void incrementVersion() {
        LocalFieldDesc [] versionFields = persistenceConfig.getVersionFields();

        for (int i = 0; i < versionFields.length; i++) {
            versionFields[i].incrementValue(this);
        }
    }

    /**
     * @inheritDoc StateManager#hasVersionConsistency
     */
    public boolean hasVersionConsistency() {
        return persistenceConfig.hasVersionConsistency();
    }

    /**
     * @inheritDoc StateManager#verifyPersistent
     */
    public boolean verifyPersistent() {
        assert persistenceConfig.hasVersionConsistency();
        boolean verified = true;

        if (state instanceof PersistentClean) {
            RetrieveDesc verificationRD = persistenceConfig.getRetrieveDescForVerificationQuery(store);
            LocalFieldDesc[] keyFields = persistenceConfig.getKeyFieldDescs();
            LocalFieldDesc[] versionFields = persistenceConfig.getVersionFields();

            // Please make sure that the order of parameter values is same as
            // order of parameters as defined by ClassDesc#getRetrieveDescForVerificationQuery()
            Object [] parameters = new Object[keyFields.length + versionFields.length];
            copyValues(parameters, keyFields, 0);
            copyValues(parameters, versionFields, keyFields.length);

            // verificationRD requires parameters for pk field and version fields
            Boolean result = (Boolean) store.
                    retrieve(persistenceManager, verificationRD, new QueryValueFetcher(parameters));
            verified = result.booleanValue();
        }
        return verified;
    }

    /**
     * Returns true if batch update might be used to store the changes
     * of this state manager.
     *
     * TODO: Because batched statements on Oracle don't return a
     * valid success indicator, batching is disabled for Version
     * Consistency.
     */
    private boolean useBatch() {
        boolean result = false;

        if (USE_BATCH) {
            switch(state.getUpdateAction()) {

            case ActionDesc.LOG_CREATE:
                result = !getUpdateDesc().hasChangedRelationships() &&
                         !getUpdateDesc().hasModifiedLobField();
                break;
            case ActionDesc.LOG_DESTROY:
            case ActionDesc.LOG_UPDATE:
                // Do not try to batch in optimitic tx for now. We need to
                // check for parallel updates, so the WHERE clause checks
                // the values from the beforeImage. We need a different SQL
                // statements for the null vs. non null case.
                result = !persistenceManager.isOptimisticTransaction() &&
                         !persistenceConfig.hasModifiedCheckAtCommitConsistency() &&
                         !getUpdateDesc().hasChangedRelationships() &&
                         !getUpdateDesc().hasModifiedLobField() &&
                         !hasVersionConsistency();
                break;
            default:
                result = false;
                break;
            }
        }

        return result;
    }

    /**
     * @inheritDoc StateManager#setVerificationFailed
     */
    public void setVerificationFailed() {
        if (hasVersionConsistency()) {
            stateFlags |= ST_VALIDATION_FAILED;
        }
    }

    /**
     * @inheritDoc StateManager#getFailed
     */
    public boolean isVerificationFailed() {
        return (stateFlags & ST_VALIDATION_FAILED) > 0;
    }

    /**
     * This method checks whether this StateManager instance needs to be
     * flushed immediately during beforeCompletion. A return of false
     * means the store manager is allowed to combine flushing of these two
     * instance in a single database roundtrip (e.g. by using batched updates).
     *
     * @param next Next state manager to be flushed.
     */
    private boolean requiresImmediateFlush(SQLStateManager next) {
        // There is no next SM =>
        // flush this sm immediately
        if (next == null)
            return true;

        // The next StateManager has a different pc class =>
        // flush this sm immediately
        if (persistenceConfig != next.persistenceConfig)
            return true;

        // The next StateManager represents a different update operation
        // INSERT/UPDATE/DELETE => flush this sm immediately
        if (state.getUpdateAction() != next.state.getUpdateAction())
            return true;

        // If the next's flush is disabled, flush this sm
        if ((next.stateFlags & ST_UPDATE_DISABLED) > 0)
            return true;

        // If next sm does not use batch update flush this sm
        if (!next.useBatch())
            return true;

        // For updates, we need to check if the next sm updates the
        // same fields. If not, flush this sm
        if (getUpdateDesc().getUpdateAction() == ActionDesc.LOG_UPDATE &&
                !compareUpdatedFields(next)) {
            return true;
        }

        // If the next stateManager has got (foreign reference) dependencies,
        // it is not considered for batching at all
        if (next.updatedForeignReferences != null) {
            return true;
        }

        // Now we can make use of batching w/o flushing
        return false;
    }

    private boolean compareUpdatedFields(SQLStateManager next) {
        BitSet updFields = getVisibleMaskBits(SET_MASK);
        BitSet nextUpdFields = (next != null) ? next.getVisibleMaskBits(SET_MASK) : null;

        return updFields.equals(nextUpdFields);
    }

    public void refreshPersistent() {
        boolean debug = logger.isLoggable();

        if (debug) {
            logger.fine("sqlstore.sqlstatemanager.refreshpersistent", // NOI18N
            persistenceConfig.getPersistenceCapableClass().getName());
        }

        // Only refresh if the state allows it.
        if (state.isRefreshable()) {
            LifeCycleState oldstate = state;
            state = state.transitionRefreshPersistent();
            reload(null);
            registerInstance(false, null, oldstate);
        }

        if (debug) {
            logger.fine("sqlstore.sqlstatemanager.refreshpersistent.exit"); // NOI18N
        }
    }

    /**
     * Reloads the instance by delegating actual work, state transition, and
     * instance registration to {@link #reload(FieldDesc) reload(FieldDesc)}
     * With null as an argument. Called by
     * {@link PersistenceManager#getObjectById(Object, boolean)
     * PersistenceManager.getObjectById(Object, boolean)} with validate
     * flag set to true
     */
    public void reload() {
        boolean debug = logger.isLoggable(Logger.FINER);

        if (debug) {
            logger.finer("sqlstore.sqlstatemanager.unconditionalreload", // NOI18N
            persistenceConfig.getPersistenceCapableClass().getName());
        }

        persistenceManager.acquireShareLock();

        try {
            getLock();

            reload(null);

        } finally {
            persistenceManager.releaseShareLock();
            releaseLock();

            if (debug) {
                logger.finer("sqlstore.sqlstatemanager.unconditionalreload.exit"); // NOI18N
            }
        }
    }

    /**
     * Reloads this SM from the state in the datastore, getting data for the
     * given field.
     * @param additionalField Field to be loaded.
     */
    private void reload(FieldDesc additionalField) {
        boolean debug = logger.isLoggable();

        if (debug) {
            String fieldName =
                (additionalField != null) ? additionalField.getName() : null;
            logger.fine("sqlstore.sqlstatemanager.reload", // NOI18N
            persistenceConfig.getPersistenceCapableClass().getName(), fieldName);
        }

        // Clear the fields PresenceMask so all the currently present
        // fields will be replaced.
        clearMask(PRESENCE_MASK);

        // Need to mark the key fields as present
        markKeyFieldsPresent();

        clearMask(SET_MASK);

        LifeCycleState oldState = state;
        state = state.transitionReload(persistenceManager.isActiveTransaction());

        if (!retrieveFromVersionConsistencyCache(additionalField)) {
            // Retrieve the instance from the data store, if this class
            // is not version consistent, or not found in the cache.

            try {
                retrieve(additionalField);
            } catch (JDOException e) {
                // Reset the state if the instance couldn't be found.
                state = oldState;
                throw e;
            }
        }
        registerInstance(false, null, oldState);

        if (persistenceManager.getFlags(persistentObject) == LOAD_REQUIRED) {
            persistenceManager.setFlags(persistentObject, READ_OK);
        }

        if (debug) {
            logger.fine("sqlstore.sqlstatemanager.reload.exit"); // NOI18N
        }
    }

    /**
     * Initialize this SM from the version consistency cache. If this
     * SM is in the cache and the additional field is not populated,
     * the field is retrieved from the store.
     * @param additionalField Field to be loaded.
     */
    private boolean retrieveFromVersionConsistencyCache(FieldDesc additionalField) {
        boolean rc =
            persistenceManager.initializeFromVersionConsistencyCache(this);

        if (rc) {

            // make sure additionalField is available
            if (additionalField != null
                    && !getPresenceMaskBit(additionalField.absoluteID)) {

                realizeField(additionalField);
            }
        }
        return rc;
    }

    /**
     * PersistenceManager calls this method to prepare a persistent
     * object for update. This is required for foreign fields only
     * as they could reference "regular" JDK Collections vs. SCO
     * Collections. Such process has the side-effect of causing more
     * objects to be registered with the transaction cache.
     */
    public void prepareToUpdatePhaseI() {
        boolean debug = logger.isLoggable();

        if (debug) {
            logger.fine("sqlstore.sqlstatemanager.preparetoupdateph1", // NOI18N
            persistenceConfig.getPersistenceCapableClass().getName());
        }

        int action = state.getUpdateAction();

        if (action == ActionDesc.LOG_NOOP || action == ActionDesc.LOG_DESTROY) {
            // Nothing extra to do
            return;
        }

        // Initialize UpdateDesc.
        getUpdateDesc();

        ArrayList newlyRegisteredSMs = new ArrayList();
        ArrayList foreignFields = persistenceConfig.foreignFields;
        int size = foreignFields.size();

        for (int i = 0; i < size; i++) {
            ForeignFieldDesc ff = (ForeignFieldDesc) foreignFields.get(i);

            if ((ff.sqlProperties & FieldDesc.PROP_SECONDARY_TRACKED_FIELD) > 0) {
                continue;
            }

            if ((ff.cardinalityUPB > 1) && (getSetMaskBit(ff.absoluteID) == true)) {
                Collection v = (Collection) ff.getValue(this);

                if ((v != null) && (!(v instanceof SCO) || (((SCO) v).getOwner() == null)) &&
                        (v.size() > 0)) {
                    ArrayList removed = null;
                    ArrayList added = new ArrayList(v);

                    processCollectionUpdates(ff, removed, added, newlyRegisteredSMs, true, false);
                }
            }
        }

        // The newRegisteredSMs should contain a list of all the state managers that
        // are registered as the result of processCollectionUpdates.
        for (int i = 0; i < newlyRegisteredSMs.size(); i++) {
            SQLStateManager sm = (SQLStateManager) newlyRegisteredSMs.get(i);

            sm.prepareToUpdatePhaseI();
        }

        if (debug) {
            logger.fine("sqlstore.sqlstatemanager.preparetoupdateph1.exit"); // NOI18N
        }
    }

    /**
     * This is the second phase of the commit processing. It populates phase3sms with all
     * the autopersistent instances that are no longer reachable from a persistent instance.
     *
     * @param phase3sms List containing autopersistent instances that are no longer reachable
     *  from a persistent instance.
     */
    public void prepareToUpdatePhaseII(HashSet phase3sms) {
        boolean debug = logger.isLoggable();

        if (debug) {
             logger.fine("sqlstore.sqlstatemanager.preparetoupdateph2", // NOI18N
             persistenceConfig.getPersistenceCapableClass().getName());
        }

        // If this instance is autopersistent, we transition it into a pending state and
        // add it to phase3sms collection. Any instance in phase3sms collection may be removed
        // later if it becomes persistent.
        if (state.isAutoPersistent()) {
            state = state.transitionMakePending();
            phase3sms.add(this);
            return;
        }

        if ((stateFlags & ST_PREPARED_PHASE_II) > 0) {
            return;
        }

        stateFlags |= ST_PREPARED_PHASE_II;

        if ((!state.isNew() && !state.isDirty()) || state.isDeleted()) {
            return;
        }

        ArrayList foreignFields = persistenceConfig.foreignFields;
        int size = foreignFields.size();

        // Walk the object graph starting from this instance and transition all
        // autopersistent instances to persistent and remove it from phase3sms.
        for (int i = 0; i < size; i++) {
            ForeignFieldDesc ff = (ForeignFieldDesc) foreignFields.get(i);

            if (ff.cardinalityUPB <= 1) {
                if (getPresenceMaskBit(ff.absoluteID)) {
                    Object v = ff.getValue(this);

                    if (v != null) {
                        transitionPersistent(v, phase3sms);
                    }
                }
            } else {
                Collection c = getCollectionValue(ff);

                if (c != null) {
                    Iterator iter = c.iterator();

                    while (iter.hasNext()) {
                        Object v = iter.next();

                        transitionPersistent(v, phase3sms);
                    }
                }
            }
        }

        if (debug) {
            logger.fine("sqlstore.sqlstatemanager.preparetoupdateph2.exit"); // NOI18N
        }
    }

    /**
     * This is the third phase of commit processing. It sets up the delete dependencies among
     * all the autopersistent instances that have been flushed to the database.
     */
    public void prepareToUpdatePhaseIII() {
        boolean debug = logger.isLoggable();

        if (debug) {
            logger.fine("sqlstore.sqlstatemanager.preparetoupdateph3", // NOI18N
            persistenceConfig.getPersistenceCapableClass().getName());
        }

        if (!state.isPersistentInDataStore()) {
            // This object will not be written to the store. But we need to
            // make sure, that scheduled jointable entries aren't written either.
            // See UpdateQueryPlan#processJoinTables().
            if (updateDesc != null) {
                updateDesc.clearUpdatedJoinTableRelationships();
            }

            // Finished for this instance.
            return;
        }

        ArrayList foreignFields = persistenceConfig.foreignFields;
        int size = foreignFields.size();

        // Sets up dependencies between this instance and all its relationship fields
        // that are autopersistent.
        for (int i = 0; i < size; i++) {
            ForeignFieldDesc ff = (ForeignFieldDesc) foreignFields.get(i);

            if (ff.cardinalityUPB <= 1) {
                if (getPresenceMaskBit(ff.absoluteID)) {
                    Object v = ff.getValue(this);

                    if (v != null) {
                        updateObjectField(ff, null, false, false);
                    }
                }
            } else {
                Collection c = getCollectionValue(ff);

                if (c != null) {
                    if (c.size() > 0) {
                        ArrayList removed = new ArrayList(c);
                        ArrayList added = null;

                        processCollectionUpdates(ff, removed, added, null, false, false);
                    }
                }
            }
        }

        if (debug) {
            logger.fine("sqlstore.sqlstatemanager.preparetoupdateph3.exit"); // NOI18N
        }
    }

    /**
     * Transitions the instance pc to persistent state if it's
     * autopersistent, and removes its state manager from the list
     * phase3sms of unreachable autopersistent
     * instances. The recursive call to prepareToUpdatePhaseII
     * removes the transitive closure of all state managers reachable from
     * pc from phase3sms. This method has got no
     * effects on transient instances.
     *
     * @param pc Instance becoming persistent and removed from phase3sms.
     * @param phase3sms List containing so far unreachable autopersistent instances.
     */
    private void transitionPersistent(Object pc, HashSet phase3sms) {
        SQLStateManager sm = (SQLStateManager) persistenceManager.getStateManager(pc);

        // Need to check if the associated state manager is null, if
        // called with an object from a collection relationship field. If
        // the collection is not a SCO collection, it is possible that it
        // contains transient instances. No need to check for object
        // relationship fields.
        if (sm != null && sm.state.isAutoPersistent()) {
            sm.state = sm.state.transitionMakePersistent();
            phase3sms.remove(sm);
            sm.prepareToUpdatePhaseII(phase3sms);
        }
    }

    /**
     * Returns the value of the collection relationship field
     * ff.  For deferred SCOCollections, only the
     * objects added in the current transaction are returned.
     * This method may only be called for Collection fields!
     *
     * @param ff Collection relationship field.
     * @return The value of the collection relationship field
     * ff. For deferred SCOCollections, only the
     * objects added in the current transaction are returned.
     */
    private Collection getCollectionValue(ForeignFieldDesc ff) {
        Collection c = null;
        if (ff.cardinalityUPB > 1) {
            c = (Collection) ff.getValue(this);
            if (c != null && c instanceof SCOCollection) {
                SCOCollection sco = (SCOCollection) c;
                if (sco.isDeferred()) {
                    c = sco.getAdded();
                }
            }
        }
        return c;
    }

    private void getUpdateActions(ArrayList actions) {
        if ((stateFlags & ST_VISITED) > 0) {
            return;
        }

        int action = state.getUpdateAction();

        if ((action == ActionDesc.LOG_NOOP) && (updateDesc == null)) {
            return;
        }

        // Initialize updateDesc.
        getUpdateDesc();

        updateDesc.setObjectInfo(getBeforeImage(), this, action);

        if ((action == ActionDesc.LOG_DESTROY) || (action == ActionDesc.LOG_CREATE) ||
                updateDesc.hasUpdatedFields() || updateDesc.hasUpdatedJoinTableRelationships()) {
            actions.add(updateDesc);
        }

        stateFlags |= ST_VISITED;

        if (updatedForeignReferences != null) {
            Iterator iter = updatedForeignReferences.iterator();

            while (iter.hasNext()) {
                SQLStateManager sm = ((UpdatedForeignReference) iter.next()).getStateManager();

                if (sm.referenceCount == 1) {
                    sm.getUpdateActions(actions);
                }

                sm.referenceCount--;
            }
        }
    }

    public void release() {
        if (null != persistenceManager) {

            // The persistenceManager can be null, for example if this
            // instance is used in the VersionConsistency cache.
            persistenceManager.setStateManager(persistentObject, null);
        }

        persistentObject = null;
        objectId = null;
        persistenceManager = null;
        beforeImage = null;
        hiddenValues = null;
        updatedForeignReferences = null;
        updateDesc = null;
        persistenceConfig = null;
        store = null;
        valid = false;
    }

    private void reset(boolean retainValues, boolean wasNew, boolean keepState) {
        boolean debug = logger.isLoggable();

        if (debug) {
             Object[] items = new Object[] {Boolean.valueOf(retainValues),
                                            Boolean.valueOf(wasNew), Boolean.valueOf(keepState)};
             logger.fine("sqlstore.sqlstatemanager.reset", items); // NOI18N

        }

        if (state == null) {
            // make the instance transient.

            if (!keepState) {
                persistenceManager.clearFields(persistentObject);
            }

            // Need to set jdoFlag to READ_WRITE_OK for transient instance.
            persistenceManager.setFlags(persistentObject, READ_WRITE_OK);

            if (needsVerifyAtDeregister) {
                persistenceManager.deregisterInstance(getObjectId(), this);
            } else {
                persistenceManager.deregisterInstance(getObjectId());
            }
            this.release();
        } else {
            // Reset the state manager for the next transaction.
            stateFlags = 0;
            beforeImage = null;
            updatedForeignReferences = null;
            referenceCount = 0;

            if (updateDesc != null) {
                updateDesc.reset();
            }

            // We retain the field values if retainValues is true or the state
            // is persistentNontransactional
            if (retainValues || (state instanceof PersistentNonTransactional)) {
                FieldDesc f = null;
                ArrayList fields = persistenceConfig.fields;
                for (int i = 0; i < fields.size(); i++) {
                    f = (FieldDesc) fields.get(i);
                    Object v = f.getValue(this);

                    // For new objects mark null references as not set if the field is
                    // not managed. This is to allow the field to be reloaded in the next
                    // transaction because a relationship may exist in the database.
                    if (wasNew && (f instanceof ForeignFieldDesc) &&
                            (v == null) && (((ForeignFieldDesc) f).getInverseRelationshipField() == null)) {
                        if (debug)
                            logger.fine("sqlstore.sqlstatemanager.unsetmask", f.getName()); // NOI18N
                        unsetMaskBit(f.absoluteID, PRESENCE_MASK);
                    }

                    // Replace java.util Collection and Date objects
                    // with SCO instances:
                    if ((v instanceof Collection) && !(v instanceof SCOCollection)
                            && !keepState) {
                        if (debug)
                            logger.fine("sqlstore.sqlstatemanager.resettingcollection"); // NOI18N

                        replaceCollection((ForeignFieldDesc) f, (Collection) v);

                        if (debug) {
                            logger.fine("sqlstore.sqlstatemanager.newtype", (f.getValue(this)).getClass()); // NOI18N
                        }
                    } else if (v instanceof SCOCollection) {
                        ((SCOCollection) v).reset();
                    }

                    // TO FIX!!!: this already replaces Date with SCO Date
                    else if ((v instanceof java.util.Date) && !(v instanceof SCODate)
                            && !keepState) {
                        if (debug)
                            logger.fine("sqlstore.sqlstatemanager.resettingdate"); // NOI18N

                        v = f.convertValue(v, this);
                        f.setValue(this, v);
                        if (debug) {
                            logger.fine("sqlstore.sqlstatemanager.newtype", (f.getValue(this)).getClass()); // NOI18N
                        }
                    }
                }

                // We need to set the jdoFlags to LOAD_REQUIRED instead of READ_OK in order to
                // have the StateManager intermediate access to this instance. The reason is
                // if the next transaction is pessimistic, the StateManager needs to reload
                // the instance. Note that the StateManager will not reload the instance
                // if the transaction is optimistic and the instance is p_nontransactional.
                persistenceManager.setFlags(persistentObject, LOAD_REQUIRED);
            } else {
                clearMask(PRESENCE_MASK);
                persistenceManager.clearFields(persistentObject);

                // Need to mark the key fields as present
                markKeyFieldsPresent();

                persistenceManager.setFlags(persistentObject, LOAD_REQUIRED);
            }

            clearMask(SET_MASK);
            isReplacementInstance = false;
            needsRegisterAtRollback = false;
            needsVerifyAtDeregister = false;
        }
    }

    /**
     * @return true if persistentObject has been flushed to db
     */
    public boolean isProcessed() {
        return (referenceCount == 0);
    }

    public void flushed() {
        // reset the current state to the point where we can accept more updates.
        state = state.transitionFlushed();

        clearMask(SET_MASK);

        stateFlags &= ~ST_VISITED;
        stateFlags &= ~ST_UPDATE_DISABLED;
        stateFlags &= ~ST_REGISTERED;

        // need to set the jdoFlags to LOAD_REQUIRED to allow updated to be tracked.
        persistenceManager.setFlags(persistentObject, LOAD_REQUIRED);

        if (updatedForeignReferences != null) {
            updatedForeignReferences.clear();
        }

        if (updateDesc != null) {
            updateDesc.reset();
        }
    }

    public void commit(boolean retainValues) {
        boolean wasNew = (state.isNew() && !state.isDeleted());
        state = state.transitionCommit(retainValues);
        reset(retainValues, wasNew, false);
    }

    public void rollback(boolean retainValues) {
        boolean wasNew = (state.isNew() && !state.isDeleted());
        boolean needsRestore = state.needsRestoreOnRollback(retainValues);
        state = state.transitionRollback(retainValues);
        boolean keepState = needsRestore;

        // Only restore if there is a before image and the flag needsRestore is true.
        if ((beforeImage != null) && (needsRestore == true)) {
            copyFields(beforeImage, true, false);

            // Keep the fields from being reset in reset()
            keepState = true;
        }

        if (needsRegisterAtRollback && !isReplacementInstance) {
            persistenceManager.registerInstance(this, getObjectId());
        }
        reset(retainValues, wasNew, keepState);
    }

    private void markKeyFieldsPresent() {
        ArrayList keyFields = persistenceConfig.getPrimaryTable().getKey().getFields();

        for (int i = 0; i < keyFields.size(); i++) {
            LocalFieldDesc fd = (LocalFieldDesc) keyFields.get(i);

            if (fd != null) {
                setPresenceMaskBit(fd.absoluteID);
            }
        }
    }

    public void prepareGetField(int fieldID) {
        FieldDesc fieldDesc = persistenceConfig.getField(fieldID);

        prepareGetField(fieldDesc, false, true);
    }

    private void prepareGetField(FieldDesc fieldDesc) {
        prepareGetField(fieldDesc, true, false);
    }

    /**
     * Loads the field described by fieldDesc. If the field is not
     * present in the instance, it will be loaded from the data store by calling
     * {@link #realizeField(FieldDesc)}. Depending on its lifecycle state,
     * the instance is registered in the transaction cache and the field
     * is marked as read.
     *
     * @param fieldDesc Field descriptor for the field to be loaded.
     */
    private void prepareGetField(FieldDesc fieldDesc, boolean internal, boolean acquireShareLock) {
        boolean debug = logger.isLoggable(Logger.FINEST);

        if (debug) {
            logger.finest("sqlstore.sqlstatemanager.preparegetfield", fieldDesc.getName()); // NOI18N
        }

        if (acquireShareLock) {
            persistenceManager.acquireShareLock();
        }

        try {
            getLock();

            boolean xactActive = persistenceManager.isActiveTransaction();
            boolean optimistic = persistenceManager.isOptimisticTransaction();
            boolean nontransactionalRead = persistenceManager.isNontransactionalRead();

            if (state.needsReload(optimistic, nontransactionalRead, xactActive)) {
                reload(fieldDesc);
            }

            LifeCycleState oldstate = state;
            state = state.transitionReadField(optimistic, nontransactionalRead, xactActive);

            registerInstance(false, null, oldstate);

            // Only allow dynamic navigation if we are not in the state that allows it.
            if (state.isNavigable() || !internal) {
                if (getPresenceMaskBit(fieldDesc.absoluteID) == false) {
                    realizeField(fieldDesc);
                }
            }
        } catch (JDOException e) {
            throw e;
        } catch (Exception e) {
            logger.log(Logger.FINE,"sqlstore.exception.log", e);
            throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
                    "core.statemanager.getfieldfailed"), e); // NOI18N
        } finally {
            if (acquireShareLock) {
                persistenceManager.releaseShareLock();
            }
            releaseLock();

            if (debug) {
                logger.finest("sqlstore.sqlstatemanager.preparegetfield.exit"); // NOI18N
            }
        }
    }

    /**
     * Retrieves a field specified by fieldDesc from the data
     * store. If the field is part of a group then all unfetched fields
     * in the group are retrieved.  realizeField is part of dynamic
     * navigation. The field is marked as present.
     *
     * @param fieldDesc The field descriptor of the field to be retrieved.

* Note: The fieldDesc parameter must not be null. */ private void realizeField(FieldDesc fieldDesc) { assert fieldDesc != null; boolean debug = logger.isLoggable(); if (debug) { logger.fine("sqlstore.sqlstatemanager.realizefield", fieldDesc.getName()); // NOI18N } if (!persistenceConfig.isNavigable()) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.statemanager.notnavigable", // NOI18N fieldDesc.getName(), persistentObject.getClass().getName())); } boolean fieldRealized = false; if (fieldDesc instanceof ForeignFieldDesc) { ForeignFieldDesc ff = (ForeignFieldDesc) fieldDesc; // We can do an enhancement if we are only getting a single // relationship field. Check if the field can be retrieved on // it's own and the relationship is not mapped to a join table. // The field can be retrieved on it's own if it's is in // an independent fetch group or not in a fetch group at all. // Independent fetch groups have group ids < FieldDesc.GROUP_NONE. if (ff.fetchGroup <= FieldDesc.GROUP_NONE && persistenceConfig.getFetchGroup(ff.fetchGroup).size() <= 1 && !ff.useJoinTable()) { fieldRealized = realizeForeignField(ff); } } if (!fieldRealized) { retrieve(fieldDesc); } if (debug) { logger.fine("sqlstore.sqlstatemanager.realizefield.exit"); // NOI18N } } /** * For foreign fields we want to take advantage of knowing the * relationship key and only selecting the foreign rather than the * primary with the foreign attached. Most of the work is in * figuring out whether we can do that. * * @param foreignField The relationship field to be retrieved. * Following is true for this field. *

    *
  • It is part of an independent fetch group with only one * field in the fetch group or not part of any fetch group.
  • *
  • It is not mapped to a join table.
  • *
* Note: The foreignField parameter must not be null. * * @return True, if relationship field has been retrieved, false otherwise. */ private boolean realizeForeignField(ForeignFieldDesc foreignField) { assert foreignField != null; boolean isPresent = false; boolean debug = logger.isLoggable(); if (debug) { logger.fine("sqlstore.sqlstatemanager.realizeforeignfield", // NOI18N foreignField.getName()); } // Check and see if all the values we need are present. for (int i = 0; i < foreignField.localFields.size(); i++) { LocalFieldDesc lf = (LocalFieldDesc) foreignField.localFields.get(i); isPresent = getPresenceMaskBit(lf.absoluteID); if (!isPresent) { break; } } if (isPresent) { // All the values we need are present. Wow. Now we'll have to // format a more specialized request and attach the object(s) // we get back to our managed object. populateForeignField(foreignField); } if (debug) { logger.fine("sqlstore.sqlstatemanager.realizeforeignfield.exit", // NOI18N Boolean.valueOf(isPresent)); } return isPresent; } /** * Retrieves the relationship. * * @param foreignField The relationship field to be retrieved. * Following is true for this field. *
    *
  • It is part of an independent fetch group with only one * field in the fetch group or not part of any fetch group.
  • *
  • It is not mapped to a join table.
  • *
* Note: The foreignField must not be null. */ private void populateForeignField(ForeignFieldDesc foreignField) { assert foreignField != null; boolean foundInstance = false; if (foreignField.hasForeignKey() && foreignField.isMappedToPk()) { // Cache lookup, returns an object. Object pc = getObjectById(foreignField, null, null, true); if (foundInstance = (pc != null)) { foreignField.setValue(this, pc); } } if (!foundInstance) { // Query lookup, returns a collection. Collection result = retrieveForeign(foreignField); attachQueryResult(foreignField, result); } // Or in PRESENT bit for this field in the properties mask setPresenceMaskBit(foreignField.absoluteID); } /** * Attaches the retrieved object(s) queryResult to the * relationship field foreignField of the instance * managed by this state manager. * * @param queryResult Retrieved value for the relationship field * foreignField. */ private void attachQueryResult(ForeignFieldDesc foreignField, Collection queryResult) { assert foreignField != null; // Attach the object(s) we got back to our managed object. // There are three cases: // 1) The foreign field contains a collection // 2) The foreign object doesn't exist (NIL) // 3) The foreign field is a reference to a single object if (foreignField.getComponentType() != null) { // Instantiate and populate a dynamic array, namely Collection. // NOTE: queryResult is null, if we didn't execute the retrieval! replaceCollection(foreignField, queryResult); } else if (queryResult == null || queryResult.size() == 0) { foreignField.setValue(this, null); } else { if (queryResult.size() > 1) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.persistencestore.toomanyobjforcard1", // NOI18N persistenceConfig.getName(), foreignField.getName(), "" + queryResult.size())); // NOI18N } Object v = queryResult.iterator().next(); foreignField.setValue(this, v); } } /** * Retrieves the relationship based on the values of the * relationship columns on the local side. Note: This method * assumes that the relationship key fields are loaded. * * RESOLVE: * The relationship might be mapped to an Unique Key. * Do databases constrain the Unique Key columns to be non null? * * @param foreignField The relationship field to be retrieved. * Following is true for this field. *
    *
  • It is part of an independent fetch group with only one * field in the fetch group or not part of any fetch group.
  • *
  • It is not mapped to a join table.
  • *
* Note: The foreignField must not be null. * * @return Collection returned by the query. Null, if the relationship * is not set or the passed relationship field is null. * @see #retrieve */ private Collection retrieveForeign(ForeignFieldDesc foreignField) { assert foreignField != null; Collection result = null; boolean debug = logger.isLoggable(); if (debug) { logger.fine("sqlstore.sqlstatemanager.retrieveforeign", // NOI18N foreignField.getName()); } Object[] values = new Object[foreignField.localFields.size()]; boolean isValidForeignKey = true; for (int i = 0; i < foreignField.localFields.size(); i++) { FieldDesc flf = (FieldDesc) foreignField.localFields.get(i); if (!getPresenceMaskBit(i)) { // throw exception } if (getSetMaskBit(flf.absoluteID)) { // This is one reason why we must have a before image // on relationship changes! values[i] = flf.getValue(beforeImage); } else { values[i] = flf.getValue(this); } // Make sure we have a valid query key. if (values[i] == null) { // The relationship must be null. No need to query! isValidForeignKey = false; } } if (isValidForeignKey) { // Getting a new generated RD or a cached one. RetrieveDesc fdesc = persistenceConfig.getRetrieveDescForFKQuery(foreignField, store); result = (Collection) store.retrieve( persistenceManager, fdesc, new QueryValueFetcher(values)); } if (debug) { logger.fine("sqlstore.sqlstatemanager.retrieveforeign.exit"); // NOI18N } return result; } /** * The retrieve method gets a retrieve descriptor to retrieve the * desired field and adds constraints necessary to limit the * retrieval set to the source object runs the retrieve * descriptor against the store, the source object is connected to, * and then merges the results back into the source object. * * @param additionalField The additional field to be retrieved.

* Note: The additionalField might be null if we just * want to reload the instance. * @see #retrieveForeign */ private void retrieve(FieldDesc additionalField) { boolean debug = logger.isLoggable(); if (debug) { String fieldName = (additionalField != null) ? additionalField.getName() : null; logger.fine("sqlstore.sqlstatemanager.retrieve", fieldName); // NOI18N } LocalFieldDesc[] keyFields = persistenceConfig.getKeyFieldDescs(); Object [] values = new Object[keyFields.length]; copyValues(values, keyFields, 0); // Getting a new generated RD or a cached one. RetrieveDesc rd = persistenceConfig.getRetrieveDescForPKQuery(additionalField, store); Collection result = (Collection) store. retrieve(persistenceManager, rd, new QueryValueFetcher(values)); if (result.size() > 1) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.statemanager.toomanyrows", // NOI18N persistenceConfig.getPersistenceCapableClass().getName())); } else if (result.size() < 1 || result.iterator().next() != persistentObject) { // If there are no instances fetched, or the fetched instances is not the one // we asked for, it means that it is not found and we throw an exception throw new JDOObjectNotFoundException(I18NHelper.getMessage(messages, "core.statemanager.objectnotfound"), // NOI18N new Object[]{persistentObject}); } if (debug) { logger.fine("sqlstore.sqlstatemanager.retrieve.exit"); // NOI18N } } /** * Copies the value of fields into the array * values. The values are copied into the * array starting at index startIndex. * * @param values Array taking the values of fields. * @param fields Array of LocalFieldDesc. * @param startIndex Starting index into values. */ private void copyValues(Object[] values, LocalFieldDesc[] fields, int startIndex) { // The values array should be long enough to hold all the fields. assert values.length - startIndex >= fields.length; for (int i = 0; i < fields.length; i++) { LocalFieldDesc field = fields[i]; values[i + startIndex] = field.getValue(this); // The field is not expected to be null. assert values[i + startIndex] != null; } } public SQLStateManager getBeforeImage() { // Do not try to get before image if not required if (beforeImage == null && isBeforeImageRequired()) { boolean debug = logger.isLoggable(); if (debug) { logger.fine("sqlstore.sqlstatemanager.getbeforeimage", // NOI18N persistenceConfig.getPersistenceCapableClass().getName()); } try { getLock(); // Make a copy of the persistentObject. beforeImage = copyPersistent(); } finally { releaseLock(); } if (debug) { logger.fine("sqlstore.sqlstatemanager.getbeforeimage.exit"); // NOI18N } } return beforeImage; } public boolean isBeforeImageRequired() { com.sun.jdo.api.persistence.support.Transaction t = persistenceManager.currentTransaction(); // NOTE: We need to create a before image on relationship changes for two reasons: // (1) Relationship management assumes relationship fields to be loaded. // See prepareUpdateField. // (2) The before image is used in realizeField. boolean isBeforeImageRequired = persistenceManager.isOptimisticTransaction() || getUpdateDesc().hasChangedRelationships() || t.getRetainValues() || t.getRestoreValues() || persistenceConfig.hasModifiedCheckAtCommitConsistency(); if (logger.isLoggable(Logger.FINER)) { logger.finer("sqlstore.sqlstatemanager.isbeforeimagerequired", // NOI18N Boolean.valueOf(isBeforeImageRequired)); } return isBeforeImageRequired; } private SQLStateManager copyPersistent() { PersistenceManager pm = (PersistenceManager) getPersistenceManagerInternal(); SQLStateManager newStateManager = (SQLStateManager) clone(); // Associate state manager with a new pc instance. pm.newInstance(newStateManager); newStateManager.copyFields(this, true, true); return newStateManager; } /** * @inheritDoc StateManager#copyFields(StateManager source) * Does not copy relationship fields. Other fields are cloned while * copying as per {@link #cloneObjectMaybe(Object source)}. * @throws IllegalArgumentException if source is null, is * not instanceof SQLStateManager, or is not managing the * same type of persistent instance as this StateManager. */ public void copyFields(StateManager source) { if (!(source instanceof SQLStateManager)) { String className = (source != null) ? source.getClass().getName() : null; throw new IllegalArgumentException(className); } SQLStateManager sqlSource = (SQLStateManager) source; if (persistenceConfig != sqlSource.getPersistenceConfig()) { Class thisPCClass = persistenceConfig.getPersistenceCapableClass(); Class sourcePCClass = sqlSource.getPersistenceConfig().getPersistenceCapableClass(); throw new IllegalArgumentException( I18NHelper.getMessage( messages, "core.statemanager.copyFields.mismatch", // NOI18N thisPCClass.getName(), sourcePCClass.getName())); } copyFields(sqlSource, false, true); } /** * @inheritDoc StateManager#copyFields(StateManager source). * @param copyRelationships if true, then relationship fields are copied, * otherwise they are not copied. * @param clone if true, then the fields are cloned while copying, * otherwise both the source and this StateManager reference * the same field value. */ private void copyFields(SQLStateManager source, boolean copyRelationships, boolean clone) { ArrayList fields = null; // Reset the field masks, as the instance will // be populated with the state from the source. clearMask(PRESENCE_MASK); clearMask(SET_MASK); for (int i = 0; i < 2; i++) { if (i == 0) { fields = persistenceConfig.fields; } else { fields = persistenceConfig.hiddenFields; } for (int j = 0; (fields != null) && (j < fields.size()); j++) { FieldDesc f = (FieldDesc) fields.get(j); if (!copyRelationships && f.isRelationshipField()) { continue; } if (source.getPresenceMaskBit(f.absoluteID)) { Object value = f.getValue(source); f.setValue(this, (clone) ? cloneObjectMaybe(value) : value); setPresenceMaskBit(f.absoluteID); } } } } private Object cloneObjectMaybe(Object source) { // RESOLVE: need to clone SCOCollection if (source != null) { // RESOLVE: Should we clone byte[]??? if ((source instanceof SCO)) { return ((SCO)source).cloneInternal(); } else if (!(source instanceof Number) && !(source instanceof String) && !(source instanceof Character) && !(source instanceof Boolean) && // RESOLVE: #jakarta.ejb package# !(source instanceof jakarta.ejb.EJBObject) && !(source instanceof com.sun.jdo.api.persistence.support.PersistenceCapable) && !(source instanceof byte[])) { try { Class type = source.getClass(); if (!type.isArray()) { Method m = type.getMethod("clone", (Class []) null); // NOI18N if (m != null) { return m.invoke(source, (Object []) null); } } else { Object srcArray[] = (Object[]) source; Object dstArray[] = (Object[]) java.lang.reflect.Array.newInstance(type.getComponentType(), srcArray.length); for (int i = 0; i < srcArray.length; i++) { dstArray[i] = srcArray[i]; } return dstArray; } } catch (NoSuchMethodException e) { if (logger.isLoggable()) { Object[] items = new Object[] {e, source.getClass().getName()}; logger.fine("sqlstore.sqlstatemanager.nosuchmethodexcep.clone", items); // NOI18N } } catch (InvocationTargetException e) { } catch (IllegalAccessException e) { } } } return source; } /** * Prepares the field described by fieldDesc for update. * This method is central to record changes to fields. The state * transitions to dirty. The instance is registered in the * transaction cache. If the field is set for the first time, * the before image is prepared and the field is marked as modified. * Updated local fields are added to the column list to be updated. * * @param fieldDesc Updated field. * @param newlyRegisteredSMs * State managers of autopersistent objects will be added to this list. * @see #prepareUpdateFieldSpecial */ private void prepareUpdateField(FieldDesc fieldDesc, ArrayList newlyRegisteredSMs) { // No updates for key fields. As this method is called by // loadForUpdate for _all_ DFG fields (to prepare a hollow // instance for update), we can't throw an exception here // like in prepareUpdateFieldSpecial. if (fieldDesc.isKeyField()) { return; } getUpdateDesc().markRelationshipChange(fieldDesc); boolean debug = logger.isLoggable(); if (debug) { Object[] items = new Object[] {fieldDesc.getName(),state}; logger.fine("sqlstore.sqlstatemanager.prepareupdatefield", items); // NOI18N } boolean optimistic = persistenceManager.isOptimisticTransaction(); boolean xactActive = persistenceManager.isActiveTransaction(); boolean nontransactionalRead = persistenceManager.isNontransactionalRead(); if (state.needsReload(optimistic, nontransactionalRead, xactActive)) { reload(fieldDesc); } // State transition. // The state transition prevends field updates on deleted instances // and must always be executed. See PersistentDeleted.transitionWriteField(). LifeCycleState oldstate = state; state = state.transitionWriteField(xactActive); registerInstance(false, newlyRegisteredSMs, oldstate); if (state == oldstate && getSetMaskBit(fieldDesc.absoluteID) && getPresenceMaskBit(fieldDesc.absoluteID)) { // The presence mask bit is NEVER set for deferred collections, // because deferred collections MUST always be reloaded. return; } // We don't reload the field for a newly created instance. if (state.isBeforeImageUpdatable()) { // Reload the field, as relationship management // assumes that relationship fields are always present. // See updateObjectField() or processCollectionupdates(). if (!getPresenceMaskBit(fieldDesc.absoluteID)) { prepareGetField(fieldDesc); } updateBeforeImage(fieldDesc, null); } recordUpdatedField(fieldDesc); if (debug) { logger.fine("sqlstore.sqlstatemanager.prepareupdatefield.exit"); // NOI18N } } /** * Initializes the beforeImage and registers the before image * value if not null. * * @param fieldDesc Updated field. * @param value BeforeImage value, null if called from * {@link #prepareUpdateField}. If the value is null it will be retrieved * from the instance. * @see #prepareUpdateFieldSpecial */ private void updateBeforeImage(FieldDesc fieldDesc, Object value) { getBeforeImage(); if (beforeImage != null && !beforeImage.getPresenceMaskBit(fieldDesc.absoluteID) && (fieldDesc.sqlProperties & FieldDesc.PROP_LOG_ON_UPDATE) > 0) { if (value == null) { value = fieldDesc.getValue(this); } if (value != null) { if (logger.isLoggable(Logger.FINEST)) { Object[] items = new Object[] {fieldDesc, value}; logger.finest("sqlstore.sqlstatemanager.updatebeforeimage", items); // NOI18N } fieldDesc.setValue(beforeImage, cloneObjectMaybe(value)); beforeImage.setPresenceMaskBit(fieldDesc.absoluteID); } } } /** * Marks the field fieldDesc as set and schedules local fields * to be written to the data store. * * @param fieldDesc Updated field. */ private void recordUpdatedField(FieldDesc fieldDesc) { boolean debug = logger.isLoggable(Logger.FINEST); if (!fieldDesc.isRelationshipField() && (fieldDesc.sqlProperties & FieldDesc.PROP_RECORD_ON_UPDATE) > 0) { if (debug) { logger.finest("sqlstore.sqlstatemanager.recordingfield", fieldDesc); // NOI18N } getUpdateDesc().recordUpdatedField((LocalFieldDesc) fieldDesc); } if (debug) { logger.finest("sqlstore.sqlstatemanager.makedirtyfield", fieldDesc); // NOI18N } setSetMaskBit(fieldDesc.absoluteID); } /** * This method adds the dependency between this StateManager * and the other. * * @param sm Second state manager. * @see StateManager#addDependency(StateManager sm) */ public void addDependency(StateManager sm) { if (logger.isLoggable()) { Object[] items = new Object[] {this, sm}; logger.fine("sqlstore.sqlstatemanager.adddependency", items); // NOI18N } // The simple solution is to call addUpdatedForeignReference() // internally. It might try to reregister both instances again, // but the caller should clean up the cache. e.g. the // PersistenceManager MUST replace the deleted StateManager // with the new instance in the weak cache AFTER this call. SQLStateManager other = (SQLStateManager)sm; if (!state.isNew() || !state.isDeleted()) { // Do not need to add a dependency to a new-deleted instance as // its flush is a no-op any way. // First parameter == null marks a non removable dependency. this.addUpdatedForeignReference(null, other); } else if ((other.stateFlags & ST_REGISTERED) == 0) { // If we did not add a dependency, we still need to register the other // instance if it is not yet registered. persistenceManager.registerInstance(other, other.getObjectId(), false, true); other.stateFlags |= ST_REGISTERED; } } /** * Resolves the dependencies for the instances waiting for this state manager. * Dependencies are registered instantly during the course of the transaction. * For this reason, the introduced dependencies must be checked, if they are * still valid at commit/flush. E.g. remove dependencies introduced on * relationship removal are only valid, if the removed instance is deleted. *

* This method checks the dependencies for all instances waiting for the * current state manager to be flushed to the store. */ public void resolveDependencies() { if (logger.isLoggable()) { logger.fine("sqlstore.sqlstatemanager.resolvedependencies", this.getPersistent()); // NOI18N } if (updatedForeignReferences != null) { Iterator iter = updatedForeignReferences.iterator(); while (iter.hasNext()) { final UpdatedForeignReference ufr = (UpdatedForeignReference) iter.next(); final ForeignFieldDesc fieldDesc = ufr.getFieldDesc(); final SQLStateManager foreignSM = ufr.getStateManager(); if (resolveDependency(fieldDesc, foreignSM)) { foreignSM.removeDependency(); iter.remove(); } } } } /** * Tries to resolve the dependency between this * and foreignSM introduced on the update of * relationship field fieldDesc. There are three * kinds of dependencies: *

    *
  • Create dependency, see {@link #registerRemoveDependency}
  • *
  • Remove dependency, see {@link #registerCreateDependency}
  • *
  • Update dependency, see {@link #manageDependencyForObjectField}
  • *
* The current implementation does not attempt to resolve * Update dependencies. * * @param fieldDesc Relationship field. * @param foreignSM Foreign state manager. * @return True, if the dependency between this * and foreignSM can be safely removed. */ private boolean resolveDependency(ForeignFieldDesc fieldDesc, SQLStateManager foreignSM) { boolean removeDependency = false; Object pc = foreignSM.getPersistent(); if (!state.isPersistentInDataStore()) { // Check for a create dependency. // The create dependency is valid, if the instance is being created. if (state.getUpdateAction() != ActionDesc.LOG_CREATE) { // The instance is not persistent and will not be created. removeDependency = true; } else if (!checkRelationship(this, fieldDesc, pc)) { // No relationship between the two instances. removeDependency = true; } } else { // Not removable dependencies are marked by fieldDesc == null. // See delete/create with the same id dependency in addDependency // or update dependencies in manageDependencyForObjectField. // RESOLVE: Are update dependencies removable? if (fieldDesc != null) { // Check for a remove dependency. // The remove dependency is valid, if the formerly referred // instance is being removed. if (foreignSM.state.getUpdateAction() != ActionDesc.LOG_DESTROY) { // The formerly referred instance will not be removed. removeDependency = true; } else if (fieldDesc.cardinalityUPB <= 1) { // Don't check collection relationships, as collection // fields might not be populated in the before image. // RESOLVE: Collection relationships shouldn't be // checked for remove dependencies anyway. if (!checkRelationship(beforeImage, fieldDesc, pc)) { // No previous relationship between the two instances. removeDependency = true; } } } } if (removeDependency && logger.isLoggable()) { Object[] items = new Object[] {this.getPersistent(), fieldDesc.getName(), pc}; logger.fine("sqlstore.sqlstatemanager.resolvedependency", items); // NOI18N } return removeDependency; } /** * Decrements the reference count and marks this instance * as updateable, if the reference count is zero. */ private void removeDependency() { if (--referenceCount == 0) { stateFlags &= ~ST_UPDATE_DISABLED; } } /** * Checks, if there is a relationship between the persistence * capable instance managed by state manager sm and * pc on field fieldDesc. If * sm represents a before image, the relationship was * reset in the current transaction. * * @param sm Either the before- or after image of the current state manager. * @param fieldDesc Relationship field. * @param pc Persistence capable instance checked for relationship. * @return True, if the persistence capable instances are (were) * related on the given field. */ static private boolean checkRelationship(SQLStateManager sm, ForeignFieldDesc fieldDesc, Object pc) { boolean related = false; if (fieldDesc != null && sm != null && sm.getPresenceMaskBit(fieldDesc.absoluteID)) { if (fieldDesc.cardinalityUPB > 1) { // Checking directly for contains doesn't work for deferred SCOCollections. Collection c = sm.getCollectionValue(fieldDesc); // Resulting collection can't be null because the presence mask is set. related = c.contains(pc); } else { related = fieldDesc.getValue(sm) == pc; } } return related; } /** * Nullify the relationship in the data store before the _possible_ removal * of removedSM. To maintain referentional integrity constraints in the * database, the relationship to the removed instance has to be nullified, * before the removed object might be deleted. Since we use immediate * dependency management, we don't know, if the removed object is deleted * later on. The data store relationship has to be erased before the * removal. Immediate dependency management determines dependencies * immediatly when the relationship is set. In contrast the deferred * approach waits until the objects are flushed to the store. Set the * dependency only if both instances are already persistent. * * @param fieldDesc Updated relationship field. * @param removedSM State manager removed from the relationship. * @see #nullifyForeignKey(ForeignFieldDesc, SQLStateManager, ForeignFieldDesc, boolean) * @see #removeJoinTableEntry(ForeignFieldDesc, SQLStateManager, ForeignFieldDesc) */ private void registerRemoveDependency(ForeignFieldDesc fieldDesc, SQLStateManager removedSM) { if (this.state.isPersistentInDataStore() && removedSM.state.isPersistentInDataStore()) { this.addUpdatedForeignReference(fieldDesc, removedSM); } } /** * The referred object has to be written to the store before the * relationship can be set. To ensure referentional integrity * constraints in the database, the added object has to be written * to the store, before the relationship can be set. The same * dependency applies for relationships mapped to a jointable. * * @param inverseFieldDesc Updated inverse relationship field. * WE must pass the inverse field, because the dependency is * registered on the added state manager. See * {@link #registerRemoveDependency}. * @param addedSM State manager added to the relationship. * @see #setForeignKey(ForeignFieldDesc, SQLStateManager, ForeignFieldDesc) * @see #addJoinTableEntry(ForeignFieldDesc, SQLStateManager, ForeignFieldDesc) */ private void registerCreateDependency(ForeignFieldDesc inverseFieldDesc, SQLStateManager addedSM) { if (!addedSM.state.isPersistentInDataStore()) { addedSM.addUpdatedForeignReference(inverseFieldDesc, this); } } /** * Adds a dependency for state manager sm. State * manager sm must wait for this to be * flushed to the store before it can be written itself. State * manager sm is added to the list of objects * depending on this and will be notified when * this is written to the store. Store updates to * sm are disabled until then. This dependency is * established to maintain referential integrity conditions in the * data store. * * @param fieldDesc Updated relationship field. * @param sm Foreign state manager depending on this. */ private void addUpdatedForeignReference(ForeignFieldDesc fieldDesc, SQLStateManager sm) { // Avoid self-dependency. if (sm == this) { return; } // IMPORTANT: The following check assumes that this StateManager needs to be // registered only if it has no updatedForeignReferences. Check if this causes // a problem after this instance has been flushed, and updatedForeignReferences // is now an empty collection. if (updatedForeignReferences == null) { updatedForeignReferences = new HashSet(); // Register this instance disregarding it's LifeCycle state. // Otherwise, its state may never be reset after the transaction commits. if ((stateFlags & ST_REGISTERED) == 0) { persistenceManager.registerInstance(this, getObjectId(), false, true); stateFlags |= ST_REGISTERED; } } if (updatedForeignReferences.add(new UpdatedForeignReference(fieldDesc, sm))) { sm.stateFlags |= ST_UPDATE_DISABLED; sm.referenceCount++; if (logger.isLoggable() ) { String fieldName = (fieldDesc != null) ? fieldDesc.getName() : null; Object[] items = new Object[] {this.persistentObject, fieldName, sm.persistentObject, new Integer(sm.referenceCount)}; logger.fine("sqlstore.sqlstatemanager.addupdate", items); // NOI18N } // Register this instance disregarding it's LifeCycle state. // Otherwise, its state may never be reset after the transaction commits. if ((sm.stateFlags & ST_REGISTERED) == 0) { persistenceManager.registerInstance(sm, sm.getObjectId(), false, true); sm.stateFlags |= ST_REGISTERED; } } } /** * Removes the dependency from state manager sm on * this. State manager sm does not need to * wait for this to be flushed to the store. The dependency * was established to maintain referential integrity conditions in the * data store. * * @param fieldDesc Updated relationship field. * @param sm Foreign state manager removed from the dependency. */ private void removeUpdatedForeignReference(ForeignFieldDesc fieldDesc, SQLStateManager sm) { if ((updatedForeignReferences == null) || (updatedForeignReferences.size() == 0)) { return; } if (updatedForeignReferences.remove(new UpdatedForeignReference(fieldDesc, sm))) { sm.referenceCount--; if (logger.isLoggable()) { String fieldName = (fieldDesc != null) ? fieldDesc.getName() : null; Object[] items = new Object[] {this.persistentObject, fieldName, sm.persistentObject, new Integer(sm.referenceCount)}; logger.fine("sqlstore.sqlstatemanager.removeupdate", items); // NOI18N } if (sm.referenceCount == 0) { sm.stateFlags &= ~ST_UPDATE_DISABLED; } } } /** * Handles relationship updates for the object side of a one-to-many or both * sides of a one-to-one relationship. This method processes i.e. the * Employee side of the Employee-Department, or both sides * Employee-Insurance relationship. Nullifies the relation on the instance * removed from the relationship. The relation is set on the added instance * value. Data store updates are scheduled. Updates the inverse * relationship side if updateInverseRelationshipField == true * and the relationship is mapped as bi-directional by the user. * * @param fieldDesc Updated relationship field. * @param addedObject Relationship object to be set. * @param updateInverseRelationshipField * True, if we need to update the inverse relationship side. * @param managedRelationshipInProgress True during relationship management. * @return Always true. * @exception JDOUserException * Thrown if the added object addedObject has already been deleted. */ private boolean updateObjectField(ForeignFieldDesc fieldDesc, Object addedObject, boolean updateInverseRelationshipField, boolean managedRelationshipInProgress) { boolean debug = logger.isLoggable(); if (debug) { Object[] items = new Object[] {fieldDesc.getName(),fieldDesc.getComponentType()}; logger.fine("sqlstore.sqlstatemanager.updateobjfield", items); // NOI18N } Object removedObject = fieldDesc.getValue(this); // Don't do anything if the before and after value are the same. if (addedObject != removedObject) { SQLStateManager addedSM = getAddedSM(addedObject, null); SQLStateManager removedSM = getRemovedSM(removedObject); SQLStateManager addedInverseFieldSM = null; // If the new value is already deleted, we throw an exception. if (addedSM != null && addedSM.isDeleted()) { JDOUserException ex = new JDOUserException(I18NHelper.getMessage(messages, "jdo.lifecycle.deleted.accessField")); // NOI18N ex.addFailedObject(addedObject); throw ex; } ForeignFieldDesc inverseFieldDesc = fieldDesc.getInverseRelationshipField(); updateRelationshipInDataStore(fieldDesc, addedSM, removedSM, inverseFieldDesc, managedRelationshipInProgress); if (updateInverseRelationshipField && inverseFieldDesc != null) { addedInverseFieldSM = manageRelationshipForObjectField(inverseFieldDesc, addedSM, removedSM, managedRelationshipInProgress); } manageDependencyForObjectField(fieldDesc, addedSM, removedSM, addedInverseFieldSM); } if (debug) { logger.fine("sqlstore.sqlstatemanager.updateobjfield.exit"); // NOI18N } return true; } /** * Updates the relationship in the data store. Updates the (hidden) local fields * corresponding to the foreign key columns if the relationship is mapped to a * foreign key. Jointable entries are scheduled for creation/removal if the * relationship is mapped to a jointable. * * @param fieldDesc Updated relationship field. * @param addedSM State manager of the added object. * @param removedSM State manager of the removed object. * @param inverseFieldDesc Inverse relationship field. * @param managedRelationshipInProgress * True during relationship management. We don't want to update the * relationship fields twice during relationship management. */ private void updateRelationshipInDataStore(ForeignFieldDesc fieldDesc, SQLStateManager addedSM, SQLStateManager removedSM, ForeignFieldDesc inverseFieldDesc, boolean managedRelationshipInProgress) { if (!fieldDesc.useJoinTable()) { processForeignKeys(fieldDesc, addedSM, removedSM, inverseFieldDesc, managedRelationshipInProgress); } else { processJoinTableEntries(fieldDesc, addedSM, removedSM, inverseFieldDesc, managedRelationshipInProgress); } } /** * Updates the (hidden) local fields corresponding to the foreign key columns * if the relationship is mapped to a foreign key. The updates are written * to the store when the instance is flushed. For relationships mapped to * foreign keys, we always update the side with the foreign key. * The collection side never has the foreign key. Data store updates for * added objects are not processed twice during relationship management. * * Data store dependencies for the update operations are established. * * @param fieldDesc Updated relationship field. * @param addedSM State manager of the added object. * @param removedSM State manager of the removed object. * @param inverseFieldDesc Inverse relationship field. * @param managedRelationshipInProgress True during relationship management. */ private void processForeignKeys(ForeignFieldDesc fieldDesc, SQLStateManager addedSM, SQLStateManager removedSM, ForeignFieldDesc inverseFieldDesc, boolean managedRelationshipInProgress) { // If the fieldDesc property has the REF_INTEGRITY_UPDATES unset, it means we // need to update the other side of the relationship naming the foreign key fields. boolean updateOtherSide = (fieldDesc.sqlProperties & FieldDesc.PROP_REF_INTEGRITY_UPDATES) == 0; if (updateOtherSide) { // Null out the foreign key on the removed object. if (removedSM != null) { removedSM.nullifyForeignKey(inverseFieldDesc, this, fieldDesc, false); } // Set the foreign key on the added object. // Don't set the fk twice during relationship management. if (addedSM != null && !managedRelationshipInProgress) { addedSM.setForeignKey(inverseFieldDesc, this, fieldDesc); } } else { // Null out the foreign key to the removed object. if (removedSM != null) { // Don't overwrite the foreign key, if both removedSM and // addedSM != null. See runtime test rel12 for an example. nullifyForeignKey(fieldDesc, removedSM, inverseFieldDesc, addedSM != null); } // Set the foreign key to the added object. // Don't set the fk twice during relationship management. if (addedSM != null && !managedRelationshipInProgress) { // See above! setForeignKey(fieldDesc, addedSM, inverseFieldDesc); } } } /** * Schedules jointable entries for relationships mapped to jointables. * The actual creation/removal of the jointable entry is deferred until * flush. Data store updates for added objects are not processed twice * during relationship management. * * Data store dependencies for the update operations are established. * * @param fieldDesc Updated relationship field. * @param addedSM State manager of the added object. * @param removedSM State manager of the removed object. * @param inverseFieldDesc Inverse relationship field. * @param managedRelationshipInProgress True during relationship management. */ private void processJoinTableEntries(ForeignFieldDesc fieldDesc, SQLStateManager addedSM, SQLStateManager removedSM, ForeignFieldDesc inverseFieldDesc, boolean managedRelationshipInProgress) { // If the fieldDesc property has the REF_INTEGRITY_UPDATES unset, // it means we need to update the other side of the relationship. boolean updateOtherSide = (fieldDesc.sqlProperties & FieldDesc.PROP_REF_INTEGRITY_UPDATES) == 0; if (updateOtherSide) { // Schedule the removal of the jointable entry to removedSM. if (removedSM != null) { removedSM.removeJoinTableEntry(inverseFieldDesc, this, fieldDesc); } // Schedule the jointable entry to the added object. // Don't schedule the jointable entry twice during relationship management. if (addedSM != null && !managedRelationshipInProgress) { addedSM.addJoinTableEntry(inverseFieldDesc, this, fieldDesc); } } else { // Schedule the removal of the jointable entry to removedSM. if (removedSM != null) { // Contrary to foreign key relationships, we always have // to remove the previous jointable entry. removeJoinTableEntry(fieldDesc, removedSM, inverseFieldDesc); } // Schedule the jointable entry to the added object. // Don't schedule the jointable entry twice during relationship management. if (addedSM != null && !managedRelationshipInProgress) { addJoinTableEntry(fieldDesc, addedSM, inverseFieldDesc); } } } /** * Updates the (inverse) relationship field for addedSM * and removedSM. * * @param inverseFieldDesc Inverse relationship field. * @param addedSM State manager of the added object. * @param removedSM State manager of the removed object. * @param managedRelationshipInProgress True during relationship management. * @return State manager managing the previous value of addedSM's * relationship field. */ private SQLStateManager manageRelationshipForObjectField(ForeignFieldDesc inverseFieldDesc, SQLStateManager addedSM, SQLStateManager removedSM, boolean managedRelationshipInProgress) { Object addedInverseFieldValue = null; SQLStateManager addedInverseFieldSM = null; if (removedSM != null) { removedSM.removeRelationship(inverseFieldDesc, this); } if (addedSM != null && !managedRelationshipInProgress) { addedInverseFieldValue = addedSM.addRelationship(inverseFieldDesc, this); if (addedInverseFieldValue != null) { addedInverseFieldSM = (SQLStateManager) persistenceManager.getStateManager(addedInverseFieldValue); } } return addedInverseFieldSM; } /** * Dependency management for database operations on the one-to-one * relationships. Establishes a dependency to nullify the foreign key on the * removed instance before the added instance's foreign key can be set. If * the relationship is mapped to a jointable, remove the jointable entry to * the removed instance before the jointable entry on the added can be * added. This update dependency is valid only for one-to-one relationships, * because only one-to-one relationships can be enforced by an unique * index on the foreign key. On the other hand, there's never a state * manager removed from the relationship for one-to-many relationships. * * We would like to do this in updateRelationshipInDataStore, * but we don't know the added instance's previous value in case of * updateOtherSide == false there. Here, we can handle this situation * at one place. * * @param fieldDesc Updated relationship field. * @param addedSM Added state manager. * @param removedSM Removed state manager. * @param addedInverseFieldSM * State manager removed from the relationship, * if the foreign key is on the local side. */ private void manageDependencyForObjectField(ForeignFieldDesc fieldDesc, SQLStateManager addedSM, SQLStateManager removedSM, SQLStateManager addedInverseFieldSM) { // If the fieldDesc property has the REF_INTEGRITY_UPDATES unset, // it means data store updates are scheduled on the other side of the relationship. boolean updateOtherSide = (fieldDesc.sqlProperties & FieldDesc.PROP_REF_INTEGRITY_UPDATES) == 0; // Nullify the foreign key on the removed object before // the added object's foreign key can be set. if (updateOtherSide && removedSM != null && addedSM != null) { // Add the dependency only if both objects involved // in the relationship being removed are already persistent. if (removedSM.state.isPersistentInDataStore() && this.state.isPersistentInDataStore()) { // First parameter == null marks a non removable dependency. // RESOLVE: Pass inverseFieldDesc here. removedSM.addUpdatedForeignReference(null, addedSM); } } // If the foreign key is on this side, addedInverseFieldSM // corresponds to the removedSM and this is the addedSM! if (!updateOtherSide && addedInverseFieldSM != null) { // Add the dependency only if both objects involved // in the relationship being removed are already persistent. if (addedInverseFieldSM.state.isPersistentInDataStore() && addedSM != null && addedSM.state.isPersistentInDataStore()) { // First parameter == null marks a non removable dependency. // RESOLVE: Pass inverseFieldDesc here. addedInverseFieldSM.addUpdatedForeignReference(null, this); } } } /** * Returns the added object's state manager. Transient objects become * "autopersistent" on the association to an already persistent instance and * are associated with a new state manager. * * If newlyRegisteredSMs is not null, the newly created state manager * is added to the list. This list is only non-null for the treatment of deferred * collection fields, which is done before the actual flush to the data store. * Autopersistence management for all other cases is handled sufficiently * in the makeAutoPersistent call. * * @param addedObject Object added to a relationship. * @param newlyRegisteredSMs * The state managers of autopersistent objects will be added to this list. * @return State manager for the added object. The statemanager will * be not null for all persistence capable objects != null. */ private SQLStateManager getAddedSM(Object addedObject, ArrayList newlyRegisteredSMs) { SQLStateManager addedSM = null; if (addedObject != null) { // Persistence by reachablity. if ((addedSM = (SQLStateManager) persistenceManager.getStateManager(addedObject)) == null) { makeAutoPersistent(addedObject); addedSM = (SQLStateManager) persistenceManager.getStateManager(addedObject); // Add the newly created state manager to the newlyRegisteredSMs // list so we can do further processing on it. if (newlyRegisteredSMs != null && !newlyRegisteredSMs.contains(addedSM)) { newlyRegisteredSMs.add(addedSM); } } } return addedSM; } /** * Returns the removed object's state manager. * * @param removedObject Object removed to a relationship. * @return State manager for the removed object. The state manager might * be null even for objects != null (in case of JDK collections). * @see #removeCollectionRelationship */ private SQLStateManager getRemovedSM(Object removedObject) { SQLStateManager removedSM = null; if (removedObject != null) { removedSM = (SQLStateManager) persistenceManager.getStateManager(removedObject); } return removedSM; } /** * Updates the relationship for the collection side of a one-to-many or * many-to-many relationship. Objects in removedList are * removed from the relation. The relation is set on all objects in * addedList. In case of user updates, relationship management * on "this" relationship side is done by the user, i.e. by * d.getEmployees().add(newEmp) for a Department d. This method is never * called during relationship management. * * @param fieldDesc Updated relationship field. * @param removedList List of objects to be removed from the relationship. * @param addedList List of objects to be added to the relationship. * @param newlyRegisteredSMs * List taking newly registered SMs for objects becoming autopersistent. * @param updateInverseRelationshipField * True, if we need to update the inverse relationship field. * @param managedRelationshipInProgress * True during relationship management. NOTE: This parameter is * always false, as the method is never called during relationship management. * @exception JDOUserException Thrown on failures in afterList handling. * @see #prepareSetField(FieldDesc,Object,boolean) */ private void processCollectionUpdates(ForeignFieldDesc fieldDesc, ArrayList removedList, ArrayList addedList, ArrayList newlyRegisteredSMs, boolean updateInverseRelationshipField, boolean managedRelationshipInProgress) { boolean debug = logger.isLoggable(); ForeignFieldDesc inverseFieldDesc = fieldDesc.getInverseRelationshipField(); // RESOLVE: What if // * inverseFieldDesc is null? // * fieldDesc.cardinalityUPB == 1 if (debug) { Object[] items = new Object[] {removedList,addedList}; logger.fine("sqlstore.sqlstatemanager.processcollectionupdate", items); // NOI18N } // removedList contains the list of objects removed. if (removedList != null) { removeCollectionRelationship(fieldDesc, removedList, inverseFieldDesc, updateInverseRelationshipField, managedRelationshipInProgress); } // addedList contains the objects added. if (addedList != null) { addCollectionRelationship(fieldDesc, addedList, inverseFieldDesc, newlyRegisteredSMs, updateInverseRelationshipField, managedRelationshipInProgress); } if (debug) { logger.fine("sqlstore.sqlstatemanager.processcollectionupdate.exit"); // NOI18N } } /** * Nullifies the relationship for the objects removed from a collection relationship. * * @param fieldDesc Updated relationship field. * @param removedList List of objects to be removed from the relationship. * @param inverseFieldDesc Inverse relationship field. * @param updateInverseRelationshipField * True, if we need to update the inverse relationship side. * @param managedRelationshipInProgress * True during relationship management. NOTE: This parameter is always * false, as the method is never called during relationship management. * @see #processCollectionUpdates */ private void removeCollectionRelationship(ForeignFieldDesc fieldDesc, ArrayList removedList, ForeignFieldDesc inverseFieldDesc, boolean updateInverseRelationshipField, boolean managedRelationshipInProgress) { for (int i = 0; i < removedList.size(); i++) { SQLStateManager removedSM = getRemovedSM(removedList.get(i)); // removedSM == null can happen if the collection is non-SCO and contains // transient instances which don't become persistent until commit. if (removedSM != null) { // The collection side never has the foreign key, i.e. // it's never processed during relationship management, // because data store updates are already done. if (!managedRelationshipInProgress) { updateRelationshipInDataStore(fieldDesc, null, removedSM, inverseFieldDesc, false); // Relationship management if (updateInverseRelationshipField && inverseFieldDesc != null) { removedSM.removeRelationship(inverseFieldDesc, this); } } } } } /** * Nullifies the (hidden) local fields corresponding to the foreign key columns * for relationship field fieldDesc. Usually the foreign key * columns are not mapped explicitly by the user. For this reason the runtime * creates hidden fields representing the foreign key columns internally. We * determine the local fields by iterating appropriate field list of either * fieldDesc or inverseFieldDesc. * * For dependency management, the removal of the foreign key has * to be nullified before the _possible_ removal of removedSM. The * same dependency applies to jointable relationships, see {@link * #registerRemoveDependency}. * * @param fieldDesc Updated relationship field. * @param removedSM State manager of the removed object. * @param inverseFieldDesc Inverse relationship field. * @param setDependencyOnly Only set the dependency between this * and removedSM. */ private void nullifyForeignKey(ForeignFieldDesc fieldDesc, SQLStateManager removedSM, ForeignFieldDesc inverseFieldDesc, boolean setDependencyOnly) { if (!isDeleted() && !setDependencyOnly) { // fieldDesc can be null for one-directional relationships. We are // only interested in the LocalFieldDescs for the foreign key columns, // which can also be retrieved from inverseFieldDesc. if (fieldDesc != null) { for (int i = 0; i < fieldDesc.localFields.size(); i++) { LocalFieldDesc la = (LocalFieldDesc) fieldDesc.localFields.get(i); nullifyForeignKey(fieldDesc, la); } } else { for (int i = 0; i < inverseFieldDesc.foreignFields.size(); i++) { LocalFieldDesc la = (LocalFieldDesc) inverseFieldDesc.foreignFields.get(i); nullifyForeignKey(fieldDesc, la); } } } // Nullify the foreign key before the _possible_ removal of removedSM. registerRemoveDependency(fieldDesc, removedSM); } /** * Actually nullifies the local field la corresponding to a * foreign key column for the relationship update. * Fields tracking the foreign key field la are updated. * * @param fieldDesc Updated relationship field. * @param la Local field corresponding to a foreign key column. * @exception JDOUserException Is thrown, if the field to be updated * is a primary key field. We don't allow pk updates. */ private void nullifyForeignKey(ForeignFieldDesc fieldDesc, LocalFieldDesc la) { if (!getSetMaskBit(la.absoluteID)) { prepareUpdateField(la, null); } JDOUserException pkUpdateEx = null; if (la.isKeyField()) { try { assertPKUpdate(la, null); } catch (JDOUserException e) { // If the relationship being set to null // and the parent instance is being deleted, we will ignore // the exception thrown from assertPKUpdate(). The reason is // that we are really just trying to set up the dependency // and not modify the relationship itself. If we do not // ignore this exception, there will be no way to delete // the parent instance at all because we don't support // modifying primary key. if (((stateFlags & ST_DELETE_INPROGRESS) == 0)) { throw e; } pkUpdateEx = e; } } if (pkUpdateEx == null) { // As la tracks fieldDesc, fieldDesc is ignored in updateTrackedFields. updateTrackedFields(la, null, fieldDesc); la.setValue(this, null); } } /** * Schedules the removal of the jointable entry between this and the * foreign state manager. A scheduled creation of the jointable entry * between these two objects is simply removed. The removal is * scheduled on the local side. * * For dependency management, the removal of the jointable entry has * to precede the _possible_ removal of removedSM. The same dependency applies * to foreign key relationships, see {@link #registerRemoveDependency}. * * RESOLVE: What happens, if a field descriptor is null, e.g. for one * way relationships, as the descriptors are taken as keys during scheduling? * * @param fieldDesc Updated relationship field. This field is mapped to a jointable. * @param removedSM State manager of the removed object. * @param inverseFieldDesc Inverse relationship field. * @see #prepareToUpdatePhaseIII */ private void removeJoinTableEntry(ForeignFieldDesc fieldDesc, SQLStateManager removedSM, ForeignFieldDesc inverseFieldDesc) { // Cleanup dependencies. We need to cleanup dependencies for // flushed autopersistent instances that aren't reachable // before commit. See runtime test Autopersistence.TestCase12. // The cleanup must be done for removals only, because the // only operations executed in prepareToUpdatePhaseIII are // removals. if (removedSM.state.isAutoPersistent() || this.state.isAutoPersistent()) { removedSM.removeUpdatedForeignReference(inverseFieldDesc, this); this.removeUpdatedForeignReference(fieldDesc, removedSM); } // Remove scheduled creation on this side. if (fieldDesc != null && getUpdateDesc().removeUpdatedJoinTableRelationship( fieldDesc, removedSM, ActionDesc.LOG_CREATE) == false) { // Remove scheduled creation on the other side. if (inverseFieldDesc == null || removedSM.getUpdateDesc().removeUpdatedJoinTableRelationship( inverseFieldDesc, this, ActionDesc.LOG_CREATE) == false) { // Schedule removal on this side. // The field descriptor taken as key must not be null, see above! getUpdateDesc().recordUpdatedJoinTableRelationship( fieldDesc, this, removedSM, ActionDesc.LOG_DESTROY); // Remove the jointable entry before the _possible_ removal of removedSM. registerRemoveDependency(fieldDesc, removedSM); } } else if (fieldDesc == null) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.statemanager.cantschedulejointable", // NOI18N this.getPersistenceConfig().getPersistenceCapableClass().getName(), removedSM.getPersistenceConfig().getPersistenceCapableClass().getName())); } } /** * Updates the relationship field fieldDesc between this * and removedSM. * * @param fieldDesc Updated relationship field. The field must be != null. * @param removedSM State manager of the removed object. */ private void removeRelationship(ForeignFieldDesc fieldDesc, SQLStateManager removedSM) { boolean isCollection = (fieldDesc.cardinalityUPB > 1); if (!isCollection) { prepareUpdateFieldSpecial(fieldDesc, removedSM.persistentObject, false); updateTrackedFields(fieldDesc, null, null); fieldDesc.setValue(this, null); } else { try { prepareUpdateFieldSpecial(fieldDesc, null, true); SCOCollection c = (SCOCollection) fieldDesc.getValue(this); c.removeInternal(removedSM.persistentObject); updateTrackedFields(fieldDesc, c, null); } catch (ClassCastException e) { // ignore } } } /** * Sets the relationship for the objects added to a collection relationship. * * @param fieldDesc Updated relationship field. * @param addedList List of objects to be added to the relationship. * @param inverseFieldDesc Inverse relationship field. * @param newlyRegisteredSMs * State managers for autopersistent objects will be added to this list. * @param updateInverseRelationshipField * True, if we need to update the inverse relationship side. * @param managedRelationshipInProgress * True during relationship management. NOTE: This parameter is always * false, as the method is never called during relationship management. * @exception JDOUserException Thrown if objects in addedList have been deleted. * @see #processCollectionUpdates */ private void addCollectionRelationship(ForeignFieldDesc fieldDesc, ArrayList addedList, ForeignFieldDesc inverseFieldDesc, ArrayList newlyRegisteredSMs, boolean updateInverseRelationshipField, boolean managedRelationshipInProgress) { JDOUserException ex = null; for (int i = 0; i < addedList.size(); i++) { Object addedObject = addedList.get(i); SQLStateManager addedSM = getAddedSM(addedObject, newlyRegisteredSMs); // addedSM == null can happen if the collection is non-SCO and contains // transient instances which don't become persistent until commit. if (addedSM != null) { if (addedSM.isDeleted()) { // For managed relationships, if the addedObject is deleted, we need // to throw an exception at the end and the exception should include // the deleted objects in its failedObjectArray. if (inverseFieldDesc != null) { if (ex == null) { ex = new JDOUserException(I18NHelper.getMessage(messages, "jdo.lifecycle.deleted.accessField")); // NOI18N } ex.addFailedObject(addedObject); } continue; } // The collection side never has the foreign key, i.e. // it's never processed during relationship management, // because data store updates are already done at that time. if (!managedRelationshipInProgress) { updateRelationshipInDataStore(fieldDesc, addedSM, null, inverseFieldDesc, false); // Relationship management if (updateInverseRelationshipField && inverseFieldDesc != null) { addedSM.addRelationship(inverseFieldDesc, this); } } } } if (ex != null) { throw ex; } } /** * Sets the foreign key corresponding to the relationship field * fieldDesc. Usually the foreign key columns are not mapped * explicitly by the user. For this reason the runtime creates hidden fields * representing the foreign key columns internally. We determine the local * fields by iterating appropriate field list of either * fieldDesc or inverseFieldDesc. * * To ensure referentional integrity constraints in the database, * the added object has to be written to the store, before the * foreign key can be set. The same dependency applies to relationships * mapped to jointables, see {@link #addJoinTableEntry}. * * @param fieldDesc Updated relationship field. * @param addedSM State manager of the added object. * @param inverseFieldDesc Inverse relationship field. */ private void setForeignKey(ForeignFieldDesc fieldDesc, SQLStateManager addedSM, ForeignFieldDesc inverseFieldDesc) { if (!isDeleted()) { // fieldDesc can be null for one-directional relationships. We are // only interested in the LocalFieldDescs for the foreign key columns, // which can also be retrieved from inverseFieldDesc. if (fieldDesc != null) { for (int i = 0; i < fieldDesc.localFields.size(); i++) { LocalFieldDesc la = (LocalFieldDesc) fieldDesc.localFields.get(i); LocalFieldDesc fa = (LocalFieldDesc) fieldDesc.foreignFields.get(i); setForeignKey(fieldDesc, la, fa.getValue(addedSM)); } } else { for (int i = 0; i < inverseFieldDesc.foreignFields.size(); i++) { LocalFieldDesc la = (LocalFieldDesc) inverseFieldDesc.foreignFields.get(i); LocalFieldDesc fa = (LocalFieldDesc) inverseFieldDesc.localFields.get(i); setForeignKey(fieldDesc, la, fa.getValue(addedSM)); } } } // The referred object has to be written to the store before the foreign key can be set. registerCreateDependency(inverseFieldDesc, addedSM); } /** * Actually sets the local field la corresponding to a foreign * key column to the new value for the relationship update. The new value * is taken from fa, which is typically a primary key field on * the other relationship side. Fields tracking the foreign key field * la are updated. * * @param fieldDesc Updated relationship field. * @param la Local field corresponding to a foreign key column. * @param faValue Value of the local field corresponding to the primary * key column on the other relationship side. * @exception JDOUserException Is thrown, if the field to be updated * is a primary key field. We don't allow pk updates. */ private void setForeignKey(ForeignFieldDesc fieldDesc, LocalFieldDesc la, Object faValue) { if (!getSetMaskBit(la.absoluteID)) { prepareUpdateField(la, null); } if (la.isKeyField()) { assertPKUpdate(la, faValue); } updateTrackedFields(la, faValue, fieldDesc); la.setValue(this, faValue); } /** * Schedules the creation of a jointable entry between this and the added * state manager. A scheduled removal of the jointable entry between these * two is simply removed. The creation is scheduled on the local side. * * For dependency management, the side creating the jointable entry has * to wait for the other to become persistent. The same dependency applies * to foreign key relationships, see {@link #setForeignKey}. * * RESOLVE: What happens, if a field descriptor is null, e.g. for one * way relationships, as the descriptors are taken as keys during scheduling? * * @param fieldDesc Updated relationship field. This field is mapped to a jointable. * @param addedSM State manager of the added object. * @param inverseFieldDesc Inverse relationship field. */ private void addJoinTableEntry(ForeignFieldDesc fieldDesc, SQLStateManager addedSM, ForeignFieldDesc inverseFieldDesc) { // Cleanup dependencies. // Note: The following lines break deadlock detection for circular dependencies. //this.removeUpdatedForeignReference(addedSM); //addedSM.removeUpdatedForeignReference(this); // Remove scheduled removal on this side. if (fieldDesc != null && getUpdateDesc().removeUpdatedJoinTableRelationship( fieldDesc, addedSM, ActionDesc.LOG_DESTROY) == false) { // Remove scheduled removal on the other side. if (inverseFieldDesc == null || addedSM.getUpdateDesc().removeUpdatedJoinTableRelationship( inverseFieldDesc, this, ActionDesc.LOG_DESTROY) == false) { // Schedule creation on this side. // The field descriptor taken as key must not be null, see above! getUpdateDesc().recordUpdatedJoinTableRelationship( fieldDesc, this, addedSM, ActionDesc.LOG_CREATE); // The side creating the jointable entry has to wait for the other to become persistent. registerCreateDependency(inverseFieldDesc, addedSM); } } else if (fieldDesc == null) { throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.statemanager.cantschedulejointable", // NOI18N this.getPersistenceConfig().getPersistenceCapableClass().getName(), addedSM.getPersistenceConfig().getPersistenceCapableClass().getName())); } } /** * Updates the relationship field fieldDesc between this * and the state manager of the added object addedSM. * * @param fieldDesc Updated relationship field. The field must be != null. * @param addedSM State manager of the added object. * @return Field fieldDesc's previous value. */ private Object addRelationship(ForeignFieldDesc fieldDesc, SQLStateManager addedSM) { Object previousValue = null; boolean isCollection = (fieldDesc.cardinalityUPB > 1); if (!isCollection) { previousValue = prepareSetField(fieldDesc, addedSM.persistentObject, true); } else { try { prepareUpdateFieldSpecial(fieldDesc, null, true); SCOCollection c = (SCOCollection) fieldDesc.getValue(this); // Note: c might be null during relationship management for a // self relationship, as in the Employee-Manager relation. // See runtime test AutoPersistence.TestCase31 for an example. if (c == null) { replaceCollection(fieldDesc, null); c = (SCOCollection) fieldDesc.getValue(this); } c.addInternal(addedSM.persistentObject); updateTrackedFields(fieldDesc, c, null); } catch (ClassCastException e) { // ignore } } return previousValue; } /** * This is a special version of prepareUpdateField that does not do navigation * if a field is not loaded. We don't need to reload the field, because the * before image value is given as parameter! This method is mostly called * during relatioship management, as the before image value is already know * in this case. *

* The createDeferredCollection parameter should be true * only if fieldDesc corresponds to a collection field. Note: *

    *
  • createDeferredCollection == false * ==> beforeImageValue must be non-null.
  • *
  • createDeferredCollection == true * ==> beforeImageValue is null.
  • *
. * * @param fieldDesc The field to be prepared. * @param beforeImageValue The before image value. * @param createDeferredCollection * Indicates whether to create a deferred SCOCollection. Deferred collections * are created during relationship management if the inverse field is not * loaded. * @see #prepareUpdateField */ private synchronized void prepareUpdateFieldSpecial(FieldDesc fieldDesc, Object beforeImageValue, boolean createDeferredCollection) { if (fieldDesc.isKeyField()) { throw new JDOUnsupportedOptionException(I18NHelper.getMessage(messages, "core.statemanager.nopkupdate")); // NOI18N } getUpdateDesc().markRelationshipChange(fieldDesc); boolean debug = logger.isLoggable(); if (debug) { Object[] items = new Object[] {fieldDesc.getName(),state}; logger.fine("sqlstore.sqlstatemanager.prepareupdatefieldspl", items); // NOI18N } boolean optimistic = persistenceManager.isOptimisticTransaction(); boolean xactActive = persistenceManager.isActiveTransaction(); boolean nontransactionalRead = persistenceManager.isNontransactionalRead(); if (state.needsReload(optimistic, nontransactionalRead, xactActive)) { if (!optimistic) { persistenceManager.clearFields(this.persistentObject); } reload(null); } LifeCycleState oldstate = state; state = state.transitionWriteField(xactActive); registerInstance(false, null, oldstate); if (getSetMaskBit(fieldDesc.absoluteID)) { // Note: The set mask is set for all fields on make persistent. return; } if (!getPresenceMaskBit(fieldDesc.absoluteID)) { if (!createDeferredCollection) { if (!(beforeImageValue instanceof SCOCollection) || !((SCOCollection) beforeImageValue).isDeferred()) { updateBeforeImage(fieldDesc, beforeImageValue); // Set the presence mask for a non deferred collection. setPresenceMaskBit(fieldDesc.absoluteID); } } else { // Deferred collection handling. if (!(fieldDesc instanceof ForeignFieldDesc) || (((ForeignFieldDesc) fieldDesc).cardinalityUPB <= 1)) { //should throw an exception return; } Object value = fieldDesc.getValue(this); if (value == null) { // If the collection field is null, we need to create a // deferred SCOCollection. SCOCollection c = (SCOCollection) persistenceManager.newCollectionInstanceInternal( fieldDesc.getType(), persistentObject, fieldDesc.getName(), fieldDesc.getComponentType(), false, 10); c.markDeferred(); fieldDesc.setValue(this, c); } // NOTE: We don't set the presence mask bit for deferred collections, // because deferred collections MUST be reloaded on the first read access! } } recordUpdatedField(fieldDesc); if (debug) { logger.fine("sqlstore.sqlstatemanager.prepareupdatefieldspl.exit"); // NOI18N } } /** * Updates the values for fields tracking field * fieldDesc. Must be called before the new value * for field fieldDesc is actually set.

* * If called when setting the local fields mapped to the * relationship on relationship updates, the relationship field * tracked by fieldDesc must be ignored when * propagating the changes.

* * For overlapping pk/fk situations or if a fk column is * explicitly mapped to a visible field, the update of the local * field triggers the update of the relationship field tracking * the local field. * * @param fieldDesc Field whose tracked fields we wish to update. * @param value New value for the field. * @param fieldToIgnore Field to be ignored when propagating * changes. This is the relationship field tracked by field * fieldDesc if fieldDesc is a * hidden local field. */ private void updateTrackedFields(FieldDesc fieldDesc, Object value, ForeignFieldDesc fieldToIgnore) { ArrayList trackedFields = fieldDesc.getTrackedFields(); if (trackedFields == null) { return; } boolean debug = logger.isLoggable(Logger.FINEST); if (debug) { Object[] items = new Object[] {fieldDesc.getName(), value, ((fieldToIgnore != null) ? fieldToIgnore.getName() : null)}; logger.finest("sqlstore.sqlstatemanager.updatetrackedfields", items); // NOI18N } Object currentValue = fieldDesc.getValue(this); int size = trackedFields.size(); ArrayList fieldsToIgnore = ((fieldToIgnore != null) ? fieldToIgnore.getTrackedFields() : null); if (fieldDesc instanceof ForeignFieldDesc) { // For tracked relationship fields, we simply set the new value. for (int i = 0; i < size; i++) { ForeignFieldDesc tf = (ForeignFieldDesc) trackedFields.get(i); prepareUpdateFieldSpecial(tf, currentValue, false); tf.setValue(this, value); } } else { Object previousValues[] = new Object[size]; LocalFieldDesc primaryTrackedField = null; Object primaryTrackedFieldValue = null; if ((fieldDesc.sqlProperties & FieldDesc.PROP_PRIMARY_TRACKED_FIELD) > 0) { primaryTrackedField = (LocalFieldDesc) fieldDesc; primaryTrackedFieldValue = value; } for (int i = 0; i < size; i++) { FieldDesc tf = (FieldDesc) trackedFields.get(i); if (tf instanceof LocalFieldDesc) { Object convertedValue = null; Object convertedCurrentValue = null; // RESOLVE: SCODate is problematic because convertValue unsets // the owner. The SCO to be used for restoring is broken. try { convertedValue = tf.convertValue(value, this); convertedCurrentValue = tf.convertValue(currentValue, this); } catch (JDOUserException e) { // We got a conversion error. We need to revert all // the tracked fields to their previous values. // NOTE: We don't have to revert relationship fields // because they come after all the primitive fields. for (int j = 0; j < i; j++) { tf = (FieldDesc) trackedFields.get(j); tf.setValue(this, previousValues[j]); } throw e; } if ((tf.sqlProperties & FieldDesc.PROP_PRIMARY_TRACKED_FIELD) > 0) { primaryTrackedField = (LocalFieldDesc) tf; primaryTrackedFieldValue = convertedValue; } prepareUpdateFieldSpecial(tf, convertedCurrentValue, false); // save the previous values for rollback previousValues[i] = tf.getValue(this); tf.setValue(this, convertedValue); } else { // We bypass fieldToIgnore and its trackedFields if (((stateFlags & ST_FIELD_TRACKING_INPROGRESS) > 0) || (tf == fieldToIgnore) || ((fieldsToIgnore != null) && fieldsToIgnore.contains(tf))) { continue; } ForeignFieldDesc ftf = (ForeignFieldDesc) tf; Object pc = null; if (primaryTrackedFieldValue != null) { pc = getObjectById(ftf, primaryTrackedField, primaryTrackedFieldValue, false); } stateFlags |= ST_FIELD_TRACKING_INPROGRESS; prepareSetField(ftf, pc); stateFlags &= ~ST_FIELD_TRACKING_INPROGRESS; } } } if (debug) { logger.finest("sqlstore.sqlstatemanager.updatetrackedfields.exit"); // NOI18N } } /** * Looks up the object associated to this state manager on * relationship ff field in the persistence manager cache. * The method first constructs the related instance's object id by * calling {@link ForeignFieldDesc#createObjectId}. Then asks the * persistence manager to retrieve the object associated to this * id from it's caches. If the referred object is not found, the * instance returned by {@link PersistenceManager#getObjectById(Object)} * is Hollow. Hollow instances are ignored for navigation. * * @param ff Relationship to be retrieved. The relationship must have * an object ("to one side") value. * @param updatedField Updated local field mapped to this relationship. * @param value updatedField's new value. * @param forNavigation If true, the lookup is executed for navigation. * @return Object found in the cache. Null, if the object wasn't found. */ private Object getObjectById(ForeignFieldDesc ff, LocalFieldDesc updatedField, Object value, boolean forNavigation) { assert ff.cardinalityUPB <=1; // If called for navigation updatedField and value should be null. assert forNavigation ? updatedField == null && value == null : true; Object rc = null; Object oid = ff.createObjectId(this, updatedField, value); if (oid != null) { rc = persistenceManager.getObjectById(oid); LifeCycleState rcState = ((SQLStateManager) ((PersistenceCapable) rc).jdoGetStateManager()).state; if (forNavigation && (rcState instanceof Hollow)) { rc = null; } } return rc; } /** * Sets field fieldDesc to value. * The update of relationship fields is triggered by either calling * {@link #updateCollectionField} or {@link #updateObjectField}, * depending on the field's cardinality. * * @param fieldDesc Field to be updated. * @param value New value. * @param managedRelationshipInProgress * True during relationship management. * @return Field fieldDesc's previous value. */ private Object doUpdateField(FieldDesc fieldDesc, Object value, boolean managedRelationshipInProgress) { prepareUpdateField(fieldDesc, null); if (fieldDesc instanceof ForeignFieldDesc) { ForeignFieldDesc ff = (ForeignFieldDesc) fieldDesc; if (ff.cardinalityUPB > 1) { updateCollectionField(ff, (Collection) value, managedRelationshipInProgress); } else { updateObjectField(ff, value, true, managedRelationshipInProgress); } } updateTrackedFields(fieldDesc, value, null); Object currentValue = fieldDesc.getValue(this); fieldDesc.setValue(this, value); return currentValue; } private Object prepareSetField(int fieldID, Object value) { FieldDesc fieldDesc = persistenceConfig.getField(fieldID); return prepareSetField(fieldDesc, value, false, true); } private Object prepareSetField(FieldDesc fieldDesc, Object value) { return prepareSetField(fieldDesc, value, false, false); } /** * Internal method setting the new value value for field * fieldDesc during relationship management. Only called on * relationship additions for object fields. This method is never called * for collection fields. Relationship management for collection fields * is done by deferred collections in {@link #addRelationship}. * Deferred collections are implemented in SCOCollection. * Relationship management on relationship removal is done in * {@link #removeRelationship} for both object and collection fields. * * @param fieldDesc Field to be updated. * @param value New value. * @param managedRelationshipInProgress Always true. * @return Field fieldDesc's previous value. * @see com.sun.jdo.spi.persistence.support.sqlstore.SCOCollection */ private Object prepareSetField(FieldDesc fieldDesc, Object value, boolean managedRelationshipInProgress) { return prepareSetField(fieldDesc, value, managedRelationshipInProgress, false); } /** * Sets field fieldDesc by calling * {@link SQLStateManager#doUpdateField(FieldDesc, Object, boolean)}. * * @param fieldDesc Field to be updated. * @param value New value. * @param managedRelationshipInProgress * True during relationship management. * @param acquireShareLock Acquire a shared lock during the update. * @return Field fieldDesc's previous value. */ private Object prepareSetField(FieldDesc fieldDesc, Object value, boolean managedRelationshipInProgress, boolean acquireShareLock) { boolean debug = logger.isLoggable(Logger.FINEST); if (debug) { logger.finest("sqlstore.sqlstatemanager.preparesetfield", fieldDesc.getName()); // NOI18N } if (acquireShareLock) { persistenceManager.acquireShareLock(); } try { getLock(); if ((fieldDesc.sqlProperties & FieldDesc.PROP_READ_ONLY) > 0) { throw new JDOUserException(I18NHelper.getMessage(messages, "core.statemanager.readonly", fieldDesc.getName(), // NOI18N persistentObject.getClass().getName())); } // We need to lock fieldUpdateLock if there is a chance that // relationship field values might be affected. This is the case if // fieldDesc is a relationship field or it tracks other fields. if ((fieldDesc.getTrackedFields() != null) || (fieldDesc instanceof ForeignFieldDesc)) { persistenceManager.acquireFieldUpdateLock(); try { return doUpdateField(fieldDesc, value, managedRelationshipInProgress); } finally { persistenceManager.releaseFieldUpdateLock(); } } else { return doUpdateField(fieldDesc, value, managedRelationshipInProgress); } } catch (JDOException e) { throw e; } catch (Exception e) { logger.log(Logger.FINE,"sqlstore.exception.log", e); throw new JDOFatalInternalException(I18NHelper.getMessage(messages, "core.statemanager.setfieldfailed"), e); // NOI18N } finally { if (acquireShareLock) { persistenceManager.releaseShareLock(); } releaseLock(); if (debug) { logger.finest("sqlstore.sqlstatemanager.preparesetfield.exit"); // NOI18N } } } /** * Sets the new value for the collection field by calling * {@link #processCollectionUpdates}. * * @param fieldDesc Field descriptor of the field to be set. * @param value New value. * @param managedRelationshipInProgress * True during relationship management. */ private void updateCollectionField(ForeignFieldDesc fieldDesc, Collection value, boolean managedRelationshipInProgress) { boolean debug = logger.isLoggable(Logger.FINEST); if (debug) { Object[] items = new Object[] {value,((value == null)? "NO" : value.getClass().getName())}; // NOI18N logger.finest("sqlstore.sqlstatemanager.processforeignfield", items); // NOI18N } Object currVal = fieldDesc.getValue(this); // Do nothing if the current value is identical to the new value. if (currVal != value) { Object owner = null; ArrayList added = null; ArrayList removed = null; // Verify SCO owner and fieldName if any if (value != null && value instanceof SCOCollection) { SCOCollection sco = (SCOCollection) value; owner = sco.getOwner(); if (owner == null) { sco.setOwner(persistentObject, fieldDesc.getName(), fieldDesc.getComponentType()); } else if (owner != persistentObject || !fieldDesc.getName().equals(sco.getFieldName())) { throw new JDOUserException(I18NHelper.getMessage( messages, "core.statemanager.anotherowner"), // NOI18N new Object[]{owner, sco.getFieldName()}); } // SCO should not behave as a JDK collection, // but become owned and tracked at setXXX operation. added = new ArrayList(value); } Object befrVal = fieldDesc.getValue(beforeImage); if (currVal != null) { if (debug) logger.finest("sqlstore.sqlstatemanager.processforeignfield.remove"); // NOI18N // This is a setXXX (i.e. replace) operation, we need to // "remove" elements from the current SCOCollection and mark it as not used if (((Collection) currVal).size() > 0) { removed = new ArrayList((Collection) currVal); } if (currVal instanceof SCOCollection) { if (debug) logger.finest("sqlstore.sqlstatemanager.processforeignfield.reset"); // NOI18N // SCOCollection: mark it as not used ((SCO) currVal).unsetOwner(); } } else if (getSetMaskBit(fieldDesc.absoluteID) == false && befrVal != null) // && value instanceof SCOCollection && owner != null) { if (debug) logger.finest("sqlstore.sqlstatemanager.processforeignfield.remove_from_bi"); // NOI18N // Replace with SCOCollection: mark beforeImage as removed if (((Collection) befrVal).size() > 0) { removed = new ArrayList((Collection) befrVal); } } processCollectionUpdates(fieldDesc, removed, added, null, true, managedRelationshipInProgress); } } public Object clone() { SQLStateManager clone = new SQLStateManager(store, persistenceConfig); clone.persistenceManager = persistenceManager; return clone; } private void assertNotPK(int fieldNumber) { if (persistenceConfig.isPKField(fieldNumber)) throw new JDOUnsupportedOptionException(I18NHelper.getMessage(messages, "core.statemanager.nopkupdate")); // NOI18N } private void assertPKUpdate(FieldDesc f, Object value) { Object currentValue = f.getValue(this); boolean throwException = false; // We only throw an exception if the new value is actually different from // the current value. if ((value != null) && (currentValue != null)) { if (value.toString().compareTo(currentValue.toString()) != 0) { throwException = true; } } else if (value != currentValue) { throwException = true; } if (throwException) { throw new JDOUnsupportedOptionException(I18NHelper.getMessage(messages, "core.statemanager.nopkupdate")); // NOI18N } } /** * ... */ public com.sun.jdo.api.persistence.support.PersistenceManager getPersistenceManagerInternal() { return persistenceManager; } /** * ... */ public com.sun.jdo.api.persistence.support.PersistenceManager getPersistenceManager() { return (persistenceManager == null)? null : persistenceManager.getCurrentWrapper(); } /** * ... */ // !!! olsen: changed to return byte instead of void (->PC.jdoSetFlags()) public byte setFlags(byte flags) { // RESOLVE: Need to verify that the flags are valid with the current // state of the state manager. return flags; } /** * Triggers the state transition for READ and registers the * instance in the transaction cache. */ public void loadForRead() { boolean debug = logger.isLoggable(Logger.FINER); if (debug) { logger.finer("sqlstore.sqlstatemanager.loadforread"); // NOI18N } persistenceManager.acquireShareLock(); try { getLock(); byte oldFlags = persistenceManager.getFlags(persistentObject); // If the jdoFlag is either READ_OK or READ_WRITE_OK, that means another // thread might have already call loadForRead on this instance. if (oldFlags != LOAD_REQUIRED) { return; } try { boolean xactActive = persistenceManager.isActiveTransaction(); boolean optimistic = persistenceManager.isOptimisticTransaction(); boolean nontransactionalRead = persistenceManager.isNontransactionalRead(); if (state.needsReload(optimistic, nontransactionalRead, xactActive)) { reload(null); } LifeCycleState oldstate = state; state = state.transitionReadField(optimistic, nontransactionalRead, xactActive); persistenceManager.setFlags(persistentObject, READ_OK); registerInstance(false, null, oldstate); } catch (JDOException e) { // restore the jdoFlags. persistenceManager.setFlags(persistentObject, oldFlags); throw e; } } finally { persistenceManager.releaseShareLock(); releaseLock(); if (debug) { logger.finer("sqlstore.sqlstatemanager.loadforread.exit"); // NOI18N } } } /** * Triggers the state transition for WRITE and registers the instance * in the transaction cache. Prepares all DFG fields for update. */ public void loadForUpdate() { boolean debug = logger.isLoggable(Logger.FINER); if (debug) { logger.finer("sqlstore.sqlstatemanager.loadforupdate"); // NOI18N } persistenceManager.acquireShareLock(); try { getLock(); byte oldFlags = persistenceManager.getFlags(persistentObject); // If the jdoFlags is already set to READ_WRITE_OK, it means that anther // thread has called loadForUpdate on this instance. if (oldFlags == READ_WRITE_OK) { return; } persistenceManager.setFlags(persistentObject, READ_WRITE_OK); ArrayList fields = persistenceConfig.fields; try { // Mark all the fields in the dfg dirty. for (int i = 0; i < fields.size(); i++) { FieldDesc f = (FieldDesc) fields.get(i); if (f.fetchGroup == FieldDesc.GROUP_DEFAULT) { //prepareSetField(f, null); prepareUpdateField(f, null); } } } catch (JDOException e) { // restore the jdoFlags. persistenceManager.setFlags(persistentObject, oldFlags); throw e; } } finally { persistenceManager.releaseShareLock(); releaseLock(); if (debug) { logger.finer("sqlstore.sqlstatemanager.loadforupdate.exit"); // NOI18N } } } /** * This method serves two purposes: * 1. If the field value is null or contains a non-SCOCollection instance, it * creates a new SCOCollection and populates with elements in c. * 2. If the field value is a SCOCollection instance, then if it is deferred, * it calls applyDeferredUpdates on the collection passing in c. Otherwise, * it clears the collection and repopulates with elements in c. */ public synchronized void replaceCollection(ForeignFieldDesc ff, Collection c) { Collection collection = (Collection) ff.getValue(this); SCOCollection scoCollection = null; if ((collection == null) || !(collection instanceof SCO)) { scoCollection = (SCOCollection) persistenceManager.newCollectionInstanceInternal( ff.getType(), persistentObject, ff.getName(), ff.getComponentType(), false, ((c != null) ? c.size() : 0)); ff.setValue(this, scoCollection); scoCollection.addAllInternal(c); } else { scoCollection = (SCOCollection) collection; if (scoCollection.isDeferred()) { scoCollection.applyDeferredUpdates(c); // We need to mark all the tracked fields as present. ArrayList trackedFields = ff.getTrackedFields(); if (trackedFields != null) { for (int i = 0; i < trackedFields.size(); i++) { ForeignFieldDesc tf = (ForeignFieldDesc) trackedFields.get(i); setPresenceMaskBit(tf.absoluteID); } } } else { scoCollection.clearInternal(); scoCollection.addAllInternal(c); } } // Should not use old collection as SCO if any if (c != null && c instanceof SCO) { ((SCO) c).unsetOwner(); } } /** * For test purposes */ protected LifeCycleState getCurrentState() { return state; } // Status interrogation methods // For each one of these methods, there is a corresponding version // of it prefixed with jdo on the PersistenceCapable class. These // methods are used to query the state o an instance. For example, // when jdoIsReadReady is called on the PersistenceCapable // instance, the generated jdoIsReadReady will delegate the // status interrogation to the StateManager by call // isReadReady(). /** * ... */ public boolean isDirty() { if (state != null) { return state.isDirty(); } return false; } /** * ... */ public boolean isTransactional() { if (state != null) { return state.isTransactional(); } return false; } /** * ... */ public boolean isNew() { if (state != null) { return state.isNew(); } return false; } /** * ... */ public boolean isDeleted() { if (state != null) { return state.isDeleted(); } return false; } /** * ... */ public boolean isPersistent() { if (state != null) { return state.isPersistent(); } return false; } /** * @inheritDoc */ public boolean needsRegisterWithVersionConsistencyCache() { boolean rc = hasVersionConsistency(); if (rc && state != null) { rc = state.isPersistent() && state.isTransactional() && !state.isNew() && !state.isDirty() && !state.isDeleted(); } return rc; } /** * @inheritDoc */ public boolean needsUpdateInVersionConsistencyCache() { boolean rc = hasVersionConsistency(); if (rc && state != null) { rc = (state.isDirty() || state.isNew() || persistenceConfig.hasLocalNonDFGFields()) && !state.isDeleted(); } return rc; } // Setter methods // These are methods for accessing the persistent field values // from the StateManager. The setter methods can also // serve as the hook for keeping track of changes made to the // StateManager. public boolean setBooleanField(int fieldNumber, boolean value) { assertNotPK(fieldNumber); prepareSetField(fieldNumber, Boolean.valueOf(value)); return value; } public boolean[] setBooleanArrayField(int fieldNumber, boolean[] value) { prepareSetField(fieldNumber, null); return value; } public byte setByteField(int fieldNumber, byte value) { assertNotPK(fieldNumber); prepareSetField(fieldNumber, Byte.valueOf(value)); return value; } public byte[] setByteArrayField(int fieldNumber, byte[] value) { prepareSetField(fieldNumber, null); return value; } public short setShortField(int fieldNumber, short value) { assertNotPK(fieldNumber); prepareSetField(fieldNumber, Short.valueOf(value)); return value; } public short[] setShortArrayField(int fieldNumber, short[] value) { prepareSetField(fieldNumber, null); return value; } public int setIntField(int fieldNumber, int value) { assertNotPK(fieldNumber); prepareSetField(fieldNumber, Integer.valueOf(value)); return value; } public int[] setIntArrayField(int fieldNumber, int[] value) { prepareSetField(fieldNumber, null); return value; } public long setLongField(int fieldNumber, long value) { assertNotPK(fieldNumber); prepareSetField(fieldNumber, Long.valueOf(value)); return value; } public long[] setLongArrayField(int fieldNumber, long[] value) { prepareSetField(fieldNumber, null); return value; } public char setCharField(int fieldNumber, char value) { assertNotPK(fieldNumber); prepareSetField(fieldNumber, Character.valueOf(value)); return value; } public char setCharArrayField(int fieldNumber, char value) { prepareSetField(fieldNumber, null); return value; } public float setFloatField(int fieldNumber, float value) { assertNotPK(fieldNumber); prepareSetField(fieldNumber, Float.valueOf(value)); return value; } public float[] setFloatArrayField(int fieldNumber, float[] value) { prepareSetField(fieldNumber, null); return value; } public double setDoubleField(int fieldNumber, double value) { assertNotPK(fieldNumber); prepareSetField(fieldNumber, Double.valueOf(value)); return value; } public double[] setDoubleArrayField(int fieldNumber, double[] value) { prepareSetField(fieldNumber, null); return value; } public String setStringField(int fieldNumber, String value) { assertNotPK(fieldNumber); prepareSetField(fieldNumber, value); return value; } public String[] setStringArrayField(int fieldNumber, String[] value) { prepareSetField(fieldNumber, null); return value; } /** * This method sets object fields, e.g. relationship fields. */ public Object setObjectField(int fieldNumber, Object value) { assertNotPK(fieldNumber); prepareSetField(fieldNumber, value); return value; } public Object[] setObjectArrayField(int fieldNumber, Object[] value) { prepareSetField(fieldNumber, value); return value; } public boolean testIsLoaded(int fieldNumber) { return getPresenceMaskBit(fieldNumber); } public boolean testIsLoaded(String fieldName) { FieldDesc f = persistenceConfig.getField(fieldName); return testIsLoaded(f.absoluteID); } public boolean testIsAutoPersistent() { return state.isAutoPersistent(); } /** * Marks this instance needs to require registering with the global (weak) cache * at rollback if it transitions to persistent state. * Used for replacing a deleted instance with the newly persistent with * the same object id. */ public void markNotRegistered() { needsRegisterAtRollback = true; } /** * Marks this instance as needs to be verified at the time it is removed from the * global (weak) cache at rollback if it transitions to transient state. */ public void markVerifyAtDeregister() { needsVerifyAtDeregister = true; } /** * Marks this instance as a replacement for a deleted instance with the same * ObjectId. */ public void markReplacement() { isReplacementInstance = true; } /** * Lock this instance. */ // For consistency's sake, this should be changed to acquireLock. public void getLock() { lock.acquire(); } /** * Release lock. */ public void releaseLock() { lock.release(); } /** * Return value for valid flag. */ public boolean isValid() { return valid; } /** * Mark this StateManager as valid. Called before returning from * getObjectById. */ public void setValid() { try { getLock(); valid = true; } finally { releaseLock(); } } /** * This class stores a database dependency between the current and * a foreign state manager. To resolve dependencies before * commit/flush, remember the relationship field intorducing this * dependency. */ private class UpdatedForeignReference { ForeignFieldDesc fieldDesc; SQLStateManager sm; private UpdatedForeignReference(ForeignFieldDesc fieldDesc, SQLStateManager sm) { this.fieldDesc = fieldDesc; this.sm = sm; } private ForeignFieldDesc getFieldDesc() { return fieldDesc; } private SQLStateManager getStateManager() { return sm; } public boolean equals(Object obj) { if (obj != null && this.getClass().equals(obj.getClass())) { final UpdatedForeignReference other = (UpdatedForeignReference) obj; return (this.fieldDesc == other.fieldDesc && this.sm == other.sm); } return (false); } public int hashCode() { int hashCode = sm.hashCode(); if (fieldDesc != null) { hashCode += fieldDesc.hashCode(); } return hashCode; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy