/* * 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$ */ 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.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 extends BaseRelationResolver { private final int _fieldIndex; /** * 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. */ public PersistanceCapableRelationResolver(final ClassMolder classMolder, final FieldMolder fieldMolder, final int fieldIndex) { super(classMolder, fieldMolder); _fieldIndex = fieldIndex; } /** * 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.getActualIdentity(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 (isCascadingCreate(tx)) { 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); } } 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 (isCascadingCreate(tx)) { 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.getActualIdentity(tx, value); if (fieldValue != null) { ProposedEntity temp = new ProposedEntity(fieldClassMolder); Object tempValue = tx.load(fieldValue, temp, null); _fieldMolder.setValue(object, tempValue, 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 (isCascadingUpdate(tx)) { 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) { //TODO: changed by ASE team to implement cascading delete... please verify! if (_fieldMolder.isDependent() || isCascadingDelete()) { tx.delete(fetched); } else { // delete the object from the other side of the relation fieldClassMolder.removeRelation(tx, fetched, _classMolder, object); } } } //TODO: changes done by ASE team... please verify if (_fieldMolder.isDependent() || isCascadingDelete()) { 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 (isCascadingUpdate(tx)) { 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; } }