package org.apache.ojb.odmg;
/* Copyright 2002-2004 The Apache Software Foundation
*
* 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.
*/
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.ojb.broker.Identity;
import org.apache.ojb.broker.OptimisticLockException;
import org.apache.ojb.broker.PersistenceBroker;
import org.apache.ojb.broker.PersistenceBrokerFactory;
import org.apache.ojb.broker.accesslayer.ConnectionManagerIF;
import org.apache.ojb.broker.core.proxy.ProxyHelper;
import org.apache.ojb.broker.metadata.ClassDescriptor;
import org.apache.ojb.broker.metadata.CollectionDescriptor;
import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
import org.apache.ojb.broker.util.BrokerHelper;
import org.apache.ojb.broker.util.logging.Logger;
import org.apache.ojb.broker.util.logging.LoggerFactory;
import org.apache.ojb.odmg.locking.LockManagerFactory;
import org.apache.ojb.odmg.states.StateOldClean;
import org.odmg.LockNotGrantedException;
import org.odmg.Transaction;
import org.odmg.TransactionAbortedException;
/**
* manages all ObjectEnvelopes included by a transaction.
* Performs commit, and rollack operations on all included Envelopes.
*
* @author Thomas Mahler
* @author <a href="mailto:mattbaird@yahoo.com">Matthew Baird</a>
*
* MBAIRD: added explicit closing and de-referencing to prevent any
* GC issues.
*/
public class ObjectEnvelopeTable
{
private Logger log = LoggerFactory.getLogger(ObjectEnvelopeTable.class);
private TransactionImpl transaction;
/**
* the internal table mapping Objects to their ObjectTransactionWrappers
*/
private Map mhtObjectEnvelopes = new HashMap();
/**
* a vector containing the ObjectEnvelope objects representing modifications
* in the order they were added. If an ObjectEnvelope is added twice, only
* the the second addition is ignored.
*/
private ArrayList mvOrderOfIds = new ArrayList();
/**
* marker used to avoid superfluous reordering and commiting
*/
private boolean needsCommit = false;
/**
* prepare this instance for reuse
*/
public void refresh()
{
/**
* MBAIRD: be nice and remove all references so they can be
* gc'd
*/
if (mhtObjectEnvelopes != null)
{
Iterator iter = mvOrderOfIds.iterator();
ObjectEnvelope temp;
while (iter.hasNext())
{
temp = (ObjectEnvelope) mhtObjectEnvelopes.get(iter.next());
temp.close();
}
}
needsCommit = false;
mhtObjectEnvelopes.clear();
mvOrderOfIds.clear();
}
/**
* Creates new ObjectEnvelopeTable
*/
public ObjectEnvelopeTable(TransactionImpl myTransaction)
{
transaction = myTransaction;
}
/**
* perform commit on all tx-states
*/
public void commit() throws TransactionAbortedException, LockNotGrantedException
{
PersistenceBroker broker = transaction.getBroker();
ConnectionManagerIF connMan = broker.serviceConnectionManager();
boolean saveBatchMode = connMan.isBatchMode();
try
{
if (log.isDebugEnabled())
{
log.debug(
"PB is in internal tx: "
+ broker.isInTransaction()
+ " broker was: "
+ broker);
}
// all neccessary db operations are executed within a PersistenceBroker transaction:
if (!broker.isInTransaction())
{
// if (log.isDebugEnabled())
// log.debug("call beginTransaction() on PB instance");
// broker.beginTransaction();
log.error("PB associated with current odmg-tx is not in tx");
throw new TransactionAbortedException("Underlying PB is not in tx");
}
// Committing has to be done in two phases. First implicitly upgrade to lock on all related
// objects of objects in this transaction. Then the list of locked objects has to be
// reordered to solve referential integrity dependencies, then the objects are
// written into the database.
// 0. turn on the batch mode
connMan.setBatchMode(true);
// 1. upgrade implicit locks.
upgradeImplicitLocksAndCheckIfCommitIsNeeded();
// 2. Reorder objects
reorder();
// 3. commit objects.
commitAllEnvelopes(broker);
// 4. execute batch
connMan.executeBatch();
// 5.Update all Envelopes to new CleanState
setCleanState();
}
catch (Throwable t)
{
// we do that in TransactionImpl#abort()
// broker.abortTransaction();
connMan.clearBatch();
log.error("Commit on object level failed for tx " + transaction, t);
if (t instanceof OptimisticLockException)
{
// PB OptimisticLockException should be clearly signalled to the user
throw new LockNotGrantedException(t.getMessage());
}
else
{
throw new TransactionAbortedExceptionOJB(t);
}
}
finally
{
needsCommit = false;
connMan.setBatchMode(saveBatchMode);
}
}
/**
* commit all envelopes against the current broker
* @param broker the PB to persist all objects
*/
private void commitAllEnvelopes(PersistenceBroker broker)
{
if (needsCommit)
{
Iterator iter;
// using clone to avoid ConcurentModificationException
iter = ((List) mvOrderOfIds.clone()).iterator();
while (iter.hasNext())
{
ObjectEnvelope mod = (ObjectEnvelope) mhtObjectEnvelopes.get(iter.next());
mod.getModificationState().commit(mod, broker);
}
}
}
/**
*
*/
private void setCleanState()
{
if (needsCommit)
{
Iterator iter;
// using clone to avoid ConcurentModificationException
iter = ((List) mvOrderOfIds.clone()).iterator();
while (iter.hasNext())
{
ObjectEnvelope mod = (ObjectEnvelope) mhtObjectEnvelopes.get(iter.next());
if(mod.getModificationState() != StateOldClean.getInstance())
{
mod.refreshObjectImage();
mod.setModificationState(StateOldClean.getInstance());
}
}
}
}
/**
* Implicitely upgrade locks on modified objects.
* Also checks if there are any operations to commit.
*/
private void upgradeImplicitLocksAndCheckIfCommitIsNeeded()
{
boolean useImplicitLocking = getConfiguration().useImplicitLocking();
// using clone to avoid ConcurentModificationException
Iterator iter = ((List) mvOrderOfIds.clone()).iterator();
while (iter.hasNext())
{
boolean markDirty = false;
ObjectEnvelope mod = (ObjectEnvelope) mhtObjectEnvelopes.get(iter.next());
// if the Object has been modified by transaction, mark object as dirty
// but only if it has not been marked during tx already !!
if ((!mod.needsDelete()) && (!mod.needsInsert()) && (!mod.needsUpdate()))
{
/**
* second check is, has the object in the envelope changed.
*/
if (mod.hasChanged())
{
/**
* now, the quickest thing to check is the useImplicitLocking flag. If we are using
* implicit locking, let's try to upgrade the lock, and mark the markDirty
*/
if (useImplicitLocking)
{
// implicitely acquire a write lock !
transaction.lock(mod.getObject(), Transaction.UPGRADE);
// objects needs commit action, thus set markDirty to true:
markDirty = true;
}
/**
* If useImplicitLocking is false, we still need to check if the object in the envelope
* is write locked. If it is, we don't have to upgrade the lock, just mark markDirty
*/
else if (LockManagerFactory.getLockManager().checkWrite(transaction, mod.getObject()))
{
// objects needs commit action, thus set markDirty to true:
markDirty = true;
}
if (markDirty)
{
needsCommit=true;
// mark object dirty
mod.setModificationState(mod.getModificationState().markDirty());
}
}
}
else
{
// objects needs commit action, thus set needCommit to true:
needsCommit = true;
}
}
}
/**
* perform rollback on all tx-states
*/
public void rollback()
{
try
{
PersistenceBroker broker = transaction.getBroker();
Iterator iter = mvOrderOfIds.iterator();
while (iter.hasNext())
{
ObjectEnvelope mod = (ObjectEnvelope) mhtObjectEnvelopes.get(iter.next());
if (log.isDebugEnabled())
log.debug("rollback: " + mod);
// if the Object has been modified has been modified by transaction, mark object as dirty
if (mod.hasChanged())
{
mod.setModificationState(mod.getModificationState().markDirty());
}
mod.getModificationState().rollback(mod, broker);
}
}
finally
{
needsCommit = false;
}
}
/**
* remove an objects entry from the Hashtable
*/
public void remove(Object pKey)
{
Identity id = null;
if (pKey instanceof Identity)
{
id = (Identity) pKey;
}
else
{
id = new Identity(pKey, transaction.getBroker());
}
mhtObjectEnvelopes.remove(id);
mvOrderOfIds.remove(id);
}
/**
*
* Get an enumeration of all the elements in this ObjectEnvelopeTable
* in random order.
*
* Creation date: (11.02.2001 12:45:08)
*
* @return Enumeration an enumeration of all elements managed by this ObjectEnvelopeTable
*
*/
public Enumeration elements()
{
return java.util.Collections.enumeration(mhtObjectEnvelopes.values());
}
/**
* retrieve an objects ObjectModification state from the hashtable
*/
public ObjectEnvelope getByIdentity(Identity id)
{
return (ObjectEnvelope) mhtObjectEnvelopes.get(id);
}
/**
* retrieve an objects ObjectEnvelope state from the hashtable.
* If no ObjectEnvelope is found, a new one is created and returned.
* @return the resulting ObjectEnvelope
*/
public ObjectEnvelope get(Object pKey)
{
Identity id = new Identity(pKey, transaction.getBroker());
//Integer keyInteger = new Integer(System.identityHashCode(key));
ObjectEnvelope result = (ObjectEnvelope) mhtObjectEnvelopes.get(id);
if (result == null)
{
result = new ObjectEnvelope(pKey, transaction);
mhtObjectEnvelopes.put(id, result);
mvOrderOfIds.add(id);
if (log.isDebugEnabled())
log.debug("register: " + result);
}
return result;
}
/**
* store an objects transactional state into the Hashtable
*/
public void put(Object pKey, ObjectEnvelope modification)
{
Identity id = new Identity(pKey, transaction.getBroker());
//Integer keyInt = new Integer(System.identityHashCode(key));
if (log.isDebugEnabled())
log.debug("register: " + modification);
if (!mhtObjectEnvelopes.containsKey(id))
mvOrderOfIds.add(id);
mhtObjectEnvelopes.put(id, modification);
}
/**
* Returns a String representation of this object
*/
public String toString()
{
ToStringBuilder buf = new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE);
buf.append("### ObjectEnvelopeTable dump:");
Enumeration enum = elements();
while (enum.hasMoreElements())
{
ObjectEnvelope mod = (ObjectEnvelope) enum.nextElement();
buf.append(mod.toString());
}
return buf.toString();
}
/**
* retrieve an objects ObjectModification state from the hashtable
*/
public boolean contains(Object pKey)
{
Identity id = new Identity(pKey, transaction.getBroker());
//Integer keyInteger = new Integer(System.identityHashCode(key));
return mhtObjectEnvelopes.containsKey(id);
}
/**
* Reorder the objects in the table to resolve referential integrity dependencies.
*/
private void reorder() throws IllegalAccessException
{
if (needsCommit)
{
ArrayList vNewVector = new ArrayList(mvOrderOfIds.size());
Map htNewHashtable = new HashMap((int) (mvOrderOfIds.size() * 1.1), 1f);
Map htOldVectorPosition = new HashMap((int) (mvOrderOfIds.size() * 1.1), 1f);
for (int i = 0; i < mvOrderOfIds.size(); i++)
htOldVectorPosition.put(mvOrderOfIds.get(i), new Integer(i));
for (int i = 0; i < mvOrderOfIds.size(); i++)
{
Identity id = (Identity) mvOrderOfIds.get(i);
if (id != null)
{
mvOrderOfIds.set(i, null);
ObjectEnvelope o = (ObjectEnvelope) mhtObjectEnvelopes.get(id);
mhtObjectEnvelopes.remove(id);
reorderObject(htNewHashtable, vNewVector, o, id, htOldVectorPosition);
}
}
mvOrderOfIds = vNewVector;
mhtObjectEnvelopes = htNewHashtable;
}
}
/**
* put an object and all its dependent objects in the new vector. If the object
* in question is going to be DELETEd, first the objects referenced in collections
* are put in the vector, then the object in question and then the references.
* Otherwise the order is reversed.
*/
private void reorderObject(
Map htNewHashtable,
List newVector,
ObjectEnvelope objectToReorder,
Identity id,
Map htOldVectorPosition)
throws IllegalAccessException
{
PersistenceBroker broker = transaction.getBroker();
if (objectToReorder != null)
{
ClassDescriptor cld = broker.getClassDescriptor(objectToReorder.getObject().getClass());
if (objectToReorder.needsDelete())
{
reorderCollection(
htNewHashtable,
newVector,
objectToReorder,
cld,
htOldVectorPosition);
newVector.add(id);
htNewHashtable.put(id, objectToReorder);
reorderReference(
htNewHashtable,
newVector,
objectToReorder,
cld,
htOldVectorPosition);
}
else
{
reorderReference(
htNewHashtable,
newVector,
objectToReorder,
cld,
htOldVectorPosition);
newVector.add(id);
htNewHashtable.put(id, objectToReorder);
reorderCollection(
htNewHashtable,
newVector,
objectToReorder,
cld,
htOldVectorPosition);
}
}
}
/**
* Resolves all objects referenced in collections of objectToReorder. The referenced
* objects are removed from the list of modified objects in this class and passed
* to reorderObject, where dependencies of the object are resolved (i.e. if the referenced
* object contains again collections or references) and it is finally put into the new order
* vector.
*/
private void reorderCollection(
Map htNewHashtable,
List newVector,
ObjectEnvelope objectToReorder,
ClassDescriptor cld,
Map htOldVectorPosition)
throws IllegalAccessException
{
Iterator i;
i = cld.getCollectionDescriptors().iterator();
while (i.hasNext())
{
CollectionDescriptor cds = (CollectionDescriptor) i.next();
Object col = cds.getPersistentField().get(objectToReorder.getObject());
if (col != null)
{
Iterator colIterator;
if (ProxyHelper.isCollectionProxy(col) && !ProxyHelper.getCollectionProxy(col).isLoaded())
{
colIterator = Collections.EMPTY_LIST.iterator();
}
else
{
colIterator = BrokerHelper.getCollectionIterator(col);
}
while (colIterator.hasNext())
{
// The collection now contains all the objects in the collection.
// Now we have to retrieve the ObjectEnvelope representing this
// Object from the hashtable, remove it from the vector and reorder
// the retrieved ObjectEnvelope.
Identity id = new Identity(colIterator.next(), transaction.getBroker());
ObjectEnvelope oe = (ObjectEnvelope) mhtObjectEnvelopes.get(id);
if (oe != null)
{
mvOrderOfIds.set(((Integer) htOldVectorPosition.get(id)).intValue(), null);
// mvOrderOfIds.set(mvOrderOfIds.indexOf(id), null);
mhtObjectEnvelopes.remove(id);
reorderObject(htNewHashtable, newVector, oe, id, htOldVectorPosition);
}
}
}
}
}
/**
* Resolves all objects referenced in references of objectToReorder. The referenced
* objects are removed from the list of modified objects in this class and passed
* to reorderObject, where dependencies of the object are resolved (i.e. if the referenced
* object contains again collections or references) and it is finally put into the new order
* vector.
*/
private void reorderReference(
Map htNewHashtable,
List newVector,
ObjectEnvelope objectToReorder,
ClassDescriptor cld,
Map htOldVectorPosition)
throws IllegalAccessException
{
Iterator i = cld.getObjectReferenceDescriptors().iterator();
while (i.hasNext())
{
ObjectReferenceDescriptor rds = (ObjectReferenceDescriptor) i.next();
Object refObj = rds.getPersistentField().get(objectToReorder.getObject());
if (refObj != null)
{
Identity id = new Identity(refObj, transaction.getBroker());
ObjectEnvelope oe = (ObjectEnvelope) mhtObjectEnvelopes.get(id);
if (oe != null)
{
mhtObjectEnvelopes.remove(id);
mvOrderOfIds.set(((Integer) htOldVectorPosition.get(id)).intValue(), null);
// mvOrderOfIds.set(mvOrderOfIds.indexOf(id), null);
reorderObject(htNewHashtable, newVector, oe, id, htOldVectorPosition);
}
}
}
}
/**
* get Configuration
* @return OdmgConfiguration
*/
private OdmgConfiguration getConfiguration()
{
OdmgConfiguration config =
(OdmgConfiguration) PersistenceBrokerFactory.getConfigurator().getConfigurationFor(
null);
return config;
}
}