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

org.datanucleus.state.StateManagerImpl Maven / Gradle / Ivy

Go to download

DataNucleus Core provides the primary components of a heterogenous Java persistence solution. It supports persistence API's being layered on top of the core functionality.

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

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

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

Contributors:
2003 Erik Bengtson - removed exist() operation
2003 Andy Jefferson - added localiser
2003 Erik Bengtson - added new constructor for App ID
2003 Erik Bengtson - fixed loadDefaultFetchGroup to call jdoPostLoad
2003 Erik Bengtson - fixed evict to call jdoPreClear
2004 Andy Jefferson - converted to use Logger
2004 Andy Jefferson - reordered methods to put in categories, split String utilities across to StringUtils.
2004 Andy Jefferson - added Lifecycle Listener callbacks
2004 Andy Jefferson - removed JDK 1.4 methods so that we support 1.3 also
2005 Martin Taal - Contrib of detach() method for "detachOnClose" functionality.
2007 Xuan Baldauf - Contrib of initialiseForHollowPreConstructed()
2007 Xuan Baldauf - Contrib of internalXXX() methods for fields
2007 Xuan Baldauf - remove the fields "jdoLoadedFields" and "jdoModifiedFields".  
2007 Xuan Baldauf - remove the fields "retrievingDetachedState" and "resettingDetachedState".
2007 Xuan Baldauf - remove the field "updatingEmbeddedFieldsWithOwner"
2008 Andy Jefferson - removed all deps on org.datanucleus.store.mapped
    ...
 **********************************************************************/
package org.datanucleus.state;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.BitSet;
import java.util.List;

import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.DetachState;
import org.datanucleus.ExecutionContext;
import org.datanucleus.ExecutionContext.EmbeddedOwnerRelation;
import org.datanucleus.FetchPlan;
import org.datanucleus.FetchPlanForClass;
import org.datanucleus.FetchPlanState;
import org.datanucleus.PersistableObjectType;
import org.datanucleus.PropertyNames;
import org.datanucleus.api.ApiAdapter;
import org.datanucleus.cache.CachedPC;
import org.datanucleus.cache.L2CachePopulateFieldManager;
import org.datanucleus.cache.L2CacheRetrieveFieldManager;
import org.datanucleus.cache.Level2Cache;
import org.datanucleus.enhancement.Detachable;
import org.datanucleus.enhancement.ExecutionContextReference;
import org.datanucleus.enhancement.Persistable;
import org.datanucleus.enhancement.StateManager;
import org.datanucleus.enhancer.EnhancementHelper;
import org.datanucleus.exceptions.ClassNotResolvedException;
import org.datanucleus.exceptions.NotYetFlushedException;
import org.datanucleus.exceptions.NucleusException;
import org.datanucleus.exceptions.NucleusObjectNotFoundException;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.flush.DeleteOperation;
import org.datanucleus.flush.PersistOperation;
import org.datanucleus.flush.UpdateMemberOperation;
import org.datanucleus.identity.IdentityReference;
import org.datanucleus.identity.IdentityUtils;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.IdentityType;
import org.datanucleus.metadata.MetaData;
import org.datanucleus.metadata.RelationType;
import org.datanucleus.metadata.VersionMetaData;
import org.datanucleus.metadata.VersionStrategy;
import org.datanucleus.store.FieldValues;
import org.datanucleus.store.ObjectReferencingStoreManager;
import org.datanucleus.store.StoreManager;
import org.datanucleus.store.federation.FederatedStoreManager;
import org.datanucleus.store.fieldmanager.AbstractFetchDepthFieldManager.EndOfFetchPlanGraphException;
import org.datanucleus.store.fieldmanager.AttachFieldManager;
import org.datanucleus.store.fieldmanager.DeleteFieldManager;
import org.datanucleus.store.fieldmanager.DetachFieldManager;
import org.datanucleus.store.fieldmanager.FieldManager;
import org.datanucleus.store.fieldmanager.LoadFieldManager;
import org.datanucleus.store.fieldmanager.MakeTransientFieldManager;
import org.datanucleus.store.fieldmanager.PersistFieldManager;
import org.datanucleus.store.fieldmanager.SingleTypeFieldManager;
import org.datanucleus.store.fieldmanager.SingleValueFieldManager;
import org.datanucleus.store.fieldmanager.UnsetOwnerFieldManager;
import org.datanucleus.store.types.SCO;
import org.datanucleus.store.types.SCOCollection;
import org.datanucleus.store.types.SCOContainer;
import org.datanucleus.store.types.SCOMap;
import org.datanucleus.store.types.SCOUtils;
import org.datanucleus.store.types.containers.ContainerHandler;
import org.datanucleus.store.types.converters.TypeConversionHelper;
import org.datanucleus.util.ClassUtils;
import org.datanucleus.util.Localiser;
import org.datanucleus.util.NucleusLogger;
import org.datanucleus.util.StringUtils;

