/*
* Copyright 2005 Werner Guttmann
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* $Id: PersistanceCapableRelationResolver.java 7902 2008-09-23 20:25:51Z rjoachim $
*/
package org.castor.persist.resolver;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.castor.persist.ProposedEntity;
import org.castor.persist.TransactionContext;
import org.castor.persist.UpdateAndRemovedFlags;
import org.castor.persist.UpdateFlags;
import org.castor.persist.proxy.LazyCGLIB;
import org.castor.persist.proxy.SingleProxy;
import org.exolab.castor.jdo.DuplicateIdentityException;
import org.exolab.castor.jdo.ObjectNotFoundException;
import org.exolab.castor.jdo.PersistenceException;
import org.exolab.castor.mapping.AccessMode;
import org.exolab.castor.persist.ClassMolder;
import org.exolab.castor.persist.ClassMolderHelper;
import org.exolab.castor.persist.FieldMolder;
import org.exolab.castor.persist.OID;
import org.exolab.castor.persist.spi.Identity;
/**
* Implementation of {@link org.castor.persist.resolver.ResolverStrategy} for 1:1 relations.
*
* @author <a href="mailto:werner DOT guttmann AT gmx DOT net">Werner Guttmann</a>
* @since 0.9.9
*/
public final class PersistanceCapableRelationResolver implements ResolverStrategy {
/**
* Class molder of the enclosing class.
*/
private ClassMolder _classMolder;
/**
* Field molder for the field to be resolved.
*/
private FieldMolder _fieldMolder;
private int _fieldIndex;
/**
* Indicates whether debug mode is active.
*/
//TODO [WG]: Investigate about its use ....
private boolean _debug;
/**
* Creates an instance of this resolver class.
* @param classMolder Enclosing class molder.
* @param fieldMolder Field Molder
* @param fieldIndex Field index within all fields of parent class molder.
* @param debug True if debug mode is on.
*/
public PersistanceCapableRelationResolver (final ClassMolder classMolder,
final FieldMolder fieldMolder,
final int fieldIndex,
final boolean debug) {
_classMolder = classMolder;
_fieldMolder = fieldMolder;
_fieldIndex = fieldIndex;
_debug = debug;
}
/**
* Common Log instance.
*/
private static final Log LOG = LogFactory.getLog (PersistanceCapableRelationResolver.class);
/**
* @see org.castor.persist.resolver.ResolverStrategy#create(
* org.castor.persist.TransactionContext, java.lang.Object)
*/
public Object create(final TransactionContext tx, final Object object) {
Object field = null;
ClassMolder fieldClassMolder = _fieldMolder.getFieldClassMolder();
Object o = _fieldMolder.getValue(object, tx.getClassLoader());
if (o != null) {
Object fid = fieldClassMolder.getIdentity(tx, o);
if (fid != null) {
field = fid;
}
}
return field;
}
/**
* @see org.castor.persist.resolver.ResolverStrategy#markCreate(
* org.castor.persist.TransactionContext, org.exolab.castor.persist.OID,
* java.lang.Object)
*/
public boolean markCreate(final TransactionContext tx, final OID oid, final Object object)
throws PersistenceException {
// create dependent object if exists
boolean updateCache = false;
ClassMolder fieldClassMolder = _fieldMolder.getFieldClassMolder();
Object o = _fieldMolder.getValue(object, tx.getClassLoader());
if (o != null) {
if (_fieldMolder.isDependent()) {
if (!tx.isRecorded(o)) {
tx.markCreate(fieldClassMolder, o, oid);
if (!_fieldMolder.isStored() && fieldClassMolder.isKeyGenUsed()) {
updateCache = true;
}
} else {
// fail-fast principle: if the object depend on another object,
// throw exception
if (!tx.isDepended(oid, o)) {
throw new PersistenceException(
"Dependent object may not change its master. Object: " + o
+ " new master: " + oid);
}
}
} else if (tx.isAutoStore()) {
if (!tx.isRecorded(o)) {
tx.markCreate(fieldClassMolder, o, null);
if (!_fieldMolder.isStored() && fieldClassMolder.isKeyGenUsed()) {
updateCache = true;
}
}
}
}
return updateCache;
}
/**
* @see org.castor.persist.resolver.ResolverStrategy#preStore(
* org.castor.persist.TransactionContext, org.exolab.castor.persist.OID,
* java.lang.Object, int, java.lang.Object)
*/
public UpdateFlags preStore(final TransactionContext tx, final OID oid,
final Object object, final int timeout, final Object field)
throws PersistenceException {
UpdateFlags flags = new UpdateFlags();
ClassMolder fieldClassMolder = _fieldMolder.getFieldClassMolder();
Object value = _fieldMolder.getValue(object, tx.getClassLoader());
Identity curIdentity = (Identity) field;
Identity newIdentity = null;
if (value != null) {
newIdentity = fieldClassMolder.getIdentity(tx, value);
flags.setNewField(newIdentity);
}
// | yip: don't delete the following comment,
// until it proved working by time. :-P
// if ids are the same or not canCreate
// if object is deleted
// warn
// if object are the same
// done
// not the same
// exception
// ids not the same or canCreate
// if depend
// if old is not null
// delete old
// removeRelation
// if canCreate
// create new
// not depend and autoStore
// if old is not null
// removeRelation
// if canCreate
// createObject
// not depend nor autoStore
// if old is not null
// removeRelation
// if new is not null
if (ClassMolderHelper.isEquals(curIdentity, newIdentity)) {
/*
* Let's deal with a situation where there's no dependent object (field == null),
* a 'new' dependent object has been set (value != null), but as we are using a key
* generator on this newly set object, calling fieldClassMolder.getIdentity() will
* return null (and hence newField == null). In this case, we still have to mark this
* new object for creation and instruct Castor to update the cache(s) as well.
*/
if ((field == null) && (value != null)
&& _fieldMolder.isDependent() && !tx.isRecorded(value)) {
if (_fieldMolder.isStored() && _fieldMolder.isCheckDirty()) {
flags.setUpdatePersist(true);
}
flags.setUpdateCache(true);
tx.markCreate(fieldClassMolder, value, oid);
}
//TODO [WG]: can anybody please explain to me the meaning of the next two lines.
if (!_debug) { return flags; }
if (curIdentity == null) { return flags; } // do the next field if both are null
if ((value != null) && tx.isDeleted(value)) {
LOG.warn ("Deleted object found!");
if (_fieldMolder.isStored() && _fieldMolder.isCheckDirty()) {
flags.setUpdatePersist(true);
}
flags.setUpdateCache(true);
_fieldMolder.setValue(object, null, tx.getClassLoader());
return flags;
}
if (tx.isAutoStore() || _fieldMolder.isDependent()) {
if (value != tx.fetch(fieldClassMolder, curIdentity, null)) {
throw new DuplicateIdentityException("");
}
}
} else {
if (_fieldMolder.isStored() /* && _fieldMolder.isCheckDirty() */) {
flags.setUpdatePersist(true);
}
flags.setUpdateCache(true);
if (_fieldMolder.isDependent()) {
if (curIdentity != null) {
Object reldel = tx.fetch(fieldClassMolder, curIdentity, null);
if (reldel != null) {
tx.delete(reldel);
}
}
if ((value != null) && !tx.isRecorded(value)) {
tx.markCreate(fieldClassMolder, value, oid);
}
} else if (tx.isAutoStore()) {
if (curIdentity != null) {
Object deref = tx.fetch(fieldClassMolder, curIdentity, null);
if (deref != null) {
fieldClassMolder.removeRelation(tx, deref, _classMolder, object);
}
}
if ((value != null) && !tx.isRecorded(value)) {
tx.markCreate(fieldClassMolder, value, null);
}
} else {
if (curIdentity != null) {
Object deref = tx.fetch(fieldClassMolder, curIdentity, null);
if (deref != null) {
fieldClassMolder.removeRelation(tx, deref, _classMolder, object);
}
}
// yip: user're pretty easily to run into cache
// integrity problem here, if user forgot to create
// "value" explicitly. We autoload the value for him.
if ((value != null) && !tx.isRecorded(value)) {
Identity fieldValue = fieldClassMolder.getIdentity(tx, value);
if (fieldValue != null) {
ProposedEntity temp = new ProposedEntity(fieldClassMolder);
tx.load(fieldValue, temp, null);
_fieldMolder.setValue(object, temp.getEntity(), tx.getClassLoader());
} else {
throw new PersistenceException(
"Object, " + object + ", links to another object, " + value
+ " that is not loaded/updated/created in this transaction");
}
}
}
}
return flags;
}
/**
* @see org.castor.persist.resolver.ResolverStrategy#store(
* org.castor.persist.TransactionContext, java.lang.Object, java.lang.Object)
*/
public Object store(final TransactionContext tx, final Object object, final Object field) {
Object newField = null;
if (_fieldMolder.isStored()) {
ClassMolder fieldClassMolder = _fieldMolder.getFieldClassMolder();
Object value = _fieldMolder.getValue(object, tx.getClassLoader());
if (value != null) {
newField = fieldClassMolder.getIdentity(tx, value);
}
}
return newField;
}
/**
* @see org.castor.persist.resolver.ResolverStrategy#update(
* org.castor.persist.TransactionContext, org.exolab.castor.persist.OID,
* java.lang.Object, org.exolab.castor.mapping.AccessMode, java.lang.Object)
*/
public void update(final TransactionContext tx, final OID oid, final Object object,
final AccessMode suggestedAccessMode, final Object field) throws PersistenceException {
Identity nfield = (Identity) field;
ClassMolder fieldClassMolder = _fieldMolder.getFieldClassMolder();
Object o = _fieldMolder.getValue(object, tx.getClassLoader());
if (_fieldMolder.isDependent()) {
// depedent class won't have persistenceInfo in LockEngine
// must look at fieldMolder for it
if ((o != null) && !tx.isRecorded(o)) {
tx.markUpdate(fieldClassMolder, o, oid);
}
// load the cached dependent object from the data store.
// The loaded will be compared with the new one
if (nfield != null) {
ProposedEntity proposedValue = new ProposedEntity(fieldClassMolder);
tx.load(nfield, proposedValue, suggestedAccessMode);
}
} else if (tx.isAutoStore()) {
if ((o != null) && !tx.isRecorded(o)) {
tx.markUpdate(fieldClassMolder, o, null);
}
if (nfield != null) {
ProposedEntity proposedValue = new ProposedEntity(fieldClassMolder);
tx.load(nfield, proposedValue, suggestedAccessMode);
}
}
}
/**
* @see org.castor.persist.resolver.ResolverStrategy#updateCache(
* org.castor.persist.TransactionContext, org.exolab.castor.persist.OID,
* java.lang.Object)
*/
public Object updateCache(final TransactionContext tx, final OID oid, final Object object) {
Object field = null;
ClassMolder fieldClassMolder = _fieldMolder.getFieldClassMolder();
Object value = _fieldMolder.getValue(object, tx.getClassLoader());
if (value != null) {
Object fid = fieldClassMolder.getIdentity(tx, value);
if (_fieldMolder.isLazy() && (value instanceof LazyCGLIB)) {
boolean hasMaterialized =
((LazyCGLIB) value).interceptedHasMaterialized().booleanValue();
if (!hasMaterialized) {
fid = fieldClassMolder.getActualIdentity(tx, value);
}
}
if (fid != null) { field = fid; }
}
return field;
}
/**
* @see org.castor.persist.resolver.ResolverStrategy#markDelete(
* org.castor.persist.TransactionContext, java.lang.Object, java.lang.Object)
*/
public void markDelete(final TransactionContext tx, final Object object, final Object field)
throws PersistenceException {
// persistanceCapable include many_to_one
ClassMolder fieldClassMolder = _fieldMolder.getFieldClassMolder();
Identity identity = (Identity) field;
if (identity != null) {
Object fetched = tx.fetch(fieldClassMolder, identity, null);
if (fetched != null) {
if (_fieldMolder.isDependent()) {
tx.delete(fetched);
} else {
// delete the object from the other side of the relation
fieldClassMolder.removeRelation(tx, fetched, _classMolder, object);
}
}
}
if (_fieldMolder.isDependent()) {
Object fobject = _fieldMolder.getValue(object, tx.getClassLoader());
if ((fobject != null) && tx.isPersistent(fobject)) {
tx.delete(fobject);
}
}
}
/**
* @see org.castor.persist.resolver.ResolverStrategy#revertObject(
* org.castor.persist.TransactionContext, org.exolab.castor.persist.OID,
* java.lang.Object, java.lang.Object)
*/
public void revertObject(final TransactionContext tx, final OID oid, final Object object,
final Object field) throws PersistenceException {
ClassMolder fieldClassMolder = _fieldMolder.getFieldClassMolder();
if (field != null) {
Object value = tx.fetch(fieldClassMolder, (Identity) field, null);
_fieldMolder.setValue(object, value, tx.getClassLoader());
} else {
_fieldMolder.setValue(object, null, tx.getClassLoader());
}
}
/**
* @see org.castor.persist.resolver.ResolverStrategy#expireCache(
* org.castor.persist.TransactionContext, java.lang.Object)
*/
public void expireCache(final TransactionContext tx, final Object field)
throws PersistenceException {
// field is not primitive type. Related object will be expired
ClassMolder fieldClassMolder = _fieldMolder.getFieldClassMolder();
if (field != null) {
// use the corresponding Persistent fields as the identity
tx.expireCache(fieldClassMolder, (Identity) field);
}
}
/**
* @see org.castor.persist.resolver.ResolverStrategy#load(
* org.castor.persist.TransactionContext, org.exolab.castor.persist.OID,
* org.castor.persist.ProposedEntity, org.exolab.castor.mapping.AccessMode)
*/
public void load(final TransactionContext tx, final OID oid,
final ProposedEntity proposedObject, final AccessMode suggestedAccessMode)
throws PersistenceException {
// field is not primitive type. Related object will be loaded
// thru the transaction in action if needed.
ClassMolder fieldClassMolder = _fieldMolder.getFieldClassMolder();
Identity fieldValue = (Identity) proposedObject.getField(_fieldIndex);
if (fieldValue != null) {
// use the corresponding Persistent fields as the identity,
// and we ask transactionContext in action to load it.
Object temp;
try {
// should I use lazy loading for this object?
if (_fieldMolder.isLazy()) {
temp = SingleProxy.getProxy(tx, fieldClassMolder, fieldValue, null,
suggestedAccessMode);
} else {
ProposedEntity proposedTemp = new ProposedEntity(fieldClassMolder);
temp = tx.load(fieldValue, proposedTemp, suggestedAccessMode);
}
} catch (ObjectNotFoundException ex) {
temp = null;
}
_fieldMolder.setValue(proposedObject.getEntity(), temp, tx.getClassLoader());
} else {
_fieldMolder.setValue(proposedObject.getEntity(), null, tx.getClassLoader());
}
}
/**
* @see org.castor.persist.resolver.ResolverStrategy#postCreate(
* org.castor.persist.TransactionContext, org.exolab.castor.persist.OID,
* java.lang.Object, java.lang.Object, org.exolab.castor.persist.spi.Identity)
*/
public Object postCreate(final TransactionContext tx, final OID oid, final Object object,
final Object field, final Identity createdId) {
return field;
}
/**
* @see org.castor.persist.resolver.ResolverStrategy#removeRelation(
* org.castor.persist.TransactionContext, java.lang.Object,
* org.exolab.castor.persist.ClassMolder, java.lang.Object)
*/
public UpdateAndRemovedFlags removeRelation(final TransactionContext tx, final Object object,
final ClassMolder relatedMolder, final Object relatedObject) {
// de-reference the object
UpdateAndRemovedFlags flags = new UpdateAndRemovedFlags();
ClassMolder fieldClassMolder = _fieldMolder.getFieldClassMolder();
ClassMolder relatedBaseMolder = relatedMolder;
while ((fieldClassMolder != relatedBaseMolder) && (relatedBaseMolder != null)) {
relatedBaseMolder = relatedBaseMolder.getExtends();
}
if (fieldClassMolder == relatedBaseMolder) {
Object related = _fieldMolder.getValue(object, tx.getClassLoader());
if (related == relatedObject) {
_fieldMolder.setValue(object, null, tx.getClassLoader());
flags.setUpdateCache(true);
flags.setUpdatePersist(true);
flags.setRemoved(true);
}
}
return flags;
}
/**
* @inheritDoc
*/
public boolean updateWhenNoTimestampSet(
final TransactionContext tx,
final OID oid,
final Object object,
final AccessMode suggestedAccessMode)
throws PersistenceException {
boolean updateCache = false;
// create dependent object if exists
ClassMolder fieldClassMolder = _fieldMolder.getFieldClassMolder();
Object o = _fieldMolder.getValue(object, tx.getClassLoader());
if (o != null) {
if (_fieldMolder.isDependent()) {
// creation of dependent object should be delayed to the
// preStore state.
// otherwise, in the case of keygenerator being used in both
// master and dependent object, and if an dependent
// object is replaced by another before commit, the
// orginial dependent object will not be removed.
//
// the only disadvantage for that appoarch is that an
// OQL Query will not able to include the newly generated
// dependent object.
if (!tx.isRecorded(o)) {
tx.markCreate(fieldClassMolder, o, oid);
if (!_fieldMolder.isStored() && fieldClassMolder._isKeyGenUsed) {
updateCache = true;
}
}
// fail-fast principle: if the object depend on another object,
// throw exception
// if ( !tx.isDepended( oid, o ) )
// throw new PersistenceException(
// "Dependent object may not change its master. Object: " + o
// + " new master: " + oid);
} else if (tx.isAutoStore()) {
if (!tx.isRecorded(o)) {
// related object should be created right the way, if autoStore
// is enabled, to obtain a database lock on the row. If both side
// uses keygenerator, the current object will be updated in the
// store state.
boolean creating = tx.markUpdate(fieldClassMolder, o, null);
// if _fhs[i].isStore is true for this field,
// and if key generator is used
// and if the related object is replaced this object by null
// and if everything else is not modified
// then, objectModifiedException will be thrown
// there are two solutions, first introduce preCreate state,
// and walk the create graph, and create non-store object
// first. However, it doesn't guarantee solution. because
// every object may have field which uses key-generator
// second, we can do another SQLStatement at the very end of
// this method.
// note, one-many and many-many doesn't affected, because
// it is always non-store fields.
if (creating && !_fieldMolder.isStored() && fieldClassMolder._isKeyGenUsed) {
updateCache = true;
}
}
}
}
return updateCache;
}
}