Package org.apache.ojb.odmg

Source Code of org.apache.ojb.odmg.TransactionImpl

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 javax.transaction.Status;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Vector;

import org.apache.commons.lang.SystemUtils;
import org.apache.ojb.broker.Identity;
import org.apache.ojb.broker.PBFactoryException;
import org.apache.ojb.broker.PersistenceBroker;
import org.apache.ojb.broker.PersistenceBrokerException;
import org.apache.ojb.broker.PersistenceBrokerFactory;
import org.apache.ojb.broker.PersistenceBrokerSQLException;
import org.apache.ojb.broker.core.ValueContainer;
import org.apache.ojb.broker.core.proxy.CollectionProxy;
import org.apache.ojb.broker.core.proxy.CollectionProxyDefaultImpl;
import org.apache.ojb.broker.core.proxy.CollectionProxyListener;
import org.apache.ojb.broker.core.proxy.IndirectionHandler;
import org.apache.ojb.broker.core.proxy.MaterializationListener;
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.FieldDescriptor;
import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
import org.apache.ojb.broker.util.BrokerHelper;
import org.apache.ojb.broker.util.GUID;
import org.apache.ojb.broker.util.configuration.Configurable;
import org.apache.ojb.broker.util.configuration.Configuration;
import org.apache.ojb.broker.util.configuration.ConfigurationException;
import org.apache.ojb.broker.util.logging.Logger;
import org.apache.ojb.broker.util.logging.LoggerFactory;
import org.apache.ojb.odmg.locking.LockManager;
import org.apache.ojb.odmg.locking.LockManagerFactory;
import org.odmg.DatabaseClosedException;
import org.odmg.LockNotGrantedException;
import org.odmg.ODMGRuntimeException;
import org.odmg.Transaction;
import org.odmg.TransactionAbortedException;
import org.odmg.TransactionNotInProgressException;