/**
 * Implementation of a StateManager, supporting the bytecode enhancement contract of DataNucleus.
 * Implemented here as one StateManager per Object so adds on functionality particular to each object. 
 * All Persistable objects will have a StateManager when they have had communication with the ExecutionContext. 
 * They will typically always have an identity also. The exception to that is for embedded/serialised objects.
 * 
 * 

Embedded/Serialised Objects

* An object that is being embedded/serialised in an owning object will NOT have an identity unless the object is subject to a makePersistent() call also. * When an object is embedded/serialised and a field is changed, the field will NOT be marked as dirty (unless it is also an object in its own right with an identity). * When a field is changed any owning objects are updated so that they can update their tables accordingly. * *

Performance and Memory

* StateManagers are very performance-critical, because for each Persistable object made persistent, * there will be one StateManager instance, adding up to the total memory footprint of that object. * In heap profiling analysis (cerca 2008), StateManagerImpl showed to consume bytes 169 per StateManager by itself * and about 500 bytes per StateManager when taking PC-individual child-object (like the id) referred by the StateManager into account. * With small Java objects this can mean a substantial memory overhead and for applications using such small objects can be critical. * For this reason the StateManager should always be minimal in memory consumption. * Any fields that are only present for some cases should, in general, either be offloaded to the ExecutionContext, or to a separate object if multiple fields. * The fields loadedFields and dirtyFields could, arguably, be made BitSet but it isn't clear of the benefit in the typical use-case * of smaller array sizes (number of fields in a class), as per https://www.baeldung.com/java-boolean-array-bitset-performance * *

Commit/Rollback

* When the managed object is changed it is saved as savedPC and its state as savedPersistenceFlags and savedLoadedFields. * These fields allow it to be rolled-back to an earlier state. Refer to the saveFields and restoreFields methods. */ public class StateManagerImpl implements DNStateManager { protected static final SingleTypeFieldManager HOLLOWFIELDMANAGER = new SingleTypeFieldManager(); /** Whether we are in the process of INSERTING the object to persistence. */ protected static final int FLAG_INSERTING = 2<<21; /** Whether we are in the process of INSERTING the object from persistence, running callbacks. */ protected static final int FLAG_INSERTING_CALLBACKS = 2<<20; /** Whether we are in the process of DELETING the object from persistence. */ protected static final int FLAG_DELETING = 2<<19; /** Whether we are managing an embedded object. */ protected static final int FLAG_EMBEDDED = 2<<18; /** Whether we are currently validating the object in the datastore. */ protected static final int FLAG_VALIDATING = 2<<17; /** Whether to restore values at StateManager. If true, overwrites the restore values at tx level. */ protected static final int FLAG_RESTORE_VALUES = 2<<16; /** Flag to signify that we are currently storing the persistable object, so we don't detach it on serialisation. */ protected static final int FLAG_STORING_PC = 2<<15; /** Whether the managed object needs the inheritance level validating before loading fields. */ protected static final int FLAG_NEED_INHERITANCE_VALIDATION = 2<<14; protected static final int FLAG_POSTINSERT_UPDATE = 2<<13; protected static final int FLAG_LOADINGFPFIELDS = 2<<12; protected static final int FLAG_POSTLOAD_PENDING = 2<<11; protected static final int FLAG_CHANGING_STATE = 2<<10; /** if the persistable object is new and was flushed to the datastore. */ protected static final int FLAG_FLUSHED_NEW = 2<<9; protected static final int FLAG_BECOMING_DELETED = 2<<8; /** Flag whether this SM is updating the ownership of its embedded/serialised field(s). */ protected static final int FLAG_UPDATING_EMBEDDING_FIELDS_WITH_OWNER = 2<<7; /** Flag for {@link #flags} whether we are retrieving detached state from the detached object. */ protected static final int FLAG_RETRIEVING_DETACHED_STATE = 2<<6; /** Flag for {@link #flags} whether we are resetting the detached state. */ protected static final int FLAG_RESETTING_DETACHED_STATE = 2<<5; /** Flag for {@link #flags} whether we are in the process of attaching the object. */ protected static final int FLAG_ATTACHING = 2<<4; /** Flag for {@link #flags} whether we are in the process of detaching the object. */ protected static final int FLAG_DETACHING = 2<<3; /** Flag for {@link #flags} whether we are in the process of making transient the object. */ protected static final int FLAG_MAKING_TRANSIENT = 2<<2; /** Flag for {@link #flags} whether we are in the process of flushing changes to the object. */ protected static final int FLAG_FLUSHING = 2<<1; /** Flag for {@link #flags} whether we are in the process of disconnecting the object. */ protected static final int FLAG_DISCONNECTING = 2<<0; /** The persistable instance managed by this StateManager. */ protected Persistable myPC; /** Bit-packed flags for operational settings (packed into "int" for memory benefit). */ protected int flags; /** The ExecutionContext for this StateManager */ protected ExecutionContext myEC; /** the metadata for the class. */ protected AbstractClassMetaData cmd; /** The object identity in the JVM. Will be "myID" (if set) or otherwise a temporary id based on this StateManager. */ protected Object myInternalID; /** The object identity in the datastore */ protected Object myID; /** The actual LifeCycleState for the persistable instance */ protected LifeCycleState myLC; /** Optimistic version, when starting any transaction. */ protected Object myVersion; /** Optimistic version, after insert/update but not yet committed (i.e incremented). */ protected Object transactionalVersion; /** Flags for state stored with the object. Maps onto org.datanucleus.enhancement.Persistable "dnFlags". */ protected byte persistenceFlags; /** Fetch plan for the class of the managed object. */ protected FetchPlanForClass myFP; /** * Indicator for whether the persistable instance is dirty. * Note that "dirty" in this case is not equated to being in the P_DIRTY state. * The P_DIRTY state means that at least one field in the object has been written by the user during * the current transaction, whereas for this parameter, a field is "dirty" if it's been written by the * user but not yet updated in the data store. The difference is, it's possible for an object's state * to be P_DIRTY, yet have no "dirty" fields because flush() has been called at least once during the transaction. */ protected boolean dirty = false; /** indicators for which fields are currently dirty in the persistable instance. */ protected boolean[] dirtyFields; /** indicators for which fields are currently loaded in the persistable instance. */ protected boolean[] loadedFields; /** Current FieldManager. */ protected FieldManager currFM = null; /** Saved state, for use during any rollback for reinstating the object. */ protected SavedState savedState = null; private static final EnhancementHelper HELPER; static { HELPER = (EnhancementHelper) AccessController.doPrivileged(new PrivilegedAction() { public Object run() { try { return EnhancementHelper.getInstance(); } catch (SecurityException e) { throw new NucleusUserException(Localiser.msg("026000"), e).setFatal(); } } }); } /** * Constructor for object of specified type managed by the provided ExecutionContext. * @param ec ExecutionContext * @param cmd the metadata for the class. */ public StateManagerImpl(ExecutionContext ec, AbstractClassMetaData cmd) { connect(ec, cmd); } /* (non-Javadoc) * @see org.datanucleus.state.AbstractStateManager#connect(org.datanucleus.ExecutionContext, org.datanucleus.metadata.AbstractClassMetaData) */ @Override public void connect(ExecutionContext ec, AbstractClassMetaData cmd) { int fieldCount = cmd.getMemberCount(); this.cmd = cmd; this.myEC = ec; dirtyFields = new boolean[fieldCount]; loadedFields = new boolean[fieldCount]; dirty = false; myFP = myEC.getFetchPlan().getFetchPlanForClass(cmd); myVersion = null; transactionalVersion = null; persistenceFlags = 0; ec.setAttachDetachReferencedObject(this, null); } /** * Disconnect from the ExecutionContext and persistable object. */ public void disconnect() { if (NucleusLogger.PERSISTENCE.isDebugEnabled()) { NucleusLogger.PERSISTENCE.debug(Localiser.msg("026011", IdentityUtils.getPersistableIdentityForId(getInternalObjectId()), this)); } // Transitioning to TRANSIENT state, so if any postLoad action is pending we do it before. // This usually happens when we make transient instances using the fetch plan and some // fields were loaded during this action which triggered a dnPostLoad event if (isPostLoadPending()) { flags &= ~FLAG_CHANGING_STATE; //hack to make sure postLoad does not return without processing setPostLoadPending(false); postLoad(); } // Call unsetOwner() on all loaded SCO fields. int[] fieldNumbers = ClassUtils.getFlagsSetTo(loadedFields, cmd.getSCOMutableMemberPositions(), true); if (fieldNumbers != null && fieldNumbers.length > 0) { provideFields(fieldNumbers, new UnsetOwnerFieldManager()); } // Disconnect any embedded related StateManagers, since we own them List subSMRelations = myEC.getEmbeddedInformationForOwner(this); if (subSMRelations != null && !subSMRelations.isEmpty()) { for (EmbeddedOwnerRelation embRel : subSMRelations) { DNStateManager embSM = embRel.getEmbeddedSM(); if (embSM.isConnected()) { embSM.disconnect(); } } } myEC.removeStateManagerFromCache(this); persistenceFlags = Persistable.READ_WRITE_OK; myPC.dnReplaceFlags(); flags |= FLAG_DISCONNECTING; try { replaceStateManager(myPC, null); } finally { flags &= ~FLAG_DISCONNECTING; } savedState = null; preDeleteLoadedFields = null; myPC = null; myID = null; myInternalID = null; myLC = null; myEC = null; myFP = null; myVersion = null; persistenceFlags = 0; flags = 0; transactionalVersion = null; currFM = null; dirty = false; cmd = null; dirtyFields = null; loadedFields = null; // TODO Remove the object from any pooling (when we enable it) via nucCtx.getStateManagerFactory().disconnectStateManager(this); } @Override public boolean isConnected() { return myPC != null; } /** * Initialises a state manager to manage a hollow instance having the given object ID and the given (optional) field values. * This constructor is used for creating new instances of existing persistent objects, and consequently shouldn't be used * when the StoreManager controls the creation of such objects (such as in an ODBMS). * @param id the JDO identity of the object. * @param fv the initial field values of the object (optional) * @param pcClass Class of the object that this will manage the state for */ public void initialiseForHollow(Object id, FieldValues fv, Class pcClass) { myID = id; myLC = myEC.getNucleusContext().getApiAdapter().getLifeCycleState(LifeCycleState.HOLLOW); persistenceFlags = Persistable.LOAD_REQUIRED; if (IdentityUtils.isDatastoreIdentity(id) || id == null) { // Create new PC myPC = HELPER.newInstance(pcClass, this); } else { // Create new PC, and copy the key class to fields myPC = HELPER.newInstance(pcClass, this, myID); markPKFieldsAsLoaded(); } // Put in L1 cache just in case referred to by other objects in the FieldValues // e.g when we retrieve objects with circular references in the same result set from a query myEC.putObjectIntoLevel1Cache(this); if (fv != null) { loadFieldValues(fv); // TODO If this object has unique key(s) then they will likely be loaded from the fieldValues, so could put in L1 cache here } } /** * Initialises a state manager to manage the given hollow instance having the given object ID. * Unlike the {@link #initialiseForHollow} method, this method does not create a new instance and instead * takes a pre-constructed instance (such as from an ODBMS). * @param id the identity of the object. * @param pc the object to be managed. */ public void initialiseForHollowPreConstructed(Object id, Persistable pc) { myID = id; myLC = myEC.getNucleusContext().getApiAdapter().getLifeCycleState(LifeCycleState.HOLLOW); persistenceFlags = Persistable.LOAD_REQUIRED; myPC = pc; replaceStateManager(myPC, this); // Assign this StateManager to the PC myPC.dnReplaceFlags(); // TODO Add to the cache } /** * Initialises a state manager to manage the passed persistent instance having the given object ID. * Used where we have retrieved a PC object from a datastore directly (not field-by-field), for example on an object datastore. * This initialiser will not add StateManagers to all related PCs. This must be done by any calling process. * This simply adds the StateManager to the specified object and records the id, setting all fields of the object as loaded. * @param id the identity of the object. * @param pc The object to be managed */ public void initialiseForPersistentClean(Object id, Persistable pc) { myID = id; myLC = myEC.getNucleusContext().getApiAdapter().getLifeCycleState(LifeCycleState.P_CLEAN); persistenceFlags = Persistable.LOAD_REQUIRED; myPC = pc; replaceStateManager(myPC, this); // Assign this StateManager to the PC myPC.dnReplaceFlags(); // Mark all fields as loaded for (int i=0; i pcClass) { myID = null; // Embedded, so don't need an ID since we're not persisting it myLC = myEC.getNucleusContext().getApiAdapter().getLifeCycleState(LifeCycleState.P_CLEAN); // TODO Should be HOLLOW? persistenceFlags = Persistable.LOAD_REQUIRED; flags |= FLAG_EMBEDDED; // Create new (embedded) PC myPC = HELPER.newInstance(pcClass, this); // Mark all fields as loaded for now, to be populated later for (int i=0;i * This constructor is used for assigning state managers to existing instances that are transitioning to a persistent state. * @param pc the instance being make persistent. * @param preInsertChanges Any changes to make before inserting */ public void initialiseForPersistentNew(Persistable pc, FieldValues preInsertChanges) { myPC = pc; myLC = myEC.getNucleusContext().getApiAdapter().getLifeCycleState(LifeCycleState.P_NEW); persistenceFlags = Persistable.READ_OK; for (int i=0; i * This constructor is used for assigning state managers to Transient * instances that are transitioning to a transient clean state. * @param pc the instance being make persistent. */ public void initialiseForTransactionalTransient(Persistable pc) { myPC = pc; myLC = null; persistenceFlags = Persistable.READ_OK; for (int i=0; i= fpFieldNumbers.length) { // Past end of FetchPlan fields nonfpFieldNumbers[j++] = i; } else { if (fpFieldNumbers[currentFPFieldIndex] == i) { // FetchPlan field so move to next currentFPFieldIndex++; } else { nonfpFieldNumbers[j++] = i; } } } } // Mark all non-FetchPlan fields as unloaded for (int i=0;i 0) { if (NucleusLogger.CACHE.isDebugEnabled()) { NucleusLogger.CACHE.debug(Localiser.msg("026034", IdentityUtils.getPersistableIdentityForId(myID), StringUtils.intArrayToString(cacheFieldsToLoad))); } L2CacheRetrieveFieldManager l2RetFM = constructL2CacheRetrieveFieldManager(this, cachedPC); this.replaceFields(cacheFieldsToLoad, l2RetFM); int[] fieldsNotLoaded = l2RetFM.getFieldsNotLoaded(); if (fieldsNotLoaded != null) { for (int i=0;i 0) { // TODO Fix this to just access the fields of the FieldManager yet this actually does a replaceField replaceFields(fieldNumbers, new LoadFieldManager(this, cmd.getSCOMutableMemberFlags(), myFP, state)); updateLevel2CacheForFields(fieldNumbers); } } finally { flags &= ~FLAG_LOADINGFPFIELDS; } } /** * Convenience method to load a field from the datastore. * Used in attaching fields and checking their old values (so we don't want any postLoad method being called). * TODO Merge this with one of the loadXXXFields methods. * @param fieldNumber The field number. If fieldNumber is -1 then this means call loadFieldsFromDatastore(null); */ public void loadFieldFromDatastore(int fieldNumber) { loadFieldsFromDatastore((fieldNumber >= 0) ? (new int[] {fieldNumber}) : new int[] {}); } /** * Convenience method to load fields from the datastore. * Note that if the fieldNumbers is null/empty we still should call the persistence handler since it may mean * that the version field needs loading. * @param fieldNumbers The field numbers. */ protected void loadFieldsFromDatastore(int[] fieldNumbers) { if (myLC.isNew() && myLC.isPersistent() && !isFlushedNew()) { // Not yet flushed new persistent object to datastore so no point in "loading" return; } if ((flags&FLAG_NEED_INHERITANCE_VALIDATION)!=0) // TODO Merge this into fetch object handler { String className = getStoreManager().getClassNameForObjectID(myID, myEC.getClassLoaderResolver(), myEC); if (!getObject().getClass().getName().equals(className)) { myEC.removeObjectFromLevel1Cache(myID); myEC.removeObjectFromLevel2Cache(myID); throw new NucleusObjectNotFoundException("Object with id " + IdentityUtils.getPersistableIdentityForId(myID) + " was created without validating of type " + getObject().getClass().getName() + " but is actually of type " + className); } flags &= ~FLAG_NEED_INHERITANCE_VALIDATION; } // Add on version field if not currently set and using version field (surrogate will be added automatically by the query if required) int[] fieldNumbersToFetch = fieldNumbers; if (isVersioned()) { VersionMetaData vermd = cmd.getVersionMetaDataForClass(); if (vermd != null && vermd.getMemberName() != null) { int verFieldNum = cmd.getMetaDataForMember(vermd.getMemberName()).getAbsoluteFieldNumber(); boolean versionPresent = false; for (int i=0;i 0) { String[] dirtyFieldNames = new String[dirtyFieldNumbers.length]; for (int i=0;i 0) { String[] loadedFieldNames = new String[loadedFieldNumbers.length]; for (int i=0;i * This method is called by the Persistable whenever dnReplaceStateManager is called and * there is already an owning StateManager. This is a security precaution to ensure that the owning * StateManager is the only source of any change to its reference in the Persistable. *

* @param pc the calling Persistable instance * @param sm the proposed new value for the StateManager * @return the new value for the StateManager */ public StateManager replacingStateManager(Persistable pc, StateManager sm) { if (myLC == null) { throw new NucleusException("Null LifeCycleState").setFatal(); } else if (myLC.stateType() == LifeCycleState.DETACHED_CLEAN) { return sm; } else if (pc == myPC) { //TODO check if we are really in transition to a transient instance if (sm == null) { return null; } if (sm == this) { return this; } if (myEC == ((DNStateManager)sm).getExecutionContext()) { NucleusLogger.PERSISTENCE.debug("StateManagerImpl.replacingStateManager this=" + this + " sm=" + sm + " with same EC"); // This is a race condition when makePersistent or makeTransactional is called on the same PC instance for the // same PM. It has been already set to this SM - just disconnect the other one. Return this SM so it won't be replaced. ((DNStateManager)sm).disconnect(); return this; } throw myEC.getApiAdapter().getUserExceptionForException(Localiser.msg("026003"), null); } else if (pc == (savedState != null ? savedState.getPC() : null)) { return null; } else { return sm; } } /** * Method that replaces the PC managed by this StateManager to be the supplied object. * This is used when we want to get an object for an id and create a Hollow object, and then validate against the datastore. * This validation can pull in a new object graph from the datastore (e.g for an ODBMS). * @param pc The persistable to use */ public void replaceManagedPC(Persistable pc) { if (pc == null) { return; } // Swap the StateManager on the objects replaceStateManager(pc, this); replaceStateManager(myPC, null); // Swap our object myPC = pc; // Put it in the cache in case the previous object was stored myEC.putObjectIntoLevel1Cache(this); } // -------------------------- Lifecycle Methods --------------------------- /** * Tests whether this object is dirty. * Instances that have been modified, deleted, or newly made persistent in the current transaction return true. * Transient nontransactional instances return false (JDO spec). * @see Persistable#dnMakeDirty(String fieldName) * @param pc the calling persistable instance * @return true if this instance has been modified in current transaction. */ public boolean isDirty(Persistable pc) { if (disconnectClone(pc)) { return false; } return myLC.isDirty(); } /** * Tests whether this object is transactional. * * Instances that respect transaction boundaries return true. These * instances include transient instances made transactional as a result of * being the target of a makeTransactional method call; newly made * persistent or deleted persistent instances; persistent instances read * in data store transactions; and persistent instances modified in * optimistic transactions. *

* Transient nontransactional instances return false. * * @param pc the calling persistable instance * @return true if this instance is transactional. */ public boolean isTransactional(Persistable pc) { if (disconnectClone(pc)) { return false; } return myLC.isTransactional(); } /** * Tests whether this object is persistent. * Instances whose state is stored in the data store return true. * Transient instances return false. * @param pc the calling persistable instance * @return true if this instance is persistent. */ public boolean isPersistent(Persistable pc) { if (disconnectClone(pc)) { return false; } return myLC.isPersistent(); } /** * Tests whether this object has been newly made persistent. * Instances that have been made persistent in the current transaction * return true. *

* Transient instances return false. * @param pc the calling persistable instance * @return true if this instance was made persistent * in the current transaction. */ public boolean isNew(Persistable pc) { if (disconnectClone(pc)) { return false; } return myLC.isNew(); } public boolean isDeleted() { return isDeleted(myPC); } /** * Tests whether this object has been deleted. * Instances that have been deleted in the current transaction return true. *

Transient instances return false. * @param pc the calling persistable instance * @return true if this instance was deleted in the current transaction. */ public boolean isDeleted(Persistable pc) { if (disconnectClone(pc)) { return false; } return myLC.isDeleted(); } // -------------------------- Version handling ---------------------------- /** * Return the object representing the version of the calling instance. * @param pc the calling persistable instance * @return the object representing the version of the calling instance */ public Object getVersion(Persistable pc) { if (pc == myPC) { if (transactionalVersion == null && isVersioned()) { // If the object is versioned and no version is loaded (e.g obtained via findObject without loading fields) and in a state where we need it then pull in the version VersionMetaData vermd = cmd.getVersionMetaDataForClass(); if (vermd != null && vermd.getStrategy() != VersionStrategy.NONE) { if (myLC.stateType() == LifeCycleState.P_CLEAN || myLC.stateType() == LifeCycleState.HOLLOW) // Add other states? { if (vermd.getMemberName() != null) { loadFieldFromDatastore(cmd.getMetaDataForMember(vermd.getMemberName()).getAbsoluteFieldNumber()); } else { loadFieldsFromDatastore(new int[] {}); } } } } return transactionalVersion; } return null; } /** * Method to return if the version is loaded. * If the class represented is not versioned then returns true * @return Whether it is loaded. */ public boolean isVersionLoaded() { if (isVersioned()) { return transactionalVersion != null; } // No version required, so return true return true; } /** * Method to return the current version of the managed object. * @return The version */ public Object getVersion() { return getVersion(myPC); } /** * Return the transactional version of the managed object. * @return Version of the managed instance at this point in the transaction */ public Object getTransactionalVersion() { return getTransactionalVersion(myPC); } // -------------------------- Field Handling Methods ------------------------------ /** * Method to clear all fields of the object. */ public void clearFields() { try { getCallbackHandler().preClear(myPC); } finally { clearFieldsByNumbers(cmd.getAllMemberPositions()); clearDirtyFlags(); if (myEC.getStoreManager() instanceof ObjectReferencingStoreManager) { // For datastores that manage the object reference ((ObjectReferencingStoreManager)myEC.getStoreManager()).notifyObjectIsOutdated(this); } persistenceFlags = Persistable.LOAD_REQUIRED; myPC.dnReplaceFlags(); getCallbackHandler().postClear(myPC); } } /** * Method to clear all fields that are not part of the primary key of the object. */ public void clearNonPrimaryKeyFields() { try { getCallbackHandler().preClear(myPC); } finally { int[] nonPKMemberPosns = cmd.getNonPKMemberPositions(); // Unset owner of any SCO wrapper so if the user holds on to a wrapper it doesn't affect the datastore int[] nonPkScoFields = ClassUtils.getFlagsSetTo(cmd.getSCOMutableMemberFlags(), ClassUtils.getFlagsSetTo(loadedFields, nonPKMemberPosns, true), true); if (nonPkScoFields != null) { provideFields(nonPkScoFields, new UnsetOwnerFieldManager()); } clearFieldsByNumbers(nonPKMemberPosns); clearDirtyFlags(nonPKMemberPosns); if (myEC.getStoreManager() instanceof ObjectReferencingStoreManager) { // For datastores that manage the object reference ((ObjectReferencingStoreManager)myEC.getStoreManager()).notifyObjectIsOutdated(this); } persistenceFlags = Persistable.LOAD_REQUIRED; myPC.dnReplaceFlags(); getCallbackHandler().postClear(myPC); } } /** * Method to clear all loaded flags on the object. * Note that the contract of this method implies, especially for object database backends, that the memory form * of the object is outdated. * Thus, for features like implicit saving of dirty object subgraphs should be switched off for this PC, even if the * object actually looks like being dirty (because it is being changed to null values). */ public void clearLoadedFlags() { if (myEC.getStoreManager() instanceof ObjectReferencingStoreManager) { // For datastores that manage the object reference ((ObjectReferencingStoreManager)myEC.getStoreManager()).notifyObjectIsOutdated(this); } persistenceFlags = Persistable.LOAD_REQUIRED; myPC.dnReplaceFlags(); ClassUtils.clearFlags(loadedFields); } /** * The StateManager uses this method to supply the value of dnFlags to the associated persistable instance. * @param pc the calling Persistable instance * @return the value of dnFlags to be stored in the Persistable instance */ public byte replacingFlags(Persistable pc) { // If this is a clone, return READ_WRITE_OK. if (pc != myPC) { return Persistable.READ_WRITE_OK; } return persistenceFlags; } /** * Method to return the current value of a particular field. * @param fieldNumber Number of field * @return The value of the field */ public Object provideField(int fieldNumber) { return provideField(myPC, fieldNumber); } /** * Method to retrieve the value of a field from the PC object. Assumes that it is loaded. * @param pc The PC object * @param fieldNumber Number of field * @return The value of the field */ protected Object provideField(Persistable pc, int fieldNumber) { Object obj; try { if (myEC.getMultithreaded()) { myEC.threadLock(); } FieldManager prevFM = currFM; currFM = new SingleValueFieldManager(); try { pc.dnProvideField(fieldNumber); obj = currFM.fetchObjectField(fieldNumber); } finally { currFM = prevFM; } } finally { if (myEC.getMultithreaded()) { myEC.threadUnlock(); } } return obj; } /** * Called from the StoreManager after StoreManager.update() is called to obtain updated values from the Persistable associated with this StateManager. * @param fieldNumbers An array of field numbers to be updated by the Store * @param fm The updated values are stored in this object. This object is only valid for the duration of this call. */ public void provideFields(int fieldNumbers[], FieldManager fm) { try { if (myEC.getMultithreaded()) { myEC.threadLock(); } FieldManager prevFM = currFM; currFM = fm; try { // This will respond by calling this.providedXXXFields() with the value of the field myPC.dnProvideFields(fieldNumbers); } finally { currFM = prevFM; } } finally { if (myEC.getMultithreaded()) { myEC.threadUnlock(); } } } // -------------------------- replacingXXXField Methods ---------------------------- /** * This method is called by the associated Persistable when the corresponding mutator method (setXXX()) is called on the Persistable. * @param pc the calling Persistable instance * @param fieldNumber the field number * @param currentValue the current value of the field * @param newValue the new value for the field */ public void setBooleanField(Persistable pc, int fieldNumber, boolean currentValue, boolean newValue) { if (pc != myPC) { replaceField(pc, fieldNumber, newValue ? Boolean.TRUE : Boolean.FALSE, true); disconnectClone(pc); } else if (myLC != null) { if (isVersioned() && transactionalVersion == null) { // Not got version but should have loadUnloadedFieldsInFetchPlanAndVersion(); } if (!loadedFields[fieldNumber] || currentValue != newValue) { if (cmd.getIdentityType() == IdentityType.NONDURABLE) { String key = DNStateManager.ORIGINAL_FIELD_VALUE_KEY_PREFIX + fieldNumber; if (!containsAssociatedValue(key)) { setAssociatedValue(key, currentValue); } } updateField(pc, fieldNumber, newValue ? Boolean.TRUE : Boolean.FALSE); if (!myEC.getTransaction().isActive()) { myEC.processNontransactionalUpdate(); } } } else { replaceField(pc, fieldNumber, newValue ? Boolean.TRUE : Boolean.FALSE, true); } } /** * This method is called by the associated Persistable when the corresponding mutator method (setXXX()) is called on the Persistable. * @param pc the calling Persistable instance * @param fieldNumber the field number * @param currentValue the current value of the field * @param newValue the new value for the field */ public void setByteField(Persistable pc, int fieldNumber, byte currentValue, byte newValue) { if (pc != myPC) { replaceField(pc, fieldNumber, Byte.valueOf(newValue), true); disconnectClone(pc); } else if (myLC != null) { if (isVersioned() && transactionalVersion == null) { // Not got version but should have loadUnloadedFieldsInFetchPlanAndVersion(); } if (!loadedFields[fieldNumber] || currentValue != newValue) { if (cmd.getIdentityType() == IdentityType.NONDURABLE) { String key = DNStateManager.ORIGINAL_FIELD_VALUE_KEY_PREFIX + fieldNumber; if (!containsAssociatedValue(key)) { setAssociatedValue(key, currentValue); } } updateField(pc, fieldNumber, Byte.valueOf(newValue)); if (!myEC.getTransaction().isActive()) { myEC.processNontransactionalUpdate(); } } } else { replaceField(pc, fieldNumber, Byte.valueOf(newValue), true); } } /** * This method is called by the associated Persistable when the corresponding mutator method (setXXX()) is called on the Persistable. * @param pc the calling Persistable instance * @param fieldNumber the field number * @param currentValue the current value of the field * @param newValue the new value for the field */ public void setCharField(Persistable pc, int fieldNumber, char currentValue, char newValue) { if (pc != myPC) { replaceField(pc, fieldNumber, Character.valueOf(newValue), true); disconnectClone(pc); } else if (myLC != null) { if (isVersioned() && transactionalVersion == null) { // Not got version but should have loadUnloadedFieldsInFetchPlanAndVersion(); } if (!loadedFields[fieldNumber] || currentValue != newValue) { if (cmd.getIdentityType() == IdentityType.NONDURABLE) { String key = DNStateManager.ORIGINAL_FIELD_VALUE_KEY_PREFIX + fieldNumber; if (!containsAssociatedValue(key)) { setAssociatedValue(key, currentValue); } } updateField(pc, fieldNumber, Character.valueOf(newValue)); if (!myEC.getTransaction().isActive()) { myEC.processNontransactionalUpdate(); } } } else { replaceField(pc, fieldNumber, Character.valueOf(newValue), true); } } /** * This method is called by the associated Persistable when the corresponding mutator method (setXXX()) is called on the Persistable. * @param pc the calling Persistable instance * @param fieldNumber the field number * @param currentValue the current value of the field * @param newValue the new value for the field */ public void setDoubleField(Persistable pc, int fieldNumber, double currentValue, double newValue) { if (pc != myPC) { replaceField(pc, fieldNumber, Double.valueOf(newValue), true); disconnectClone(pc); } else if (myLC != null) { if (isVersioned() && transactionalVersion == null) { // Not got version but should have loadUnloadedFieldsInFetchPlanAndVersion(); } if (!loadedFields[fieldNumber] || currentValue != newValue) { if (cmd.getIdentityType() == IdentityType.NONDURABLE) { String key = DNStateManager.ORIGINAL_FIELD_VALUE_KEY_PREFIX + fieldNumber; if (!containsAssociatedValue(key)) { setAssociatedValue(key, currentValue); } } updateField(pc, fieldNumber, Double.valueOf(newValue)); if (!myEC.getTransaction().isActive()) { myEC.processNontransactionalUpdate(); } } } else { replaceField(pc, fieldNumber, Double.valueOf(newValue), true); } } /** * This method is called by the associated Persistable when the corresponding mutator method (setXXX()) is called on the Persistable. * @param pc the calling Persistable instance * @param fieldNumber the field number * @param currentValue the current value of the field * @param newValue the new value for the field */ public void setFloatField(Persistable pc, int fieldNumber, float currentValue, float newValue) { if (pc != myPC) { replaceField(pc, fieldNumber, Float.valueOf(newValue), true); disconnectClone(pc); } else if (myLC != null) { if (isVersioned() && transactionalVersion == null) { // Not got version but should have loadUnloadedFieldsInFetchPlanAndVersion(); } if (!loadedFields[fieldNumber] || currentValue != newValue) { if (cmd.getIdentityType() == IdentityType.NONDURABLE) { String key = DNStateManager.ORIGINAL_FIELD_VALUE_KEY_PREFIX + fieldNumber; if (!containsAssociatedValue(key)) { setAssociatedValue(key, currentValue); } } updateField(pc, fieldNumber, Float.valueOf(newValue)); if (!myEC.getTransaction().isActive()) { myEC.processNontransactionalUpdate(); } } } else { replaceField(pc, fieldNumber, Float.valueOf(newValue), true); } } /** * This method is called by the associated Persistable when the corresponding mutator method (setXXX()) is called on the Persistable. * @param pc the calling Persistable instance * @param fieldNumber the field number * @param currentValue the current value of the field * @param newValue the new value for the field */ public void setIntField(Persistable pc, int fieldNumber, int currentValue, int newValue) { if (pc != myPC) { replaceField(pc, fieldNumber, Integer.valueOf(newValue), true); disconnectClone(pc); } else if (myLC != null) { if (isVersioned() && transactionalVersion == null) { // Not got version but should have loadUnloadedFieldsInFetchPlanAndVersion(); } if (!loadedFields[fieldNumber] || currentValue != newValue) { if (cmd.getIdentityType() == IdentityType.NONDURABLE) { String key = DNStateManager.ORIGINAL_FIELD_VALUE_KEY_PREFIX + fieldNumber; if (!containsAssociatedValue(key)) { setAssociatedValue(key, currentValue); } } updateField(pc, fieldNumber, Integer.valueOf(newValue)); if (!myEC.getTransaction().isActive()) { myEC.processNontransactionalUpdate(); } } } else { replaceField(pc, fieldNumber, Integer.valueOf(newValue), true); } } /** * This method is called by the associated Persistable when the corresponding mutator method (setXXX()) is called on the Persistable. * @param pc the calling Persistable instance * @param fieldNumber the field number * @param currentValue the current value of the field * @param newValue the new value for the field */ public void setLongField(Persistable pc, int fieldNumber, long currentValue, long newValue) { if (pc != myPC) { replaceField(pc, fieldNumber, Long.valueOf(newValue), true); disconnectClone(pc); } else if (myLC != null) { if (isVersioned() && transactionalVersion == null) { // Not got version but should have loadUnloadedFieldsInFetchPlanAndVersion(); } if (!loadedFields[fieldNumber] || currentValue != newValue) { if (cmd.getIdentityType() == IdentityType.NONDURABLE) { String key = DNStateManager.ORIGINAL_FIELD_VALUE_KEY_PREFIX + fieldNumber; if (!containsAssociatedValue(key)) { setAssociatedValue(key, currentValue); } } updateField(pc, fieldNumber, Long.valueOf(newValue)); if (!myEC.getTransaction().isActive()) { myEC.processNontransactionalUpdate(); } } } else { replaceField(pc, fieldNumber, Long.valueOf(newValue), true); } } /** * This method is called by the associated Persistable when the corresponding mutator method (setXXX()) is called on the Persistable. * @param pc the calling Persistable instance * @param fieldNumber the field number * @param currentValue the current value of the field * @param newValue the new value for the field */ public void setShortField(Persistable pc, int fieldNumber, short currentValue, short newValue) { if (pc != myPC) { replaceField(pc, fieldNumber, Short.valueOf(newValue), true); disconnectClone(pc); } else if (myLC != null) { if (isVersioned() && transactionalVersion == null) { // Not got version but should have loadUnloadedFieldsInFetchPlanAndVersion(); } if (!loadedFields[fieldNumber] || currentValue != newValue) { if (cmd.getIdentityType() == IdentityType.NONDURABLE) { String key = DNStateManager.ORIGINAL_FIELD_VALUE_KEY_PREFIX + fieldNumber; if (!containsAssociatedValue(key)) { setAssociatedValue(key, currentValue); } } updateField(pc, fieldNumber, Short.valueOf(newValue)); if (!myEC.getTransaction().isActive()) { myEC.processNontransactionalUpdate(); } } } else { replaceField(pc, fieldNumber, Short.valueOf(newValue), true); } } /** * This method is called by the associated Persistable when the corresponding mutator method (setXXX()) is called on the Persistable. * @param pc the calling Persistable instance * @param fieldNumber the field number * @param currentValue the current value of the field * @param newValue the new value for the field */ public void setStringField(Persistable pc, int fieldNumber, String currentValue, String newValue) { if (pc != myPC) { replaceField(pc, fieldNumber, newValue, true); disconnectClone(pc); } else if (myLC != null) { if (isVersioned() && transactionalVersion == null) { // Not got version but should have loadUnloadedFieldsInFetchPlanAndVersion(); } if (!loadedFields[fieldNumber] || !(currentValue == null ? (newValue == null) : currentValue.equals(newValue))) { if (cmd.getIdentityType() == IdentityType.NONDURABLE) { String key = DNStateManager.ORIGINAL_FIELD_VALUE_KEY_PREFIX + fieldNumber; if (!containsAssociatedValue(key)) { setAssociatedValue(key, currentValue); } } updateField(pc, fieldNumber, newValue); if (!myEC.getTransaction().isActive()) { myEC.processNontransactionalUpdate(); } } } else { replaceField(pc, fieldNumber, newValue, true); } } /** * This method is called by the associated Persistable when the corresponding mutator method (setXXX()) is called on the Persistable. * @param pc the calling Persistable instance * @param fieldNumber the field number * @param currentValue the current value of the field * @param newValue the new value for the field */ public void setObjectField(Persistable pc, int fieldNumber, Object currentValue, Object newValue) { if (currentValue != null && currentValue != newValue && currentValue instanceof Persistable) { // Where the object is embedded, remove the owner from its old value since it is no longer managed by this StateManager DNStateManager currentSM = myEC.findStateManager(currentValue); if (currentSM != null && currentSM.isEmbedded()) { myEC.removeEmbeddedOwnerRelation(this, fieldNumber, currentSM); } } if (pc != myPC) { // Clone replaceField(pc, fieldNumber, newValue, true); disconnectClone(pc); } else if (myLC != null) { if (isVersioned() && transactionalVersion == null) { // Not got version but should have loadUnloadedFieldsInFetchPlanAndVersion(); } boolean loadedOldValue = false; Object oldValue = currentValue; AbstractMemberMetaData mmd = cmd.getMetaDataForManagedMemberAtAbsolutePosition(fieldNumber); ClassLoaderResolver clr = myEC.getClassLoaderResolver(); RelationType relationType = mmd.getRelationType(clr); // Remove this object from L2 cache since now dirty to avoid potential problems myEC.removeObjectFromLevel2Cache(myID); if (!loadedFields[fieldNumber] && currentValue == null) { // Updating value of a field that isnt currently loaded if (myEC.getManageRelations() && (relationType == RelationType.ONE_TO_ONE_BI || relationType == RelationType.MANY_TO_ONE_BI)) { // Managed relation field, so load old value loadField(fieldNumber); loadedOldValue = true; oldValue = provideField(fieldNumber); } else if (relationType != RelationType.NONE && newValue == null && (mmd.isDependent() || mmd.isCascadeRemoveOrphans())) { // Field being nulled and is dependent so load the existing value so it can be deleted loadField(fieldNumber); loadedOldValue = true; oldValue = provideField(fieldNumber); } else if (cmd.getSCOContainerMemberFlags()[fieldNumber] && !mmd.isSerialized()) { // Situation such as SCO field being replaced by "new HashMap(...)" so old value not loaded yet loadField(fieldNumber); oldValue = provideField(fieldNumber); } } // Check equality of old and new values boolean equal = false; boolean equalButContainerRefChanged = false; if (oldValue == null && newValue == null) { equal = true; } else if (oldValue != null && newValue != null) { if (RelationType.isRelationSingleValued(relationType)) { // Persistable field so compare object equality // See JDO2 [5.4] "The JDO implementation must not use the application's hashCode and equals methods // from the persistence-capable classes except as needed to implement the Collections Framework" if (oldValue == newValue) { equal = true; } } else { // Not 1-1/N-1 relation so compare using equals() if (oldValue.equals(newValue)) { equal = true; if (oldValue instanceof SCOContainer && ((SCOContainer)oldValue).getValue() != newValue && !(newValue instanceof SCO)) { if (oldValue.getClass().isAssignableFrom(newValue.getClass()) || newValue.getClass().isAssignableFrom(oldValue.getClass())) { // Field value is container and equal (i.e same elements/keys/values) BUT different container reference so need to update the delegate in SCO wrappers equalButContainerRefChanged = true; } } } } } // Update the field boolean needsSCOUpdating = false; if (!loadedFields[fieldNumber] || !equal || equalButContainerRefChanged || mmd.hasArray()) { if (cmd.getIdentityType() == IdentityType.NONDURABLE && relationType == RelationType.NONE) { String key = DNStateManager.ORIGINAL_FIELD_VALUE_KEY_PREFIX + fieldNumber; if (!containsAssociatedValue(key)) { setAssociatedValue(key, oldValue); } } // Either field isn't loaded, or has changed, or is an array. // We include arrays here since we have no way of knowing if the array element has changed except if the user sets the array field. // See JDO2 spec [6.3] that the application should replace the value with its current value. if (oldValue instanceof SCO) { if (oldValue instanceof SCOContainer) { // Make sure container values are loaded ((SCOContainer)oldValue).load(); } if (!equalButContainerRefChanged) { ((SCO)oldValue).unsetOwner(); } } if (newValue instanceof SCO) { SCO sco = (SCO) newValue; Object owner = sco.getOwner(); if (owner != null) { throw myEC.getApiAdapter().getUserExceptionForException(Localiser.msg("026007", sco.getFieldName(), owner), null); } } updateField(pc, fieldNumber, newValue); if (cmd.getSCOMutableMemberFlags()[fieldNumber] && !(newValue instanceof SCO)) { // Need to wrap this field change needsSCOUpdating = true; } } else if (loadedOldValue) { // We've updated the value with the old value (when retrieving it above), so put the new value back again updateField(pc, fieldNumber, newValue); } if (!equal) { if (RelationType.isBidirectional(relationType)&& myEC.getManageRelations()) { // Managed Relationships - add the field to be managed so we can analyse its value at flush myEC.getRelationshipManager(this).relationChange(fieldNumber, oldValue, newValue); } if (myEC.operationQueueIsActive()) { myEC.addOperationToQueue(new UpdateMemberOperation(this, fieldNumber, newValue, oldValue)); } } else if (equalButContainerRefChanged) { // Previous value was a SCO wrapper and this new value is equal to the old wrappers delegate so swap the delegate for the original wrapper to this new value ((SCOContainer)oldValue).setValue(newValue); newValue = oldValue; // Point to the old wrapper needsSCOUpdating = false; replaceField(fieldNumber, oldValue); } if (needsSCOUpdating) { // Wrap with SCO so we can detect future updates newValue = SCOUtils.wrapAndReplaceSCOField(this, fieldNumber, newValue, oldValue, true); } if (oldValue != null && newValue == null) { if (RelationType.isRelationSingleValued(relationType) && (mmd.isDependent() || mmd.isCascadeRemoveOrphans())) { // Persistable field being nulled, so delete previous persistable value if in a position to be deleted if (myEC.getApiAdapter().isPersistent(oldValue)) { // TODO Queue this when using optimistic txns, so the old value could be assigned somewhere else NucleusLogger.PERSISTENCE.debug(Localiser.msg("026026", oldValue, mmd.getFullFieldName())); myEC.deleteObjectInternal(oldValue); } } } if (!myEC.getTransaction().isActive()) { myEC.processNontransactionalUpdate(); } } else { replaceField(pc, fieldNumber, newValue, true); } } /** * Convenience method to perform the update of a field value when a setter is invoked. * Called by setXXXField methods. * @param pc The PC object * @param fieldNumber The field number * @param value The new value */ protected void updateField(Persistable pc, int fieldNumber, Object value) { boolean wasDirty = dirty; // If we're writing a field in the process of inserting it must be due to dnPreStore(). // We haven't actually done the INSERT yet so we don't want to mark anything as dirty, which would make us want to do an UPDATE later. if ((flags&FLAG_INSERTING) == 0 && (flags&FLAG_INSERTING_CALLBACKS) == 0) { if (!wasDirty) // (only do it for first dirty event). { // Call any lifecycle listeners waiting for this event getCallbackHandler().preDirty(myPC); } // Update lifecycle state as required transitionWriteField(); dirty = true; dirtyFields[fieldNumber] = true; loadedFields[fieldNumber] = true; } replaceField(pc, fieldNumber, value, true); if (dirty && !wasDirty) // (only do it for first dirty event). { // Call any lifecycle listeners waiting for this event getCallbackHandler().postDirty(myPC); } // TODO replaceField typically does a markDirty above, so need to catch those cases and avoid multiple calls to it if ((flags&FLAG_INSERTING) == 0 && (flags&FLAG_INSERTING_CALLBACKS) == 0 && !isFlushing() && !(myLC.isTransactional() && !myLC.isPersistent())) { // Not during flush, and not transactional-transient, and not inserting - so mark as dirty myEC.markDirty(this, true); } } /** * Method to change the value of a field in the PC object. * @param pc The PC object * @param fieldNumber Number of field * @param value The new value of the field */ protected void replaceField(Persistable pc, int fieldNumber, Object value) { try { if (myEC.getMultithreaded()) { myEC.threadLock(); } // Update the field in our PC object FieldManager prevFM = currFM; currFM = new SingleValueFieldManager(); try { currFM.storeObjectField(fieldNumber, value); pc.dnReplaceField(fieldNumber); } finally { currFM = prevFM; } if (containsAssociatedValue(MEMBER_VALUE_STORED_PREFIX + fieldNumber)) { removeAssociatedValue(MEMBER_VALUE_STORED_PREFIX + fieldNumber); } } finally { if (myEC.getMultithreaded()) { myEC.threadUnlock(); } } } /** * Method to disconnect any cloned persistence capable objects from their StateManager. * @param pc The Persistable object * @return Whether the object was disconnected. */ protected boolean disconnectClone(Persistable pc) { if (isDetaching()) { return false; } if (pc != myPC) { NucleusLogger.PERSISTENCE.warn("StateManager.disconnectClone : Please let developers know how you got this - myPC=" + StringUtils.toJVMIDString(myPC) + " other=" + StringUtils.toJVMIDString(pc), new Exception()); if (NucleusLogger.PERSISTENCE.isDebugEnabled()) { NucleusLogger.PERSISTENCE.debug(Localiser.msg("026001", StringUtils.toJVMIDString(pc), this)); } // Reset dnFlags in the clone to Persistable.READ_WRITE_OK and clear its state manager. pc.dnReplaceFlags(); replaceStateManager(pc, null); return true; } return false; } /** * Convenience method to retrieve the detach state from the passed StateManager's object. * @param sm StateManager */ public void retrieveDetachState(DNStateManager sm) { if (sm.getObject() instanceof Detachable) { ((StateManagerImpl)sm).flags |= FLAG_RETRIEVING_DETACHED_STATE; ((Detachable)sm.getObject()).dnReplaceDetachedState(); ((StateManagerImpl)sm).flags &= ~FLAG_RETRIEVING_DETACHED_STATE; } } /** * Convenience method to reset the detached state in the current object. */ public void resetDetachState() { if (getObject() instanceof Detachable) { flags |= FLAG_RESETTING_DETACHED_STATE; try { ((Detachable)getObject()).dnReplaceDetachedState(); } finally { flags &= ~FLAG_RESETTING_DETACHED_STATE; } } } /** * Method to update the "detached state" in the detached object to obtain the "detached state" from the detached object, or to reset it (to null). * @param pc The Persistable being updated * @param currentState The current state values * @return The detached state to assign to the object */ public Object[] replacingDetachedState(Detachable pc, Object[] currentState) { if ((flags&FLAG_RESETTING_DETACHED_STATE) != 0) { return null; } else if ((flags&FLAG_RETRIEVING_DETACHED_STATE) != 0) { // Retrieving the detached state from the detached object // Don't need the id or version since they can't change BitSet theLoadedFields = (BitSet)currentState[2]; for (int i = 0; i < this.loadedFields.length; i++) { this.loadedFields[i] = theLoadedFields.get(i); } BitSet theModifiedFields = (BitSet)currentState[3]; for (int i = 0; i < dirtyFields.length; i++) { dirtyFields[i] = theModifiedFields.get(i); } setVersion(currentState[1]); return currentState; } else { // Updating the detached state in the detached object with our state Object[] state = new Object[4]; state[0] = myID; state[1] = getVersion(myPC); // Loaded fields BitSet loadedState = new BitSet(); for (int i = 0; i < loadedFields.length; i++) { if (loadedFields[i]) { loadedState.set(i); } else { loadedState.clear(i); } } state[2] = loadedState; // Modified fields BitSet modifiedState = new BitSet(); for (int i = 0; i < dirtyFields.length; i++) { if (dirtyFields[i]) { modifiedState.set(i); } else { modifiedState.clear(i); } } state[3] = modifiedState; return state; } } /** * Marks the given field dirty. * @param fieldNumber The no of field to mark as dirty. */ public void makeDirty(int fieldNumber) { if ((flags & FLAG_DELETING) == 0/* && !isDeleted() */) { // Mark dirty unless in the process of being deleted boolean wasDirty = preWriteField(fieldNumber); postWriteField(wasDirty); if (isEmbedded()) { EmbeddedOwnerRelation embeddedRel = myEC.getOwnerInformationForEmbedded(this); if (embeddedRel != null) { // Notify owner that this embedded object has just changed StateManagerImpl ownerSM = (StateManagerImpl)embeddedRel.getOwnerSM(); if ((ownerSM.flags&FLAG_UPDATING_EMBEDDING_FIELDS_WITH_OWNER)==0) { ownerSM.makeDirty(embeddedRel.getOwnerMemberNum()); } } } } } /** * Mark the associated persistable field dirty. * @param pc the calling persistable instance * @param fieldName the name of the field */ public void makeDirty(Persistable pc, String fieldName) { if (!disconnectClone(pc)) { int fieldNumber = cmd.getAbsolutePositionOfMember(fieldName); if (fieldNumber == -1) { throw myEC.getApiAdapter().getUserExceptionForException(Localiser.msg("026002", fieldName, cmd.getFullClassName()), null); } makeDirty(fieldNumber); } } // -------------------------- Object Identity Methods ----------------------------- /** * Accessor for the internal object id of the object we are managing. * This will return the "id" if it has been set, otherwise a temporary id (IdentityReference). * @return The internal object id */ @Override public Object getInternalObjectId() { if (myID != null) { return myID; } if (myInternalID == null) { // Assign a temporary internal "id" based on the object itself until our real identity is assigned myInternalID = new IdentityReference(myPC); } return myInternalID; } /** * Return the object representing the persistent identity of the calling instance. * According to the JDO specification, if the persistent identity is being changed in the current transaction, * this method returns the persistent identify as of the beginning of the transaction. * In DataNucleus we don't allow change of identity so this is always the same as the result of getExternalObjectId(Persistable). * @param pc the calling Persistable instance * @return the object representing the persistent identity of the calling instance */ @Override public Object getObjectId(Persistable pc) { if (disconnectClone(pc)) { return null; } try { return getExternalObjectId(); } catch (NucleusException ne) { // This method can be called from user-facing methods (e.g JDOHelper.getObjectId) so wrap any exception with API variant throw myEC.getApiAdapter().getApiExceptionForNucleusException(ne); } } /** * Return the object representing the persistent identity of the calling instance. * If the persistent identity is being changed in the current transaction, this method returns the current identity as changed in the transaction. * In DataNucleus we don't allow change of identity so this is always the same as the result of getObjectId(Persistable). * @param pc the calling Persistable instance * @return the object representing the persistent identity of the calling instance */ @Override public Object getTransactionalObjectId(Persistable pc) { return getObjectId(pc); } @Override public Object getExternalObjectId() { if (myID != null) { // Id already set and we don't support changing id, so return it return myID; } else if (isEmbedded()) { // Embedded object has no id return null; } // Ensure that the id is generated if (cmd.getIdentityType() == IdentityType.DATASTORE) { if (!isFlushing()) { // Flush any datastore changes so that myID is set by the time we return if (!isFlushedNew() && (flags&FLAG_INSERTING) == 0 && (flags&FLAG_INSERTING_CALLBACKS) == 0 && myLC.stateType() == LifeCycleState.P_NEW) { if (getStoreManager().isValueGenerationStrategyDatastoreAttributed(cmd, -1)) { flush(); } } } } else if (cmd.getIdentityType() == IdentityType.APPLICATION) { // Note that we always create a new application identity since it is mutable and we can't allow the user to change it. // The only drawback of this is that we *must* have the relevant fields set when this method is called, so that the identity can be generated. if (!isFlushing()) { // Flush any datastore changes so that we have all necessary fields populated only if the datastore generates the field numbers if (!isFlushedNew() && (flags&FLAG_INSERTING) == 0 && (flags&FLAG_INSERTING_CALLBACKS) == 0 && myLC.stateType() == LifeCycleState.P_NEW) { int[] pkMemberNumbers = cmd.getPKMemberPositions(); for (int i = 0; i < pkMemberNumbers.length; i++) { if (getStoreManager().isValueGenerationStrategyDatastoreAttributed(cmd, pkMemberNumbers[i])) { flush(); break; } } } } } return myID; } /** * Utility to set the identity for the Persistable object. * Creates the identity instance if the required PK field(s) are all already set (by the user, or by a value-strategy). * If the identity is set in the datastore (sequence, autoassign, etc) then this will not set the identity. * @param afterPreStore Whether preStore has (just) been invoked */ private void setIdentity(boolean afterPreStore) { if (isEmbedded()) { // Embedded objects don't have an "identity" return; } boolean idSet = false; if (cmd.getIdentityType() == IdentityType.DATASTORE) { if (cmd.getDatastoreIdentityMetaData() == null || !getStoreManager().isValueGenerationStrategyDatastoreAttributed(cmd, -1)) { // Assumed to be set myID = myEC.newObjectId(cmd.getFullClassName(), myPC); idSet = true; } } else if (cmd.getIdentityType() == IdentityType.APPLICATION) { boolean idSetInDatastore = false; int[] pkMemberNumbers = cmd.getPKMemberPositions(); for (int i=0;i 0) { boolean callPostLoad = myFP.isToCallPostLoadFetchPlan(this.loadedFields); int[] unloadedFieldNumbers = loadFieldsFromLevel2Cache(fieldNumbers); if (unloadedFieldNumbers != null) { loadFieldsFromDatastore(unloadedFieldNumbers); } int[] secondClassMutableFieldNumbers = cmd.getSCOMutableMemberPositions(); for (int i=0;i 0) { boolean callPostLoad = myFP.isToCallPostLoadFetchPlan(this.loadedFields); int[] unloadedFieldNumbers = loadFieldsFromLevel2Cache(fieldNumbers); if (unloadedFieldNumbers != null) { loadFieldsFromDatastore(unloadedFieldNumbers); } // Make sure all SCO lazy-loaded fields have contents loaded int[] secondClassMutableFieldNumbers = cmd.getSCOMutableMemberPositions(); for (int i=0;i 0) { boolean callPostLoad = myFP.isToCallPostLoadFetchPlan(this.loadedFields); int[] unloadedFieldNumbers = loadFieldsFromLevel2Cache(fieldNumbers); if (unloadedFieldNumbers != null) { loadFieldsFromDatastore(unloadedFieldNumbers); updateLevel2CacheForFields(unloadedFieldNumbers); } if (callPostLoad && areFieldsLoaded(myFP.getMemberNumbers())) // If a FK is in the STORED cache then wont be marked as loaded yet { postLoad(); } } } /** * Fetch from the database all fields in current fetch plan that are not currently loaded as well as the version. Called by lifecycle transitions. */ protected void loadUnloadedFieldsInFetchPlanAndVersion() { if (!isVersioned()) { loadUnloadedFieldsInFetchPlan(); } else { int[] fieldNumbers = ClassUtils.getFlagsSetTo(loadedFields, myFP.getMemberNumbers(), false); if (fieldNumbers == null) { fieldNumbers = new int[0]; } boolean callPostLoad = myFP.isToCallPostLoadFetchPlan(this.loadedFields); int[] unloadedFieldNumbers = loadFieldsFromLevel2Cache(fieldNumbers); if (unloadedFieldNumbers != null) { loadFieldsFromDatastore(unloadedFieldNumbers); updateLevel2CacheForFields(unloadedFieldNumbers); } if (callPostLoad && fieldNumbers.length > 0 && areFieldsLoaded(myFP.getMemberNumbers())) // If a FK is in the STORED cache then wont be marked as loaded yet { postLoad(); } } } @Override public void loadUnloadedFieldsOfClassInFetchPlan(FetchPlan fetchPlan) { FetchPlanForClass fpc = fetchPlan.getFetchPlanForClass(this.cmd); int[] fieldNumbers = ClassUtils.getFlagsSetTo(loadedFields, fpc.getMemberNumbers(), false); if (fieldNumbers != null && fieldNumbers.length > 0) { boolean callPostLoad = fpc.isToCallPostLoadFetchPlan(this.loadedFields); int[] unloadedFieldNumbers = loadFieldsFromLevel2Cache(fieldNumbers); if (unloadedFieldNumbers != null) { loadFieldsFromDatastore(unloadedFieldNumbers); updateLevel2CacheForFields(unloadedFieldNumbers); } if (callPostLoad && areFieldsLoaded(myFP.getMemberNumbers())) // If a FK is in the STORED cache then wont be marked as loaded yet { postLoad(); } } } @Override public void refreshFieldsInFetchPlan() { int[] fieldNumbers = myFP.getMemberNumbers(); if (fieldNumbers != null && fieldNumbers.length > 0) { clearDirtyFlags(fieldNumbers); ClassUtils.clearFlags(loadedFields, fieldNumbers); markPKFieldsAsLoaded(); // Can't refresh PK fields! boolean callPostLoad = myFP.isToCallPostLoadFetchPlan(this.loadedFields); // Refresh the fetch plan fields in this object setTransactionalVersion(null); // Make sure that the version is reset upon fetch loadFieldsFromDatastore(fieldNumbers); if (cmd.hasRelations(myEC.getClassLoaderResolver())) { // Check for cascade refreshes to related objects for (int i=0;i 0) { clearDirtyFlags(); ClassUtils.clearFlags(loadedFields); markPKFieldsAsLoaded(); // Can't refresh PK fields! boolean callPostLoad = myFP.isToCallPostLoadFetchPlan(this.loadedFields); loadFieldsFromDatastore(fieldNumbers); updateLevel2CacheForFields(fieldNumbers); if (callPostLoad) { postLoad(); } } } @Override public boolean isLoaded(int fieldNumber) { return isLoaded(myPC, fieldNumber); } /** * Return true if the field is cached in the calling instance; in this implementation we always return true. * If the field is not loaded, it will be loaded as a side effect of the call to this method. * If it is in the default fetch group, the default fetch group, including this field, will be loaded. * @param pc the calling Persistable instance * @param fieldNumber the absolute field number * @return always returns true (this implementation) */ public boolean isLoaded(Persistable pc, int fieldNumber) { try { if (disconnectClone(pc)) { return true; } boolean checkRead = true; boolean beingDeleted = false; if ((myLC.isDeleted() && myEC.isFlushing()) || (flags&FLAG_DELETING) != 0) { // Bypass "read-field" check when deleting, or when marked for deletion and flushing checkRead = false; beingDeleted = true; } if (checkRead) { transitionReadField(loadedFields[fieldNumber]); } if (!loadedFields[fieldNumber]) { // Field not loaded, so load it if (isEmbedded()) { // TODO When we have nested embedded objects that can have relations to non-embedded then this needs to change // Embedded object so we assume that all was loaded before (when it was read) return true; } if (beingDeleted && preDeleteLoadedFields != null && preDeleteLoadedFields[fieldNumber]) { // Field was loaded prior to starting delete so just return true return true; } else if (!beingDeleted && myFP.hasMember(fieldNumber)) { if (!loadStoredField(fieldNumber)) { // Load rest of FetchPlan if this is part of it (and not in the process of deletion) loadUnloadedFieldsInFetchPlan(); if (!loadedFields[fieldNumber]) { // Case where the field is marked for storing, and was stored during the loadSpecifiedFields call, so load it now loadStoredField(fieldNumber); } } } else { if (!loadStoredField(fieldNumber)) { // Just load this field loadSpecifiedFields(new int[] {fieldNumber}); if (!loadedFields[fieldNumber]) { // Case where the field is marked for storing, and was stored during the loadSpecifiedFields call, so load it now loadStoredField(fieldNumber); } } } } return true; } catch (NucleusException ne) { NucleusLogger.PERSISTENCE.warn("Exception thrown by StateManager.isLoaded for field=" + fieldNumber + " of " + this + " : " + StringUtils.getMessageFromRootCauseOfThrowable(ne)); // Convert into an exception suitable for the current API since this is called from a user update of a field throw myEC.getApiAdapter().getApiExceptionForNucleusException(ne); } } /** * Convenience method to change the value of a field that is assumed loaded. * Will mark the object/field as dirty if it isn't previously. If the object is deleted then does nothing. * Doesn't cater for embedded fields. * *** Only for use in management of relations. *** * @param fieldNumber Number of field * @param newValue The new value */ public void replaceFieldValue(int fieldNumber, Object newValue) { if (myLC.isDeleted()) { // Object is deleted so do nothing return; } boolean currentWasDirty = preWriteField(fieldNumber); replaceField(myPC, fieldNumber, newValue, true); postWriteField(currentWasDirty); } /** * Method to change the value of a particular field and not mark it dirty. * @param fieldNumber Number of field * @param value New value */ public void replaceField(int fieldNumber, Object value) { replaceField(myPC, fieldNumber, value, false); } /** * Method to change the value of a particular field and mark it dirty. * @param fieldNumber Number of field * @param value New value */ public void replaceFieldMakeDirty(int fieldNumber, Object value) { replaceField(myPC, fieldNumber, value, true); } /** * Method to change the value of a field in the PC object. * Adds on handling for embedded fields to the superclass handler. * @param pc The PC object * @param fieldNumber Number of field * @param value The new value of the field * @param makeDirty Whether to make the field dirty while replacing its value (in embedded owners) */ protected void replaceField(Persistable pc, int fieldNumber, Object value, boolean makeDirty) { if (isEmbedded()) { // Inform the owner of this change in the embedded object if necessary EmbeddedOwnerRelation ownerRel = myEC.getOwnerInformationForEmbedded(this); if (ownerRel != null) { // Notify the owner of this embedded object that it has just changed // We do this before we actually change the object so we can compare with the old value StateManagerImpl ownerSM = (StateManagerImpl) ownerRel.getOwnerSM(); AbstractMemberMetaData ownerMmd = ownerSM.getClassMetaData().getMetaDataForManagedMemberAtAbsolutePosition(ownerRel.getOwnerMemberNum()); if (ownerMmd.getCollection() != null) { // PC Object embedded in collection element Object ownerField = ownerSM.provideField(ownerRel.getOwnerMemberNum()); if (ownerField instanceof SCOCollection) { ((SCOCollection)ownerField).updateEmbeddedElement(myPC, fieldNumber, value, makeDirty); } } else if (ownerMmd.getMap() != null) { // PC Object embedded in map key / value Object ownerField = ownerSM.provideField(ownerRel.getOwnerMemberNum()); if (ownerField instanceof SCOMap) { if (ownerRel.getObjectType() == PersistableObjectType.EMBEDDED_MAP_KEY_PC) { ((SCOMap)ownerField).updateEmbeddedKey(myPC, fieldNumber, value, makeDirty); } if (ownerRel.getObjectType() == PersistableObjectType.EMBEDDED_MAP_VALUE_PC) { ((SCOMap)ownerField).updateEmbeddedValue(myPC, fieldNumber, value, makeDirty); } } } else { // PC Object embedded in PC object } if ((ownerSM.flags&FLAG_UPDATING_EMBEDDING_FIELDS_WITH_OWNER)==0) { // Update the owner when one of our fields have changed, EXCEPT when they have just notified us of our owner field! if (makeDirty) { ownerSM.makeDirty(ownerRel.getOwnerMemberNum()); } } } // Replace the field value in this embedded object replaceField(pc, fieldNumber, value); } else { // Non-embedded TODO Why don't we mark as dirty if non-tx ? Maybe need P_NONTRANS_DIRTY if (makeDirty && !myLC.isDeleted() && myEC.getTransaction().isActive()) { // Mark dirty (if not being deleted) boolean wasDirty = preWriteField(fieldNumber); replaceField(pc, fieldNumber, value); postWriteField(wasDirty); } else { replaceField(pc, fieldNumber, value); } } } /** * Called from the StoreManager to refresh data in the Persistable object associated with this StateManager. * Typically called as a result of a query, so as to populate the query results into the associated persistable object(s). * @param fieldNumbers Field numbers to be refreshed from the store data * @param fm The updated values are stored in this object. This object is only valid for the duration of this call. * @param replaceWhenDirty Whether to replace the fields when they are dirty here */ public void replaceFields(int fieldNumbers[], FieldManager fm, boolean replaceWhenDirty) { try { if (myEC.getMultithreaded()) { myEC.threadLock(); } FieldManager prevFM = currFM; currFM = fm; try { int[] fieldsToReplace = fieldNumbers; if (!replaceWhenDirty) { int numberToReplace = fieldNumbers.length; for (int i=0;i 0 && numberToReplace != fieldNumbers.length) { fieldsToReplace = new int[numberToReplace]; int n = 0; for (int i=0;i 0) { myPC.dnReplaceFields(fieldsToReplace); } } finally { currFM = prevFM; } if (callPostLoad && areFieldsLoaded(myFP.getMemberNumbers())) { // The fetch plan is now loaded so fire off any necessary post load postLoad(); } } finally { if (myEC.getMultithreaded()) { myEC.threadUnlock(); } } } /** * Method to replace all loaded SCO fields with wrappers. * If the loaded field already uses a SCO wrapper nothing happens to that field. */ public void replaceAllLoadedSCOFieldsWithWrappers() { boolean[] scoMutableFieldFlags = cmd.getSCOMutableMemberFlags(); for (int i=0;i= 0) { flags |= FLAG_UPDATING_EMBEDDING_FIELDS_WITH_OWNER; subSM.replaceFieldMakeDirty(ownerAbsFieldNum, myPC); flags &= ~FLAG_UPDATING_EMBEDDING_FIELDS_WITH_OWNER; } } } } // ------------------------- Lifecycle Methods ----------------------------- /** * Method to make the object persistent. */ public void makePersistent() { if (myLC.isDeleted() && !myEC.getNucleusContext().getApiAdapter().allowPersistOfDeletedObject()) { // API doesn't allow re-persist of deleted objects return; } if ((flags&FLAG_INSERTING) != 0 || (flags&FLAG_INSERTING_CALLBACKS) != 0) { // Already making persistent return; } if (myEC.operationQueueIsActive()) { myEC.addOperationToQueue(new PersistOperation(this)); } if (dirty && !myLC.isDeleted() && myLC.isTransactional() && myEC.isDelayDatastoreOperationsEnabled()) { // Already provisionally persistent, but delaying til commit so just re-run reachability to bring in any new objects that are now reachable if (cmd.hasRelations(myEC.getClassLoaderResolver())) { provideFields(cmd.getAllMemberPositions(), new PersistFieldManager(this, false)); } return; } getCallbackHandler().prePersist(myPC); if (isFlushedNew()) { // With CompoundIdentity bidir relations when the SM is created for this object ("initialiseForPersistentNew") the persist // of the PK PC fields can cause the flush of this object, and so it is already persisted by the time we get here registerTransactional(); return; } if (isEmbedded()) { // Object is embedded so return return; } if (myID == null) { // Assign identity when not yet assigned (is this possible?) setIdentity(false); } dirty = true; if (myEC.isDelayDatastoreOperationsEnabled()) { // Delaying datastore flush til later myEC.markDirty(this, false); if (NucleusLogger.PERSISTENCE.isDebugEnabled()) { NucleusLogger.PERSISTENCE.debug(Localiser.msg("026028", StringUtils.toJVMIDString(myPC))); } registerTransactional(); if (myLC.isTransactional() && myLC.isDeleted()) { // Re-persist of a previously deleted object myLC = myLC.transitionMakePersistent(this); } if (cmd.hasRelations(myEC.getClassLoaderResolver())) { // Run reachability on all fields of this PC - JDO2 [12.6.7] provideFields(cmd.getAllMemberPositions(), new PersistFieldManager(this, false)); } } else { // Persist the object and all reachables internalMakePersistent(); registerTransactional(); } } /** * Method to persist the object to the datastore. */ private void internalMakePersistent() { flags |= FLAG_INSERTING; boolean[] tmpDirtyFields = dirtyFields.clone(); try { getCallbackHandler().preStore(myPC); // This comes after setting the INSERTING flag so we know we are inserting it now if (myID == null) { setIdentity(true); // Just in case user is setting it in preStore } // in InstanceLifecycleEvents this object could get dirty if a field is changed in preStore/postCreate; clear dirty flags to make sure this object will not be flushed again clearDirtyFlags(); getStoreManager().getPersistenceHandler().insertObject(this); setFlushedNew(true); getCallbackHandler().postStore(myPC); } catch (NotYetFlushedException ex) { // can happen on cyclic relationships with RDBMS; if not yet flushed error, we rollback dirty fields, so we can retry inserting dirtyFields = tmpDirtyFields; myEC.markDirty(this, false); dirty = true; throw ex; // throw exception, so the owning relationship will mark its FK to update later } finally { flags &= ~FLAG_INSERTING; flags &= ~FLAG_INSERTING_CALLBACKS; } } /** * Method to change the object state to transactional. */ public void makeTransactional() { preStateChange(); try { if (myLC == null) { // Initialise the StateManager in T_CLEAN state final DNStateManager thisSM = this; myLC = myEC.getNucleusContext().getApiAdapter().getLifeCycleState(LifeCycleState.T_CLEAN); try { // Set SM reference in PC - can be done only after myLC is set to delegate validation to the LC and objectId verified for uniqueness replaceStateManager(myPC, thisSM); } catch (SecurityException e) { throw new NucleusUserException(e.getMessage()); } catch (NucleusException ne) { if (myEC.findStateManager(myEC.getObjectFromCache(myID)) == this) { myEC.removeStateManagerFromCache(this); } throw ne; } flags |= FLAG_RESTORE_VALUES; } else { myLC = myLC.transitionMakeTransactional(this, true); } } finally { postStateChange(); } } /** * Method to change the object state to transient. * @param state Object containing the state of any fetchplan processing */ public void makeTransient(FetchPlanState state) { if ((flags&FLAG_MAKING_TRANSIENT) != 0) { return; // In the process of becoming transient } try { flags |= FLAG_MAKING_TRANSIENT; if (state == null) { // No FetchPlan in use so just unset the owner of all loaded SCO fields int[] fieldNumbers = ClassUtils.getFlagsSetTo(loadedFields, cmd.getSCOMutableMemberPositions(), true); if (fieldNumbers != null && fieldNumbers.length > 0) { provideFields(fieldNumbers, new UnsetOwnerFieldManager()); } } else { // Make all loaded SCO fields transient appropriate to this fetch plan loadUnloadedFieldsInFetchPlan(); int[] fieldNumbers = ClassUtils.getFlagsSetTo(loadedFields, cmd.getAllMemberPositions(), true); if (fieldNumbers != null && fieldNumbers.length > 0) { // TODO Fix this to just access the fields of the FieldManager yet this actually does a replaceField replaceFields(fieldNumbers, new MakeTransientFieldManager(this, cmd.getSCOMutableMemberFlags(), myFP, state)); } } preStateChange(); try { myLC = myLC.transitionMakeTransient(this, state != null, myEC.isRunningDetachAllOnCommit()); } finally { postStateChange(); } } finally { flags &= ~FLAG_MAKING_TRANSIENT; } } /** * Make the managed object transient as a result of persistence-by-reachability when run at commit time. * The object was brought into persistence by reachability but found to not be needed at commit time. * Here we delete it from persistence (since it will have been persisted/flushed to the datastore), and * then we migrate the lifecycle to transient (which disconnects this StateManager). */ public void makeTransientForReachability() { // Call any lifecycle listeners waiting for this event. getCallbackHandler().preDelete(myPC); // Delete the object from the datastore (includes reachability) internalDeletePersistent(); // Call any lifecycle listeners waiting for this event. getCallbackHandler().postDelete(myPC); // Update lifecycle state to TRANSIENT dirty = true; preStateChange(); try { myLC = myLC.transitionMakeTransient(this, false, true); } finally { postStateChange(); } } /** * Method to detach this object. * If the object is detachable then it will be migrated to DETACHED state, otherwise will migrate to TRANSIENT. Used by "DetachAllOnCommit"/"DetachAllOnRollback" * @param state State for the detachment process */ public void detach(FetchPlanState state) { if (myEC == null) { return; } ApiAdapter api = myEC.getApiAdapter(); if (myLC.isDeleted() || api.isDetached(myPC) || isDetaching()) { // Already deleted, detached or being detached return; } // Check if detachable ... if so then we detach a copy, otherwise we return a transient copy boolean detachable = api.isDetachable(myPC); if (detachable) { if (NucleusLogger.PERSISTENCE.isDebugEnabled()) { NucleusLogger.PERSISTENCE.debug(Localiser.msg("010009", StringUtils.toJVMIDString(myPC), "" + state.getCurrentFetchDepth())); } // Call any "pre-detach" listeners getCallbackHandler().preDetach(myPC); } try { setDetaching(true); String detachedState = myEC.getNucleusContext().getConfiguration().getStringProperty(PropertyNames.PROPERTY_DETACH_DETACHED_STATE).toLowerCase(); if (detachedState.equals("all")) { loadUnloadedFields(); } else if (detachedState.equals("loaded")) { // Do nothing since just using currently loaded fields } else { // Using fetch-groups, so honour detachmentOptions for loading/unloading if ((myEC.getFetchPlan().getDetachmentOptions() & FetchPlan.DETACH_LOAD_FIELDS) != 0) { // Load any unloaded fetch-plan fields loadUnloadedFieldsInFetchPlan(); } if ((myEC.getFetchPlan().getDetachmentOptions() & FetchPlan.DETACH_UNLOAD_FIELDS) != 0) { // Unload any loaded fetch-plan fields that aren't in the current fetch plan unloadNonFetchPlanFields(); // Remove the values from the detached object - not required by the spec int[] unloadedFields = ClassUtils.getFlagsSetTo(loadedFields, cmd.getAllMemberPositions(), false); if (unloadedFields != null && unloadedFields.length > 0) { Persistable dummyPC = myPC.dnNewInstance(this); myPC.dnCopyFields(dummyPC, unloadedFields); replaceStateManager(dummyPC, null); } } } // Detach all (loaded) fields in the FetchPlan FieldManager detachFieldManager = new DetachFieldManager(this, cmd.getSCOMutableMemberFlags(), myFP, state, false); for (int i = 0; i < loadedFields.length; i++) { if (loadedFields[i]) { try { // Just fetch the field since we are usually called in postCommit() so dont want to update it detachFieldManager.fetchObjectField(i); } catch (EndOfFetchPlanGraphException eofpge) { Object value = provideField(i); if (api.isPersistable(value)) { // PC field beyond end of graph DNStateManager valueSM = myEC.findStateManager(value); if (!api.isDetached(value) && !(valueSM != null && valueSM.isDetaching())) { // Field value is not detached or being detached so unload it if (NucleusLogger.PERSISTENCE.isDebugEnabled()) { NucleusLogger.PERSISTENCE.debug(Localiser.msg("026032", IdentityUtils.getPersistableIdentityForId(myID), cmd.getMetaDataForManagedMemberAtAbsolutePosition(i).getName())); } unloadField(i); } } // TODO What if we have collection/map that includes some objects that are not detached? // Currently we just leave as persistent etc but should we???? // The problem is that with 1-N bidir fields we could unload the field incorrectly } } } if (detachable) { // Migrate the lifecycle state to DETACHED_CLEAN myLC = myLC.transitionDetach(this); // Update the object with its detached state myPC.dnReplaceFlags(); ((Detachable)myPC).dnReplaceDetachedState(); // Call any "post-detach" listeners getCallbackHandler().postDetach(myPC, myPC); // there is no copy, so give the same object Persistable toCheckPC = myPC; Object toCheckID = myID; disconnect(); if (!toCheckPC.dnIsDetached()) { // Sanity check on the objects detached state throw new NucleusUserException(Localiser.msg("026025", toCheckPC.getClass().getName(), toCheckID)); } } else { // Warn the user since they selected detachAllOnCommit NucleusLogger.PERSISTENCE.warn(Localiser.msg("026031", IdentityUtils.getPersistableIdentityForId(myID))); // Make the object transient makeTransient(null); } } finally { setDetaching(false); } } /** * Method to make detached copy of this instance * If the object is detachable then the copy will be migrated to DETACHED state, otherwise will migrate the copy to TRANSIENT. * Used by "ExecutionContext.detachObjectCopy()". * @param state State for the detachment process * @return the detached Persistable instance */ public Persistable detachCopy(FetchPlanState state) { if (myLC.isDeleted()) { throw new NucleusUserException(Localiser.msg("026023", myPC.getClass().getName(), myID)); } if (myEC.getApiAdapter().isDetached(myPC)) { throw new NucleusUserException(Localiser.msg("026024", myPC.getClass().getName(), myID)); } if (dirty) { myEC.flushInternal(false); } if (isDetaching()) { // Object in the process of detaching (recursive) so return the object which will be the detached object return getReferencedPC(); } // Look for an existing detached copy DetachState detachState = (DetachState) state; DetachState.Entry existingDetached = detachState.getDetachedCopyEntry(myPC); Persistable detachedPC; if (existingDetached == null) { // No existing detached copy - create new one detachedPC = myPC.dnNewInstance(this); detachState.setDetachedCopyEntry(myPC, detachedPC); } else { // Found one - if it's sufficient for current FetchPlanState, return it immediately detachedPC = (Persistable) existingDetached.getDetachedCopyObject(); if (existingDetached.checkCurrentState()) { return detachedPC; } // Need to process the detached copy using current FetchPlanState } myEC.setAttachDetachReferencedObject(this, detachedPC); // Check if detachable ... if so then we detach a copy, otherwise we return a transient copy boolean detachable = myEC.getApiAdapter().isDetachable(myPC); // make sure a detaching PC is not read by another thread while we are detaching Object referencedPC = getReferencedPC(); synchronized (referencedPC) { int[] detachFieldNums = getFieldsNumbersToDetach(); if (detachable) { if (NucleusLogger.PERSISTENCE.isDebugEnabled()) { int[] fieldsToLoad = null; if ((myEC.getFetchPlan().getDetachmentOptions() & FetchPlan.DETACH_LOAD_FIELDS) != 0) { fieldsToLoad = ClassUtils.getFlagsSetTo(loadedFields, myFP.getMemberNumbers(), false); } NucleusLogger.PERSISTENCE.debug(Localiser.msg("010010", StringUtils.toJVMIDString(myPC), "" + state.getCurrentFetchDepth(), StringUtils.toJVMIDString(detachedPC), StringUtils.intArrayToString(detachFieldNums), StringUtils.intArrayToString(fieldsToLoad))); } // Call any "pre-detach" listeners getCallbackHandler().preDetach(myPC); } try { setDetaching(true); // Handle any field loading/unloading before the detach if ((myEC.getFetchPlan().getDetachmentOptions() & FetchPlan.DETACH_LOAD_FIELDS) != 0) { // Load any unloaded fetch-plan fields loadUnloadedFieldsInFetchPlan(); } if (myLC == myEC.getNucleusContext().getApiAdapter().getLifeCycleState(LifeCycleState.HOLLOW) || myLC == myEC.getNucleusContext().getApiAdapter().getLifeCycleState(LifeCycleState.P_NONTRANS)) { // Migrate any HOLLOW/P_NONTRANS to P_CLEAN etc myLC = myLC.transitionReadField(this, true); } // Create a SM for our copy object DNStateManager smDetachedPC = new StateManagerImpl(myEC, cmd); smDetachedPC.initialiseForDetached(detachedPC, getExternalObjectId(), getVersion(myPC)); myEC.setAttachDetachReferencedObject(smDetachedPC, myPC); // If detached copy already existed, take note of fields previously loaded if (existingDetached != null) { smDetachedPC.retrieveDetachState(smDetachedPC); } smDetachedPC.replaceFields(detachFieldNums, new DetachFieldManager(this, cmd.getSCOMutableMemberFlags(), myFP, state, true)); myEC.setAttachDetachReferencedObject(smDetachedPC, null); if (detachable) { // Update the object with its detached state - not to be confused with the "state" object above detachedPC.dnReplaceFlags(); ((Detachable)detachedPC).dnReplaceDetachedState(); } else { smDetachedPC.makeTransient(null); } // Remove its StateManager since now detached or transient replaceStateManager(detachedPC, null); } catch (Exception e) { // What could possibly be wrong here ? Log it and let the user provide a testcase, yeah right NucleusLogger.PERSISTENCE.warn("DETACH ERROR : Error thrown while detaching " + StringUtils.toJVMIDString(myPC) + " (id=" + myID + "). Provide a testcase that demonstrates this", e); } finally { setDetaching(false); referencedPC = null; } if (detachable && !myEC.getApiAdapter().isDetached(detachedPC)) { // Sanity check on the objects detached state throw new NucleusUserException(Localiser.msg("026025", detachedPC.getClass().getName(), myID)); } if (detachable) { // Call any "post-detach" listeners getCallbackHandler().postDetach(myPC, detachedPC); } } return detachedPC; } void setDetaching(boolean flag) { if (flag) { flags |= FLAG_DETACHING; } else { flags &= ~FLAG_DETACHING; } } public boolean isDetaching() { return (flags&FLAG_DETACHING) != 0; } /** * Return an array of field numbers that must be included in the detached object * @return the field numbers array for detaching */ private int[] getFieldsNumbersToDetach() { String detachedState = myEC.getNucleusContext().getConfiguration().getStringProperty(PropertyNames.PROPERTY_DETACH_DETACHED_STATE).toLowerCase(); if (detachedState.equals("all")) { return cmd.getAllMemberPositions(); } else if (detachedState.equals("loaded")) { return getLoadedFieldNumbers(); } else if ((myEC.getFetchPlan().getDetachmentOptions() & FetchPlan.DETACH_UNLOAD_FIELDS) == 0) { if ((myEC.getFetchPlan().getDetachmentOptions() & FetchPlan.DETACH_LOAD_FIELDS) == 0) { // Return loaded fields return getLoadedFieldNumbers(); } // Return all loaded plus any unloaded FP fields int[] fieldsToDetach = myFP.getMemberNumbers(); int[] allFieldNumbers = cmd.getAllMemberPositions(); int[] loadedFieldNumbers = ClassUtils.getFlagsSetTo(loadedFields, allFieldNumbers, true); if (loadedFieldNumbers != null && loadedFieldNumbers.length > 0) { boolean[] flds = new boolean[allFieldNumbers.length]; for (int i=0;i 0) { // Attach the (non-PK) fields from the transient if (NucleusLogger.PERSISTENCE.isDebugEnabled()) { NucleusLogger.PERSISTENCE.debug(Localiser.msg("026035", IdentityUtils.getPersistableIdentityForId(getInternalObjectId()), StringUtils.intArrayToString(nonPKFieldNumbers))); } detachedSM.provideFields(nonPKFieldNumbers, new AttachFieldManager(this, cmd.getSCOMutableMemberFlags(), cmd.getNonPKMemberFlags(), true, true, false)); } // Disconnect the transient object replaceStateManager(detachedPC, null); // Call any "post-attach" listeners getCallbackHandler().postAttach(myPC, myPC); } finally { setAttaching(false); } } /** * Method to attach the object managed by this StateManager. * @param embedded Whether it is embedded */ public void attach(boolean embedded) { if (isAttaching()) { return; } setAttaching(true); try { // Check if the object is already persisted boolean persistent = false; if (embedded) { persistent = true; } else { if (!myEC.getBooleanProperty(PropertyNames.PROPERTY_ATTACH_SAME_DATASTORE)) { // We cant assume that this object was detached from this datastore so we check it try { locate(); persistent = true; } catch (NucleusObjectNotFoundException onfe) { // Not currently present! } } else { // Assumed detached from this datastore persistent = true; } } // Call any "pre-attach" listeners getCallbackHandler().preAttach(myPC); // Retrieve the updated values from the detached object replaceStateManager(myPC, this); retrieveDetachState(this); if (!persistent) { // Persist the object into this datastore first makePersistent(); } // Migrate the lifecycle state to persistent myLC = myLC.transitionAttach(this); // Make sure the attached object goes in the cache // [would not get cached when not changed if we didnt do this here] myEC.putObjectIntoLevel1Cache(this); int[] attachFieldNumbers = getFieldNumbersOfLoadedOrDirtyFields(loadedFields, dirtyFields); if (attachFieldNumbers != null) { // Only update the fields that were detached, and only update them if there are any to update if (NucleusLogger.PERSISTENCE.isDebugEnabled()) { NucleusLogger.PERSISTENCE.debug(Localiser.msg("026035", IdentityUtils.getPersistableIdentityForId(getInternalObjectId()), StringUtils.intArrayToString(attachFieldNumbers))); } provideFields(attachFieldNumbers, new AttachFieldManager(this, cmd.getSCOMutableMemberFlags(), dirtyFields, persistent, true, false)); } // Call any "post-attach" listeners getCallbackHandler().postAttach(myPC, myPC); } finally { setAttaching(false); } } /** * Method to attach a copy of the detached persistable instance and return the (attached) copy. * @param detachedPC the detached persistable instance to be attached * @param embedded Whether the object is stored embedded/serialised in another object * @return The attached copy */ public Persistable attachCopy(Persistable detachedPC, boolean embedded) { if (isAttaching()) { return myPC; } setAttaching(true); try { // Check if the object is already persisted boolean persistent = false; if (embedded) { persistent = true; } else { if (!myEC.getBooleanProperty(PropertyNames.PROPERTY_ATTACH_SAME_DATASTORE)) { // We cant assume that this object was detached from this datastore so we check it try { locate(); persistent = true; } catch (NucleusObjectNotFoundException onfe) { // Not currently present! } } else { // Assumed detached from this datastore persistent = true; } } // Call any "pre-attach" listeners getCallbackHandler().preAttach(detachedPC); if (myEC.getApiAdapter().isDeleted(detachedPC)) { // The detached object has been deleted myLC = myLC.transitionDeletePersistent(this); } if (!myEC.getTransaction().getOptimistic() && (myLC == myEC.getApiAdapter().getLifeCycleState(LifeCycleState.HOLLOW) || myLC == myEC.getApiAdapter().getLifeCycleState(LifeCycleState.P_NONTRANS))) { // Pessimistic txns and in HOLLOW/P_NONTRANS, so move to P_CLEAN // TODO Move this into the lifecycle state classes as a "transitionAttach" myLC = myLC.transitionMakeTransactional(this, persistent); } StateManagerImpl smDetachedPC = null; if (persistent) { // Attaching object that was detached from this datastore, so perform as update // Make sure that all non-container SCO fields are loaded so we can make valid dirty checks // for whether these fields have been updated whilst detached. The detached object doesnt know if the contents // have been changed. int[] noncontainerFieldNumbers = cmd.getSCONonContainerMemberPositions(); int[] fieldNumbers = ClassUtils.getFlagsSetTo(loadedFields, noncontainerFieldNumbers, false); if (fieldNumbers != null && fieldNumbers.length > 0) { int[] unloadedFieldNumbers = loadFieldsFromLevel2Cache(fieldNumbers); if (unloadedFieldNumbers != null) { loadFieldsFromDatastore(unloadedFieldNumbers); updateLevel2CacheForFields(unloadedFieldNumbers); } // We currently don't call postLoad here since this is only called as part of attaching an object // and consequently we just read to get the current (attached) values. // Could add a flag on input to allow postLoad } // Add a state manager to the detached PC so that we can retrieve its detached state smDetachedPC = new StateManagerImpl(myEC, cmd); smDetachedPC.initialiseForDetached(detachedPC, getExternalObjectId(), null); // Cross-reference the attached and detached objects for the attach process myEC.setAttachDetachReferencedObject(smDetachedPC, myPC); myEC.setAttachDetachReferencedObject(this, detachedPC); // Retrieve the updated values from the detached object retrieveDetachState(smDetachedPC); } else { // Attaching object that was detached from another datastore, so perform as replicate // Reset lifecycle to P_NEW since not persistent yet in this datastore myLC = myEC.getNucleusContext().getApiAdapter().getLifeCycleState(LifeCycleState.P_NEW); // Copy field values from detached to attached so we know what value will need inserting replaceStateManager(detachedPC, this); myPC.dnCopyFields(detachedPC, cmd.getAllMemberPositions()); replaceStateManager(detachedPC, null); // Add a state manager to the detached PC so that we can retrieve its detached state smDetachedPC = new StateManagerImpl(myEC, cmd); smDetachedPC.initialiseForDetached(detachedPC, getExternalObjectId(), null); // Cross-reference the attached and detached objects for the attach process myEC.setAttachDetachReferencedObject(smDetachedPC, myPC); myEC.setAttachDetachReferencedObject(this, detachedPC); // Retrieve the updated values from the detached object retrieveDetachState(smDetachedPC); // Object is not yet persisted so make it persistent // Make sure all field values in the attach object are ready for inserts (but dont trigger any cascade attaches) internalAttachCopy(smDetachedPC, smDetachedPC.getLoadedFields(), smDetachedPC.getDirtyFields(), persistent, smDetachedPC.myVersion, false); makePersistent(); } // Go through all related fields and attach them (including relationships) internalAttachCopy(smDetachedPC, smDetachedPC.getLoadedFields(), smDetachedPC.getDirtyFields(), persistent, smDetachedPC.myVersion, true); // Remove the state manager from the detached PC replaceStateManager(detachedPC, null); // Remove the cross-referencing now we have finished the attach process myEC.setAttachDetachReferencedObject(smDetachedPC, null); myEC.setAttachDetachReferencedObject(this, null); // Call any "post-attach" listeners getCallbackHandler().postAttach(myPC,detachedPC); } catch (NucleusException ne) { // Log any errors in the attach if (NucleusLogger.PERSISTENCE.isDebugEnabled()) { NucleusLogger.PERSISTENCE.debug(Localiser.msg("026036", IdentityUtils.getPersistableIdentityForId(getInternalObjectId()), ne.getMessage()), ne); } throw ne; } finally { setAttaching(false); } return myPC; } void setAttaching(boolean flag) { if (flag) { flags |= FLAG_ATTACHING; } else { flags &= ~FLAG_ATTACHING; } } public boolean isAttaching() { return (flags&FLAG_ATTACHING) != 0; } /** * Attach the fields for this object using the provided detached object. * This will attach all loaded plus all dirty fields. * @param detachedSM StateManager for the detached object. * @param loadedFields Fields that were detached with the object * @param dirtyFields Fields that have been modified while detached * @param persistent whether the object is already persistent * @param version the version * @param cascade Whether to cascade the attach to related fields */ private void internalAttachCopy(DNStateManager detachedSM, boolean[] loadedFields, boolean[] dirtyFields, boolean persistent, Object version, boolean cascade) { // Need to take all loaded fields plus all modified fields // (maybe some werent detached but have been modified) and attach them int[] attachFieldNumbers = getFieldNumbersOfLoadedOrDirtyFields(loadedFields, dirtyFields); setVersion(version); if (attachFieldNumbers != null) { // Attach all dirty fields, and load other loaded fields if (NucleusLogger.PERSISTENCE.isDebugEnabled()) { NucleusLogger.PERSISTENCE.debug(Localiser.msg("026035", IdentityUtils.getPersistableIdentityForId(getInternalObjectId()), StringUtils.intArrayToString(attachFieldNumbers))); } detachedSM.provideFields(attachFieldNumbers, new AttachFieldManager(this, cmd.getSCOMutableMemberFlags(), dirtyFields, persistent, cascade, true)); } } /** * Method to delete the object from persistence. */ public void deletePersistent() { if (!myLC.isDeleted()) { if (myEC.isDelayDatastoreOperationsEnabled()) { // Optimistic transactions, with all updates delayed til flush/commit if (myEC.operationQueueIsActive()) { myEC.addOperationToQueue(new DeleteOperation(this)); } // Call any lifecycle listeners waiting for this event getCallbackHandler().preDelete(myPC); // Delay deletion until flush/commit so run reachability now to tag all reachable instances as necessary myEC.markDirty(this, false); // Reachability if (myLC.stateType() == LifeCycleState.P_CLEAN || myLC.stateType() == LifeCycleState.P_DIRTY || myLC.stateType() == LifeCycleState.HOLLOW || myLC.stateType() == LifeCycleState.P_NONTRANS || myLC.stateType() == LifeCycleState.P_NONTRANS_DIRTY) { // Make sure all fields are loaded so we can perform reachability loadUnloadedRelationFields(); } flags |= FLAG_BECOMING_DELETED; // Run reachability for relations if (cmd.hasRelations(myEC.getClassLoaderResolver())) { provideFields(cmd.getAllMemberPositions(), new DeleteFieldManager(this)); } // Update lifecycle state (after running reachability since it will unload all fields) dirty = true; preStateChange(); try { // Keep "loadedFields" settings til after delete is complete to save reloading preDeleteLoadedFields = new boolean[loadedFields.length]; System.arraycopy(loadedFields, 0, preDeleteLoadedFields, 0, loadedFields.length); myLC = myLC.transitionDeletePersistent(this); } finally { flags &= ~FLAG_BECOMING_DELETED; postStateChange(); } } else { // Datastore transactions, with all updates processed now // Call any lifecycle listeners waiting for this event. getCallbackHandler().preDelete(myPC); // Update lifecycle state dirty = true; preStateChange(); try { // Keep "loadedFields" settings til after delete is complete to save reloading preDeleteLoadedFields = new boolean[loadedFields.length]; System.arraycopy(loadedFields, 0, preDeleteLoadedFields, 0, loadedFields.length); myLC = myLC.transitionDeletePersistent(this); } finally { postStateChange(); } // TODO If this is an embedded object (cascaded from the owner) need to make sure we cascade as required // Delete the object from the datastore (includes reachability) internalDeletePersistent(); // Call any lifecycle listeners waiting for this event. getCallbackHandler().postDelete(myPC); } } } public boolean becomingDeleted() { return (flags&FLAG_BECOMING_DELETED)>0; } /** * Validates whether the persistable instance exists in the datastore. * If the instance doesn't exist in the datastore, this method will fail raising a NucleusObjectNotFoundException. * If the object is transactional then does nothing. * If the object has unloaded (non-SCO, non-PK) fetch plan fields then fetches them. * Else it checks the existence of the object in the datastore. */ public void validate() { if (!myLC.isTransactional()) { // Find all FetchPlan fields that are not PK, not SCO and still not loaded int[] fieldNumbers = ClassUtils.getFlagsSetTo(loadedFields, myFP.getMemberNumbers(), false); if (fieldNumbers != null && fieldNumbers.length > 0) { fieldNumbers = ClassUtils.getFlagsSetTo(cmd.getNonPKMemberFlags(), fieldNumbers, true); } if (fieldNumbers != null && fieldNumbers.length > 0) { fieldNumbers = ClassUtils.getFlagsSetTo(cmd.getSCOMutableMemberFlags(), fieldNumbers, false); } boolean versionNeedsLoading = false; if (isVersioned() && transactionalVersion == null) { versionNeedsLoading = true; } if ((fieldNumbers != null && fieldNumbers.length > 0) || versionNeedsLoading) { if ((flags&FLAG_VALIDATING) == 0) { try { // It is possible to get recursive validation when using things like ODF, Cassandra etc and having a bidir relation, and nontransactional. flags |= FLAG_VALIDATING; transitionReadField(false); // Some fetch plan fields, or the version are not loaded so try to load them, and this by itself // validates the existence. Loads the fields in the current FetchPlan (JDO2 spec 12.6.5) fieldNumbers = myFP.getMemberNumbers(); if (fieldNumbers != null || versionNeedsLoading) { boolean callPostLoad = myFP.isToCallPostLoadFetchPlan(this.loadedFields); setTransactionalVersion(null); // Make sure we get the latest version loadFieldsFromDatastore(fieldNumbers); if (callPostLoad && areFieldsLoaded(fieldNumbers)) // If a FK is in the STORED cache then wont be marked as loaded yet { postLoad(); } } } finally { flags &= ~FLAG_VALIDATING; } } } else { // Validate the object existence locate(); transitionReadField(false); } } } // --------------------------- Process Methods ----------------------------- /** * Method called before a write of the specified field. * @param fieldNumber The field to write * @return true if the field was already dirty before */ protected boolean preWriteField(int fieldNumber) { boolean wasDirty = dirty; // If we're writing a field in the process of inserting it must be due to dnPreStore(). // We haven't actually done the INSERT yet so we don't want to mark anything as dirty, which would make us want to do an UPDATE later. if ((flags&FLAG_INSERTING) == 0 && (flags&FLAG_INSERTING_CALLBACKS) == 0) { if (!wasDirty) // (only do it for first dirty event). { // Call any lifecycle listeners waiting for this event getCallbackHandler().preDirty(myPC); } // Update lifecycle state as required transitionWriteField(); dirty = true; dirtyFields[fieldNumber] = true; loadedFields[fieldNumber] = true; } return wasDirty; } /** * Method called after the write of a field. * @param wasDirty whether before writing this field the pc was dirty */ protected void postWriteField(boolean wasDirty) { if (dirty && !wasDirty) // (only do it for first dirty event). { // Call any lifecycle listeners waiting for this event getCallbackHandler().postDirty(myPC); } if ((flags&FLAG_INSERTING) == 0 && (flags&FLAG_INSERTING_CALLBACKS) == 0 && !isFlushing() && !(myLC.isTransactional() && !myLC.isPersistent())) { if (isDetaching() && getReferencedPC() == null) { // detachAllOnCommit caused a field to be dirty so ignore it return; } // Not during flush, and not transactional-transient, and not inserting - so mark as dirty myEC.markDirty(this, true); } } /** * Method called before a change in state. */ protected void preStateChange() { flags |= FLAG_CHANGING_STATE; } /** * Method called after a change in state. */ protected void postStateChange() { flags &= ~FLAG_CHANGING_STATE; if (isPostLoadPending() && areFieldsLoaded(myFP.getMemberNumbers())) { // Only call postLoad when all FetchPlan fields are loaded setPostLoadPending(false); postLoad(); } } void setPostLoadPending(boolean flag) { if (flag) { flags |= FLAG_POSTLOAD_PENDING; } else { flags &= ~FLAG_POSTLOAD_PENDING; } } protected boolean isPostLoadPending() { return (flags&FLAG_POSTLOAD_PENDING) != 0; } /** * Called whenever the default fetch group fields have all been loaded. * Updates dnFlags and calls dnPostLoad() as appropriate. *

* If it's called in the midst of a life-cycle transition both actions will * be deferred until the transition is complete. * This deferral is important. Without it, we could enter user * code (dnPostLoad()) while still making a state transition, and that way * lies madness. *

* As an example, consider a dnPostLoad() that calls other enhanced methods * that read fields (dnPostLoad() itself is not enhanced). A P_NONTRANS * object accessed within a transaction would produce the following infinite * loop: *

*

* *
     *  isLoaded()
     *  transitionReadField()
     *  refreshLoadedFields()
     *  dnPostLoad()
     *  isLoaded()
     *  ...
     * 
* *
*

* because the transition from P_NONTRANS to P_CLEAN can never be completed. */ private void postLoad() { if (isChangingState()) { setPostLoadPending(true); } else { // A tx object whose DFG fields are loaded doesn't need to contact us in order to read those fields, so we can safely set READ_OK. // A non-tx object needs to notify us on all field reads so that we can decide whether or not any transition should occur, so we leave the flags at LOAD_REQUIRED. if (persistenceFlags == Persistable.LOAD_REQUIRED && myLC.isTransactional()) { persistenceFlags = Persistable.READ_OK; myPC.dnReplaceFlags(); } getCallbackHandler().postLoad(myPC); } } /** * Guarantee that the serializable transactional and persistent fields are loaded into the instance. * This method is called by the generated dnPreSerialize method prior to serialization of the instance. * @param pc the calling Persistable instance */ public void preSerialize(Persistable pc) { if (disconnectClone(pc)) { return; } // Retrieve all fields prior to serialisation retrieve(false); myLC = myLC.transitionSerialize(this); if (!isStoringPC() && pc instanceof Detachable) { if (!myLC.isDeleted() && myLC.isPersistent()) { if (myLC.isDirty()) { flush(); } // Normal PC Detachable object being serialised so load up the detached state into the instance // JDO spec "For Detachable classes, the dnPreSerialize method must also initialize the dnDetachedState // instance so that the detached state is serialized along with the instance." ((Detachable)pc).dnReplaceDetachedState(); } } } public void setStoringPC() { flags |= FLAG_STORING_PC; } public void unsetStoringPC() { flags &= ~FLAG_STORING_PC; } protected boolean isStoringPC() { return (flags&FLAG_STORING_PC) != 0; } /** * Flushes any outstanding changes to the object to the datastore. * This will process :- *

    *
  • Any objects that have been marked as provisionally persistent yet haven't been flushed to the datastore.
  • *
  • Any objects that have been marked as provisionally deleted yet haven't been flushed to the datastore.
  • *
  • Any fields that have been updated.
  • *
*/ public void flush() { if (dirty) { if (isFlushing()) { // In the case of persisting a new object using autoincrement id within an optimistic // transaction, flush() will initially be called at the point of recognising that the // id is generated in the datastore, and will then be called again at the point of doing // the InsertRequest for the object itself. Just return since we are flushing right now return; } if ((flags&FLAG_INSERTING) != 0 || (flags&FLAG_INSERTING_CALLBACKS) != 0) { return; } setFlushing(true); try { if (myLC.stateType() == LifeCycleState.P_NEW && !isFlushedNew()) { // Newly persisted object but not yet flushed to datastore (e.g optimistic transactions) if (!isEmbedded()) { // internalMakePersistent does preStore, postStore internalMakePersistent(); } else { getCallbackHandler().preStore(myPC); if (myID == null) { setIdentity(true); // Just in case user is setting it in preStore } getCallbackHandler().postStore(myPC); } dirty = false; } else if (myLC.stateType() == LifeCycleState.P_DELETED) { // Object marked as deleted but not yet deleted from datastore getCallbackHandler().preDelete(myPC); if (!isEmbedded()) { internalDeletePersistent(); } getCallbackHandler().postDelete(myPC); } else if (myLC.stateType() == LifeCycleState.P_NEW_DELETED) { // Newly persisted object marked as deleted but not yet deleted from datastore if (isFlushedNew()) { // Only delete it if it was actually persisted into the datastore getCallbackHandler().preDelete(myPC); if (!isEmbedded()) { internalDeletePersistent(); } setFlushedNew(false); // No longer newly persisted flushed object since has been deleted getCallbackHandler().postDelete(myPC); } else { // Was never persisted to the datastore so nothing to do dirty = false; } } else { // Updated object with changes to flush to datastore if (!isDeleting()) { getCallbackHandler().preStore(myPC); if (myID == null) { setIdentity(true); // Just in case user is setting it in preStore } } if (!isEmbedded()) { int[] dirtyFieldNumbers = ClassUtils.getFlagsSetTo(dirtyFields, true); if (dirtyFieldNumbers == null) { // StateManager is dirty but no fields. What happened? throw new NucleusException(Localiser.msg("026010")).setFatal(); } if (myEC.getNucleusContext().isClassCacheable(getClassMetaData())) { myEC.markFieldsForUpdateInLevel2Cache(getInternalObjectId(), dirtyFields); } getStoreManager().getPersistenceHandler().updateObject(this, dirtyFieldNumbers); // Update the object in the cache(s) myEC.putObjectIntoLevel1Cache(this); } clearDirtyFlags(); getCallbackHandler().postStore(myPC); } } finally { setFlushing(false); } } } /** * Method to save all fields of the object, for use in any rollback. */ @Override public void saveFields() { if (savedState == null) { // Create SavedState, and copy PC fields savedState = new SavedState(myPC.dnNewInstance(this), loadedFields.clone(), persistenceFlags); savedState.getPC().dnCopyFields(myPC, cmd.getAllMemberPositions()); } else { // Update SavedState with current PC fields, flags etc savedState.getPC().dnCopyFields(myPC, cmd.getAllMemberPositions()); savedState.setPersistenceFlags(persistenceFlags); savedState.setLoadedFields(loadedFields.clone()); } } /** * Method to restore all fields of the object. */ @Override public void restoreFields() { if (savedState != null) { // Restore PC fields, flags etc from SavedState loadedFields = savedState.getLoadedFields(); persistenceFlags = savedState.getPersistenceFlags(); myPC.dnReplaceFlags(); myPC.dnCopyFields(savedState.getPC(), cmd.getAllMemberPositions()); savedState = null; clearDirtyFlags(); } } /** * Method to clear all saved fields on the object. */ @Override public void clearSavedFields() { savedState = null; } // ------------------------------ Helper Methods --------------------------- /** * Method to convert the persistable object into String form. * @param pc The persistable object * @param cmd Metadata for the class * @return The string containing the description of the Persistable */ private static String convertPCToString(Object pc, AbstractClassMetaData cmd) { StringBuilder str = new StringBuilder(); str.append(StringUtils.toJVMIDString(pc)); if (pc == null) { return str.toString(); } str.append(" ["); str.append("dnStateManager=").append(peekField(pc, "dnStateManager")); Object flagsObj = peekField(pc, "dnFlags"); if (flagsObj instanceof Byte) { switch (((Byte)flagsObj).byteValue()) { case Persistable.LOAD_REQUIRED: str.append(", dnFlags=LOAD_REQUIRED"); break; case Persistable.READ_OK: str.append(", dnFlags=READ_OK"); break; case Persistable.READ_WRITE_OK: str.append(", dnFlags=READ_WRITE_OK"); break; default: str.append(", dnFlags=???"); break; } } else { str.append(", dnFlags=").append(flagsObj); } Class c = pc.getClass(); do { AbstractMemberMetaData[] mmds = cmd.getManagedMembers(); for (AbstractMemberMetaData mmd : mmds) { str.append(", ").append(mmd.getName()); str.append("="); str.append(peekField(pc, mmd.getName())); } c = c.getSuperclass(); cmd = cmd.getSuperAbstractClassMetaData(); } while (c != null && Persistable.class.isAssignableFrom(c)); str.append("]"); return str.toString(); } /** * Utility to dump the contents of the StateManager to the provided log. * @param log Logger to use */ public void log(NucleusLogger log) { log.debug("StateManager[" + StringUtils.toJVMIDString(this) + "]"); log.debug(" myEC=" + myEC); log.debug(" myID=" + myID); log.debug(" myLC=" + myLC); log.debug(" cmd=" + cmd); log.debug(" fieldCount=" + cmd.getMemberCount()); log.debug(" dirty=" + dirty); log.debug(" flushing=" + isFlushing()); log.debug(" changingState=" + isChangingState()); log.debug(" postLoadPending=" + isPostLoadPending()); log.debug(" disconnecting=" + ((flags&FLAG_DISCONNECTING) != 0)); log.debug(" loadedFields=" + StringUtils.booleanArrayToString(loadedFields)); log.debug(" dirtyFields=" + StringUtils.booleanArrayToString(dirtyFields)); log.debug(" getSecondClassMutableFields()=" + StringUtils.booleanArrayToString(cmd.getSCOMutableMemberFlags())); log.debug(" getAllFieldNumbers()=" + StringUtils.intArrayToString(cmd.getAllMemberPositions())); log.debug(" secondClassMutableFieldNumbers=" + StringUtils.intArrayToString(cmd.getSCOMutableMemberPositions())); switch (persistenceFlags) { case Persistable.LOAD_REQUIRED: log.debug(" persistenceFlags=LOAD_REQUIRED"); break; case Persistable.READ_OK: log.debug(" persistenceFlags=READ_OK"); break; case Persistable.READ_WRITE_OK: log.debug(" persistenceFlags=READ_WRITE_OK"); break; default: log.debug(" persistenceFlags=???"); break; } log.debug(" myPC=" + convertPCToString(myPC, cmd)); if (savedState != null) { switch (savedState.getPersistenceFlags()) { case Persistable.LOAD_REQUIRED: log.debug(" savedState.persistableFlags=LOAD_REQUIRED"); break; case Persistable.READ_OK: log.debug(" savedState.persistableFlags=READ_OK"); break; case Persistable.READ_WRITE_OK: log.debug(" savedState.persistableFlags=READ_WRITE_OK"); break; default: log.debug(" savedState.persistableFlags=???"); break; } log.debug(" savedState.loadedFields=" + StringUtils.booleanArrayToString(savedState.getLoadedFields())); log.debug(" savedState.PC= " + convertPCToString(savedState.getPC(), cmd)); } } /** * Utility to take a peek at a field in the persistable object. * @param obj The persistable object * @param fieldName The field to peek at * @return The value of the field. */ protected static Object peekField(Object obj, String fieldName) { try { // This doesn't work due to security problems but you get the idea. // I'm trying to get field values directly without going through the provideField machinery. Object value = obj.getClass().getDeclaredField(fieldName).get(obj); if (value instanceof Persistable) { return StringUtils.toJVMIDString(value); } return value; } catch (Exception e) { return e.toString(); } } public void updateFieldAfterInsert(Object pc, int fieldNumber) { // Does nothing in this implementation; refer to ReferentialJDOStateManager } // ============================================= DEPRECATED METHODS ============================================== /** * Initialises a state manager to manage a HOLLOW / P_CLEAN instance having the given FieldValues. * This constructor is used for creating new instances of existing persistent objects using application identity, * and consequently shouldn't be used when the StoreManager controls the creation of such objects (such as in an ODBMS). * @param fv the initial field values of the object. * @param pcClass Class of the object that this will manage the state for * @deprecated Remove use of this and use initialiseForHollow */ public void initialiseForHollowAppId(FieldValues fv, Class pcClass) { if (cmd.getIdentityType() != IdentityType.APPLICATION) { throw new NucleusUserException("This constructor is only for objects using application identity.").setFatal(); } myLC = myEC.getNucleusContext().getApiAdapter().getLifeCycleState(LifeCycleState.HOLLOW); persistenceFlags = Persistable.LOAD_REQUIRED; myPC = HELPER.newInstance(pcClass, this); // Create new PC if (myPC == null) { if (!HELPER.getRegisteredClasses().contains(pcClass)) { // probably never will get here, as EnhancementHelper.newInstance() internally already throws JDOFatalUserException when class is not registered throw new NucleusUserException(Localiser.msg("026018", pcClass.getName())).setFatal(); } // Provide advisory information since we can't create an instance of this class, so maybe they have an error in their data ? throw new NucleusUserException(Localiser.msg("026019", pcClass.getName())).setFatal(); } loadFieldValues(fv); // as a minimum the PK fields are loaded here // Create the ID now that we have the PK fields loaded myID = myEC.getNucleusContext().getIdentityManager().getApplicationId(myPC, cmd); } /** * Look to the database to determine which class this object is. This parameter is a hint. Set false, if it's * already determined the correct pcClass for this pc "object" in a certain * level in the hierarchy. Set to true and it will look to the database. * TODO This is only called by some outdated code in LDAPUtils; remove it when that is fixed * @param fv the initial field values of the object. * @deprecated Dont use this, to be removed */ public void checkInheritance(FieldValues fv) { // Inheritance case, check the level of the instance ClassLoaderResolver clr = myEC.getClassLoaderResolver(); String className = getStoreManager().getClassNameForObjectID(myID, clr, myEC); if (className == null) { // className is null when id class exists, and object has been validated and doesn't exist. throw new NucleusObjectNotFoundException(Localiser.msg("026013", IdentityUtils.getPersistableIdentityForId(myID)), myID); } else if (!cmd.getFullClassName().equals(className)) { Class pcClass; try { //load the class and make sure the class is initialized pcClass = clr.classForName(className, myID.getClass().getClassLoader(), true); cmd = myEC.getMetaDataManager().getMetaDataForClass(pcClass, clr); } catch (ClassNotResolvedException e) { NucleusLogger.PERSISTENCE.warn(Localiser.msg("026014", IdentityUtils.getPersistableIdentityForId(myID))); throw new NucleusUserException(Localiser.msg("026014", IdentityUtils.getPersistableIdentityForId(myID)), e); } if (cmd == null) { throw new NucleusUserException(Localiser.msg("026012", pcClass)).setFatal(); } if (cmd.getIdentityType() != IdentityType.APPLICATION) { throw new NucleusUserException("This method should only be used for objects using application identity.").setFatal(); } myFP = myEC.getFetchPlan().getFetchPlanForClass(cmd); int fieldCount = cmd.getMemberCount(); dirtyFields = new boolean[fieldCount]; loadedFields = new boolean[fieldCount]; // Create new PC at right inheritance level myPC = HELPER.newInstance(pcClass, this); if (myPC == null) { throw new NucleusUserException(Localiser.msg("026018", cmd.getFullClassName())).setFatal(); } // Note that this will mean the fields are loaded twice (loaded earlier in this method) // and also that postLoad will be called twice loadFieldValues(fv); // Create the id for the new PC myID = myEC.getNucleusContext().getIdentityManager().getApplicationId(myPC, cmd); } } public boolean isVersioned() { return cmd.isVersioned(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy