/*
* 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 java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.castor.persist.ProposedEntity;
import org.castor.persist.TransactionContext;
import org.castor.persist.UpdateFlags;
import org.castor.persist.proxy.LazyCollection;
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 M:N relations.
*
* @author <a href="mailto:werner DOT guttmann AT gmx DOT net">Werner Guttmann</a>
* @since 0.9.9
*/
public final class ManyToManyRelationResolver extends ManyRelationResolver {
/**
* Creates an instance of ManyToManyRelationResolver.
*
* @param classMolder Associated ClassMolder.
* @param fieldMolder Associated FieldMolder.
* @param fieldIndex Field index within all fields of parent class molder.
*/
public ManyToManyRelationResolver(final ClassMolder classMolder,
final FieldMolder fieldMolder, final int fieldIndex) {
super(classMolder, fieldMolder, fieldIndex);
}
/**
* @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 {
boolean updateCache = false;
// create relation if the relation table
ClassMolder fieldClassMolder = _fieldMolder.getFieldClassMolder();
Object objects = _fieldMolder.getValue(object, tx.getClassLoader());
if (objects != null) {
Iterator itor = ClassMolderHelper.getIterator(objects);
// many-to-many relation is never dependent relation
while (itor.hasNext()) {
Object objectToCreate = itor.next();
// TODO[ASE]: investigate whether this requires 'cascading update' as well.
if (isCascadingCreate(tx) && !tx.isRecorded(objectToCreate)) {
tx.markCreate(fieldClassMolder, objectToCreate, null);
updateCache = true;
}
}
}
return updateCache;
}
/**
* @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 {
// delete the relation in relation table too
/*
* _fhs[i].getRelationLoader().deleteRelation(
* tx.getConnection(oid.getLockEngine()), oid.getIdentity() );
*/
ClassMolder fieldClassMolder = _fieldMolder.getFieldClassMolder();
// markDelete mix with prestore
// so, store is not yet called, and only the loaded (or created)
// relation have to be deleted.
// not really. cus, the other created relation, may already
// has reference to this object. so, how to deal with that?
if (field != null) {
ArrayList alist = (ArrayList) field;
for (int j = 0; j < alist.size(); j++) {
Identity fid = (Identity) alist.get(j);
Object fetched = null;
if (fid != null) {
fetched = tx.fetch(fieldClassMolder, fid, null);
if (fetched != null) {
fieldClassMolder.removeRelation(tx, fetched,
_classMolder, object);
}
}
}
}
Iterator itor = ClassMolderHelper.getIterator(_fieldMolder.getValue(
object, tx.getClassLoader()));
while (itor.hasNext()) {
Object fobject = itor.next();
if (fobject != null && tx.isPersistent(fobject)) {
fieldClassMolder.removeRelation(tx, fobject, _classMolder,
object);
}
}
}
/**
* @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 {
ClassMolder fieldClassMolder = _fieldMolder.getFieldClassMolder();
Object value = _fieldMolder.getValue(object, tx.getClassLoader());
Iterator<Identity> removedItor;
Iterator addedItor;
if (!(value instanceof LazyCollection)) {
List orgFields = (List) field;
Collection<Identity> removed = ClassMolderHelper.getRemovedIdsList(tx,
orgFields, value, fieldClassMolder);
removedItor = removed.iterator();
Collection added = ClassMolderHelper.getAddedEntitiesList(tx,
orgFields, value, fieldClassMolder);
addedItor = added.iterator();
} else {
LazyCollection lazy = (LazyCollection) value;
// RelationCollection has to clean up its state at the end of the transaction
tx.addTxSynchronizable(lazy);
removedItor = lazy.getRemovedIdsList().iterator();
addedItor = lazy.getAddedEntitiesList().iterator();
}
UpdateFlags flags = new UpdateFlags();
if (removedItor.hasNext()) {
if (_fieldMolder.isStored() && _fieldMolder.isCheckDirty()) {
flags.setUpdatePersist(true);
}
flags.setUpdateCache(true);
while (removedItor.hasNext()) {
Identity removedId = removedItor.next();
// must be loaded in transaction, so that the related object
// is properly locked and updated before we delete it.
if (!tx.isDeletedByOID(new OID(fieldClassMolder, removedId))) {
ProposedEntity proposedValue = new ProposedEntity(fieldClassMolder);
Object removedEntity = tx.load(removedId, proposedValue, null);
if (removedEntity != null && tx.isPersistent(removedEntity)) {
tx.writeLock(removedEntity, 0);
_fieldMolder.getRelationLoader().deleteRelation(
tx.getConnection(oid.getMolder().getLockEngine()),
oid.getIdentity(), removedId);
fieldClassMolder.removeRelation(tx, removedEntity, _classMolder, object);
}
}
}
}
if (addedItor.hasNext()) {
if (_fieldMolder.isStored() && _fieldMolder.isCheckDirty()) {
flags.setUpdatePersist(true);
}
flags.setUpdateCache(true);
while (addedItor.hasNext()) {
Object addedEntity = addedItor.next();
tx.markModified(addedEntity, false, true);
if (tx.isPersistent(addedEntity)) {
_fieldMolder.getRelationLoader().createRelation(
tx.getConnection(oid.getMolder().getLockEngine()),
oid.getIdentity(),
fieldClassMolder.getIdentity(tx, addedEntity));
} else {
if (isCascadingCreate(tx)) {
if (!tx.isRecorded(addedEntity)) {
tx.markCreate(fieldClassMolder, addedEntity, null);
}
}
}
}
}
return flags;
}
/**
* @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 {
List values = (List) field;
ClassMolder fieldClassMolder = _fieldMolder.getFieldClassMolder();
if (isCascadingUpdate(tx)) {
List<Identity> newSetOfIds = new ArrayList<Identity>();
// iterate the collection of this data object field
Iterator itor = ClassMolderHelper.getIterator(_fieldMolder
.getValue(object, tx.getClassLoader()));
while (itor.hasNext()) {
Object element = itor.next();
Identity actualIdentity = fieldClassMolder.getActualIdentity(tx, element);
newSetOfIds.add(actualIdentity);
if (!tx.isRecorded(element)) {
tx.markUpdate(fieldClassMolder, element, null);
}
}
// load all old objects for comparison in the preStore state
if (values != null) {
for (int j = 0; j < values.size(); j++) {
if (!newSetOfIds.contains(values.get(j))) {
// load all the dependent object in cache for
// modification check at commit time.
ProposedEntity proposedValue = new ProposedEntity(fieldClassMolder);
tx.load((Identity) values.get(j), proposedValue, suggestedAccessMode);
}
}
}
}
}
/**
* @see org.castor.persist.resolver.ManyRelationResolver#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)
throws PersistenceException {
ClassMolder fieldClassMolder = _fieldMolder.getFieldClassMolder();
Object o = _fieldMolder.getValue(object, tx.getClassLoader());
Object result = field;
if (o != null) {
result = ClassMolderHelper.getIdsList(tx, fieldClassMolder, o);
Iterator itor = ClassMolderHelper.getIterator(o);
while (itor.hasNext()) {
Object oo = itor.next();
//TODO[ASE]: another option would be to change this if clause!
// tx.isPersistent only checks with the tracker but not with the
// persistent storage! If this check would work properly
// most of the problems would be saved too
// but we might also have to check that we are not going to store
// the same relation twice what is possible if both objects are already
// created due to another relation they occur in!
if (tx.isPersistent(oo)) {
_fieldMolder.getRelationLoader().createRelation(
tx.getConnection(oid.getMolder().getLockEngine()), createdId,
fieldClassMolder.getIdentity(tx, oo));
}
}
}
return result;
}
/**
* @inheritDoc
*/
public boolean updateWhenNoTimestampSet(
final TransactionContext tx,
final OID oid,
final Object object,
final AccessMode suggestedAccessMode)
throws PersistenceException {
boolean updateCache = false;
// create relation if the relation table
ClassMolder fieldClassMolder = _fieldMolder.getFieldClassMolder();
Object value = _fieldMolder.getValue(object, tx.getClassLoader());
if (value != null) {
Iterator itor = ClassMolderHelper.getIterator(value);
// many-to-many relation is never dependent relation
while (itor.hasNext()) {
Object oo = itor.next();
if (isCascadingUpdate(tx) && !tx.isRecorded(oo)) {
if (tx.markUpdate(fieldClassMolder, oo, null)) {
updateCache = true;
}
}
}
}
return updateCache;
}
}