/**
*
* Implementation of Transaction for org.odmg.Transaction.
*
* @author     Thomas Mahler & David Dixon-Peugh
* @author <a href="mailto:mattbaird@yahoo.com">Matthew Baird</a>
* @author <a href="mailto:armin@codeAuLait.de">Armin Waibel</a>
* @author <a href="mailto:brianm@apache.org">Brian McCallister</a>
* @version $Id: TransactionImpl.java,v 1.59 2004/06/24 15:36:56 arminw Exp $
*
*/
public class TransactionImpl
        implements Transaction, MaterializationListener, Configurable, CollectionProxyListener, TransactionExt
{
    private Logger log = LoggerFactory.getLogger(TransactionImpl.class);
    private Hashtable myNrm = null;
    private boolean useWriteLocks;
    private boolean useImplicitLocking;
    private String txGUID;
    protected PersistenceBroker broker = null;
    private ArrayList registeredForLock = new ArrayList();
    private OJBTxManager txManager;

    /**
     * The status of the current transaction, as specified by the
     * javax.transaction package.
     * See {@link javax.transaction.Status} for list of valid values.
     */
    private int m_txStatus = Status.STATUS_NO_TRANSACTION;

    /**
     * the internal table containing all Objects "touched" by this tx and their
     * respective transactional state
     */
    private ObjectEnvelopeTable objectEnvelopeTable = null;

    /**
     * reference to the currently opened database
     */
    private DatabaseImpl curDB;
    /**
     * The tx may me listening to a number of IndirectionHandlers.
     * on abort or commit these Handlers must be informed to remove
     * tx from their List of Listeners.
     */
    private ArrayList registeredIndirectionHandlers = new ArrayList();

    /**
     * Unloaded collection proxies which will be registered with tx when
     * collection is loaded
     */
    private ArrayList registeredCollectionProxies = new ArrayList();

    /**
     * list of proxy objects that were locked, but haven't been materialized yet.
     * This is necessary so the locks can be released on closing the transaction
     */
    private ArrayList unmaterializedLocks = new ArrayList();

    /**
     * If a checkpoint call is made we keep the locks till commit or
     * abort is called.
     */
    private ArrayList locksToRelease = new ArrayList();

    /**
     * Creates new Transaction
     * @param theCurrentDB - create a transaction that is associated with the database.
     */
    public TransactionImpl(DatabaseImpl theCurrentDB)
    {
        txManager = TxManagerFactory.instance();
        // assign a globally uniqe id to this tx
        txGUID = new GUID().toString();
        curDB = theCurrentDB;
    }

    /**
     * Returns the associated database
     */
    public DatabaseImpl getAssociatedDatabase()
    {
        return this.curDB;
    }

    protected int getStatus()
    {
        return m_txStatus;
    }

    protected void setStatus(int status)
    {
        this.m_txStatus = status;
    }

    private void checkForDB()
    {
        if (curDB == null || !curDB.isOpen())
        {
            log.error("Transaction without a associated open Database.");
            throw new TransactionAbortedExceptionOJB(
                    "No open database found. Open the database before handling transactions");
        }
    }

    /**
     * Determine whether the transaction is open or not. A transaction is open if
     * a call has been made to <code>begin</code> , but a subsequent call to
     * either <code>commit</code> or <code>abort</code> has not been made.
     * @return    True if the transaction is open, otherwise false.
     */
    public boolean isOpen()
    {
        return (getStatus() == Status.STATUS_ACTIVE ||
                getStatus() == Status.STATUS_MARKED_ROLLBACK ||
                getStatus() == Status.STATUS_PREPARED ||
                getStatus() == Status.STATUS_PREPARING ||
                getStatus() == Status.STATUS_COMMITTING);
    }

    private void checkOpen()
    {
        if (!isOpen())
        {
            throw new TransactionNotInProgressException(
                    "Transaction was not open, call tx.begin() before perform action, current status is: " +
                    TxUtil.getStatusString(getStatus()));
        }
    }


    /**
     * Attach the caller's thread to this <code>Transaction</code> and detach the
     * thread from any former <code>Transaction</code> the thread may have been
     * associated with.
     */
    public void join()
    {
        checkOpen();
        txManager.deregisterTx(this);
        txManager.registerTx(this);
    }

    /**
     * Upgrade the lock on the given object to the given lock mode. The call has
     * no effect if the object's current lock is already at or above that level of
     * lock mode.
     *
     * @param  obj       object to acquire a lock on.
     * @param  lockMode  lock mode to acquire. The lock modes
     * are <code>READ</code> , <code>UPGRADE</code> , and <code>WRITE</code> .
     *
     * @exception  LockNotGrantedException    Description of Exception
     */
    public void lock(Object obj, int lockMode) throws LockNotGrantedException
    {
        if (log.isDebugEnabled()) log.debug("lock object was called on tx " + this + ", object is " + obj.toString());
        checkOpen();

        if (!ProxyHelper.isProxy(obj))
        {
            if (log.isDebugEnabled()) log.debug("object is a proxy");
            ClassDescriptor cld = this.getBroker().getClassDescriptor(obj.getClass());
            if (!cld.isAcceptLocks())
            {
                if (log.isDebugEnabled())
                    log.debug("skipping lock on class: " + cld.getClassNameOfObject() + " object " + obj.toString());
                return;
            }
            else
            {
                if (log.isDebugEnabled())
                    log.debug("proceeding with lock on class: " + cld.getClassNameOfObject() + " object " + obj.toString());
            }
            assignReferenceFKs(
                    obj,
                    this.getBroker().getClassDescriptor(obj.getClass()).getObjectReferenceDescriptors());
        }

        LockManager lm = LockManagerFactory.getLockManager();
        if (lockMode == Transaction.READ)
        {
            if (!lm.readLock(this, obj))
            {
                throw new LockNotGrantedException("Can not lock for READ: " + obj);
            }
        }
        else if (lockMode == Transaction.WRITE)
        {
            if (!lm.writeLock(this, obj))
            {
                throw new LockNotGrantedException("Can not lock for WRITE: " + obj);
            }
        }
        else if (lockMode == Transaction.UPGRADE)
        {
            if (!lm.upgradeLock(this, obj))
            {
                throw new LockNotGrantedException("Can not lock for UPGRADE: " + obj);
            }
        }
        try
        {
            if (log.isDebugEnabled()) log.debug("registering lock on object at " + System.currentTimeMillis());
            register(obj, lockMode);
        }
        catch (Throwable t)
        {
            log.error("Locking obj " + obj + " with lock mode " + lockMode + " failed", t);
            lm.releaseLock(this, obj);
            throw new LockNotGrantedException(t.getMessage());
        }
    }

    /**
     * Detach the caller's thread from this <code>Transaction</code> , but do not
     * attach the thread to another <code>Transaction</code> .
     */
    public void leave()
    {
        checkOpen();
        txManager.deregisterTx(this);
    }

    /**
     * Do the Commits, but don't release the locks.
     * I don't know what we should do if we are in a checkpoint and
     * we need to abort.
     */
    private synchronized void doCommitOnObjects(boolean keepLogs) throws TransactionAbortedException, LockNotGrantedException
    {
        ObjectEnvelope item;
        /*
        arminw:
        if broker isn't in PB-tx, start tx
        */
        if (log.isDebugEnabled()) log.debug("call beginTransaction() on PB instance");
        if (!getBroker().isInTransaction()) broker.beginTransaction();

        // Notify objects of impending commits.
        Enumeration enum = objectEnvelopeTable.elements();
        while (enum.hasMoreElements())
        {
            item = (ObjectEnvelope) enum.nextElement();
            item.beforeCommit();
        }

        // Now perfom the real work
        objectEnvelopeTable.commit();

        // Now, we notify everything the commit is done.
        enum = objectEnvelopeTable.elements();
        while (enum.hasMoreElements())
        {
            item = (ObjectEnvelope) enum.nextElement();
            // keep locked objects in a list, release locks on end of commit (see checkpoint)
            if(keepLogs)
            {
                locksToRelease.add(item.getObject());
            }
            item.afterCommit();
        }
        registeredForLock.clear();
    }

    /**
     * Do the Aborts, but don't release the locks.
     * Do the aborts on the NamedRootsMap first, then
     * abort the other stuff.
     */
    protected synchronized void doAbort()
    {
        ObjectEnvelope item;
        Enumeration objectsToAbort = objectEnvelopeTable.elements();
        // Notify objects of impending aborts.
        while (objectsToAbort.hasMoreElements())
        {
            item = (ObjectEnvelope) objectsToAbort.nextElement();
            item.beforeAbort();
        }
        // Now, we abort everything. . .
        objectEnvelopeTable.rollback();
        // Now, we notify everything the abort is done.
        objectsToAbort = objectEnvelopeTable.elements();
        while (objectsToAbort.hasMoreElements())
        {
            item = (ObjectEnvelope) objectsToAbort.nextElement();
            item.afterAbort();
        }
    }

    /**
     * Close a transaction and do all the cleanup associated with it.
     */
    protected synchronized void doClose()
    {
        if (!isOpen())
        {
            throw new IllegalStateException("persist.missingEnd. Tx Status: " +
                    TxUtil.getStatusString(getStatus()));
        }

        try
        {
            Enumeration enum = objectEnvelopeTable.elements();
            while (enum.hasMoreElements())
            {
                Object obj = ((ObjectEnvelope) enum.nextElement()).getObject();
                removeLock(obj, Transaction.WRITE);
            }

            //remove locks for objects which haven't been materialized yet
            for (Iterator it = unmaterializedLocks.iterator(); it.hasNext();)
            {
                removeLock(it.next(), Transaction.WRITE);
            }

            //remove locks for objects which were committed within a checkpoint
            for (Iterator it = locksToRelease.iterator(); it.hasNext();)
            {
                Object obj = it.next();
                removeLock(obj, Transaction.WRITE);
            }

            // this tx is no longer interested in materialization callbacks
            unRegisterFromAllIndirectionHandlers();
            unRegisterFromAllCollectionProxies();
        }
        finally
        {
            /**
             * MBAIRD: Be nice and close the table to release all refs
             */
            if (log.isDebugEnabled())
                log.debug("Close Transaction and release current PB " + broker + " on tx " + this);
            // remove current thread from LocalTxManager
            // to avoid problems for succeeding calls of the same thread
            txManager.deregisterTx(this);
            // now cleanup and prepare for reuse
            refresh();
        }
    }

    /**
     * cleanup tx and prepare for reuse
     */
    protected void refresh()
    {
        try
        {
            objectEnvelopeTable.refresh();
            // we reuse ObjectEnvelopeTable instance
            // objectEnvelopeTable = null;
        }
        catch (Exception e)
        {
            if (log.isDebugEnabled())
            {
                log.debug("error closing object envelope table : " + e.getMessage());
                e.printStackTrace();
            }
        }
        if (broker != null && !broker.isClosed())
        {
            try
            {
                broker.close();
            }
            catch (Exception ignore)
            {
            }
        }
        // clear the temporary used named roots map
        // we should do that, because same tx instance
        // could be used several times
        myNrm.clear();
        broker = null;
        registeredForLock.clear();
        unmaterializedLocks.clear();
        locksToRelease.clear();
        m_txStatus = Status.STATUS_NO_TRANSACTION;
    }

    /**
     * Commit the transaction, but reopen the transaction, retaining all locks.
     * Calling <code>checkpoint</code> commits persistent object modifications
     * made within the transaction since the last checkpoint to the database. The
     * transaction retains all locks it held on those objects at the time the
     * checkpoint was invoked.
     */
    public void checkpoint()
    {
        if (log.isDebugEnabled()) log.debug("Checkpoint was called, commit changes hold locks on tx " + this);
        try
        {
            checkOpen();
            doCommitOnObjects(true);
            // do commit on PB
            if (broker != null && broker.isInTransaction()) broker.commitTransaction();
        }
        catch (Throwable t)
        {
            log.error("checkpoint call failed, do abort transaction", t);
            try
            {
                doAbort();
            }
            finally
            {
                doClose();
                m_txStatus = Status.STATUS_ROLLEDBACK;
            }
            if (t instanceof TransactionAbortedException)
            {
                throw (TransactionAbortedException) t;
            }
            if (t instanceof LockNotGrantedException)
            {
                throw (LockNotGrantedException) t;
            }
            else
            {
                throw new TransactionAbortedExceptionOJB(t);
            }
        }
    }

    /**
     * @see org.apache.ojb.odmg.TransactionExt#flush
     */
    public void flush()
    {
        if (log.isDebugEnabled()) log.debug("Flush was called - write to database, do not commit, hold locks on tx " + this);
        try
        {
            checkOpen();
            doCommitOnObjects(true);
        }
        catch (Throwable t)
        {
            log.error("flush failed", t);
            try
            {
                doAbort();
            }
            finally
            {
                doClose();
                m_txStatus = Status.STATUS_ROLLEDBACK;
            }
            if (t instanceof TransactionAbortedException)
            {
                throw (TransactionAbortedException) t;
            }
            if (t instanceof LockNotGrantedException)
            {
                throw (LockNotGrantedException) t;
            }
            else
            {
                throw new TransactionAbortedExceptionOJB(t);
            }
        }
    }

    /**
     * @see org.apache.ojb.odmg.TransactionExt#markDelete
     */
    public void markDelete(Object anObject)
    {
        ObjectEnvelope otw = objectEnvelopeTable.get(anObject);
        otw.setModificationState(otw.getModificationState().markDelete());
    }

    /**
     * @see org.apache.ojb.odmg.TransactionExt#markDirty
     */
    public void markDirty(Object anObject)
    {
        ObjectEnvelope otw = objectEnvelopeTable.get(anObject);
        otw.setModificationState(otw.getModificationState().markDirty());
    }

    /**
     * Upgrade the lock on the given object to the given lock mode. Method <code>
     * tryLock</code> is the same as <code>lock</code> except it returns a boolean
     * indicating whether the lock was granted instead of generating an exception.
     * @param  obj          Description of Parameter
     * @param  lockMode     Description of Parameter
     * @return              Description of the Returned Value
     * </code>, <code>UPGRADE</code> , and <code>WRITE</code> .
     * @return true          if the lock has been acquired, otherwise false.
     */
    public boolean tryLock(Object obj, int lockMode)
    {
        if (log.isDebugEnabled()) log.debug("Try to lock object was called on tx " + this);
        checkOpen();
        try
        {
            lock(obj, lockMode);
            return true;
        }
        catch (LockNotGrantedException ex)
        {
            return false;
        }
    }

    /**
     * removeLock removes the transactions lock from the Object.
     */
    private boolean removeLock(Object obj, int lockType)
    {
        return LockManagerFactory.getLockManager().releaseLock(this, obj);
    }

    /**
     * Commit and close the transaction. Calling <code>commit</code> commits to
     * the database all persistent object modifications within the transaction and
     * releases any locks held by the transaction. A persistent object
     * modification is an update of any field of an existing persistent object, or
     * an update or creation of a new named object in the database. If a
     * persistent object modification results in a reference from an existing
     * persistent object to a transient object, the transient object is moved to
     * the database, and all references to it updated accordingly. Note that the
     * act of moving a transient object to the database may create still more
     * persistent references to transient objects, so its referents must be
     * examined and moved as well. This process continues until the database
     * contains no references to transient objects, a condition that is guaranteed
     * as part of transaction commit. Committing a transaction does not remove
     * from memory transient objects created during the transaction.
     *
     * The updateObjectList contains a list of all objects for which this transaction
     * has write privledge to.  We need to update these objects.
     */
    public void commit()
    {
        checkOpen();
        try
        {
            prepare();
            // Never commit transaction that has been marked for rollback
            if (m_txStatus == Status.STATUS_MARKED_ROLLBACK)
                throw new TransactionAbortedExceptionOJB("persist.markedRollback");
            if (m_txStatus != Status.STATUS_PREPARED)
                throw new IllegalStateException("persist.missingPrepare");

            m_txStatus = Status.STATUS_COMMITTING;
            if (log.isDebugEnabled()) log.debug("Commit transaction " + this + ", commit on broker " + broker);
            getBroker().commitTransaction();
            doClose();
            m_txStatus = Status.STATUS_COMMITTED;
        }
        catch (ODMGRuntimeException ex)
        {
            m_txStatus = Status.STATUS_MARKED_ROLLBACK;
            if (log.isDebugEnabled()) log.debug("Commit fails, do abort this tx", ex);
            abort();
            throw ex;
        }
    }

    /**
     * Prepare does the actual work of moving the changes at the object level
     * into storage (the underlying rdbms for instance). prepare Can be called multiple times, and
     * does not release locks.
     * @return True if the transaction can commit, false if the
     * transaction is read only.
     * @throws TransactionAbortedException if the transaction has been aborted
     * for any reason.
     * @throws  IllegalStateException Method called if transaction is
     *  not in the proper state to perform this operation
     * @throws TransactionNotInProgressException if the transaction is closed.
     */
    protected boolean prepare() throws TransactionAbortedException, LockNotGrantedException
    {
        if (m_txStatus == Status.STATUS_MARKED_ROLLBACK)
            throw new TransactionAbortedExceptionOJB("persist.markedRollback");
        if (m_txStatus != Status.STATUS_ACTIVE)
            throw new IllegalStateException("persist.noTransaction");
        try
        {
            m_txStatus = Status.STATUS_PREPARING;
            doCommitOnObjects(false);
            m_txStatus = Status.STATUS_PREPARED;
        }
        catch (LockNotGrantedException e)
        {
            log.error("Could not prepare for commit: " + e.getMessage());
            m_txStatus = Status.STATUS_MARKED_ROLLBACK;
            throw e;
        }
        catch (TransactionAbortedException e)
        {
            log.error("Could not prepare for commit: " + e.getMessage());
            m_txStatus = Status.STATUS_MARKED_ROLLBACK;
            throw e;
        }
        catch (PersistenceBrokerSQLException pbse)
        {
            log.error("Could not prepare for commit: " + pbse.getMessage());
            m_txStatus = Status.STATUS_MARKED_ROLLBACK;
            throw pbse;
        }
        return true;
    }

    /**
     * Abort and close the transaction. Calling abort abandons all persistent
     * object modifications and releases the associated locks. Aborting a
     * transaction does not restore the state of modified transient objects
     */
    public void abort()
    {
        /*
        do nothing if already rolledback
        */
        if (m_txStatus == Status.STATUS_ROLLEDBACK)
        {
            return;
        }
        if (m_txStatus != Status.STATUS_ACTIVE && m_txStatus != Status.STATUS_PREPARED &&
                m_txStatus != Status.STATUS_MARKED_ROLLBACK)
            throw new IllegalStateException("Illegal state for abort call, state was '" + TxUtil.getStatusString(m_txStatus) + "'");
        log.info("Abort transaction was called on tx " + this + ", associated PB was " + broker);
        try
        {
            doAbort();
            PersistenceBroker _broker = getBroker();
            if (_broker.isInTransaction())
            {
                _broker.abortTransaction();
            }
        }
        finally
        {
            doClose();
            m_txStatus = Status.STATUS_ROLLEDBACK;
        }
    }

    /**
     * Start a transaction. Calling <code>begin</code> multiple times on the same
     * transaction object, without an intervening call to <code>commit</code> or
     * <code>abort</code> , causes the exception <code>
     * TransactionInProgressException</code> to be thrown on the second and
     * subsequent calls. Operations executed before a transaction has been opened,
     * or before reopening after a transaction is aborted or committed, have
     * undefined results; these may throw a <code>
     * TransactionNotInProgressException</code> exception.
     */
    public synchronized void begin()
    {
        /**
         * Is the associated database non-null and open? ODMG 3.0 says it must be.
         */
        if ((curDB == null) || !curDB.isOpen())
        {
            throw new DatabaseClosedException("Database is not open. Must have an open DB to begin the Tx.");
        }
        if (isOpen())
        {
            log.error("Transaction is already open");
            throw new org.odmg.TransactionInProgressException("Impossible to call begin on already opened tx");
        }
        // initialize the ObjectEnvelope table
        objectEnvelopeTable = new ObjectEnvelopeTable(this);
        // initialize the temporary hashtable
        myNrm = new Hashtable();
        // register transaction
        txManager.registerTx(this);
        // mark tx as active (open)
        m_txStatus = Status.STATUS_ACTIVE;
        if (log.isDebugEnabled()) log.debug("Begin transaction was called on tx " + this + ", with associated PB " + broker);
    }

    public String getGUID()
    {
        return txGUID;
    }

    /**
     * Get object by identity. First lookup among objects registered in the
     * transaction, then in persistent storage.
     * @param id The identity
     * @return The object
     * @throws PersistenceBrokerException
     */
    public Object getObjectByIdentity(Identity id)
            throws PersistenceBrokerException
    {
        checkOpen();
        ObjectEnvelope envelope = objectEnvelopeTable.getByIdentity(id);
        if (envelope != null)
        {
            return (envelope.needsDelete() ? null : envelope.getObject());
        }
        else
        {
            return getBroker().getObjectByIdentity(id);
        }
    }

    /**
     * Checks if the object with the given identity has been deleted
     * within the transaction.
     * @param id The identity
     * @return true if the object has been deleted
     * @throws PersistenceBrokerException
     */
    public boolean isDeleted(Identity id)
    {
        ObjectEnvelope envelope = objectEnvelopeTable.getByIdentity(id);

        return (envelope == null ? false : envelope.needsDelete());
    }

    /**
     * registers the object with this transaction recursively with all associations.
     * OJB implicitely acquires read locks on registered objects.
     */
    private synchronized void register(Object newTxObject, int lockMode)
            throws LockNotGrantedException, PersistenceBrokerException
    {
        Object objectToRegister = newTxObject;
        IndirectionHandler handler = null;

        // Proxies must be treated specially
        handler = ProxyHelper.getIndirectionHandler(newTxObject);

        /*
        if the object is a Proxy there are two options:
        1. The proxies real subject has already been materialized:
           we take this real subject as the object to register and proceed
           as if it were a ordinary object.
        2. The real subject has not been materialized: Then there is nothing
           to be registered now!
           Of course we might just materialize the real subject to have something
           to register. But this would make proxies useless for ODMG as proxies would
           get materialized even if their real subjects were not used by the
           client app.
           Thus we register the current transaction as a Listener to the IndirectionHandler
           of the Proxy.
           Only when the IndirectionHandler performs the materialization of the real subject
           at some later point in time it invokes callbacks on all it's listeners.
           Using this callback we can defer the registering until it's really needed.
        */
        if (handler != null)
        {
            if (handler.alreadyMaterialized())
            {
                objectToRegister = handler.getRealSubject();
            }
            else
            {
                registerToIndirectionHandler(handler);
                registerUnmaterializedLocks(newTxObject);
                // all work is done, so set to null
                objectToRegister = null;
            }
        }

        // no Proxy and is not null, register real object
        if (objectToRegister != null)
        {
            ClassDescriptor cld = this.getBroker().getClassDescriptor(objectToRegister.getClass());
            ObjectEnvelope envelope = objectEnvelopeTable.getByIdentity(new Identity(objectToRegister, getBroker(), cld));
            // if we found an envelope, object is already registered and we do nothing!
            if ((envelope == null) || envelope.needsDelete())
            {
                // 1. register associated objects
                doLockReferences(cld, objectToRegister, lockMode);
                // 2. register object itself
                objectEnvelopeTable.put(objectToRegister, new ObjectEnvelope(objectToRegister, this));
                doLockCollections(cld, objectToRegister, lockMode);
            }
        }
    }

    protected void doLockReferences(ClassDescriptor cld, Object objectToRegister,
                                    int assLockMode) throws LockNotGrantedException
    {
        // if OJB is configured to use only implicit readlocks, change the lockmode
        if (!useWriteLocks)
        {
            assLockMode = Transaction.READ;
        }
        if (useImplicitLocking)
        {
            lockReferences(cld, objectToRegister, assLockMode);
        }
    }

    protected void doLockCollections(ClassDescriptor cld, Object objectToRegister,
                                    int assLockMode) throws LockNotGrantedException
    {
        // if OJB is configured to use only implicit readlocks, change the lockmode
        if (!useWriteLocks)
        {
            assLockMode = Transaction.READ;
        }
        if (useImplicitLocking)
        {
            lockCollections(cld, objectToRegister, assLockMode);
        }
    }

    private void assertFkAssignment(Object obj, Object ref, ObjectReferenceDescriptor rds)
    {
        try
        {
            if (!ProxyHelper.isProxy(obj) && (ref != null))
            {
                Object refInstance = ProxyHelper.getRealObject(ref);
                ClassDescriptor objCld = this.getBroker().getClassDescriptor(obj.getClass());
                FieldDescriptor[] objFkFields = rds.getForeignKeyFieldDescriptors(objCld);

                // oma: refInstance might be null in case of dangling foreign keys.
                ValueContainer[] refPkValues;
                if (refInstance != null)
                {
                    ClassDescriptor refCld = this.getBroker().getClassDescriptor(refInstance.getClass());
                    refPkValues = getBroker().serviceBrokerHelper().getKeyValues(refCld, refInstance, false);
                }
                else
                {
                    refPkValues = new ValueContainer[objFkFields.length];
                    Arrays.fill(refPkValues, null);
                }

                /**
                 * MBAIRD:
                 * objFkFields could end up being null in case of non-mapped indirection table of m:n relationship
                 *
                 */
                if (objFkFields != null)
                {
                    org.apache.ojb.broker.metadata.FieldDescriptor fld = null;
                    for (int i = 0; i < objFkFields.length; i++)
                    {
                        fld = objFkFields[i];
                        fld.getPersistentField().set(obj, (refPkValues[i] != null ? refPkValues[i].getValue() : null));
                    }
                }
            }
        }
        catch (Throwable t)
        {
            throw new PersistenceBrokerException(t);
        }
    }

    /**
     *   assigns all foreign key attributes of the Object obj.
     *  used during store(obj, boolean);
     */
    private void assignReferenceFKs(Object obj, Vector vecRds) throws PersistenceBrokerException
    {
        try
        {
            // get all members of obj that are references and assign FKs
            Iterator i = vecRds.iterator();
            while (i.hasNext())
            {
                ObjectReferenceDescriptor rds = (ObjectReferenceDescriptor) i.next();
                Object ref = rds.getPersistentField().get(obj);
                assertFkAssignment(obj, ref, rds);
            }
        }
        catch (Throwable t)
        {
            throw new PersistenceBrokerException(t);
        }
    }

    private void lockCollections(ClassDescriptor cld, Object newTxObject, int lockMode)
            throws PersistenceBrokerException
    {
        Iterator i;
        i = cld.getCollectionDescriptors().iterator();
        while (i.hasNext())
        {
            CollectionDescriptor cds = (CollectionDescriptor) i.next();
            Object col = cds.getPersistentField().get(newTxObject);
            if (col != null)
            {
                CollectionProxy proxy = ProxyHelper.getCollectionProxy(col);
                if (proxy != null)
                {
                    if (!proxy.isLoaded())
                    {
                        if (log.isDebugEnabled()) log.debug("adding self as listener to collection proxy");
                        proxy.addListener(this);
                        continue;
                    }
                }
                Iterator colIterator = BrokerHelper.getCollectionIterator(col);
                // get foreign info for collection elements
                ClassDescriptor itemCld = this.getBroker().getClassDescriptor(cds.getItemClass());
                // BRJ: do not convertToSql
                ValueContainer[] objPkValues = this.getBroker().serviceBrokerHelper().getKeyValues(cld, newTxObject, false);
                FieldDescriptor[] itemFkFields = cds.getForeignKeyFieldDescriptors(itemCld);

                Object item = null;
                try
                {
                    while (colIterator.hasNext())
                    {
                        item = colIterator.next();
                        IndirectionHandler handler = ProxyHelper.getIndirectionHandler(item);
                        if (handler != null)
                        {
                            if (!handler.alreadyMaterialized())
                            {
                                continue;
                            }
                            else
                            {
                                // @todo consider registering to hear when this is
                                // derefernced instead of just loading here -bmc
                                item = handler.getRealSubject();
                            }
                        }
                        if(!cds.isMtoNRelation())
                        {
                            //if itemCld refers to an interface the foreignKeyFieldDescriptors
                            //have to be computed again for each concrete class
                            if (itemCld.isInterface())
                            {
                                ClassDescriptor concreteItemCld = getBroker().getClassDescriptor(item.getClass());
                                itemFkFields = cds.getForeignKeyFieldDescriptors(concreteItemCld);
                            }
                            // provide all items in collection attributes
                            // with foreign key information before locking them!
                            for (int j = 0; j < itemFkFields.length; j++)
                            {
                                FieldDescriptor fld = itemFkFields[j];
                                fld.getPersistentField().set(item, objPkValues[j].getValue());
                            }
                        }
                        lock(item, lockMode);
                    }
                }
                catch (PersistenceBrokerException e)
                {
                    String eol = SystemUtils.LINE_SEPARATOR;
                    log.error("Error while set FK in collection references[" +
                            eol + "current reference descriptor:" +
                            eol + cds.toXML() +
                            eol + "current item object: " + item +
                            eol + "main object class: " + newTxObject.getClass().getName() +
                            eol + "]", e);
                    throw e;
                }
                catch (LockNotGrantedException e)
                {
                    String eol = SystemUtils.LINE_SEPARATOR;
                    log.error("Lock not granted, while set FK in collection references[" +
                            eol + "current reference descriptor:" +
                            eol + cds.toXML() +
                            eol + "object to lock: " + item +
                            eol + "main object class: " + newTxObject.getClass().getName() +
                            eol + "]", e);
                    throw e;
                }
            }
        }
    }

    /**
     * we only use the registeredForLock map if the object is not a proxy. During the
     * reference locking, we will materialize objects and they will enter the registered for
     * lock map.
     */
    private void lockReferences(ClassDescriptor cld, Object newTxObject, int lockMode)
            throws PersistenceBrokerException
    {
        if ((newTxObject != null) && !ProxyHelper.isProxy(newTxObject))
        {
            registeredForLock.add(newTxObject);
        }

        Iterator i = cld.getObjectReferenceDescriptors().iterator();
        while (i.hasNext())
        {
            ObjectReferenceDescriptor rds = (ObjectReferenceDescriptor) i.next();
            Object refObj = rds.getPersistentField().get(newTxObject);
            if (refObj != null)
            {
                if (ProxyHelper.isProxy(refObj))
                {
                    lock(refObj, lockMode);
                }
                else if (!registeredForLock.contains(refObj))
                {
                    lock(refObj, lockMode);
                }
            }
        }
    }

    /**
     * lookup an identity from the transactions temporary nrm table.
     * @param name - the name to lookup.
     * @return Identity - the found Identity or null if name not defined.
     */
    Identity getNrmEntry(String name)
    {
        return (Identity) myNrm.get(name);
    }

    /**
     * make a binding name/identity to the transactions temporary nrm table.
     * @param key - the identifying name
     * @param value the Identity to store
     */
    void putNrmEntry(String key, Identity value)
    {
        myNrm.put(key, value);
    }

    /**
     * unbind an entry from the transactions temporary nrm table.
     * @param key - the identifying string whose entry is to be deleted.
     */
    boolean unbindNrmEntry(String key)
    {
        // remove from envelopeTable to prevent commit of old entry on tx commit!
        Identity oid = (Identity) myNrm.get(key);
        if (oid != null)
        {
            objectEnvelopeTable.remove(oid);
        }

        // remove from transient NRM
        if (myNrm.remove(key) == null)
            return false;
        else
            return true;
    }

    /**
     *  this callback is invoked before an Object is materialized
     *  within an IndirectionHandler.
     *  @param handler the invoking handler
     *  @param oid the identity of the object to be materialized
     */
    public void beforeMaterialization(IndirectionHandler handler, Identity oid)
    {
        //noop
    }

    /**
     *  this callback is invoked after an Object is materialized
     *  within an IndirectionHandler.
     *  this callback allows to defer registration of objects until
     *  it's really neccessary.
     *  @param handler the invoking handler
     *  @param materializedObject the materialized Object
     */
    public void afterMaterialization(IndirectionHandler handler, Object materializedObject)
    {
        if (log.isDebugEnabled())
            log.debug("deferred registration: " + new Identity(materializedObject, getBroker()).toString());
        try
        {
            register(materializedObject, READ);
        }
        catch (Throwable t)
        {
            log.error("Register materialized object with this tx failed", t);
            throw new LockNotGrantedException(t.getMessage());
        }
        unregisterFromIndirectionHandler(handler);
    }

    protected synchronized void unRegisterFromAllIndirectionHandlers()
    {
        // unregistering manipulates the registeredIndirectionHandlers vector
        // we have to loop through this vector to avoid index proplems.
        for (int i = registeredIndirectionHandlers.size() - 1; i >= 0; i--)
        {
            unregisterFromIndirectionHandler((IndirectionHandler) registeredIndirectionHandlers.get(i));
        }
    }

    protected synchronized void unRegisterFromAllCollectionProxies()
    {
        for (int i = registeredCollectionProxies.size() - 1; i >= 0; i--)
        {
            unregisterFromCollectionProxy((CollectionProxy) registeredCollectionProxies.get(i));
        }
    }

    protected synchronized void unregisterFromCollectionProxy(CollectionProxy handler)
    {
        handler.removeListener(this);
        registeredCollectionProxies.remove(handler);
    }

    protected synchronized void unregisterFromIndirectionHandler(IndirectionHandler handler)
    {
        handler.removeListener(this);
        registeredIndirectionHandlers.remove(handler);
    }

    protected synchronized void registerToIndirectionHandler(IndirectionHandler handler)
    {
        handler.addListener(this);
        registeredIndirectionHandlers.add(handler);
    }

    /**
     * register proxy objects that were locked but haven't been materialized yet
     * so they can be unlocked when closing the transaction
     */
    protected void registerUnmaterializedLocks(Object obj)
    {
        unmaterializedLocks.add(obj);
    }

    /**
     * Gets the broker associated with the transaction.
     * MBAIRD: only return the associated broker if the transaction is open,
     * if it's closed, throw a TransactionNotInProgressException. If we allow
     * brokers to be reaquired by an already closed transaction, there is a
     * very good chance the broker will be leaked as the doClose() method of
     * transactionImpl will never be called and thus the broker will never
     * be closed and returned to the pool.
     * @return Returns a PersistenceBroker
     * @throws TransactionNotInProgressException is the transaction is not open;
     */
    public PersistenceBroker getBroker()
    {
        if (broker == null)
        {
            checkOpen();
            try
            {
                checkForDB();
                broker = PersistenceBrokerFactory.createPersistenceBroker(curDB.getPBKey());
            }
            catch (PBFactoryException e)
            {
                log.error("Cannot obtain PersistenceBroker from PersistenceBrokerFactory, " +
                        "found PBKey was " + curDB.getPBKey(), e);
                throw new PersistenceBrokerException(e);
            }
        }
        return broker;
    }

    /*
     * @see Configurable#configure(Configuration)
     */
    public void configure(Configuration config) throws ConfigurationException
    {
        OdmgConfiguration odmgConfig = (OdmgConfiguration) config;

        useWriteLocks = odmgConfig.lockAssociationAsWrites();
        useImplicitLocking = odmgConfig.useImplicitLocking();
    }

    /**
     * @see org.apache.ojb.odmg.TransactionExt#setImplicitLocking(boolean)
     */
    public synchronized void setImplicitLocking(boolean value)
    {
        useImplicitLocking = value;
    }

    /**
     * noop -- here for interface
     */
    public void beforeLoading(CollectionProxyDefaultImpl colProxy)
    {
        // noop
    }

    /**
     * Remove colProxy from list of pending collections and
     * register its contents with the transaction.
     */
    public void afterLoading(CollectionProxyDefaultImpl colProxy)
    {
        if (log.isDebugEnabled()) log.debug("loading a proxied collection a collection: " + colProxy);
        if (this.registeredCollectionProxies.contains(colProxy))
        {
            this.registeredCollectionProxies.remove(colProxy);
            colProxy.removeListener(this);
        }
        Collection data = colProxy.getData();
        for (Iterator iterator = data.iterator(); iterator.hasNext();)
        {
            Object o = iterator.next();
            if (useImplicitLocking && this.isOpen())
            {
                int lock = useWriteLocks ? Transaction.WRITE : Transaction.READ;
                this.register(o, lock);
            }
        }
    }
}
TOP

Related Classes of org.apache.ojb.odmg.TransactionImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.