The Glassfish SQLStateManager.java source code
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
/*
* SQLStateManager.java
*
* Created on March 3, 2000
*/
package com.sun.jdo.spi.persistence.support.sqlstore;
import com.sun.jdo.api.persistence.support.*;
import com.sun.jdo.spi.persistence.support.sqlstore.ejb.EJBHelper;
import com.sun.jdo.spi.persistence.support.sqlstore.model.*;
import com.sun.jdo.spi.persistence.support.sqlstore.query.jqlc.QueryValueFetcher;
import com.sun.jdo.spi.persistence.support.sqlstore.sco.SqlTimestamp;
import com.sun.jdo.spi.persistence.support.sqlstore.sql.UpdateObjectDescImpl;
import com.sun.jdo.spi.persistence.support.sqlstore.state.LifeCycleState;
import com.sun.jdo.spi.persistence.support.sqlstore.state.PersistentNonTransactional;
import com.sun.jdo.spi.persistence.support.sqlstore.state.PersistentClean;
import com.sun.jdo.spi.persistence.support.sqlstore.state.Hollow;
import com.sun.jdo.spi.persistence.utility.NullSemaphore;
import com.sun.jdo.spi.persistence.utility.Semaphore;
import com.sun.jdo.spi.persistence.utility.SemaphoreImpl;
import com.sun.jdo.spi.persistence.utility.logging.Logger;
import org.glassfish.persistence.common.I18NHelper;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Timestamp;
import java.util.*;
/**
*
*/
public class SQLStateManager implements Cloneable, StateManager, TestStateManager {
private static final int PRESENCE_MASK = 0;
private static final int SET_MASK = 1;
private static final int MAX_MASKS = 2;
private BitSet fieldMasks;
/** Array of Object. */
public ArrayList hiddenValues;
private ClassDesc persistenceConfig;
private PersistenceManager persistenceManager;
private PersistenceStore store;
private SQLStateManager beforeImage;
private Object persistentObject;
private Object objectId;
private LifeCycleState state;
/** This flag is used to disable updates due to dependency management. */
private static final short ST_UPDATE_DISABLED = 0x1;
private static final short ST_REGISTERED = 0x2;
private static final short ST_VISITED = 0x4;
private static final short ST_PREPARED_PHASE_II = 0x8;
private static final short ST_FIELD_TRACKING_INPROGRESS = 0x10;
private static final short ST_DELETE_INPROGRESS = 0x20;
private static final short ST_VALIDATION_FAILED = 0x40;
private short stateFlags;
// This instance is a replacement for a deleted instance with the same
// ObjectId.
private boolean isReplacementInstance = false;
// This instance needs to be registered with the global (weak) cache at
// rollback if it transitions to persistent (HOLLOW or P_NONTX) state.
private boolean needsRegisterAtRollback = false;
// This instance needs to be to be verified at the time it is removed
// from the global (weak) cache at rollback if it transitions to transient state.
private boolean needsVerifyAtDeregister = false;
// This flag is initially set to false and changed to true when the first
// operation (e.g. makePersistent, loadForRead, or getObjectById) succeeds.
private boolean valid = false;
/** Stores the updates to the associated object. */
private UpdateObjectDescImpl updateDesc;
/** Contains state managers depending on this object. */
private HashSet updatedForeignReferences;
/** Counts the foreign state managers this state manager depends on. */
private int referenceCount;
/** Serializes access to this StateManager. */
private final Semaphore lock;
/** The logger. */
private static Logger logger = LogHelperStateManager.getLogger();
/** I18N message handler. */
private final static ResourceBundle messages = I18NHelper.loadBundle(
SQLStateManager.class);
/** Name of the USE_BATCH property. */
public static final String USE_BATCH_PROPERTY =
"com.sun.jdo.spi.persistence.support.sqlstore.USE_BATCH"; // NOI18N
/**
* Property to swich on/off batching. Note, the default is true, meaning we
* try to do batching if the property is not specified.
*/
private static final boolean USE_BATCH = Boolean.valueOf(
System.getProperty(USE_BATCH_PROPERTY, "true")).booleanValue(); // NOI18N
/**
* Construct a new SQLStateManager so that it locks or does not lock as
* per whether or not it is used in a managed environment.
*/
public SQLStateManager(PersistenceStore store, ClassDesc persistenceConfig) {
this.store = store;
this.persistenceConfig = persistenceConfig;
if (EJBHelper.isManaged()) {
this.lock = new NullSemaphore("SQLStateManager"); // NOI18N
} else {
this.lock = new SemaphoreImpl("SQLStateManager"); // NOI18N
}
}
public synchronized void initialize(boolean persistentInDB) {
boolean xactActive = persistenceManager.isActiveTransaction();
boolean optimistic = persistenceManager.isOptimisticTransaction();
boolean nontransactionalRead = persistenceManager.isNontransactionalRead();
LifeCycleState oldstate = state;
if (state == null) {
if (persistentInDB == false) {
// Hollow object aquired by PM.getObjectByOid() does not require
// to be persistent in DB
state = LifeCycleState.getLifeCycleState(LifeCycleState.HOLLOW);
persistenceManager.setFlags(persistentObject, LOAD_REQUIRED);
} else {
if (xactActive && !optimistic) {
state = LifeCycleState.getLifeCycleState(LifeCycleState.P_CLEAN);
persistenceManager.setFlags(persistentObject, READ_OK);
} else {
state = LifeCycleState.getLifeCycleState(LifeCycleState.P_NON_TX);
persistenceManager.setFlags(persistentObject, LOAD_REQUIRED);
}
valid = true;
}
} else if (state.needMerge()) {
state = state.transitionReadField(optimistic, nontransactionalRead, xactActive);
// If we are in a state that requires the instance to be reloaded
// we need to set the jdoFlags to LOAD_REQUIRED to enable field mediation.
if (state.needsReload(optimistic, nontransactionalRead, xactActive)) {
persistenceManager.setFlags(persistentObject, LOAD_REQUIRED);
} else {
if (persistenceManager.getFlags(persistentObject) == LOAD_REQUIRED) {
persistenceManager.setFlags(persistentObject, READ_OK);
}
}
}
registerInstance(false, null, oldstate);
}
private void registerInstance(boolean throwDuplicateException,
ArrayList newlyRegisteredSMs, LifeCycleState oldstate) {
if ((stateFlags & ST_REGISTERED) == 0 || // not registered or
(oldstate != state && // state changed from clean to dirty or transactional type.
(oldstate == null || oldstate.isDirty() != state.isDirty() ||
oldstate.isTransactional() != state.isTransactional()))) {
persistenceManager.registerInstance(this, getObjectId(), throwDuplicateException, false);
stateFlags |= ST_REGISTERED;
if (newlyRegisteredSMs != null) {
if (!newlyRegisteredSMs.contains(this))
newlyRegisteredSMs.add(this);
}
}
}
public void setPersistenceManager(com.sun.jdo.api.persistence.support.PersistenceManager pm) {
this.persistenceManager = (PersistenceManager) pm;
}
public void setPersistent(Object pc) {
this.persistentObject = pc;
}
public PersistenceStore getStore() {
return store;
}
public Object getPersistent() {
return persistentObject;
}
public PersistenceConfig getPersistenceConfig() {
return persistenceConfig;
}
private UpdateObjectDescImpl getUpdateDesc() {
if (updateDesc == null) {
updateDesc = (UpdateObjectDescImpl) store.getUpdateObjectDesc(
persistenceConfig.getPersistenceCapableClass());
}
if (updateDesc.getConcurrency() == null) {
boolean optimistic = persistenceManager.isOptimisticTransaction();
updateDesc.setConcurrency(persistenceConfig.getConcurrency(optimistic));
}
return updateDesc;
}
private void unsetMaskBit(int index, int mask) {
if (fieldMasks == null) {
newFieldMasks();
} else {
if (index >= 0) {
fieldMasks.clear(index + mask * persistenceConfig.maxFields);
} else {
fieldMasks.clear(-(index + 1) + persistenceConfig.maxVisibleFields +
mask * persistenceConfig.maxFields);
}
}
}
private void clearMask(int mask) {
if (fieldMasks != null) {
fieldMasks.clear(mask * persistenceConfig.maxFields,
(mask+1) * persistenceConfig.maxFields);
}
}
private void setVisibleMaskBits(int mask) {
if (fieldMasks == null) {
newFieldMasks();
}
int offset = mask * persistenceConfig.maxFields;
fieldMasks.set(offset, offset + persistenceConfig.maxVisibleFields);
}
private BitSet getVisibleMaskBits(int mask) {
if (fieldMasks == null) {
newFieldMasks();
}
int offset = mask * persistenceConfig.maxFields;
return fieldMasks.get(offset, offset + persistenceConfig.maxVisibleFields);
}
private void newFieldMasks() {
this.fieldMasks = new BitSet(MAX_MASKS * persistenceConfig.maxFields);
}
public void setPresenceMaskBit(int index) {
if (fieldMasks == null) {
newFieldMasks();
}
if (index >= 0) {
fieldMasks.set(index + PRESENCE_MASK * persistenceConfig.maxFields);
} else {
fieldMasks.set(-(index + 1) + persistenceConfig.maxVisibleFields +
PRESENCE_MASK * persistenceConfig.maxFields);
}
}
private void setSetMaskBit(int index) {
if (fieldMasks == null) {
newFieldMasks();
}
if (index >= 0) {
fieldMasks.set(index + SET_MASK * persistenceConfig.maxFields);
} else {
fieldMasks.set(-(index + 1) + persistenceConfig.maxVisibleFields +
SET_MASK * persistenceConfig.maxFields);
}
}
public boolean getPresenceMaskBit(int index) {
if (fieldMasks == null) {
newFieldMasks();
}
if (index >= 0) {
return fieldMasks.get(index + PRESENCE_MASK * persistenceConfig.maxFields);
} else {
return fieldMasks.get(-(index + 1) + persistenceConfig.maxVisibleFields +
PRESENCE_MASK * persistenceConfig.maxFields);
}
}
public boolean getSetMaskBit(int index) {
if (fieldMasks == null) {
newFieldMasks();
}
if (index >= 0) {
return fieldMasks.get(index + SET_MASK * persistenceConfig.maxFields);
} else {
return fieldMasks.get(-(index + 1) + persistenceConfig.maxVisibleFields +
SET_MASK * persistenceConfig.maxFields);
}
}
public Object getHiddenValue(int index) {
// This method expects index to be negative for hidden fields.
if (index >= 0) {
throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
"core.statemanager.poshiddenindex", "" + index)); // NOI18N
}
int realIndex = -(index + 1);
if ((hiddenValues != null) && (realIndex < hiddenValues.size())) {
return hiddenValues.get(realIndex);
}
return null;
}
public void setHiddenValue(int index, Object value) {
// This method expects index to be negative for hidden fields.
if (index >= 0) {
throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
"core.statemanager.poshiddenindex", "" + index)); // NOI18N
}
int realIndex = -(index + 1);
if (hiddenValues == null) {
hiddenValues = new ArrayList();
}
for (int i = hiddenValues.size(); i <= realIndex; i++) {
hiddenValues.add(null);
}
hiddenValues.set(realIndex, value);
}
public synchronized void replaceObjectField(String fieldName, Object o) {
boolean debug = logger.isLoggable();
if (debug) {
Object[] items = new Object[] {fieldName, o.getClass().getName()};
logger.fine("sqlstore.sqlstatemanager.replaceobjectfield", items); // NOI18N
}
FieldDesc fieldDesc = persistenceConfig.getField(fieldName);
Object oldo = prepareSetField(fieldDesc, o);
if ((oldo instanceof SCO) && oldo != o) {
if (debug)
logger.fine("sqlstore.sqlstatemanager.replaceobjectfield.unsetsco"); // NOI18N
((SCO) oldo).unsetOwner();
}
}
public synchronized void makeDirty(String fieldName) {
boolean debug = logger.isLoggable();
if (debug) {
logger.fine("sqlstore.sqlstatemanager.makedirty", fieldName); // NOI18N
}
FieldDesc fieldDesc = persistenceConfig.getField(fieldName);
// Save current value: if it is SCO object we need to replace it with the
// new value instead of new instance
Object oldo = fieldDesc.getValue(this);
prepareUpdateField(fieldDesc, null);
// Now adjust SCO instance
Object newo = fieldDesc.getValue(this);
if ((newo instanceof SCO) && oldo != newo) {
if (oldo instanceof SCOCollection) {
if (debug) {
logger.fine("sqlstore.sqlstatemanager.makedirty.fixscocollection"); // NOI18N
}
((SCOCollection) oldo).clearInternal();
((SCOCollection) oldo).addAllInternal((Collection) newo);
}
else if (oldo instanceof SCODate) {
if (debug) {
logger.fine("sqlstore.sqlstatemanager.makedirty.fixscodate"); // NOI18N
}
long l = ((java.util.Date) newo).getTime();
// Adjust nanoseconds if necessary:
int n = 0;
if (newo instanceof Timestamp) {
n = ((Timestamp) newo).getNanos();
} else {
n = (int) ((l % 1000) * 1000000);
}
if (oldo instanceof SqlTimestamp) {
((SCODate) oldo).setTimeInternal(l);
((SqlTimestamp) oldo).setNanosInternal(n);
} else if (newo instanceof Timestamp) {
((SCODate) oldo).setTimeInternal(l + (n / 1000000));
} else {
((SCODate) oldo).setTimeInternal(l);
}
}
updateTrackedFields(fieldDesc, oldo, null);
fieldDesc.setValue(this, oldo);
// disconnect temp SCO instance
if (newo instanceof SCO)
((SCO) newo).unsetOwner();
}
}
/**
* This method is central to record changes to SCOCollections.
*/
public void applyUpdates(String fieldName, SCOCollection c) {
boolean debug = logger.isLoggable();
if (debug) {
logger.fine("sqlstore.sqlstatemanager.applyupdates", fieldName); // NOI18N
}
FieldDesc fieldDesc = persistenceConfig.getField(fieldName);
if (fieldDesc instanceof ForeignFieldDesc) {
ArrayList removed = new ArrayList(c.getRemoved());
ArrayList added = new ArrayList(c.getAdded());
// We reset the collection to clear the added and removed list before calling
// processCollectionUpdates() which can throw an exception.
c.reset();
processCollectionUpdates((ForeignFieldDesc) fieldDesc, removed, added, null, true, false);
}
// else it is an ERROR?
if (debug) {
logger.fine("sqlstore.sqlstatemanager.applyupdates.exit"); // NOI18N
}
}
public void makePresent(String fieldName, Object value) {
boolean debug = logger.isLoggable();
if (debug) {
logger.fine("sqlstore.sqlstatemanager.makepresent", fieldName); // NOI18N
}
FieldDesc fieldDesc = persistenceConfig.getField(fieldName);
fieldDesc.setValue(this, value);
setPresenceMaskBit(fieldDesc.absoluteID);
}
public void setObjectId(Object objectId) {
// RESOLVE: do we allow to replace existing?
this.objectId = objectId;
}
public Object getObjectId() {
// Note: PM.getObjectId() makes copy of the actual object id.
if (objectId == null) {
Class oidClass = persistenceConfig.getOidClass();
Object oid = null;
try {
oid = oidClass.newInstance();
} catch (Exception e) {
throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
"core.statemanager.cantnewoid", oidClass.getName()), e); // NOI18N
}
Field keyFields[] = persistenceConfig.getKeyFields();
String keyFieldNames[] = persistenceConfig.getKeyFieldNames();
for (int i = 0; i < keyFields.length; i++) {
Field keyField = keyFields[i];
try {
FieldDesc fd = persistenceConfig.getField(keyFieldNames[i]);
if (fd != null) {
keyField.set(oid, fd.getValue(this));
}
} catch (IllegalAccessException e) {
throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
"core.statemanager.cantsetkeyfield", keyField.getName()), e); // NOI18N
}
}
objectId = oid;
}
return objectId;
}
private void makeAutoPersistent(Object pc) {
persistenceManager.makePersistent(pc);
SQLStateManager sm = (SQLStateManager) persistenceManager.getStateManager(pc);
sm.state = LifeCycleState.getLifeCycleState(LifeCycleState.AP_NEW);
}
/**
* Prepares the associated object to be stored in the datastore.
* This method is called by PersistenceManager.makePersistent().
* Thread synchronization is done in the persistence manager.
*/
public void makePersistent(PersistenceManager pm, Object pc) {
boolean debug = logger.isLoggable();
if (debug) {
logger.fine("sqlstore.sqlstatemanager.makepersistence", // NOI18N
persistenceConfig.getPersistenceCapableClass().getName());
}
// If the instance is autopersistent, we simply transition it to persistent_new.
if (state != null) {
if (state.isAutoPersistent()) {
state = state.transitionMakePersistent();
}
return;
}
this.persistenceManager = pm;
this.persistentObject = pc;
// Mark all the visible fields as present to prevent navigation and
// to allow us to create a before image that contains all the fields.
setVisibleMaskBits(PRESENCE_MASK);
getBeforeImage();
state = LifeCycleState.getLifeCycleState(LifeCycleState.P_NEW);
try {
registerInstance(true, null, null);
} catch (JDOException e) {
this.release();
throw e;
}
// We set the statemanager for the pc now so the instance is considered
// persistent. We need to do this in order for persistent-by-reachability to
// work properly in the case of self-referencing relationship.
pm.setStateManager(pc, this);
valid = true;
// Now that the state manager has been set in the pc, we need to
// synchronize it so other threads can't modify this instance while
// we perform the persistence-by-reachability algorithm.
try {
getLock();
// Make sure all the fields have been marked dirty.
Object obj = null;
ArrayList fields = persistenceConfig.fields;
for (int i = 0; i < fields.size(); i++) {
FieldDesc f = (FieldDesc) fields.get(i);
// In case of makePersistent, we skip all secondary tracked fields
// and use the primary to propagate changes. In addition, we take
// the policy that a tracked relationship field takes precedence
// over its primitive counterpart. In other words, we skip all
// primitive fields that also tracks relationship fields.
if ((f.sqlProperties & FieldDesc.PROP_SECONDARY_TRACKED_FIELD) > 0) {
continue;
}
obj = f.getValue(this);
if (f instanceof ForeignFieldDesc) {
ForeignFieldDesc ff = (ForeignFieldDesc) f;
ArrayList trackedFields = null;
if (debug) {
logger.fine("sqlstore.sqlstatemanager.processforeign", ff.getName()); // NOI18N
}
if ((ff.sqlProperties & FieldDesc.PROP_PRIMARY_TRACKED_FIELD) > 0) {
trackedFields = ff.getTrackedFields();
Object theValue = obj;
for (int j = 0; j < trackedFields.size(); j++) {
FieldDesc tf = (FieldDesc) trackedFields.get(j);
Object value = tf.getValue(this);
if ((theValue != null) && (value != null) && (theValue != value)) {
if (needsVerifyAtDeregister) {
persistenceManager.deregisterInstance(getObjectId(), this);
needsVerifyAtDeregister = false;
} else {
persistenceManager.deregisterInstance(getObjectId());
}
this.release();
throw new JDOUserException(I18NHelper.getMessage(messages,
"core.statemanager.conflictingvalues", ff.getName(), tf.getName())); // NOI18N
} else if ((theValue == null) && (value != null)) {
theValue = value;
}
}
if (theValue != obj) {
obj = theValue;
ff.setValue(this, obj);
}
}
if (obj != null) {
if (obj instanceof Collection) {
if (((Collection) obj).size() > 0) {
ArrayList removed = null;
ArrayList added = new ArrayList((Collection) obj);
processCollectionUpdates(ff, removed, added, null, true, false);
}
} else {
// null out this field to pretend we are setting this field for the first time
ff.setValue(this, null);
updateObjectField(ff, obj, true, false);
// now restore the value
ff.setValue(this, obj);
}
} else {
// For a null managed collection relationship field, we replace it
// with an empty SCOCollection
if ((ff.getInverseRelationshipField() != null) && (ff.cardinalityUPB > 1)) {
replaceCollection(ff, null);
}
}
updateTrackedFields(ff, ff.getValue(this), null);
} else {
// We ignore primitive fields that also tracks relationship field
if ((f.sqlProperties & FieldDesc.PROP_TRACK_RELATIONSHIP_FIELD) > 0) {
ArrayList trackedFields = f.getTrackedFields();
boolean found = false;
for (int j = trackedFields.size() - 1; j >= 0; j--) {
FieldDesc tf = (FieldDesc) trackedFields.get(j);
if (tf instanceof ForeignFieldDesc) {
if (tf.getValue(this) != null) {
found = true;
break;
}
} else {
break;
}
}
if (!found) {
//f.setValue(this, null);
updateTrackedFields(f, obj, null);
//f.setValue(this, obj);
}
} else {
updateTrackedFields(f, obj, null);
}
if ((f.sqlProperties & FieldDesc.PROP_RECORD_ON_UPDATE) > 0) {
getUpdateDesc().recordUpdatedField((LocalFieldDesc) f);
}
}
if (debug) {
logger.fine("sqlstore.sqlstatemanager.makedirtyfield", f.getName()); // NOI18N
}
setSetMaskBit(f.absoluteID);
}
} finally {
releaseLock();
}
}
/**
* Prepares the associated object for delete. This method is
* called by PersistenceManager.deletePersistent(). After
* nullifying the relationship fields, the instance transitions to
* deleted state.
*/
public void deletePersistent() {
if (logger.isLoggable()) {
logger.fine("sqlstore.sqlstatemanager.deletepersistence", // NOI18N
persistenceConfig.getPersistenceCapableClass().getName());
}
// Why try try? The difference is in whether you try and then
// acquire/get, or acquire/get and then try.
// Prior to having acquireFieldUpdateLock, this code synchronized on
// a field, following that with the try for {get,release}Lock. That
// pattern of usage calls for that order.
// {acquire,release}FieldUpdateLock calls for the other order. We
// are close to FCS, so this strange situation persists for now
// (i.e., it ain't broken).
persistenceManager.acquireFieldUpdateLock();
try {
try {
getLock();
if (state.isDeleted()) {
return;
}
deleteRelationships();
LifeCycleState oldstate = state;
state = state.transitionDeletePersistent();
persistenceManager.setFlags(persistentObject, LOAD_REQUIRED);
registerInstance(false, null, oldstate);
} finally {
releaseLock();
}
} finally {
persistenceManager.releaseFieldUpdateLock();
}
}
/**
* Prepares the current instance for delete by nullifying all
* relationships. The deletion is propagated to relationship
* fields marked for cascade delete.
*/
private void deleteRelationships() {
ArrayList foreignFields = persistenceConfig.foreignFields;
int size = foreignFields.size();
stateFlags |= ST_DELETE_INPROGRESS;
for (int i = 0; i < size; i++) {
ForeignFieldDesc ff = (ForeignFieldDesc) foreignFields.get(i);
ForeignFieldDesc irf = ff.getInverseRelationshipField();
// Skip this field if it is secondary.
if ((ff.sqlProperties & FieldDesc.PROP_SECONDARY_TRACKED_FIELD) > 0) {
continue;
}
// Skip this field if it is not managed nor marked for cascade delete.
if ((ff.deleteAction != ForeignFieldDesc.ACT_CASCADE) && (irf == null)) {
continue;
}
prepareUpdateField(ff, null);
if (ff.cardinalityUPB > 1) {
Collection c = (Collection) ff.getValue(this);
if (c != null) {
ArrayList removed = new ArrayList(c);
// For managed relationship or cascade delete, we need to call
// processCollectionUpdates() to set up the dependency. In case of
// managed relationship, the inverse relationship field should also
// be set to null.
processCollectionUpdates(ff, removed, null, null, true, false);
if (c instanceof SCOCollection) {
((SCOCollection) c).clearInternal();
} else {
c.clear();
}
if (ff.deleteAction == ForeignFieldDesc.ACT_CASCADE) {
Iterator iter = removed.iterator();
while (iter.hasNext()) {
Object obj = iter.next();
if (obj != null) {
SQLStateManager sm = (SQLStateManager)
persistenceManager.getStateManager(obj);
// Ignore if this sm is in the process of being cascade
// deleted. This is to prevent infinite recursive in case
// of self-referencing relationship.
if ((sm != null) && !sm.isDeleted() &&
((sm.stateFlags & ST_DELETE_INPROGRESS) == 0)) {
try {
persistenceManager.deletePersistent(obj);
} catch (Throwable e) {
}
}
}
}
}
}
} else {
Object obj = ff.getValue(this);
if (obj != null) {
updateObjectField(ff, null, true, false);
ff.setValue(this, null);
if (ff.deleteAction == ForeignFieldDesc.ACT_CASCADE) {
SQLStateManager sm = (SQLStateManager)
persistenceManager.getStateManager(obj);
// Ignore if this sm is in the process of being cascade
// deleted. This is to prevent infinite recursive in case
// of self-referencing relationships.
if ((sm != null) && !sm.isDeleted() &&
((sm.stateFlags & ST_DELETE_INPROGRESS) == 0)) {
try {
persistenceManager.deletePersistent(obj);
} catch (Throwable e) {
}
}
}
}
}
}
stateFlags &= ~ST_DELETE_INPROGRESS;
}
/**
* Stores the associated object in the datastore. This method is
* called by {@link PersistenceManager#beforeCompletion} on
* flush/commit. The specified state manager argument is used to
* determine whether the actual instance should be flushed
* immediately or whether batch update is possible.
*
* @param next Next state manager in the transaction cache.
*/
public void updatePersistent(StateManager next) {
boolean debug = logger.isLoggable();
if ((stateFlags & ST_UPDATE_DISABLED) > 0) {
if (debug) {
Object[] items = new Object[] {persistenceConfig.getPersistenceCapableClass().getName(),
persistentObject};
logger.fine("sqlstore.sqlstatemanager.updatepersistent.skipped", items); // NOI18N
}
return;
}
try {
if (debug) {
logger.fine("sqlstore.sqlstatemanager.updatepersistent", // NOI18N
persistenceConfig.getPersistenceCapableClass().getName());
}
ArrayList actions = new ArrayList();
// Get a list of actions to perform.
getUpdateActions(actions);
if (actions.size() == 1 && useBatch()) {
// Batch update only if actions consists of a single action
UpdateObjectDesc updateDesc = (UpdateObjectDesc)actions.get(0);
boolean immediateFlush = requiresImmediateFlush((SQLStateManager)next);
if (debug && immediateFlush) {
Object[] items = new Object[] {getPersistent(), (next != null) ? next.getPersistent() : null};
logger.fine("sqlstore.sqlstatemanager.updatepersistent.immediateflush", items); // NOI18N
}
store.executeBatch(persistenceManager, updateDesc, immediateFlush);
} else if (actions.size() > 0) {
store.execute(persistenceManager, actions);
}
incrementVersion(actions);
if (debug) {
logger.fine("sqlstore.sqlstatemanager.updatepersistent.exit"); // NOI18N
}
} catch (JDOException e) {
e.addFailedObject(persistentObject);
throw e;
} catch (Exception e) {
logger.throwing("sqlstore.SQLStateManager", "updatePersistent", e); // NOI18N
throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
"core.generic.unknownexception"), e); // NOI18N
}
}
/**
* Increments the version for all state managers in
* <code>actions registered for version consistency.
*
* @param actions List of updated state managers.
*/
static private void incrementVersion(List actions) {
for (Iterator iter = actions.iterator(); iter.hasNext(); ) {
((UpdateObjectDescImpl) iter.next()).incrementVersion();
}
}
/**
* Increments the version fields for this state manager. Instances
* mapped to multiple tables have got a version field for each table.
*/
public void incrementVersion() {
LocalFieldDesc [] versionFields = persistenceConfig.getVersionFields();
for (int i = 0; i < versionFields.length; i++) {
versionFields[i].incrementValue(this);
}
}
/**
* @inheritDoc StateManager#hasVersionConsistency
*/
public boolean hasVersionConsistency() {
return persistenceConfig.hasVersionConsistency();
}
/**
* @inheritDoc StateManager#verifyPersistent
*/
public boolean verifyPersistent() {
assert persistenceConfig.hasVersionConsistency();
boolean verified = true;
if (state instanceof PersistentClean) {
RetrieveDesc verificationRD = persistenceConfig.getRetrieveDescForVerificationQuery(store);
LocalFieldDesc[] keyFields = persistenceConfig.getKeyFieldDescs();
LocalFieldDesc[] versionFields = persistenceConfig.getVersionFields();
// Please make sure that the order of parameter values is same as
// order of parameters as defined by ClassDesc#getRetrieveDescForVerificationQuery()
Object [] parameters = new Object[keyFields.length + versionFields.length];
copyValues(parameters, keyFields, 0);
copyValues(parameters, versionFields, keyFields.length);
// verificationRD requires parameters for pk field and version fields
Boolean result = (Boolean) store.
retrieve(persistenceManager, verificationRD, new QueryValueFetcher(parameters));
verified = result.booleanValue();
}
return verified;
}
/**
* Returns true if batch update might be used to store the changes
* of this state manager.
*
* TODO: Because batched statements on Oracle don't return a
* valid success indicator, batching is disabled for Version
* Consistency.
*/
private boolean useBatch() {
boolean result = false;
if (USE_BATCH) {
switch(state.getUpdateAction()) {
case ActionDesc.LOG_CREATE:
result = !getUpdateDesc().hasChangedRelationships() &&
!getUpdateDesc().hasModifiedLobField();
break;
case ActionDesc.LOG_DESTROY:
case ActionDesc.LOG_UPDATE:
// Do not try to batch in optimitic tx for now. We need to
// check for parallel updates, so the WHERE clause checks
// the values from the beforeImage. We need a different SQL
// statements for the null vs. non null case.
result = !persistenceManager.isOptimisticTransaction() &&
!persistenceConfig.hasModifiedCheckAtCommitConsistency() &&
!getUpdateDesc().hasChangedRelationships() &&
!getUpdateDesc().hasModifiedLobField() &&
!hasVersionConsistency();
break;
default:
result = false;
break;
}
}
return result;
}
/**
* @inheritDoc StateManager#setVerificationFailed
*/
public void setVerificationFailed() {
if (hasVersionConsistency()) {
stateFlags |= ST_VALIDATION_FAILED;
}
}
/**
* @inheritDoc StateManager#getFailed
*/
public boolean isVerificationFailed() {
return (stateFlags & ST_VALIDATION_FAILED) > 0;
}
/**
* This method checks whether this StateManager instance needs to be
* flushed immediately during beforeCompletion. A return of <code>false
* means the store manager is allowed to combine flushing of these two
* instance in a single database roundtrip (e.g. by using batched updates).
*
* @param next Next state manager to be flushed.
*/
private boolean requiresImmediateFlush(SQLStateManager next) {
// There is no next SM =>
// flush this sm immediately
if (next == null)
return true;
// The next StateManager has a different pc class =>
// flush this sm immediately
if (persistenceConfig != next.persistenceConfig)
return true;
// The next StateManager represents a different update operation
// INSERT/UPDATE/DELETE => flush this sm immediately
if (state.getUpdateAction() != next.state.getUpdateAction())
return true;
// If the next's flush is disabled, flush this sm
if ((next.stateFlags & ST_UPDATE_DISABLED) > 0)
return true;
// If next sm does not use batch update flush this sm
if (!next.useBatch())
return true;
// For updates, we need to check if the next sm updates the
// same fields. If not, flush this sm
if (getUpdateDesc().getUpdateAction() == ActionDesc.LOG_UPDATE &&
!compareUpdatedFields(next)) {
return true;
}
// If the next stateManager has got (foreign reference) dependencies,
// it is not considered for batching at all
if (next.updatedForeignReferences != null) {
return true;
}
// Now we can make use of batching w/o flushing
return false;
}
private boolean compareUpdatedFields(SQLStateManager next) {
BitSet updFields = getVisibleMaskBits(SET_MASK);
BitSet nextUpdFields = (next != null) ? next.getVisibleMaskBits(SET_MASK) : null;
return updFields.equals(nextUpdFields);
}
public void refreshPersistent() {
boolean debug = logger.isLoggable();
if (debug) {
logger.fine("sqlstore.sqlstatemanager.refreshpersistent", // NOI18N
persistenceConfig.getPersistenceCapableClass().getName());
}
// Only refresh if the state allows it.
if (state.isRefreshable()) {
LifeCycleState oldstate = state;
state = state.transitionRefreshPersistent();
reload(null);
registerInstance(false, null, oldstate);
}
if (debug) {
logger.fine("sqlstore.sqlstatemanager.refreshpersistent.exit"); // NOI18N
}
}
/**
* Reloads the instance by delegating actual work, state transition, and
* instance registration to {@link #reload(FieldDesc) reload(FieldDesc)}
* With <code>null as an argument. Called by
* {@link PersistenceManager#getObjectById(Object, boolean)
* PersistenceManager.getObjectById(Object, boolean)} with validate
* flag set to <code>true
*/
public void reload() {
boolean debug = logger.isLoggable(Logger.FINER);
if (debug) {
logger.finer("sqlstore.sqlstatemanager.unconditionalreload", // NOI18N
persistenceConfig.getPersistenceCapableClass().getName());
}
persistenceManager.acquireShareLock();
try {
getLock();
reload(null);
} finally {
persistenceManager.releaseShareLock();
releaseLock();
if (debug) {
logger.finer("sqlstore.sqlstatemanager.unconditionalreload.exit"); // NOI18N
}
}
}
/**
* Reloads this SM from the state in the datastore, getting data for the
* given field.
* @param additionalField Field to be loaded.
*/
private void reload(FieldDesc additionalField) {
boolean debug = logger.isLoggable();
if (debug) {
String fieldName =
(additionalField != null) ? additionalField.getName() : null;
logger.fine("sqlstore.sqlstatemanager.reload", // NOI18N
persistenceConfig.getPersistenceCapableClass().getName(), fieldName);
}
// Clear the fields PresenceMask so all the currently present
// fields will be replaced.
clearMask(PRESENCE_MASK);
// Need to mark the key fields as present
markKeyFieldsPresent();
clearMask(SET_MASK);
LifeCycleState oldState = state;
state = state.transitionReload(persistenceManager.isActiveTransaction());
if (!retrieveFromVersionConsistencyCache(additionalField)) {
// Retrieve the instance from the data store, if this class
// is not version consistent, or not found in the cache.
try {
retrieve(additionalField);
} catch (JDOException e) {
// Reset the state if the instance couldn't be found.
state = oldState;
throw e;
}
}
registerInstance(false, null, oldState);
if (persistenceManager.getFlags(persistentObject) == LOAD_REQUIRED) {
persistenceManager.setFlags(persistentObject, READ_OK);
}
if (debug) {
logger.fine("sqlstore.sqlstatemanager.reload.exit"); // NOI18N
}
}
/**
* Initialize this SM from the version consistency cache. If this
* SM is in the cache and the additional field is not populated,
* the field is retrieved from the store.
* @param additionalField Field to be loaded.
*/
private boolean retrieveFromVersionConsistencyCache(FieldDesc additionalField) {
boolean rc =
persistenceManager.initializeFromVersionConsistencyCache(this);
if (rc) {
// make sure additionalField is available
if (additionalField != null
&& !getPresenceMaskBit(additionalField.absoluteID)) {
realizeField(additionalField);
}
}
return rc;
}
/**
* PersistenceManager calls this method to prepare a persistent
* object for update. This is required for foreign fields only
* as they could reference "regular" JDK Collections vs. SCO
* Collections. Such process has the side-effect of causing more
* objects to be registered with the transaction cache.
*/
public void prepareToUpdatePhaseI() {
boolean debug = logger.isLoggable();
if (debug) {
logger.fine("sqlstore.sqlstatemanager.preparetoupdateph1", // NOI18N
persistenceConfig.getPersistenceCapableClass().getName());
}
int action = state.getUpdateAction();
if (action == ActionDesc.LOG_NOOP || action == ActionDesc.LOG_DESTROY) {
// Nothing extra to do
return;
}
// Initialize UpdateDesc.
getUpdateDesc();
ArrayList newlyRegisteredSMs = new ArrayList();
ArrayList foreignFields = persistenceConfig.foreignFields;
int size = foreignFields.size();
for (int i = 0; i < size; i++) {
ForeignFieldDesc ff = (ForeignFieldDesc) foreignFields.get(i);
if ((ff.sqlProperties & FieldDesc.PROP_SECONDARY_TRACKED_FIELD) > 0) {
continue;
}
if ((ff.cardinalityUPB > 1) && (getSetMaskBit(ff.absoluteID) == true)) {
Collection v = (Collection) ff.getValue(this);
if ((v != null) && (!(v instanceof SCO) || (((SCO) v).getOwner() == null)) &&
(v.size() > 0)) {
ArrayList removed = null;
ArrayList added = new ArrayList(v);
processCollectionUpdates(ff, removed, added, newlyRegisteredSMs, true, false);
}
}
}
// The newRegisteredSMs should contain a list of all the state managers that
// are registered as the result of processCollectionUpdates.
for (int i = 0; i < newlyRegisteredSMs.size(); i++) {
SQLStateManager sm = (SQLStateManager) newlyRegisteredSMs.get(i);
sm.prepareToUpdatePhaseI();
}
if (debug) {
logger.fine("sqlstore.sqlstatemanager.preparetoupdateph1.exit"); // NOI18N
}
}
/**
* This is the second phase of the commit processing. It populates phase3sms with all
* the autopersistent instances that are no longer reachable from a persistent instance.
*
* @param phase3sms List containing autopersistent instances that are no longer reachable
* from a persistent instance.
*/
public void prepareToUpdatePhaseII(HashSet phase3sms) {
boolean debug = logger.isLoggable();
if (debug) {
logger.fine("sqlstore.sqlstatemanager.preparetoupdateph2", // NOI18N
persistenceConfig.getPersistenceCapableClass().getName());
}
// If this instance is autopersistent, we transition it into a pending state and
// add it to phase3sms collection. Any instance in phase3sms collection may be removed
// later if it becomes persistent.
if (state.isAutoPersistent()) {
state = state.transitionMakePending();
phase3sms.add(this);
return;
}
if ((stateFlags & ST_PREPARED_PHASE_II) > 0) {
return;
}
stateFlags |= ST_PREPARED_PHASE_II;
if ((!state.isNew() && !state.isDirty()) || state.isDeleted()) {
return;
}
ArrayList foreignFields = persistenceConfig.foreignFields;
int size = foreignFields.size();
// Walk the object graph starting from this instance and transition all
// autopersistent instances to persistent and remove it from phase3sms.
for (int i = 0; i < size; i++) {
ForeignFieldDesc ff = (ForeignFieldDesc) foreignFields.get(i);
if (ff.cardinalityUPB <= 1) {
if (getPresenceMaskBit(ff.absoluteID)) {
Object v = ff.getValue(this);
if (v != null) {
transitionPersistent(v, phase3sms);
}
}
} else {
Collection c = getCollectionValue(ff);
if (c != null) {
Iterator iter = c.iterator();
while (iter.hasNext()) {
Object v = iter.next();
transitionPersistent(v, phase3sms);
}
}
}
}
if (debug) {
logger.fine("sqlstore.sqlstatemanager.preparetoupdateph2.exit"); // NOI18N
}
}
/**
* This is the third phase of commit processing. It sets up the delete dependencies among
* all the autopersistent instances that have been flushed to the database.
*/
public void prepareToUpdatePhaseIII() {
boolean debug = logger.isLoggable();
if (debug) {
logger.fine("sqlstore.sqlstatemanager.preparetoupdateph3", // NOI18N
persistenceConfig.getPersistenceCapableClass().getName());
}
if (!state.isPersistentInDataStore()) {
// This object will not be written to the store. But we need to
// make sure, that scheduled jointable entries aren't written either.
// See UpdateQueryPlan#processJoinTables().
if (updateDesc != null) {
updateDesc.clearUpdatedJoinTableRelationships();
}
// Finished for this instance.
return;
}
ArrayList foreignFields = persistenceConfig.foreignFields;
int size = foreignFields.size();
// Sets up dependencies between this instance and all its relationship fields
// that are autopersistent.
for (int i = 0; i < size; i++) {
ForeignFieldDesc ff = (ForeignFieldDesc) foreignFields.get(i);
if (ff.cardinalityUPB <= 1) {
if (getPresenceMaskBit(ff.absoluteID)) {
Object v = ff.getValue(this);
if (v != null) {
updateObjectField(ff, null, false, false);
}
}
} else {
Collection c = getCollectionValue(ff);
if (c != null) {
if (c.size() > 0) {
ArrayList removed = new ArrayList(c);
ArrayList added = null;
processCollectionUpdates(ff, removed, added, null, false, false);
}
}
}
}
if (debug) {
logger.fine("sqlstore.sqlstatemanager.preparetoupdateph3.exit"); // NOI18N
}
}
/**
* Transitions the instance <code>pc to persistent state if it's
* autopersistent, and removes its state manager from the list
* <code>phase3sms of unreachable autopersistent
* instances. The recursive call to <code>prepareToUpdatePhaseII
* removes the transitive closure of all state managers reachable from
* <code>pc from phase3sms
. This method has got no
* effects on transient instances.
*
* @param pc Instance becoming persistent and removed from <code>phase3sms.
* @param phase3sms List containing so far unreachable autopersistent instances.
*/
private void transitionPersistent(Object pc, HashSet phase3sms) {
SQLStateManager sm = (SQLStateManager) persistenceManager.getStateManager(pc);
// Need to check if the associated state manager is null, if
// called with an object from a collection relationship field. If
// the collection is not a SCO collection, it is possible that it
// contains transient instances. No need to check for object
// relationship fields.
if (sm != null && sm.state.isAutoPersistent()) {
sm.state = sm.state.transitionMakePersistent();
phase3sms.remove(sm);
sm.prepareToUpdatePhaseII(phase3sms);
}
}
/**
* Returns the value of the collection relationship field
* <code>ff. For deferred SCOCollections, only the
* objects added in the current transaction are returned.
* This method may only be called for Collection fields!
*
* @param ff Collection relationship field.
* @return The value of the collection relationship field
* <code>ff. For deferred SCOCollections, only the
* objects added in the current transaction are returned.
*/
private Collection getCollectionValue(ForeignFieldDesc ff) {
Collection c = null;
if (ff.cardinalityUPB > 1) {
c = (Collection) ff.getValue(this);
if (c != null && c instanceof SCOCollection) {
SCOCollection sco = (SCOCollection) c;
if (sco.isDeferred()) {
c = sco.getAdded();
}
}
}
return c;
}
private void getUpdateActions(ArrayList actions) {
if ((stateFlags & ST_VISITED) > 0) {
return;
}
int action = state.getUpdateAction();
if ((action == ActionDesc.LOG_NOOP) && (updateDesc == null)) {
return;
}
// Initialize updateDesc.
getUpdateDesc();
updateDesc.setObjectInfo(getBeforeImage(), this, action);
if ((action == ActionDesc.LOG_DESTROY) || (action == ActionDesc.LOG_CREATE) ||
updateDesc.hasUpdatedFields() || updateDesc.hasUpdatedJoinTableRelationships()) {
actions.add(updateDesc);
}
stateFlags |= ST_VISITED;
if (updatedForeignReferences != null) {
Iterator iter = updatedForeignReferences.iterator();
while (iter.hasNext()) {
SQLStateManager sm = ((UpdatedForeignReference) iter.next()).getStateManager();
if (sm.referenceCount == 1) {
sm.getUpdateActions(actions);
}
sm.referenceCount--;
}
}
}
public void release() {
if (null != persistenceManager) {
// The persistenceManager can be null, for example if this
// instance is used in the VersionConsistency cache.
persistenceManager.setStateManager(persistentObject, null);
}
persistentObject = null;
objectId = null;
persistenceManager = null;
beforeImage = null;
hiddenValues = null;
updatedForeignReferences = null;
updateDesc = null;
persistenceConfig = null;
store = null;
valid = false;
}
private void reset(boolean retainValues, boolean wasNew, boolean keepState) {
boolean debug = logger.isLoggable();
if (debug) {
Object[] items = new Object[] {Boolean.valueOf(retainValues),
Boolean.valueOf(wasNew), Boolean.valueOf(keepState)};
logger.fine("sqlstore.sqlstatemanager.reset", items); // NOI18N
}
if (state == null) {
// make the instance transient.
if (!keepState) {
persistenceManager.clearFields(persistentObject);
}
// Need to set jdoFlag to READ_WRITE_OK for transient instance.
persistenceManager.setFlags(persistentObject, READ_WRITE_OK);
if (needsVerifyAtDeregister) {
persistenceManager.deregisterInstance(getObjectId(), this);
} else {
persistenceManager.deregisterInstance(getObjectId());
}
this.release();
} else {
// Reset the state manager for the next transaction.
stateFlags = 0;
beforeImage = null;
updatedForeignReferences = null;
referenceCount = 0;
if (updateDesc != null) {
updateDesc.reset();
}
// We retain the field values if retainValues is true or the state
// is persistentNontransactional
if (retainValues || (state instanceof PersistentNonTransactional)) {
FieldDesc f = null;
ArrayList fields = persistenceConfig.fields;
for (int i = 0; i < fields.size(); i++) {
f = (FieldDesc) fields.get(i);
Object v = f.getValue(this);
// For new objects mark null references as not set if the field is
// not managed. This is to allow the field to be reloaded in the next
// transaction because a relationship may exist in the database.
if (wasNew && (f instanceof ForeignFieldDesc) &&
(v == null) && (((ForeignFieldDesc) f).getInverseRelationshipField() == null)) {
if (debug)
logger.fine("sqlstore.sqlstatemanager.unsetmask", f.getName()); // NOI18N
unsetMaskBit(f.absoluteID, PRESENCE_MASK);
}
// Replace java.util Collection and Date objects
// with SCO instances:
if ((v instanceof Collection) && !(v instanceof SCOCollection)
&& !keepState) {
if (debug)
logger.fine("sqlstore.sqlstatemanager.resettingcollection"); // NOI18N
replaceCollection((ForeignFieldDesc) f, (Collection) v);
if (debug) {
logger.fine("sqlstore.sqlstatemanager.newtype", (f.getValue(this)).getClass()); // NOI18N
}
} else if (v instanceof SCOCollection) {
((SCOCollection) v).reset();
}
// TO FIX!!!: this already replaces Date with SCO Date
else if ((v instanceof java.util.Date) && !(v instanceof SCODate)
&& !keepState) {
if (debug)
logger.fine("sqlstore.sqlstatemanager.resettingdate"); // NOI18N
v = f.convertValue(v, this);
f.setValue(this, v);
if (debug) {
logger.fine("sqlstore.sqlstatemanager.newtype", (f.getValue(this)).getClass()); // NOI18N
}
}
}
// We need to set the jdoFlags to LOAD_REQUIRED instead of READ_OK in order to
// have the StateManager intermediate access to this instance. The reason is
// if the next transaction is pessimistic, the StateManager needs to reload
// the instance. Note that the StateManager will not reload the instance
// if the transaction is optimistic and the instance is p_nontransactional.
persistenceManager.setFlags(persistentObject, LOAD_REQUIRED);
} else {
clearMask(PRESENCE_MASK);
persistenceManager.clearFields(persistentObject);
// Need to mark the key fields as present
markKeyFieldsPresent();
persistenceManager.setFlags(persistentObject, LOAD_REQUIRED);
}
clearMask(SET_MASK);
isReplacementInstance = false;
needsRegisterAtRollback = false;
needsVerifyAtDeregister = false;
}
}
/**
* @return true if persistentObject has been flushed to db
*/
public boolean isProcessed() {
return (referenceCount == 0);
}
public void flushed() {
// reset the current state to the point where we can accept more updates.
state = state.transitionFlushed();
clearMask(SET_MASK);
stateFlags &= ~ST_VISITED;
stateFlags &= ~ST_UPDATE_DISABLED;
stateFlags &= ~ST_REGISTERED;
// need to set the jdoFlags to LOAD_REQUIRED to allow updated to be tracked.
persistenceManager.setFlags(persistentObject, LOAD_REQUIRED);
if (updatedForeignReferences != null) {
updatedForeignReferences.clear();
}
if (updateDesc != null) {
updateDesc.reset();
}
}
public void commit(boolean retainValues) {
boolean wasNew = (state.isNew() && !state.isDeleted());
state = state.transitionCommit(retainValues);
reset(retainValues, wasNew, false);
}
public void rollback(boolean retainValues) {
boolean wasNew = (state.isNew() && !state.isDeleted());
boolean needsRestore = state.needsRestoreOnRollback(retainValues);
state = state.transitionRollback(retainValues);
boolean keepState = needsRestore;
// Only restore if there is a before image and the flag needsRestore is true.
if ((beforeImage != null) && (needsRestore == true)) {
copyFields(beforeImage, true, false);
// Keep the fields from being reset in reset()
keepState = true;
}
if (needsRegisterAtRollback && !isReplacementInstance) {
persistenceManager.registerInstance(this, getObjectId());
}
reset(retainValues, wasNew, keepState);
}
private void markKeyFieldsPresent() {
ArrayList keyFields = persistenceConfig.getPrimaryTable().getKey().getFields();
for (int i = 0; i < keyFields.size(); i++) {
LocalFieldDesc fd = (LocalFieldDesc) keyFields.get(i);
if (fd != null) {
setPresenceMaskBit(fd.absoluteID);
}
}
}
public void prepareGetField(int fieldID) {
FieldDesc fieldDesc = persistenceConfig.getField(fieldID);
prepareGetField(fieldDesc, false, true);
}
private void prepareGetField(FieldDesc fieldDesc) {
prepareGetField(fieldDesc, true, false);
}
/**
* Loads the field described by <code>fieldDesc. If the field is not
* present in the instance, it will be loaded from the data store by calling
* {@link #realizeField(FieldDesc)}. Depending on its lifecycle state,
* the instance is registered in the transaction cache and the field
* is marked as read.
*
* @param fieldDesc Field descriptor for the field to be loaded.
*/
private void prepareGetField(FieldDesc fieldDesc, boolean internal, boolean acquireShareLock) {
boolean debug = logger.isLoggable(Logger.FINEST);
if (debug) {
logger.finest("sqlstore.sqlstatemanager.preparegetfield", fieldDesc.getName()); // NOI18N
}
if (acquireShareLock) {
persistenceManager.acquireShareLock();
}
try {
getLock();
boolean xactActive = persistenceManager.isActiveTransaction();
boolean optimistic = persistenceManager.isOptimisticTransaction();
boolean nontransactionalRead = persistenceManager.isNontransactionalRead();
if (state.needsReload(optimistic, nontransactionalRead, xactActive)) {
reload(fieldDesc);
}
LifeCycleState oldstate = state;
state = state.transitionReadField(optimistic, nontransactionalRead, xactActive);
registerInstance(false, null, oldstate);
// Only allow dynamic navigation if we are not in the state that allows it.
if (state.isNavigable() || !internal) {
if (getPresenceMaskBit(fieldDesc.absoluteID) == false) {
realizeField(fieldDesc);
}
}
} catch (JDOException e) {
throw e;
} catch (Exception e) {
logger.log(Logger.FINE,"sqlstore.exception.log", e);
throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
"core.statemanager.getfieldfailed"), e); // NOI18N
} finally {
if (acquireShareLock) {
persistenceManager.releaseShareLock();
}
releaseLock();
if (debug) {
logger.finest("sqlstore.sqlstatemanager.preparegetfield.exit"); // NOI18N
}
}
}
/**
* Retrieves a field specified by <code>fieldDesc from the data
* store. If the field is part of a group then all unfetched fields
* in the group are retrieved. realizeField is part of dynamic
* navigation. The field is marked as present.
*
* @param fieldDesc The field descriptor of the field to be retrieved.<p>
* Note: The <code>fieldDesc parameter must not be null.
*/
private void realizeField(FieldDesc fieldDesc) {
assert fieldDesc != null;
boolean debug = logger.isLoggable();
if (debug) {
logger.fine("sqlstore.sqlstatemanager.realizefield", fieldDesc.getName()); // NOI18N
}
if (!persistenceConfig.isNavigable()) {
throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
"core.statemanager.notnavigable", // NOI18N
fieldDesc.getName(), persistentObject.getClass().getName()));
}
boolean fieldRealized = false;
if (fieldDesc instanceof ForeignFieldDesc) {
ForeignFieldDesc ff = (ForeignFieldDesc) fieldDesc;
// We can do an enhancement if we are only getting a single
// relationship field. Check if the field can be retrieved on
// it's own and the relationship is not mapped to a join table.
// The field can be retrieved on it's own if it's is in
// an independent fetch group or not in a fetch group at all.
// Independent fetch groups have group ids < FieldDesc.GROUP_NONE.
if (ff.fetchGroup <= FieldDesc.GROUP_NONE
&& persistenceConfig.getFetchGroup(ff.fetchGroup).size() <= 1
&& !ff.useJoinTable()) {
fieldRealized = realizeForeignField(ff);
}
}
if (!fieldRealized) {
retrieve(fieldDesc);
}
if (debug) {
logger.fine("sqlstore.sqlstatemanager.realizefield.exit"); // NOI18N
}
}
/**
* For foreign fields we want to take advantage of knowing the
* relationship key and only selecting the foreign rather than the
* primary with the foreign attached. Most of the work is in
* figuring out whether we can do that.
*
* @param foreignField The relationship field to be retrieved.
* Following is true for this field.
* <ul>
* <li>It is part of an independent fetch group with only one
* field in the fetch group or not part of any fetch group.</li>
* <li>It is not mapped to a join table.
* </ul>
* Note: The <code>foreignField parameter must not be null.
*
* @return True, if relationship field has been retrieved, false otherwise.
*/
private boolean realizeForeignField(ForeignFieldDesc foreignField) {
assert foreignField != null;
boolean isPresent = false;
boolean debug = logger.isLoggable();
if (debug) {
logger.fine("sqlstore.sqlstatemanager.realizeforeignfield", // NOI18N
foreignField.getName());
}
// Check and see if all the values we need are present.
for (int i = 0; i < foreignField.localFields.size(); i++) {
LocalFieldDesc lf = (LocalFieldDesc) foreignField.localFields.get(i);
isPresent = getPresenceMaskBit(lf.absoluteID);
if (!isPresent) {
break;
}
}
if (isPresent) {
// All the values we need are present. Wow. Now we'll have to
// format a more specialized request and attach the object(s)
// we get back to our managed object.
populateForeignField(foreignField);
}
if (debug) {
logger.fine("sqlstore.sqlstatemanager.realizeforeignfield.exit", // NOI18N
Boolean.valueOf(isPresent));
}
return isPresent;
}
/**
* Retrieves the relationship.
*
* @param foreignField The relationship field to be retrieved.
* Following is true for this field.
* <ul>
* <li>It is part of an independent fetch group with only one
* field in the fetch group or not part of any fetch group.</li>
* <li>It is not mapped to a join table.
* </ul>
* Note: The <code>foreignField must not be null.
*/
private void populateForeignField(ForeignFieldDesc foreignField) {
assert foreignField != null;
boolean foundInstance = false;
if (foreignField.hasForeignKey() && foreignField.isMappedToPk()) {
// Cache lookup, returns an object.
Object pc = getObjectById(foreignField, null, null, true);
if (foundInstance = (pc != null)) {
foreignField.setValue(this, pc);
}
}
if (!foundInstance) {
// Query lookup, returns a collection.
Collection result = retrieveForeign(foreignField);
attachQueryResult(foreignField, result);
}
// Or in PRESENT bit for this field in the properties mask
setPresenceMaskBit(foreignField.absoluteID);
}
/**
* Attaches the retrieved object(s) <code>queryResult to the
* relationship field <code>foreignField of the instance
* managed by this state manager.
*
* @param queryResult Retrieved value for the relationship field
* <code>foreignField.
*/
private void attachQueryResult(ForeignFieldDesc foreignField,
Collection queryResult) {
assert foreignField != null;
// Attach the object(s) we got back to our managed object.
// There are three cases:
// 1) The foreign field contains a collection
// 2) The foreign object doesn't exist (NIL)
// 3) The foreign field is a reference to a single object
if (foreignField.getComponentType() != null) {
// Instantiate and populate a dynamic array, namely Collection.
// NOTE: queryResult is null, if we didn't execute the retrieval!
replaceCollection(foreignField, queryResult);
} else if (queryResult == null || queryResult.size() == 0) {
foreignField.setValue(this, null);
} else {
if (queryResult.size() > 1) {
throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
"core.persistencestore.toomanyobjforcard1", // NOI18N
persistenceConfig.getName(),
foreignField.getName(), "" + queryResult.size())); // NOI18N
}
Object v = queryResult.iterator().next();
foreignField.setValue(this, v);
}
}
/**
* Retrieves the relationship based on the values of the
* relationship columns on the local side. Note: This method
* assumes that the relationship key fields are loaded.
*
* RESOLVE:
* The relationship might be mapped to an Unique Key.
* Do databases constrain the Unique Key columns to be non null?
*
* @param foreignField The relationship field to be retrieved.
* Following is true for this field.
* <ul>
* <li>It is part of an independent fetch group with only one
* field in the fetch group or not part of any fetch group.</li>
* <li>It is not mapped to a join table.
* </ul>
* Note: The <code>foreignField must not be null.
*
* @return Collection returned by the query. Null, if the relationship
* is not set or the passed relationship field is null.
* @see #retrieve
*/
private Collection retrieveForeign(ForeignFieldDesc foreignField) {
assert foreignField != null;
Collection result = null;
boolean debug = logger.isLoggable();
if (debug) {
logger.fine("sqlstore.sqlstatemanager.retrieveforeign", // NOI18N
foreignField.getName());
}
Object[] values = new Object[foreignField.localFields.size()];
boolean isValidForeignKey = true;
for (int i = 0; i < foreignField.localFields.size(); i++) {
FieldDesc flf = (FieldDesc) foreignField.localFields.get(i);
if (!getPresenceMaskBit(i)) {
// throw exception
}
if (getSetMaskBit(flf.absoluteID)) {
// This is one reason why we must have a before image
// on relationship changes!
values[i] = flf.getValue(beforeImage);
} else {
values[i] = flf.getValue(this);
}
// Make sure we have a valid query key.
if (values[i] == null) {
// The relationship must be null. No need to query!
isValidForeignKey = false;
}
}
if (isValidForeignKey) {
// Getting a new generated RD or a cached one.
RetrieveDesc fdesc =
persistenceConfig.getRetrieveDescForFKQuery(foreignField, store);
result = (Collection) store.retrieve(
persistenceManager, fdesc, new QueryValueFetcher(values));
}
if (debug) {
logger.fine("sqlstore.sqlstatemanager.retrieveforeign.exit"); // NOI18N
}
return result;
}
/**
* The retrieve method gets a retrieve descriptor to retrieve the
* desired field and adds constraints necessary to limit the
* retrieval set to the source object runs the retrieve
* descriptor against the store, the source object is connected to,
* and then merges the results back into the source object.
*
* @param additionalField The additional field to be retrieved.<p>
* Note: The <code>additionalField might be null if we just
* want to reload the instance.
* @see #retrieveForeign
*/
private void retrieve(FieldDesc additionalField) {
boolean debug = logger.isLoggable();
if (debug) {
String fieldName = (additionalField != null) ? additionalField.getName() : null;
logger.fine("sqlstore.sqlstatemanager.retrieve", fieldName); // NOI18N
}
LocalFieldDesc[] keyFields = persistenceConfig.getKeyFieldDescs();
Object [] values = new Object[keyFields.length];
copyValues(values, keyFields, 0);
// Getting a new generated RD or a cached one.
RetrieveDesc rd = persistenceConfig.getRetrieveDescForPKQuery(additionalField, store);
Collection result = (Collection) store.
retrieve(persistenceManager, rd, new QueryValueFetcher(values));
if (result.size() > 1) {
throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
"core.statemanager.toomanyrows", // NOI18N
persistenceConfig.getPersistenceCapableClass().getName()));
} else if (result.size() < 1 || result.iterator().next() != persistentObject) {
// If there are no instances fetched, or the fetched instances is not the one
// we asked for, it means that it is not found and we throw an exception
throw new JDOObjectNotFoundException(I18NHelper.getMessage(messages,
"core.statemanager.objectnotfound"), // NOI18N
new Object[]{persistentObject});
}
if (debug) {
logger.fine("sqlstore.sqlstatemanager.retrieve.exit"); // NOI18N
}
}
/**
* Copies the value of <code>fields into the array
* <code>values. The values are copied into the
* array starting at index <code>startIndex.
*
* @param values Array taking the values of <code>fields.
* @param fields Array of LocalFieldDesc.
* @param startIndex Starting index into <code>values.
*/
private void copyValues(Object[] values, LocalFieldDesc[] fields, int startIndex) {
// The values array should be long enough to hold all the fields.
assert values.length - startIndex >= fields.length;
for (int i = 0; i < fields.length; i++) {
LocalFieldDesc field = fields[i];
values[i + startIndex] = field.getValue(this);
// The field is not expected to be null.
assert values[i + startIndex] != null;
}
}
public SQLStateManager getBeforeImage() {
// Do not try to get before image if not required
if (beforeImage == null && isBeforeImageRequired()) {
boolean debug = logger.isLoggable();
if (debug) {
logger.fine("sqlstore.sqlstatemanager.getbeforeimage", // NOI18N
persistenceConfig.getPersistenceCapableClass().getName());
}
try {
getLock();
// Make a copy of the persistentObject.
beforeImage = copyPersistent();
} finally {
releaseLock();
}
if (debug) {
logger.fine("sqlstore.sqlstatemanager.getbeforeimage.exit"); // NOI18N
}
}
return beforeImage;
}
public boolean isBeforeImageRequired() {
com.sun.jdo.api.persistence.support.Transaction t = persistenceManager.currentTransaction();
// NOTE: We need to create a before image on relationship changes for two reasons:
// (1) Relationship management assumes relationship fields to be loaded.
// See prepareUpdateField.
// (2) The before image is used in realizeField.
boolean isBeforeImageRequired =
persistenceManager.isOptimisticTransaction() ||
getUpdateDesc().hasChangedRelationships() ||
t.getRetainValues() || t.getRestoreValues() ||
persistenceConfig.hasModifiedCheckAtCommitConsistency();
if (logger.isLoggable(Logger.FINER)) {
logger.finer("sqlstore.sqlstatemanager.isbeforeimagerequired", // NOI18N
Boolean.valueOf(isBeforeImageRequired));
}
return isBeforeImageRequired;
}
private SQLStateManager copyPersistent() {
PersistenceManager pm = (PersistenceManager) getPersistenceManagerInternal();
SQLStateManager newStateManager = (SQLStateManager) clone();
// Associate state manager with a new pc instance.
pm.newInstance(newStateManager);
newStateManager.copyFields(this, true, true);
return newStateManager;
}
/**
* @inheritDoc StateManager#copyFields(StateManager source)
* Does not copy relationship fields. Other fields are cloned while
* copying as per {@link #cloneObjectMaybe(Object source)}.
* @throws IllegalArgumentException if source is null, is
* not <code>instanceof SQLStateManager, or is not managing the
* same type of persistent instance as this StateManager.
*/
public void copyFields(StateManager source) {
if (!(source instanceof SQLStateManager)) {
String className =
(source != null) ? source.getClass().getName() : null;
throw new IllegalArgumentException(className);
}
SQLStateManager sqlSource = (SQLStateManager) source;
if (persistenceConfig != sqlSource.getPersistenceConfig()) {
Class thisPCClass =
persistenceConfig.getPersistenceCapableClass();
Class sourcePCClass =
sqlSource.getPersistenceConfig().getPersistenceCapableClass();
throw new IllegalArgumentException(
I18NHelper.getMessage(
messages,
"core.statemanager.copyFields.mismatch", // NOI18N
thisPCClass.getName(),
sourcePCClass.getName()));
}
copyFields(sqlSource, false, true);
}
/**
* @inheritDoc StateManager#copyFields(StateManager source).
* @param copyRelationships if true, then relationship fields are copied,
* otherwise they are not copied.
* @param clone if true, then the fields are cloned while copying,
* otherwise both the <code>source and this StateManager reference
* the same field value.
*/
private void copyFields(SQLStateManager source,
boolean copyRelationships,
boolean clone) {
ArrayList fields = null;
// Reset the field masks, as the instance will
// be populated with the state from the source.
clearMask(PRESENCE_MASK);
clearMask(SET_MASK);
for (int i = 0; i < 2; i++) {
if (i == 0) {
fields = persistenceConfig.fields;
} else {
fields = persistenceConfig.hiddenFields;
}
for (int j = 0; (fields != null) && (j < fields.size()); j++) {
FieldDesc f = (FieldDesc) fields.get(j);
if (!copyRelationships && f.isRelationshipField()) {
continue;
}
if (source.getPresenceMaskBit(f.absoluteID)) {
Object value = f.getValue(source);
f.setValue(this, (clone) ? cloneObjectMaybe(value) : value);
setPresenceMaskBit(f.absoluteID);
}
}
}
}
private Object cloneObjectMaybe(Object source) {
// RESOLVE: need to clone SCOCollection
if (source != null) {
// RESOLVE: Should we clone byte[]???
if ((source instanceof SCO)) {
return ((SCO)source).cloneInternal();
} else if (!(source instanceof Number) &&
!(source instanceof String) &&
!(source instanceof Character) &&
!(source instanceof Boolean) &&
// RESOLVE: #javax.ejb package# !(source instanceof javax.ejb.EJBObject) &&
!(source instanceof com.sun.jdo.api.persistence.support.PersistenceCapable) &&
!(source instanceof byte[])) {
try {
Class type = source.getClass();
if (!type.isArray()) {
Method m = type.getMethod("clone", (Class []) null); // NOI18N
if (m != null) {
return m.invoke(source, (Object []) null);
}
} else {
Object srcArray[] = (Object[]) source;
Object dstArray[] = (Object[])
java.lang.reflect.Array.newInstance(type.getComponentType(), srcArray.length);
for (int i = 0; i < srcArray.length; i++) {
dstArray[i] = srcArray[i];
}
return dstArray;
}
} catch (NoSuchMethodException e) {
if (logger.isLoggable()) {
Object[] items = new Object[] {e, source.getClass().getName()};
logger.fine("sqlstore.sqlstatemanager.nosuchmethodexcep.clone", items); // NOI18N
}
} catch (InvocationTargetException e) {
} catch (IllegalAccessException e) {
}
}
}
return source;
}
/**
* Prepares the field described by <code>fieldDesc for update.
* This method is central to record changes to fields. The state
* transitions to dirty. The instance is registered in the
* transaction cache. If the field is set for the first time,
* the before image is prepared and the field is marked as modified.
* Updated local fields are added to the column list to be updated.
*
* @param fieldDesc Updated field.
* @param newlyRegisteredSMs
* State managers of autopersistent objects will be added to this list.
* @see #prepareUpdateFieldSpecial
*/
private void prepareUpdateField(FieldDesc fieldDesc, ArrayList newlyRegisteredSMs) {
// No updates for key fields. As this method is called by
// loadForUpdate for _all_ DFG fields (to prepare a hollow
// instance for update), we can't throw an exception here
// like in prepareUpdateFieldSpecial.
if (fieldDesc.isKeyField()) {
return;
}
getUpdateDesc().markRelationshipChange(fieldDesc);
boolean debug = logger.isLoggable();
if (debug) {
Object[] items = new Object[] {fieldDesc.getName(),state};
logger.fine("sqlstore.sqlstatemanager.prepareupdatefield", items); // NOI18N
}
boolean optimistic = persistenceManager.isOptimisticTransaction();
boolean xactActive = persistenceManager.isActiveTransaction();
boolean nontransactionalRead = persistenceManager.isNontransactionalRead();
if (state.needsReload(optimistic, nontransactionalRead, xactActive)) {
reload(fieldDesc);
}
// State transition.
// The state transition prevends field updates on deleted instances
// and must always be executed. See PersistentDeleted.transitionWriteField().
LifeCycleState oldstate = state;
state = state.transitionWriteField(xactActive);
registerInstance(false, newlyRegisteredSMs, oldstate);
if (state == oldstate && getSetMaskBit(fieldDesc.absoluteID) &&
getPresenceMaskBit(fieldDesc.absoluteID)) {
// The presence mask bit is NEVER set for deferred collections,
// because deferred collections MUST always be reloaded.
return;
}
// We don't reload the field for a newly created instance.
if (state.isBeforeImageUpdatable()) {
// Reload the field, as relationship management
// assumes that relationship fields are always present.
// See updateObjectField() or processCollectionupdates().
if (!getPresenceMaskBit(fieldDesc.absoluteID)) {
prepareGetField(fieldDesc);
}
updateBeforeImage(fieldDesc, null);
}
recordUpdatedField(fieldDesc);
if (debug) {
logger.fine("sqlstore.sqlstatemanager.prepareupdatefield.exit"); // NOI18N
}
}
/**
* Initializes the <code>beforeImage and registers the before image
* value if not null.
*
* @param fieldDesc Updated field.
* @param value BeforeImage value, null if called from
* {@link #prepareUpdateField}. If the value is null it will be retrieved
* from the instance.
* @see #prepareUpdateFieldSpecial
*/
private void updateBeforeImage(FieldDesc fieldDesc, Object value) {
getBeforeImage();
if (beforeImage != null
&& !beforeImage.getPresenceMaskBit(fieldDesc.absoluteID)
&& (fieldDesc.sqlProperties & FieldDesc.PROP_LOG_ON_UPDATE) > 0) {
if (value == null) {
value = fieldDesc.getValue(this);
}
if (value != null) {
if (logger.isLoggable(Logger.FINEST)) {
Object[] items = new Object[] {fieldDesc, value};
logger.finest("sqlstore.sqlstatemanager.updatebeforeimage", items); // NOI18N
}
fieldDesc.setValue(beforeImage, cloneObjectMaybe(value));
beforeImage.setPresenceMaskBit(fieldDesc.absoluteID);
}
}
}
/**
* Marks the field <code>fieldDesc as set and schedules local fields
* to be written to the data store.
*
* @param fieldDesc Updated field.
*/
private void recordUpdatedField(FieldDesc fieldDesc) {
boolean debug = logger.isLoggable(Logger.FINEST);
if (!fieldDesc.isRelationshipField() &&
(fieldDesc.sqlProperties & FieldDesc.PROP_RECORD_ON_UPDATE) > 0) {
if (debug) {
logger.finest("sqlstore.sqlstatemanager.recordingfield", fieldDesc); // NOI18N
}
getUpdateDesc().recordUpdatedField((LocalFieldDesc) fieldDesc);
}
if (debug) {
logger.finest("sqlstore.sqlstatemanager.makedirtyfield", fieldDesc); // NOI18N
}
setSetMaskBit(fieldDesc.absoluteID);
}
/**
* This method adds the dependency between this StateManager
* and the other.
*
* @param sm Second state manager.
* @see StateManager#addDependency(StateManager sm)
*/
public void addDependency(StateManager sm) {
if (logger.isLoggable()) {
Object[] items = new Object[] {this, sm};
logger.fine("sqlstore.sqlstatemanager.adddependency", items); // NOI18N
}
// The simple solution is to call addUpdatedForeignReference()
// internally. It might try to reregister both instances again,
// but the caller should clean up the cache. e.g. the
// PersistenceManager MUST replace the deleted StateManager
// with the new instance in the weak cache AFTER this call.
SQLStateManager other = (SQLStateManager)sm;
if (!state.isNew() || !state.isDeleted()) {
// Do not need to add a dependency to a new-deleted instance as
// its flush is a no-op any way.
// First parameter == null marks a non removable dependency.
this.addUpdatedForeignReference(null, other);
} else if ((other.stateFlags & ST_REGISTERED) == 0) {
// If we did not add a dependency, we still need to register the other
// instance if it is not yet registered.
persistenceManager.registerInstance(other, other.getObjectId(), false, true);
other.stateFlags |= ST_REGISTERED;
}
}
/**
* Resolves the dependencies for the instances waiting for this state manager.
* Dependencies are registered instantly during the course of the transaction.
* For this reason, the introduced dependencies must be checked, if they are
* still valid at commit/flush. E.g. remove dependencies introduced on
* relationship removal are only valid, if the removed instance is deleted.
* <p>
* This method checks the dependencies for all instances waiting for the
* current state manager to be flushed to the store.
*/
public void resolveDependencies() {
if (logger.isLoggable()) {
logger.fine("sqlstore.sqlstatemanager.resolvedependencies", this.getPersistent()); // NOI18N
}
if (updatedForeignReferences != null) {
Iterator iter = updatedForeignReferences.iterator();
while (iter.hasNext()) {
final UpdatedForeignReference ufr = (UpdatedForeignReference) iter.next();
final ForeignFieldDesc fieldDesc = ufr.getFieldDesc();
final SQLStateManager foreignSM = ufr.getStateManager();
if (resolveDependency(fieldDesc, foreignSM)) {
foreignSM.removeDependency();
iter.remove();
}
}
}
}
/**
* Tries to resolve the dependency between <code>this
* and <code>foreignSM introduced on the update of
* relationship field <code>fieldDesc. There are three
* kinds of dependencies:
* <ul>
* <li>Create dependency, see {@link #registerRemoveDependency}
* <li>Remove dependency, see {@link #registerCreateDependency}
* <li>Update dependency, see {@link #manageDependencyForObjectField}
* </ul>
* The current implementation does not attempt to resolve
* <em>Update dependencies.
*
* @param fieldDesc Relationship field.
* @param foreignSM Foreign state manager.
* @return True, if the dependency between <code>this
* and <code>foreignSM can be safely removed.
*/
private boolean resolveDependency(ForeignFieldDesc fieldDesc,
SQLStateManager foreignSM) {
boolean removeDependency = false;
Object pc = foreignSM.getPersistent();
if (!state.isPersistentInDataStore()) {
// Check for a create dependency.
// The create dependency is valid, if the instance is being created.
if (state.getUpdateAction() != ActionDesc.LOG_CREATE) {
// The instance is not persistent and will not be created.
removeDependency = true;
} else if (!checkRelationship(this, fieldDesc, pc)) {
// No relationship between the two instances.
removeDependency = true;
}
} else {
// Not removable dependencies are marked by fieldDesc == null.
// See delete/create with the same id dependency in addDependency
// or update dependencies in manageDependencyForObjectField.
// RESOLVE: Are update dependencies removable?
if (fieldDesc != null) {
// Check for a remove dependency.
// The remove dependency is valid, if the formerly referred
// instance is being removed.
if (foreignSM.state.getUpdateAction() != ActionDesc.LOG_DESTROY) {
// The formerly referred instance will not be removed.
removeDependency = true;
} else if (fieldDesc.cardinalityUPB <= 1) {
// Don't check collection relationships, as collection
// fields might not be populated in the before image.
// RESOLVE: Collection relationships shouldn't be
// checked for remove dependencies anyway.
if (!checkRelationship(beforeImage, fieldDesc, pc)) {
// No previous relationship between the two instances.
removeDependency = true;
}
}
}
}
if (removeDependency && logger.isLoggable()) {
Object[] items = new Object[] {this.getPersistent(), fieldDesc.getName(), pc};
logger.fine("sqlstore.sqlstatemanager.resolvedependency", items); // NOI18N
}
return removeDependency;
}
/**
* Decrements the reference count and marks this instance
* as updateable, if the reference count is zero.
*/
private void removeDependency() {
if (--referenceCount == 0) {
stateFlags &= ~ST_UPDATE_DISABLED;
}
}
/**
* Checks, if there is a relationship between the persistence
* capable instance managed by state manager <code>sm and
* <code>pc on field fieldDesc
. If
* <code>sm represents a before image, the relationship was
* reset in the current transaction.
*
* @param sm Either the before- or after image of the current state manager.
* @param fieldDesc Relationship field.
* @param pc Persistence capable instance checked for relationship.
* @return True, if the persistence capable instances are (were)
* related on the given field.
*/
static private boolean checkRelationship(SQLStateManager sm,
ForeignFieldDesc fieldDesc,
Object pc) {
boolean related = false;
if (fieldDesc != null && sm != null
&& sm.getPresenceMaskBit(fieldDesc.absoluteID)) {
if (fieldDesc.cardinalityUPB > 1) {
// Checking directly for contains doesn't work for deferred SCOCollections.
Collection c = sm.getCollectionValue(fieldDesc);
// Resulting collection can't be null because the presence mask is set.
related = c.contains(pc);
} else {
related = fieldDesc.getValue(sm) == pc;
}
}
return related;
}
/**
* Nullify the relationship in the data store before the _possible_ removal
* of removedSM. To maintain referentional integrity constraints in the
* database, the relationship to the removed instance has to be nullified,
* before the removed object might be deleted. Since we use immediate
* dependency management, we don't know, if the removed object is deleted
* later on. The data store relationship has to be erased before the
* removal. Immediate dependency management determines dependencies
* immediatly when the relationship is set. In contrast the deferred
* approach waits until the objects are flushed to the store. Set the
* dependency only if both instances are already persistent.
*
* @param fieldDesc Updated relationship field.
* @param removedSM State manager removed from the relationship.
* @see #nullifyForeignKey(ForeignFieldDesc, SQLStateManager, ForeignFieldDesc, boolean)
* @see #removeJoinTableEntry(ForeignFieldDesc, SQLStateManager, ForeignFieldDesc)
*/
private void registerRemoveDependency(ForeignFieldDesc fieldDesc, SQLStateManager removedSM) {
if (this.state.isPersistentInDataStore() &&
removedSM.state.isPersistentInDataStore()) {
this.addUpdatedForeignReference(fieldDesc, removedSM);
}
}
/**
* The referred object has to be written to the store before the
* relationship can be set. To ensure referentional integrity
* constraints in the database, the added object has to be written
* to the store, before the relationship can be set. The same
* dependency applies for relationships mapped to a jointable.
*
* @param inverseFieldDesc Updated inverse relationship field.
* WE must pass the inverse field, because the dependency is
* registered on the added state manager. See
* {@link #registerRemoveDependency}.
* @param addedSM State manager added to the relationship.
* @see #setForeignKey(ForeignFieldDesc, SQLStateManager, ForeignFieldDesc)
* @see #addJoinTableEntry(ForeignFieldDesc, SQLStateManager, ForeignFieldDesc)
*/
private void registerCreateDependency(ForeignFieldDesc inverseFieldDesc, SQLStateManager addedSM) {
if (!addedSM.state.isPersistentInDataStore()) {
addedSM.addUpdatedForeignReference(inverseFieldDesc, this);
}
}
/**
* Adds a dependency for state manager <code>sm. State
* manager <code>sm must wait for this
to be
* flushed to the store before it can be written itself. State
* manager <code>sm is added to the list of objects
* depending on <code>this and will be notified when
* <code>this is written to the store. Store updates to
* <code>sm are disabled until then. This dependency is
* established to maintain referential integrity conditions in the
* data store.
*
* @param fieldDesc Updated relationship field.
* @param sm Foreign state manager depending on <code>this.
*/
private void addUpdatedForeignReference(ForeignFieldDesc fieldDesc, SQLStateManager sm) {
// Avoid self-dependency.
if (sm == this) {
return;
}
// IMPORTANT: The following check assumes that this StateManager needs to be
// registered only if it has no updatedForeignReferences. Check if this causes
// a problem after this instance has been flushed, and updatedForeignReferences
// is now an empty collection.
if (updatedForeignReferences == null) {
updatedForeignReferences = new HashSet();
// Register this instance disregarding it's LifeCycle state.
// Otherwise, its state may never be reset after the transaction commits.
if ((stateFlags & ST_REGISTERED) == 0) {
persistenceManager.registerInstance(this, getObjectId(), false, true);
stateFlags |= ST_REGISTERED;
}
}
if (updatedForeignReferences.add(new UpdatedForeignReference(fieldDesc, sm))) {
sm.stateFlags |= ST_UPDATE_DISABLED;
sm.referenceCount++;
if (logger.isLoggable() ) {
String fieldName = (fieldDesc != null) ? fieldDesc.getName() : null;
Object[] items = new Object[] {this.persistentObject, fieldName,
sm.persistentObject, new Integer(sm.referenceCount)};
logger.fine("sqlstore.sqlstatemanager.addupdate", items); // NOI18N
}
// Register this instance disregarding it's LifeCycle state.
// Otherwise, its state may never be reset after the transaction commits.
if ((sm.stateFlags & ST_REGISTERED) == 0) {
persistenceManager.registerInstance(sm, sm.getObjectId(), false, true);
sm.stateFlags |= ST_REGISTERED;
}
}
}
/**
* Removes the dependency from state manager <code>sm on
* <code>this. State manager sm
does not need to
* wait for <code>this to be flushed to the store. The dependency
* was established to maintain referential integrity conditions in the
* data store.
*
* @param fieldDesc Updated relationship field.
* @param sm Foreign state manager removed from the dependency.
*/
private void removeUpdatedForeignReference(ForeignFieldDesc fieldDesc, SQLStateManager sm) {
if ((updatedForeignReferences == null) ||
(updatedForeignReferences.size() == 0)) {
return;
}
if (updatedForeignReferences.remove(new UpdatedForeignReference(fieldDesc, sm))) {
sm.referenceCount--;
if (logger.isLoggable()) {
String fieldName = (fieldDesc != null) ? fieldDesc.getName() : null;
Object[] items = new Object[] {this.persistentObject, fieldName,
sm.persistentObject, new Integer(sm.referenceCount)};
logger.fine("sqlstore.sqlstatemanager.removeupdate", items); // NOI18N
}
if (sm.referenceCount == 0) {
sm.stateFlags &= ~ST_UPDATE_DISABLED;
}
}
}
/**
* Handles relationship updates for the object side of a one-to-many or both
* sides of a one-to-one relationship. This method processes i.e. the
* Employee side of the Employee-Department, or both sides
* Employee-Insurance relationship. Nullifies the relation on the instance
* removed from the relationship. The relation is set on the added instance
* <code>value. Data store updates are scheduled. Updates the inverse
* relationship side if <code>updateInverseRelationshipField == true
* and the relationship is mapped as bi-directional by the user.
*
* @param fieldDesc Updated relationship field.
* @param addedObject Relationship object to be set.
* @param updateInverseRelationshipField
* True, if we need to update the inverse relationship side.
* @param managedRelationshipInProgress True during relationship management.
* @return Always true.
* @exception JDOUserException
* Thrown if the added object <code>addedObject has already been deleted.
*/
private boolean updateObjectField(ForeignFieldDesc fieldDesc,
Object addedObject,
boolean updateInverseRelationshipField,
boolean managedRelationshipInProgress) {
boolean debug = logger.isLoggable();
if (debug) {
Object[] items = new Object[] {fieldDesc.getName(),fieldDesc.getComponentType()};
logger.fine("sqlstore.sqlstatemanager.updateobjfield", items); // NOI18N
}
Object removedObject = fieldDesc.getValue(this);
// Don't do anything if the before and after value are the same.
if (addedObject != removedObject) {
SQLStateManager addedSM = getAddedSM(addedObject, null);
SQLStateManager removedSM = getRemovedSM(removedObject);
SQLStateManager addedInverseFieldSM = null;
// If the new value is already deleted, we throw an exception.
if (addedSM != null && addedSM.isDeleted()) {
JDOUserException ex = new JDOUserException(I18NHelper.getMessage(messages,
"jdo.lifecycle.deleted.accessField")); // NOI18N
ex.addFailedObject(addedObject);
throw ex;
}
ForeignFieldDesc inverseFieldDesc = fieldDesc.getInverseRelationshipField();
updateRelationshipInDataStore(fieldDesc, addedSM, removedSM,
inverseFieldDesc, managedRelationshipInProgress);
if (updateInverseRelationshipField && inverseFieldDesc != null) {
addedInverseFieldSM = manageRelationshipForObjectField(inverseFieldDesc, addedSM, removedSM,
managedRelationshipInProgress);
}
manageDependencyForObjectField(fieldDesc, addedSM, removedSM, addedInverseFieldSM);
}
if (debug) {
logger.fine("sqlstore.sqlstatemanager.updateobjfield.exit"); // NOI18N
}
return true;
}
/**
* Updates the relationship in the data store. Updates the (hidden) local fields
* corresponding to the foreign key columns if the relationship is mapped to a
* foreign key. Jointable entries are scheduled for creation/removal if the
* relationship is mapped to a jointable.
*
* @param fieldDesc Updated relationship field.
* @param addedSM State manager of the added object.
* @param removedSM State manager of the removed object.
* @param inverseFieldDesc Inverse relationship field.
* @param managedRelationshipInProgress
* True during relationship management. We don't want to update the
* relationship fields twice during relationship management.
*/
private void updateRelationshipInDataStore(ForeignFieldDesc fieldDesc,
SQLStateManager addedSM,
SQLStateManager removedSM,
ForeignFieldDesc inverseFieldDesc,
boolean managedRelationshipInProgress) {
if (!fieldDesc.useJoinTable()) {
processForeignKeys(fieldDesc, addedSM, removedSM, inverseFieldDesc,
managedRelationshipInProgress);
} else {
processJoinTableEntries(fieldDesc, addedSM, removedSM, inverseFieldDesc,
managedRelationshipInProgress);
}
}
/**
* Updates the (hidden) local fields corresponding to the foreign key columns
* if the relationship is mapped to a foreign key. The updates are written
* to the store when the instance is flushed. For relationships mapped to
* foreign keys, we always update the side with the foreign key.
* The collection side never has the foreign key. Data store updates for
* added objects are not processed twice during relationship management.
*
* Data store dependencies for the update operations are established.
*
* @param fieldDesc Updated relationship field.
* @param addedSM State manager of the added object.
* @param removedSM State manager of the removed object.
* @param inverseFieldDesc Inverse relationship field.
* @param managedRelationshipInProgress True during relationship management.
*/
private void processForeignKeys(ForeignFieldDesc fieldDesc,
SQLStateManager addedSM,
SQLStateManager removedSM,
ForeignFieldDesc inverseFieldDesc,
boolean managedRelationshipInProgress) {
// If the fieldDesc property has the REF_INTEGRITY_UPDATES unset, it means we
// need to update the other side of the relationship naming the foreign key fields.
boolean updateOtherSide = (fieldDesc.sqlProperties & FieldDesc.PROP_REF_INTEGRITY_UPDATES) == 0;
if (updateOtherSide) {
// Null out the foreign key on the removed object.
if (removedSM != null) {
removedSM.nullifyForeignKey(inverseFieldDesc, this, fieldDesc, false);
}
// Set the foreign key on the added object.
// Don't set the fk twice during relationship management.
if (addedSM != null && !managedRelationshipInProgress) {
addedSM.setForeignKey(inverseFieldDesc, this, fieldDesc);
}
} else {
// Null out the foreign key to the removed object.
if (removedSM != null) {
// Don't overwrite the foreign key, if both removedSM and
// addedSM != null. See runtime test rel12 for an example.
nullifyForeignKey(fieldDesc, removedSM, inverseFieldDesc, addedSM != null);
}
// Set the foreign key to the added object.
// Don't set the fk twice during relationship management.
if (addedSM != null && !managedRelationshipInProgress) {
// See above!
setForeignKey(fieldDesc, addedSM, inverseFieldDesc);
}
}
}
/**
* Schedules jointable entries for relationships mapped to jointables.
* The actual creation/removal of the jointable entry is deferred until
* flush. Data store updates for added objects are not processed twice
* during relationship management.
*
* Data store dependencies for the update operations are established.
*
* @param fieldDesc Updated relationship field.
* @param addedSM State manager of the added object.
* @param removedSM State manager of the removed object.
* @param inverseFieldDesc Inverse relationship field.
* @param managedRelationshipInProgress True during relationship management.
*/
private void processJoinTableEntries(ForeignFieldDesc fieldDesc,
SQLStateManager addedSM,
SQLStateManager removedSM,
ForeignFieldDesc inverseFieldDesc,
boolean managedRelationshipInProgress) {
// If the fieldDesc property has the REF_INTEGRITY_UPDATES unset,
// it means we need to update the other side of the relationship.
boolean updateOtherSide = (fieldDesc.sqlProperties & FieldDesc.PROP_REF_INTEGRITY_UPDATES) == 0;
if (updateOtherSide) {
// Schedule the removal of the jointable entry to removedSM.
if (removedSM != null) {
removedSM.removeJoinTableEntry(inverseFieldDesc, this, fieldDesc);
}
// Schedule the jointable entry to the added object.
// Don't schedule the jointable entry twice during relationship management.
if (addedSM != null && !managedRelationshipInProgress) {
addedSM.addJoinTableEntry(inverseFieldDesc, this, fieldDesc);
}
} else {
// Schedule the removal of the jointable entry to removedSM.
if (removedSM != null) {
// Contrary to foreign key relationships, we always have
// to remove the previous jointable entry.
removeJoinTableEntry(fieldDesc, removedSM, inverseFieldDesc);
}
// Schedule the jointable entry to the added object.
// Don't schedule the jointable entry twice during relationship management.
if (addedSM != null && !managedRelationshipInProgress) {
addJoinTableEntry(fieldDesc, addedSM, inverseFieldDesc);
}
}
}
/**
* Updates the (inverse) relationship field for <code>addedSM
* and <code>removedSM.
*
* @param inverseFieldDesc Inverse relationship field.
* @param addedSM State manager of the added object.
* @param removedSM State manager of the removed object.
* @param managedRelationshipInProgress True during relationship management.
* @return State manager managing the previous value of addedSM's
* relationship field.
*/
private SQLStateManager manageRelationshipForObjectField(ForeignFieldDesc inverseFieldDesc,
SQLStateManager addedSM,
SQLStateManager removedSM,
boolean managedRelationshipInProgress) {
Object addedInverseFieldValue = null;
SQLStateManager addedInverseFieldSM = null;
if (removedSM != null) {
removedSM.removeRelationship(inverseFieldDesc, this);
}
if (addedSM != null && !managedRelationshipInProgress) {
addedInverseFieldValue = addedSM.addRelationship(inverseFieldDesc, this);
if (addedInverseFieldValue != null) {
addedInverseFieldSM = (SQLStateManager)
persistenceManager.getStateManager(addedInverseFieldValue);
}
}
return addedInverseFieldSM;
}
/**
* Dependency management for database operations on the one-to-one
* relationships. Establishes a dependency to nullify the foreign key on the
* removed instance before the added instance's foreign key can be set. If
* the relationship is mapped to a jointable, remove the jointable entry to
* the removed instance before the jointable entry on the added can be
* added. This update dependency is valid only for one-to-one relationships,
* because only one-to-one relationships can be enforced by an unique
* index on the foreign key. On the other hand, there's never a state
* manager removed from the relationship for one-to-many relationships.
*
* We would like to do this in updateRelationshipInDataStore,
* but we don't know the added instance's previous value in case of
* updateOtherSide == false there. Here, we can handle this situation
* at one place.
*
* @param fieldDesc Updated relationship field.
* @param addedSM Added state manager.
* @param removedSM Removed state manager.
* @param addedInverseFieldSM
* State manager removed from the relationship,
* if the foreign key is on the local side.
*/
private void manageDependencyForObjectField(ForeignFieldDesc fieldDesc,
SQLStateManager addedSM,
SQLStateManager removedSM,
SQLStateManager addedInverseFieldSM) {
// If the fieldDesc property has the REF_INTEGRITY_UPDATES unset,
// it means data store updates are scheduled on the other side of the relationship.
boolean updateOtherSide = (fieldDesc.sqlProperties & FieldDesc.PROP_REF_INTEGRITY_UPDATES) == 0;
// Nullify the foreign key on the removed object before
// the added object's foreign key can be set.
if (updateOtherSide && removedSM != null && addedSM != null) {
// Add the dependency only if both objects involved
// in the relationship being removed are already persistent.
if (removedSM.state.isPersistentInDataStore()
&& this.state.isPersistentInDataStore()) {
// First parameter == null marks a non removable dependency.
// RESOLVE: Pass inverseFieldDesc here.
removedSM.addUpdatedForeignReference(null, addedSM);
}
}
// If the foreign key is on this side, addedInverseFieldSM
// corresponds to the removedSM and this is the addedSM!
if (!updateOtherSide && addedInverseFieldSM != null) {
// Add the dependency only if both objects involved
// in the relationship being removed are already persistent.
if (addedInverseFieldSM.state.isPersistentInDataStore()
&& addedSM.state.isPersistentInDataStore()) {
// First parameter == null marks a non removable dependency.
// RESOLVE: Pass inverseFieldDesc here.
addedInverseFieldSM.addUpdatedForeignReference(null, this);
}
}
}
/**
* Returns the added object's state manager. Transient objects become
* "autopersistent" on the association to an already persistent instance and
* are associated with a new state manager.
*
* If <code>newlyRegisteredSMs is not null, the newly created state manager
* is added to the list. This list is only non-null for the treatment of deferred
* collection fields, which is done before the actual flush to the data store.
* Autopersistence management for all other cases is handled sufficiently
* in the makeAutoPersistent call.
*
* @param addedObject Object added to a relationship.
* @param newlyRegisteredSMs
* The state managers of autopersistent objects will be added to this list.
* @return State manager for the added object. The statemanager will
* be not null for all persistence capable objects != null.
*/
private SQLStateManager getAddedSM(Object addedObject, ArrayList newlyRegisteredSMs) {
SQLStateManager addedSM = null;
if (addedObject != null) {
// Persistence by reachablity.
if ((addedSM = (SQLStateManager) persistenceManager.getStateManager(addedObject)) == null) {
makeAutoPersistent(addedObject);
addedSM = (SQLStateManager) persistenceManager.getStateManager(addedObject);
// Add the newly created state manager to the newlyRegisteredSMs
// list so we can do further processing on it.
if (newlyRegisteredSMs != null && !newlyRegisteredSMs.contains(addedSM)) {
newlyRegisteredSMs.add(addedSM);
}
}
}
return addedSM;
}
/**
* Returns the removed object's state manager.
*
* @param removedObject Object removed to a relationship.
* @return State manager for the removed object. The state manager might
* be null even for objects != null (in case of JDK collections).
* @see #removeCollectionRelationship
*/
private SQLStateManager getRemovedSM(Object removedObject) {
SQLStateManager removedSM = null;
if (removedObject != null) {
removedSM = (SQLStateManager) persistenceManager.getStateManager(removedObject);
}
return removedSM;
}
/**
* Updates the relationship for the collection side of a one-to-many or
* many-to-many relationship. Objects in <code>removedList are
* removed from the relation. The relation is set on all objects in
* <code>addedList. In case of user updates, relationship management
* on "this" relationship side is done by the user, i.e. by
* d.getEmployees().add(newEmp) for a Department d. This method is never
* called during relationship management.
*
* @param fieldDesc Updated relationship field.
* @param removedList List of objects to be removed from the relationship.
* @param addedList List of objects to be added to the relationship.
* @param newlyRegisteredSMs
* List taking newly registered SMs for objects becoming autopersistent.
* @param updateInverseRelationshipField
* True, if we need to update the inverse relationship field.
* @param managedRelationshipInProgress
* True during relationship management. NOTE: This parameter is
* always false, as the method is never called during relationship management.
* @exception JDOUserException Thrown on failures in <code>afterList handling.
* @see #prepareSetField(FieldDesc,Object,boolean)
*/
private void processCollectionUpdates(ForeignFieldDesc fieldDesc,
ArrayList removedList,
ArrayList addedList,
ArrayList newlyRegisteredSMs,
boolean updateInverseRelationshipField,
boolean managedRelationshipInProgress) {
boolean debug = logger.isLoggable();
ForeignFieldDesc inverseFieldDesc = fieldDesc.getInverseRelationshipField();
// RESOLVE: What if
// * inverseFieldDesc is null?
// * fieldDesc.cardinalityUPB == 1
if (debug) {
Object[] items = new Object[] {removedList,addedList};
logger.fine("sqlstore.sqlstatemanager.processcollectionupdate", items); // NOI18N
}
// removedList contains the list of objects removed.
if (removedList != null) {
removeCollectionRelationship(fieldDesc, removedList, inverseFieldDesc,
updateInverseRelationshipField, managedRelationshipInProgress);
}
// addedList contains the objects added.
if (addedList != null) {
addCollectionRelationship(fieldDesc, addedList, inverseFieldDesc,
newlyRegisteredSMs,
updateInverseRelationshipField, managedRelationshipInProgress);
}
if (debug) {
logger.fine("sqlstore.sqlstatemanager.processcollectionupdate.exit"); // NOI18N
}
}
/**
* Nullifies the relationship for the objects removed from a collection relationship.
*
* @param fieldDesc Updated relationship field.
* @param removedList List of objects to be removed from the relationship.
* @param inverseFieldDesc Inverse relationship field.
* @param updateInverseRelationshipField
* True, if we need to update the inverse relationship side.
* @param managedRelationshipInProgress
* True during relationship management. NOTE: This parameter is always
* false, as the method is never called during relationship management.
* @see #processCollectionUpdates
*/
private void removeCollectionRelationship(ForeignFieldDesc fieldDesc,
ArrayList removedList,
ForeignFieldDesc inverseFieldDesc,
boolean updateInverseRelationshipField,
boolean managedRelationshipInProgress) {
for (int i = 0; i < removedList.size(); i++) {
SQLStateManager removedSM = getRemovedSM(removedList.get(i));
// removedSM == null can happen if the collection is non-SCO and contains
// transient instances which don't become persistent until commit.
if (removedSM != null) {
// The collection side never has the foreign key, i.e.
// it's never processed during relationship management,
// because data store updates are already done.
if (!managedRelationshipInProgress) {
updateRelationshipInDataStore(fieldDesc, null, removedSM, inverseFieldDesc, false);
// Relationship management
if (updateInverseRelationshipField && inverseFieldDesc != null) {
removedSM.removeRelationship(inverseFieldDesc, this);
}
}
}
}
}
/**
* Nullifies the (hidden) local fields corresponding to the foreign key columns
* for relationship field <code>fieldDesc. Usually the foreign key
* columns are not mapped explicitly by the user. For this reason the runtime
* creates hidden fields representing the foreign key columns internally. We
* determine the local fields by iterating appropriate field list of either
* <code>fieldDesc or inverseFieldDesc
.
*
* For dependency management, the removal of the foreign key has
* to be nullified before the _possible_ removal of removedSM. The
* same dependency applies to jointable relationships, see {@link
* #registerRemoveDependency}.
*
* @param fieldDesc Updated relationship field.
* @param removedSM State manager of the removed object.
* @param inverseFieldDesc Inverse relationship field.
* @param setDependencyOnly Only set the dependency between <code>this
* and <code>removedSM.
*/
private void nullifyForeignKey(ForeignFieldDesc fieldDesc,
SQLStateManager removedSM,
ForeignFieldDesc inverseFieldDesc,
boolean setDependencyOnly) {
if (!isDeleted() && !setDependencyOnly) {
// fieldDesc can be null for one-directional relationships. We are
// only interested in the LocalFieldDescs for the foreign key columns,
// which can also be retrieved from inverseFieldDesc.
if (fieldDesc != null) {
for (int i = 0; i < fieldDesc.localFields.size(); i++) {
LocalFieldDesc la = (LocalFieldDesc) fieldDesc.localFields.get(i);
nullifyForeignKey(fieldDesc, la);
}
} else {
for (int i = 0; i < inverseFieldDesc.foreignFields.size(); i++) {
LocalFieldDesc la = (LocalFieldDesc) inverseFieldDesc.foreignFields.get(i);
nullifyForeignKey(fieldDesc, la);
}
}
}
// Nullify the foreign key before the _possible_ removal of removedSM.
registerRemoveDependency(fieldDesc, removedSM);
}
/**
* Actually nullifies the local field <code>la corresponding to a
* foreign key column for the relationship update.
* Fields tracking the foreign key field <code>la are updated.
*
* @param fieldDesc Updated relationship field.
* @param la Local field corresponding to a foreign key column.
* @exception JDOUserException Is thrown, if the field to be updated
* is a primary key field. We don't allow pk updates.
*/
private void nullifyForeignKey(ForeignFieldDesc fieldDesc, LocalFieldDesc la) {
if (!getSetMaskBit(la.absoluteID)) {
prepareUpdateField(la, null);
}
JDOUserException pkUpdateEx = null;
if (la.isKeyField()) {
try {
assertPKUpdate(la, null);
} catch (JDOUserException e) {
// If the relationship being set to null
// and the parent instance is being deleted, we will ignore
// the exception thrown from assertPKUpdate(). The reason is
// that we are really just trying to set up the dependency
// and not modify the relationship itself. If we do not
// ignore this exception, there will be no way to delete
// the parent instance at all because we don't support
// modifying primary key.
if (((stateFlags & ST_DELETE_INPROGRESS) == 0)) {
throw e;
}
pkUpdateEx = e;
}
}
if (pkUpdateEx == null) {
// As la tracks fieldDesc, fieldDesc is ignored in updateTrackedFields.
updateTrackedFields(la, null, fieldDesc);
la.setValue(this, null);
}
}
/**
* Schedules the removal of the jointable entry between this and the
* foreign state manager. A scheduled creation of the jointable entry
* between these two objects is simply removed. The removal is
* scheduled on the local side.
*
* For dependency management, the removal of the jointable entry has
* to precede the _possible_ removal of removedSM. The same dependency applies
* to foreign key relationships, see {@link #registerRemoveDependency}.
*
* RESOLVE: What happens, if a field descriptor is null, e.g. for one
* way relationships, as the descriptors are taken as keys during scheduling?
*
* @param fieldDesc Updated relationship field. This field is mapped to a jointable.
* @param removedSM State manager of the removed object.
* @param inverseFieldDesc Inverse relationship field.
* @see #prepareToUpdatePhaseIII
*/
private void removeJoinTableEntry(ForeignFieldDesc fieldDesc,
SQLStateManager removedSM,
ForeignFieldDesc inverseFieldDesc) {
// Cleanup dependencies. We need to cleanup dependencies for
// flushed autopersistent instances that aren't reachable
// before commit. See runtime test Autopersistence.TestCase12.
// The cleanup must be done for removals only, because the
// only operations executed in prepareToUpdatePhaseIII are
// removals.
if (removedSM.state.isAutoPersistent() || this.state.isAutoPersistent()) {
removedSM.removeUpdatedForeignReference(inverseFieldDesc, this);
this.removeUpdatedForeignReference(fieldDesc, removedSM);
}
// Remove scheduled creation on this side.
if (fieldDesc != null && getUpdateDesc().removeUpdatedJoinTableRelationship(
fieldDesc, removedSM, ActionDesc.LOG_CREATE) == false) {
// Remove scheduled creation on the other side.
if (inverseFieldDesc == null || removedSM.getUpdateDesc().removeUpdatedJoinTableRelationship(
inverseFieldDesc, this, ActionDesc.LOG_CREATE) == false) {
// Schedule removal on this side.
// The field descriptor taken as key must not be null, see above!
getUpdateDesc().recordUpdatedJoinTableRelationship(
fieldDesc, this, removedSM, ActionDesc.LOG_DESTROY);
// Remove the jointable entry before the _possible_ removal of removedSM.
registerRemoveDependency(fieldDesc, removedSM);
}
} else if (fieldDesc == null) {
throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
"core.statemanager.cantschedulejointable", // NOI18N
this.getPersistenceConfig().getPersistenceCapableClass().getName(),
removedSM.getPersistenceConfig().getPersistenceCapableClass().getName()));
}
}
/**
* Updates the relationship field <code>fieldDesc between this
* and <code>removedSM.
*
* @param fieldDesc Updated relationship field. The field must be != null.
* @param removedSM State manager of the removed object.
*/
private void removeRelationship(ForeignFieldDesc fieldDesc, SQLStateManager removedSM) {
boolean isCollection = (fieldDesc.cardinalityUPB > 1);
if (!isCollection) {
prepareUpdateFieldSpecial(fieldDesc, removedSM.persistentObject, false);
updateTrackedFields(fieldDesc, null, null);
fieldDesc.setValue(this, null);
} else {
try {
prepareUpdateFieldSpecial(fieldDesc, null, true);
SCOCollection c = (SCOCollection) fieldDesc.getValue(this);
c.removeInternal(removedSM.persistentObject);
updateTrackedFields(fieldDesc, c, null);
} catch (ClassCastException e) {
// ignore
}
}
}
/**
* Sets the relationship for the objects added to a collection relationship.
*
* @param fieldDesc Updated relationship field.
* @param addedList List of objects to be added to the relationship.
* @param inverseFieldDesc Inverse relationship field.
* @param newlyRegisteredSMs
* State managers for autopersistent objects will be added to this list.
* @param updateInverseRelationshipField
* True, if we need to update the inverse relationship side.
* @param managedRelationshipInProgress
* True during relationship management. NOTE: This parameter is always
* false, as the method is never called during relationship management.
* @exception JDOUserException Thrown if objects in <code>addedList have been deleted.
* @see #processCollectionUpdates
*/
private void addCollectionRelationship(ForeignFieldDesc fieldDesc,
ArrayList addedList,
ForeignFieldDesc inverseFieldDesc,
ArrayList newlyRegisteredSMs,
boolean updateInverseRelationshipField,
boolean managedRelationshipInProgress) {
JDOUserException ex = null;
for (int i = 0; i < addedList.size(); i++) {
Object addedObject = addedList.get(i);
SQLStateManager addedSM = getAddedSM(addedObject, newlyRegisteredSMs);
// addedSM == null can happen if the collection is non-SCO and contains
// transient instances which don't become persistent until commit.
if (addedSM != null) {
if (addedSM.isDeleted()) {
// For managed relationships, if the addedObject is deleted, we need
// to throw an exception at the end and the exception should include
// the deleted objects in its failedObjectArray.
if (inverseFieldDesc != null) {
if (ex == null) {
ex = new JDOUserException(I18NHelper.getMessage(messages,
"jdo.lifecycle.deleted.accessField")); // NOI18N
}
ex.addFailedObject(addedObject);
}
continue;
}
// The collection side never has the foreign key, i.e.
// it's never processed during relationship management,
// because data store updates are already done at that time.
if (!managedRelationshipInProgress) {
updateRelationshipInDataStore(fieldDesc, addedSM, null, inverseFieldDesc, false);
// Relationship management
if (updateInverseRelationshipField && inverseFieldDesc != null) {
addedSM.addRelationship(inverseFieldDesc, this);
}
}
}
}
if (ex != null) {
throw ex;
}
}
/**
* Sets the foreign key corresponding to the relationship field
* <code>fieldDesc. Usually the foreign key columns are not mapped
* explicitly by the user. For this reason the runtime creates hidden fields
* representing the foreign key columns internally. We determine the local
* fields by iterating appropriate field list of either
* <code>fieldDesc or inverseFieldDesc
.
*
* To ensure referentional integrity constraints in the database,
* the added object has to be written to the store, before the
* foreign key can be set. The same dependency applies to relationships
* mapped to jointables, see {@link #addJoinTableEntry}.
*
* @param fieldDesc Updated relationship field.
* @param addedSM State manager of the added object.
* @param inverseFieldDesc Inverse relationship field.
*/
private void setForeignKey(ForeignFieldDesc fieldDesc,
SQLStateManager addedSM,
ForeignFieldDesc inverseFieldDesc) {
if (!isDeleted()) {
// fieldDesc can be null for one-directional relationships. We are
// only interested in the LocalFieldDescs for the foreign key columns,
// which can also be retrieved from inverseFieldDesc.
if (fieldDesc != null) {
for (int i = 0; i < fieldDesc.localFields.size(); i++) {
LocalFieldDesc la = (LocalFieldDesc) fieldDesc.localFields.get(i);
LocalFieldDesc fa = (LocalFieldDesc) fieldDesc.foreignFields.get(i);
setForeignKey(fieldDesc, la, fa.getValue(addedSM));
}
} else {
for (int i = 0; i < inverseFieldDesc.foreignFields.size(); i++) {
LocalFieldDesc la = (LocalFieldDesc) inverseFieldDesc.foreignFields.get(i);
LocalFieldDesc fa = (LocalFieldDesc) inverseFieldDesc.localFields.get(i);
setForeignKey(fieldDesc, la, fa.getValue(addedSM));
}
}
}
// The referred object has to be written to the store before the foreign key can be set.
registerCreateDependency(inverseFieldDesc, addedSM);
}
/**
* Actually sets the local field <code>la corresponding to a foreign
* key column to the new value for the relationship update. The new value
* is taken from <code>fa, which is typically a primary key field on
* the other relationship side. Fields tracking the foreign key field
* <code>la are updated.
*
* @param fieldDesc Updated relationship field.
* @param la Local field corresponding to a foreign key column.
* @param faValue Value of the local field corresponding to the primary
* key column on the other relationship side.
* @exception JDOUserException Is thrown, if the field to be updated
* is a primary key field. We don't allow pk updates.
*/
private void setForeignKey(ForeignFieldDesc fieldDesc,
LocalFieldDesc la,
Object faValue) {
if (!getSetMaskBit(la.absoluteID)) {
prepareUpdateField(la, null);
}
if (la.isKeyField()) {
assertPKUpdate(la, faValue);
}
updateTrackedFields(la, faValue, fieldDesc);
la.setValue(this, faValue);
}
/**
* Schedules the creation of a jointable entry between this and the added
* state manager. A scheduled removal of the jointable entry between these
* two is simply removed. The creation is scheduled on the local side.
*
* For dependency management, the side creating the jointable entry has
* to wait for the other to become persistent. The same dependency applies
* to foreign key relationships, see {@link #setForeignKey}.
*
* RESOLVE: What happens, if a field descriptor is null, e.g. for one
* way relationships, as the descriptors are taken as keys during scheduling?
*
* @param fieldDesc Updated relationship field. This field is mapped to a jointable.
* @param addedSM State manager of the added object.
* @param inverseFieldDesc Inverse relationship field.
*/
private void addJoinTableEntry(ForeignFieldDesc fieldDesc,
SQLStateManager addedSM,
ForeignFieldDesc inverseFieldDesc) {
// Cleanup dependencies.
// Note: The following lines break deadlock detection for circular dependencies.
//this.removeUpdatedForeignReference(addedSM);
//addedSM.removeUpdatedForeignReference(this);
// Remove scheduled removal on this side.
if (fieldDesc != null && getUpdateDesc().removeUpdatedJoinTableRelationship(
fieldDesc, addedSM, ActionDesc.LOG_DESTROY) == false) {
// Remove scheduled removal on the other side.
if (inverseFieldDesc == null || addedSM.getUpdateDesc().removeUpdatedJoinTableRelationship(
inverseFieldDesc, this, ActionDesc.LOG_DESTROY) == false) {
// Schedule creation on this side.
// The field descriptor taken as key must not be null, see above!
getUpdateDesc().recordUpdatedJoinTableRelationship(
fieldDesc, this, addedSM, ActionDesc.LOG_CREATE);
// The side creating the jointable entry has to wait for the other to become persistent.
registerCreateDependency(inverseFieldDesc, addedSM);
}
} else if (fieldDesc == null) {
throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
"core.statemanager.cantschedulejointable", // NOI18N
this.getPersistenceConfig().getPersistenceCapableClass().getName(),
addedSM.getPersistenceConfig().getPersistenceCapableClass().getName()));
}
}
/**
* Updates the relationship field <code>fieldDesc between this
* and the state manager of the added object <code>addedSM.
*
* @param fieldDesc Updated relationship field. The field must be != null.
* @param addedSM State manager of the added object.
* @return Field <code>fieldDesc's previous value.
*/
private Object addRelationship(ForeignFieldDesc fieldDesc,
SQLStateManager addedSM) {
Object previousValue = null;
boolean isCollection = (fieldDesc.cardinalityUPB > 1);
if (!isCollection) {
previousValue = prepareSetField(fieldDesc, addedSM.persistentObject, true);
} else {
try {
prepareUpdateFieldSpecial(fieldDesc, null, true);
SCOCollection c = (SCOCollection) fieldDesc.getValue(this);
// Note: c might be null during relationship management for a
// self relationship, as in the Employee-Manager relation.
// See runtime test AutoPersistence.TestCase31 for an example.
if (c == null) {
replaceCollection(fieldDesc, null);
c = (SCOCollection) fieldDesc.getValue(this);
}
c.addInternal(addedSM.persistentObject);
updateTrackedFields(fieldDesc, c, null);
} catch (ClassCastException e) {
// ignore
}
}
return previousValue;
}
/**
* This is a special version of prepareUpdateField that does not do navigation
* if a field is not loaded. We don't need to reload the field, because the
* before image value is given as parameter! This method is mostly called
* during relatioship management, as the before image value is already know
* in this case.
* <p>
* The <code>createDeferredCollection parameter should be true
* only if <code>fieldDesc corresponds to a collection field. Note:
* <ul>
* <li>createDeferredCollection
== false
* ==> <code>beforeImageValue must be non-null.
* <li>createDeferredCollection
== true
* ==> <code>beforeImageValue is null.
* </ul>.
*
* @param fieldDesc The field to be prepared.
* @param beforeImageValue The before image value.
* @param createDeferredCollection
* Indicates whether to create a deferred SCOCollection. Deferred collections
* are created during relationship management if the inverse field is not
* loaded.
* @see #prepareUpdateField
*/
private synchronized void prepareUpdateFieldSpecial(FieldDesc fieldDesc,
Object beforeImageValue,
boolean createDeferredCollection) {
if (fieldDesc.isKeyField()) {
throw new JDOUnsupportedOptionException(I18NHelper.getMessage(messages,
"core.statemanager.nopkupdate")); // NOI18N
}
getUpdateDesc().markRelationshipChange(fieldDesc);
boolean debug = logger.isLoggable();
if (debug) {
Object[] items = new Object[] {fieldDesc.getName(),state};
logger.fine("sqlstore.sqlstatemanager.prepareupdatefieldspl", items); // NOI18N
}
boolean optimistic = persistenceManager.isOptimisticTransaction();
boolean xactActive = persistenceManager.isActiveTransaction();
boolean nontransactionalRead = persistenceManager.isNontransactionalRead();
if (state.needsReload(optimistic, nontransactionalRead, xactActive)) {
if (!optimistic) {
persistenceManager.clearFields(this.persistentObject);
}
reload(null);
}
LifeCycleState oldstate = state;
state = state.transitionWriteField(xactActive);
registerInstance(false, null, oldstate);
if (getSetMaskBit(fieldDesc.absoluteID)) {
// Note: The set mask is set for all fields on make persistent.
return;
}
if (!getPresenceMaskBit(fieldDesc.absoluteID)) {
if (!createDeferredCollection) {
if (!(beforeImageValue instanceof SCOCollection) ||
!((SCOCollection) beforeImageValue).isDeferred()) {
updateBeforeImage(fieldDesc, beforeImageValue);
// Set the presence mask for a non deferred collection.
setPresenceMaskBit(fieldDesc.absoluteID);
}
} else {
// Deferred collection handling.
if (!(fieldDesc instanceof ForeignFieldDesc) ||
(((ForeignFieldDesc) fieldDesc).cardinalityUPB <= 1)) {
//should throw an exception
return;
}
Object value = fieldDesc.getValue(this);
if (value == null) {
// If the collection field is null, we need to create a
// deferred SCOCollection.
SCOCollection c = (SCOCollection) persistenceManager.newCollectionInstanceInternal(
fieldDesc.getType(),
persistentObject,
fieldDesc.getName(),
fieldDesc.getComponentType(),
false,
10);
c.markDeferred();
fieldDesc.setValue(this, c);
}
// NOTE: We don't set the presence mask bit for deferred collections,
// because deferred collections MUST be reloaded on the first read access!
}
}
recordUpdatedField(fieldDesc);
if (debug) {
logger.fine("sqlstore.sqlstatemanager.prepareupdatefieldspl.exit"); // NOI18N
}
}
/**
* Updates the values for fields tracking field
* <code>fieldDesc. Must be called before the new value
* for field <code>fieldDesc is actually set.
*
* If called when setting the local fields mapped to the
* relationship on relationship updates, the relationship field
* tracked by <code>fieldDesc must be ignored when
* propagating the changes.<p>
*
* For overlapping pk/fk situations or if a fk column is
* explicitly mapped to a visible field, the update of the local
* field triggers the update of the relationship field tracking
* the local field.
*
* @param fieldDesc Field whose tracked fields we wish to update.
* @param value New value for the field.
* @param fieldToIgnore Field to be ignored when propagating
* changes. This is the relationship field tracked by field
* <code>fieldDesc if fieldDesc
is a
* <b>hidden local field.
*/
private void updateTrackedFields(FieldDesc fieldDesc,
Object value,
ForeignFieldDesc fieldToIgnore) {
ArrayList trackedFields = fieldDesc.getTrackedFields();
if (trackedFields == null) {
return;
}
boolean debug = logger.isLoggable(Logger.FINEST);
if (debug) {
Object[] items = new Object[] {fieldDesc.getName(), value,
((fieldToIgnore != null) ? fieldToIgnore.getName() : null)};
logger.finest("sqlstore.sqlstatemanager.updatetrackedfields", items); // NOI18N
}
Object currentValue = fieldDesc.getValue(this);
int size = trackedFields.size();
ArrayList fieldsToIgnore = ((fieldToIgnore != null) ? fieldToIgnore.getTrackedFields() : null);
if (fieldDesc instanceof ForeignFieldDesc) {
// For tracked relationship fields, we simply set the new value.
for (int i = 0; i < size; i++) {
ForeignFieldDesc tf = (ForeignFieldDesc) trackedFields.get(i);
prepareUpdateFieldSpecial(tf, currentValue, false);
tf.setValue(this, value);
}
} else {
Object previousValues[] = new Object[size];
LocalFieldDesc primaryTrackedField = null;
Object primaryTrackedFieldValue = null;
if ((fieldDesc.sqlProperties & FieldDesc.PROP_PRIMARY_TRACKED_FIELD) > 0) {
primaryTrackedField = (LocalFieldDesc) fieldDesc;
primaryTrackedFieldValue = value;
}
for (int i = 0; i < size; i++) {
FieldDesc tf = (FieldDesc) trackedFields.get(i);
if (tf instanceof LocalFieldDesc) {
Object convertedValue = null;
Object convertedCurrentValue = null;
// RESOLVE: SCODate is problematic because convertValue unsets
// the owner. The SCO to be used for restoring is broken.
try {
convertedValue = tf.convertValue(value, this);
convertedCurrentValue = tf.convertValue(currentValue, this);
} catch (JDOUserException e) {
// We got a conversion error. We need to revert all
// the tracked fields to their previous values.
// NOTE: We don't have to revert relationship fields
// because they come after all the primitive fields.
for (int j = 0; j < i; j++) {
tf = (FieldDesc) trackedFields.get(j);
tf.setValue(this, previousValues[j]);
}
throw e;
}
if ((tf.sqlProperties & FieldDesc.PROP_PRIMARY_TRACKED_FIELD) > 0) {
primaryTrackedField = (LocalFieldDesc) tf;
primaryTrackedFieldValue = convertedValue;
}
prepareUpdateFieldSpecial(tf, convertedCurrentValue, false);
// save the previous values for rollback
previousValues[i] = tf.getValue(this);
tf.setValue(this, convertedValue);
} else {
// We bypass fieldToIgnore and its trackedFields
if (((stateFlags & ST_FIELD_TRACKING_INPROGRESS) > 0)
|| (tf == fieldToIgnore)
|| ((fieldsToIgnore != null) && fieldsToIgnore.contains(tf))) {
continue;
}
ForeignFieldDesc ftf = (ForeignFieldDesc) tf;
Object pc = null;
if (primaryTrackedFieldValue != null) {
pc = getObjectById(ftf, primaryTrackedField, primaryTrackedFieldValue, false);
}
stateFlags |= ST_FIELD_TRACKING_INPROGRESS;
prepareSetField(ftf, pc);
stateFlags &= ~ST_FIELD_TRACKING_INPROGRESS;
}
}
}
if (debug) {
logger.finest("sqlstore.sqlstatemanager.updatetrackedfields.exit"); // NOI18N
}
}
/**
* Looks up the object associated to this state manager on
* relationship <code>ff field in the persistence manager cache.
* The method first constructs the related instance's object id by
* calling {@link ForeignFieldDesc#createObjectId}. Then asks the
* persistence manager to retrieve the object associated to this
* id from it's caches. If the referred object is not found, the
* instance returned by {@link PersistenceManager#getObjectById(Object)}
* is Hollow. Hollow instances are ignored for navigation.
*
* @param ff Relationship to be retrieved. The relationship must have
* an object ("to one side") value.
* @param updatedField Updated local field mapped to this relationship.
* @param value <code>updatedField's new value.
* @param forNavigation If true, the lookup is executed for navigation.
* @return Object found in the cache. Null, if the object wasn't found.
*/
private Object getObjectById(ForeignFieldDesc ff,
LocalFieldDesc updatedField,
Object value,
boolean forNavigation) {
assert ff.cardinalityUPB <=1;
// If called for navigation updatedField and value should be null.
assert forNavigation ? updatedField == null && value == null : true;
Object rc = null;
Object oid = ff.createObjectId(this, updatedField, value);
if (oid != null) {
rc = persistenceManager.getObjectById(oid);
LifeCycleState rcState = ((SQLStateManager)
((PersistenceCapable) rc).jdoGetStateManager()).state;
if (forNavigation && (rcState instanceof Hollow)) {
rc = null;
}
}
return rc;
}
/**
* Sets field <code>fieldDesc to value
.
* The update of relationship fields is triggered by either calling
* {@link #updateCollectionField} or {@link #updateObjectField},
* depending on the field's cardinality.
*
* @param fieldDesc Field to be updated.
* @param value New value.
* @param managedRelationshipInProgress
* True during relationship management.
* @return Field <code>fieldDesc's previous value.
*/
private Object doUpdateField(FieldDesc fieldDesc,
Object value,
boolean managedRelationshipInProgress) {
prepareUpdateField(fieldDesc, null);
if (fieldDesc instanceof ForeignFieldDesc) {
ForeignFieldDesc ff = (ForeignFieldDesc) fieldDesc;
if (ff.cardinalityUPB > 1) {
updateCollectionField(ff, (Collection) value,
managedRelationshipInProgress);
} else {
updateObjectField(ff, value, true,
managedRelationshipInProgress);
}
}
updateTrackedFields(fieldDesc, value, null);
Object currentValue = fieldDesc.getValue(this);
fieldDesc.setValue(this, value);
return currentValue;
}
private Object prepareSetField(int fieldID, Object value) {
FieldDesc fieldDesc = persistenceConfig.getField(fieldID);
return prepareSetField(fieldDesc, value, false, true);
}
private Object prepareSetField(FieldDesc fieldDesc, Object value) {
return prepareSetField(fieldDesc, value, false, false);
}
/**
* Internal method setting the new value <code>value for field
* <code>fieldDesc during relationship management. Only called on
* relationship additions for object fields. This method is never called
* for collection fields. Relationship management for collection fields
* is done by deferred collections in {@link #addRelationship}.
* Deferred collections are implemented in <code>SCOCollection.
* Relationship management on relationship removal is done in
* {@link #removeRelationship} for both object and collection fields.
*
* @param fieldDesc Field to be updated.
* @param value New value.
* @param managedRelationshipInProgress Always true.
* @return Field <code>fieldDesc's previous value.
* @see com.sun.jdo.spi.persistence.support.sqlstore.SCOCollection
*/
private Object prepareSetField(FieldDesc fieldDesc, Object value,
boolean managedRelationshipInProgress) {
return prepareSetField(fieldDesc, value, managedRelationshipInProgress, false);
}
/**
* Sets field <code>fieldDesc by calling
* {@link SQLStateManager#doUpdateField(FieldDesc, Object, boolean)}.
*
* @param fieldDesc Field to be updated.
* @param value New value.
* @param managedRelationshipInProgress
* True during relationship management.
* @param acquireShareLock Acquire a shared lock during the update.
* @return Field <code>fieldDesc's previous value.
*/
private Object prepareSetField(FieldDesc fieldDesc, Object value,
boolean managedRelationshipInProgress,
boolean acquireShareLock) {
boolean debug = logger.isLoggable(Logger.FINEST);
if (debug) {
logger.finest("sqlstore.sqlstatemanager.preparesetfield", fieldDesc.getName()); // NOI18N
}
if (acquireShareLock) {
persistenceManager.acquireShareLock();
}
try {
getLock();
if ((fieldDesc.sqlProperties & FieldDesc.PROP_READ_ONLY) > 0) {
throw new JDOUserException(I18NHelper.getMessage(messages,
"core.statemanager.readonly", fieldDesc.getName(), // NOI18N
persistentObject.getClass().getName()));
}
// We need to lock fieldUpdateLock if there is a chance that
// relationship field values might be affected. This is the case if
// fieldDesc is a relationship field or it tracks other fields.
if ((fieldDesc.getTrackedFields() != null) ||
(fieldDesc instanceof ForeignFieldDesc)) {
persistenceManager.acquireFieldUpdateLock();
try {
return doUpdateField(fieldDesc, value, managedRelationshipInProgress);
} finally {
persistenceManager.releaseFieldUpdateLock();
}
} else {
return doUpdateField(fieldDesc, value, managedRelationshipInProgress);
}
} catch (JDOException e) {
throw e;
} catch (Exception e) {
logger.log(Logger.FINE,"sqlstore.exception.log", e);
throw new JDOFatalInternalException(I18NHelper.getMessage(messages,
"core.statemanager.setfieldfailed"), e); // NOI18N
} finally {
if (acquireShareLock) {
persistenceManager.releaseShareLock();
}
releaseLock();
if (debug) {
logger.finest("sqlstore.sqlstatemanager.preparesetfield.exit"); // NOI18N
}
}
}
/**
* Sets the new value for the collection field by calling
* {@link #processCollectionUpdates}.
*
* @param fieldDesc Field descriptor of the field to be set.
* @param value New value.
* @param managedRelationshipInProgress
* True during relationship management.
*/
private void updateCollectionField(ForeignFieldDesc fieldDesc,
Collection value,
boolean managedRelationshipInProgress) {
boolean debug = logger.isLoggable(Logger.FINEST);
if (debug) {
Object[] items = new Object[] {value,((value == null)? "NO" : value.getClass().getName())}; // NOI18N
logger.finest("sqlstore.sqlstatemanager.processforeignfield", items); // NOI18N
}
Object currVal = fieldDesc.getValue(this);
// Do nothing if the current value is identical to the new value.
if (currVal != value) {
Object owner = null;
ArrayList added = null;
ArrayList removed = null;
// Verify SCO owner and fieldName if any
if (value != null && value instanceof SCOCollection) {
SCOCollection sco = (SCOCollection) value;
owner = sco.getOwner();
if (owner == null) {
sco.setOwner(persistentObject, fieldDesc.getName(),
fieldDesc.getComponentType());
} else if (owner != persistentObject ||
!fieldDesc.getName().equals(sco.getFieldName())) {
throw new JDOUserException(I18NHelper.getMessage(
messages, "core.statemanager.anotherowner"), // NOI18N
new Object[]{owner, sco.getFieldName()});
}
// SCO should not behave as a JDK collection,
// but become owned and tracked at setXXX operation.
added = new ArrayList(value);
}
Object befrVal = fieldDesc.getValue(beforeImage);
if (currVal != null) {
if (debug)
logger.finest("sqlstore.sqlstatemanager.processforeignfield.remove"); // NOI18N
// This is a setXXX (i.e. replace) operation, we need to
// "remove" elements from the current SCOCollection and mark it as not used
if (((Collection) currVal).size() > 0) {
removed = new ArrayList((Collection) currVal);
}
if (currVal instanceof SCOCollection) {
if (debug)
logger.finest("sqlstore.sqlstatemanager.processforeignfield.reset"); // NOI18N
// SCOCollection: mark it as not used
((SCO) currVal).unsetOwner();
}
} else if (getSetMaskBit(fieldDesc.absoluteID) == false && befrVal != null)
// && value instanceof SCOCollection && owner != null)
{
if (debug)
logger.finest("sqlstore.sqlstatemanager.processforeignfield.remove_from_bi"); // NOI18N
// Replace with SCOCollection: mark beforeImage as removed
if (((Collection) befrVal).size() > 0) {
removed = new ArrayList((Collection) befrVal);
}
}
processCollectionUpdates(fieldDesc, removed, added, null, true,
managedRelationshipInProgress);
}
}
public Object clone() {
SQLStateManager clone = new SQLStateManager(store, persistenceConfig);
clone.persistenceManager = persistenceManager;
return clone;
}
private void assertNotPK(int fieldNumber) {
if (persistenceConfig.isPKField(fieldNumber))
throw new JDOUnsupportedOptionException(I18NHelper.getMessage(messages,
"core.statemanager.nopkupdate")); // NOI18N
}
private void assertPKUpdate(FieldDesc f, Object value) {
Object currentValue = f.getValue(this);
boolean throwException = false;
// We only throw an exception if the new value is actually different from
// the current value.
if ((value != null) && (currentValue != null)) {
if (value.toString().compareTo(currentValue.toString()) != 0) {
throwException = true;
}
} else if (value != currentValue) {
throwException = true;
}
if (throwException) {
throw new JDOUnsupportedOptionException(I18NHelper.getMessage(messages,
"core.statemanager.nopkupdate")); // NOI18N
}
}
/**
* ...
*/
public com.sun.jdo.api.persistence.support.PersistenceManager getPersistenceManagerInternal() {
return persistenceManager;
}
/**
* ...
*/
public com.sun.jdo.api.persistence.support.PersistenceManager getPersistenceManager() {
return (persistenceManager == null)? null : persistenceManager.getCurrentWrapper();
}
/**
* ...
*/
// !!! olsen: changed to return byte instead of void (->PC.jdoSetFlags())
public byte setFlags(byte flags) {
// RESOLVE: Need to verify that the flags are valid with the current
// state of the state manager.
return flags;
}
/**
* Triggers the state transition for READ and registers the
* instance in the transaction cache.
*/
public void loadForRead() {
boolean debug = logger.isLoggable(Logger.FINER);
if (debug) {
logger.finer("sqlstore.sqlstatemanager.loadforread"); // NOI18N
}
persistenceManager.acquireShareLock();
try {
getLock();
byte oldFlags = persistenceManager.getFlags(persistentObject);
// If the jdoFlag is either READ_OK or READ_WRITE_OK, that means another
// thread might have already call loadForRead on this instance.
if (oldFlags != LOAD_REQUIRED) {
return;
}
try {
boolean xactActive = persistenceManager.isActiveTransaction();
boolean optimistic = persistenceManager.isOptimisticTransaction();
boolean nontransactionalRead = persistenceManager.isNontransactionalRead();
if (state.needsReload(optimistic, nontransactionalRead, xactActive)) {
reload(null);
}
LifeCycleState oldstate = state;
state = state.transitionReadField(optimistic, nontransactionalRead, xactActive);
persistenceManager.setFlags(persistentObject, READ_OK);
registerInstance(false, null, oldstate);
} catch (JDOException e) {
// restore the jdoFlags.
persistenceManager.setFlags(persistentObject, oldFlags);
throw e;
}
} finally {
persistenceManager.releaseShareLock();
releaseLock();
if (debug) {
logger.finer("sqlstore.sqlstatemanager.loadforread.exit"); // NOI18N
}
}
}
/**
* Triggers the state transition for WRITE and registers the instance
* in the transaction cache. Prepares all DFG fields for update.
*/
public void loadForUpdate() {
boolean debug = logger.isLoggable(Logger.FINER);
if (debug) {
logger.finer("sqlstore.sqlstatemanager.loadforupdate"); // NOI18N
}
persistenceManager.acquireShareLock();
try {
getLock();
byte oldFlags = persistenceManager.getFlags(persistentObject);
// If the jdoFlags is already set to READ_WRITE_OK, it means that anther
// thread has called loadForUpdate on this instance.
if (oldFlags == READ_WRITE_OK) {
return;
}
persistenceManager.setFlags(persistentObject, READ_WRITE_OK);
ArrayList fields = persistenceConfig.fields;
try {
// Mark all the fields in the dfg dirty.
for (int i = 0; i < fields.size(); i++) {
FieldDesc f = (FieldDesc) fields.get(i);
if (f.fetchGroup == FieldDesc.GROUP_DEFAULT) {
//prepareSetField(f, null);
prepareUpdateField(f, null);
}
}
} catch (JDOException e) {
// restore the jdoFlags.
persistenceManager.setFlags(persistentObject, oldFlags);
throw e;
}
} finally {
persistenceManager.releaseShareLock();
releaseLock();
if (debug) {
logger.finer("sqlstore.sqlstatemanager.loadforupdate.exit"); // NOI18N
}
}
}
/**
* This method serves two purposes:
* 1. If the field value is null or contains a non-SCOCollection instance, it
* creates a new SCOCollection and populates with elements in c.
* 2. If the field value is a SCOCollection instance, then if it is deferred,
* it calls applyDeferredUpdates on the collection passing in c. Otherwise,
* it clears the collection and repopulates with elements in c.
*/
public synchronized void replaceCollection(ForeignFieldDesc ff, Collection c) {
Collection collection = (Collection) ff.getValue(this);
SCOCollection scoCollection = null;
if ((collection == null) || !(collection instanceof SCO)) {
scoCollection = (SCOCollection) persistenceManager.newCollectionInstanceInternal(
ff.getType(), persistentObject, ff.getName(),
ff.getComponentType(), false, ((c != null) ? c.size() : 0));
ff.setValue(this, scoCollection);
scoCollection.addAllInternal(c);
} else {
scoCollection = (SCOCollection) collection;
if (scoCollection.isDeferred()) {
scoCollection.applyDeferredUpdates(c);
// We need to mark all the tracked fields as present.
ArrayList trackedFields = ff.getTrackedFields();
if (trackedFields != null) {
for (int i = 0; i < trackedFields.size(); i++) {
ForeignFieldDesc tf = (ForeignFieldDesc) trackedFields.get(i);
setPresenceMaskBit(tf.absoluteID);
}
}
} else {
scoCollection.clearInternal();
scoCollection.addAllInternal(c);
}
}
// Should not use old collection as SCO if any
if (c != null && c instanceof SCO) {
((SCO) c).unsetOwner();
}
}
/**
* For test purposes
*/
protected LifeCycleState getCurrentState() {
return state;
}
// Status interrogation methods
// For each one of these methods, there is a corresponding version
// of it prefixed with jdo on the PersistenceCapable class. These
// methods are used to query the state o an instance. For example,
// when jdoIsReadReady is called on the PersistenceCapable
// instance, the generated <code>jdoIsReadReady will delegate the
// status interrogation to the <code>StateManager by call
// <code>isReadReady().
/**
* ...
*/
public boolean isDirty() {
if (state != null) {
return state.isDirty();
}
return false;
}
/**
* ...
*/
public boolean isTransactional() {
if (state != null) {
return state.isTransactional();
}
return false;
}
/**
* ...
*/
public boolean isNew() {
if (state != null) {
return state.isNew();
}
return false;
}
/**
* ...
*/
public boolean isDeleted() {
if (state != null) {
return state.isDeleted();
}
return false;
}
/**
* ...
*/
public boolean isPersistent() {
if (state != null) {
return state.isPersistent();
}
return false;
}
/**
* @inheritDoc
*/
public boolean needsRegisterWithVersionConsistencyCache() {
boolean rc = hasVersionConsistency();
if (rc && state != null) {
rc = state.isPersistent()
&& state.isTransactional()
&& !state.isNew()
&& !state.isDirty()
&& !state.isDeleted();
}
return rc;
}
/**
* @inheritDoc
*/
public boolean needsUpdateInVersionConsistencyCache() {
boolean rc = hasVersionConsistency();
if (rc && state != null) {
rc = (state.isDirty()
|| state.isNew()
|| persistenceConfig.hasLocalNonDFGFields())
&& !state.isDeleted();
}
return rc;
}
// Setter methods
// These are methods for accessing the persistent field values
// from the <code>StateManager. The setter methods can also
// serve as the hook for keeping track of changes made to the
// <code>StateManager.
public boolean setBooleanField(int fieldNumber, boolean value) {
assertNotPK(fieldNumber);
prepareSetField(fieldNumber, new Boolean(value));
return value;
}
public boolean[] setBooleanArrayField(int fieldNumber, boolean[] value) {
prepareSetField(fieldNumber, null);
return value;
}
public byte setByteField(int fieldNumber, byte value) {
assertNotPK(fieldNumber);
prepareSetField(fieldNumber, new Byte(value));
return value;
}
public byte[] setByteArrayField(int fieldNumber, byte[] value) {
prepareSetField(fieldNumber, null);
return value;
}
public short setShortField(int fieldNumber, short value) {
assertNotPK(fieldNumber);
prepareSetField(fieldNumber, new Short(value));
return value;
}
public short[] setShortArrayField(int fieldNumber, short[] value) {
prepareSetField(fieldNumber, null);
return value;
}
public int setIntField(int fieldNumber, int value) {
assertNotPK(fieldNumber);
prepareSetField(fieldNumber, new Integer(value));
return value;
}
public int[] setIntArrayField(int fieldNumber, int[] value) {
prepareSetField(fieldNumber, null);
return value;
}
public long setLongField(int fieldNumber, long value) {
assertNotPK(fieldNumber);
prepareSetField(fieldNumber, new Long(value));
return value;
}
public long[] setLongArrayField(int fieldNumber, long[] value) {
prepareSetField(fieldNumber, null);
return value;
}
public char setCharField(int fieldNumber, char value) {
assertNotPK(fieldNumber);
prepareSetField(fieldNumber, new Character(value));
return value;
}
public char setCharArrayField(int fieldNumber, char value) {
prepareSetField(fieldNumber, null);
return value;
}
public float setFloatField(int fieldNumber, float value) {
assertNotPK(fieldNumber);
prepareSetField(fieldNumber, new Float(value));
return value;
}
public float[] setFloatArrayField(int fieldNumber, float[] value) {
prepareSetField(fieldNumber, null);
return value;
}
public double setDoubleField(int fieldNumber, double value) {
assertNotPK(fieldNumber);
prepareSetField(fieldNumber, new Double(value));
return value;
}
public double[] setDoubleArrayField(int fieldNumber, double[] value) {
prepareSetField(fieldNumber, null);
return value;
}
public String setStringField(int fieldNumber, String value) {
assertNotPK(fieldNumber);
prepareSetField(fieldNumber, value);
return value;
}
public String[] setStringArrayField(int fieldNumber, String[] value) {
prepareSetField(fieldNumber, null);
return value;
}
/**
* This method sets object fields, e.g. relationship fields.
*/
public Object setObjectField(int fieldNumber, Object value) {
assertNotPK(fieldNumber);
prepareSetField(fieldNumber, value);
return value;
}
public Object[] setObjectArrayField(int fieldNumber, Object[] value) {
prepareSetField(fieldNumber, value);
return value;
}
public boolean testIsLoaded(int fieldNumber) {
return getPresenceMaskBit(fieldNumber);
}
public boolean testIsLoaded(String fieldName) {
FieldDesc f = persistenceConfig.getField(fieldName);
return testIsLoaded(f.absoluteID);
}
public boolean testIsAutoPersistent() {
return state.isAutoPersistent();
}
/**
* Marks this instance needs to require registering with the global (weak) cache
* at rollback if it transitions to persistent state.
* Used for replacing a deleted instance with the newly persistent with
* the same object id.
*/
public void markNotRegistered() {
needsRegisterAtRollback = true;
}
/**
* Marks this instance as needs to be verified at the time it is removed from the
* global (weak) cache at rollback if it transitions to transient state.
*/
public void markVerifyAtDeregister() {
needsVerifyAtDeregister = true;
}
/**
* Marks this instance as a replacement for a deleted instance with the same
* ObjectId.
*/
public void markReplacement() {
isReplacementInstance = true;
}
/**
* Lock this instance.
*/
// For consistency's sake, this should be changed to acquireLock.
public void getLock() {
lock.acquire();
}
/**
* Release lock.
*/
public void releaseLock() {
lock.release();
}
/**
* Return value for valid flag.
*/
public boolean isValid() {
return valid;
}
/**
* Mark this StateManager as valid. Called before returning from
* getObjectById.
*/
public void setValid() {
try {
getLock();
valid = true;
} finally {
releaseLock();
}
}
/**
* This class stores a database dependency between the current and
* a foreign state manager. To resolve dependencies before
* commit/flush, remember the relationship field intorducing this
* dependency.
*/
private class UpdatedForeignReference {
ForeignFieldDesc fieldDesc;
SQLStateManager sm;
private UpdatedForeignReference(ForeignFieldDesc fieldDesc, SQLStateManager sm) {
this.fieldDesc = fieldDesc;
this.sm = sm;
}
private ForeignFieldDesc getFieldDesc() {
return fieldDesc;
}
private SQLStateManager getStateManager() {
return sm;
}
public boolean equals(Object obj) {
if (obj != null &&
this.getClass().equals(obj.getClass())) {
final UpdatedForeignReference other = (UpdatedForeignReference) obj;
return (this.fieldDesc == other.fieldDesc && this.sm == other.sm);
}
return (false);
}
public int hashCode() {
int hashCode = sm.hashCode();
if (fieldDesc != null) {
hashCode += fieldDesc.hashCode();
}
return hashCode;
}
}
}
Other Glassfish examples (source code examples)
Here is a short list of links related to this Glassfish SQLStateManager.java source code file: