/**
 * Redistribution and use of this software and associated documentation
 * ("Software"), with or without modification, are permitted provided
 * that the following conditions are met:
 *
 * 1. Redistributions of source code must retain copyright
 *    statements and notices.  Redistributions must also contain a
 *    copy of this document.
 *
 * 2. Redistributions in binary form must reproduce the
 *    above copyright notice, this list of conditions and the
 *    following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *
 * 3. The name "Exolab" must not be used to endorse or promote
 *    products derived from this Software without prior written
 *    permission of Intalio, Inc.  For written permission,
 *    please contact info@exolab.org.
 *
 * 4. Products derived from this Software may not be called "Exolab"
 *    nor may "Exolab" appear in their names without prior written
 *    permission of Intalio, Inc. Exolab is a registered
 *    trademark of Intalio, Inc.
 *
 * 5. Due credit should be given to the Exolab Project
 *    (http://www.exolab.org/).
 *
 * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
 * INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Copyright 1999 (C) Intalio, Inc. All Rights Reserved.
 *
 * $Id: ObjectLock.java 8294 2009-07-24 08:28:23Z lukaslang $
 */
package org.exolab.castor.persist;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.castor.core.util.Messages;
import org.castor.persist.TransactionContext;
import org.exolab.castor.jdo.LockNotGrantedException;

/**
 * Read/write locks and lock synchronization on an object. Each object
 * is required to have one <tt>ObjectLock</tt> which at any given time
 * may be unlocked, write locked by one transaction, or read locked
 * by one or more transactions.
 * <p>
 * In order to obtain a lock, the transaction must call one of the
 * acquire, passing itself, the lock type and the lock timeout. The
 * transaction must attempt to obtain only one lock at any given time
 * by synchronizing all calls to one of the <tt>acquire</tt>. If the transaction
 * has acquired a read lock it may attempt to re-acquire the read
 * lock. If the transaction attempts to acquire a write lock the lock
 * will be upgraded.
 * <p>
 * A read lock cannot be acquired while there is a write lock on the
 * object, and a write lock cannot be acquired while there is one or
 * more read locks. If a lock cannot be acquired, the transaction
 * will hold until the lock is available or timeout occurs. If timeout
 * occured (or a dead lock has been detected), {@link
 * LockNotGrantedException} is thrown. If the object has been delete
 * while waiting for the lock, {@link ObjectDeletedException} is
 * thrown.
 * <p>
 * When the lock is acquired, the locked object is returned.
 * <p>
 * The transaction must call {@link #release} when the lock is no
 * longer required, allowing other transactions to obtain a lock. The
 * transaction must release all references to the object prior to
 * calling {@link #release}.
 * <p>
 * If the object has been deleted, the transaction must call {@link
 * #delete} instead of {@link #release}.
 *
 *
 * @author <a href="arkin@intalio.com">Assaf Arkin</a>
 * @author <a href="yip@intalio.com">Thomas Yip</a>
 * @version $Revision: 8294 $ $Date: 2006-04-25 15:08:23 -0600 (Tue, 25 Apr 2006) $
 * @see TransactionContext
 */

public final class ObjectLock implements DepositBox {
    /** The <a href="http://jakarta.apache.org/commons/logging/">Jakarta
     *  Commons Logging</a> instance used for all logging. */
    private static final Log LOG = LogFactory.getFactory().getInstance(ObjectLock.class);

    protected static final short ACTION_READ = 1;

    protected static final short ACTION_WRITE = 2;

    protected static final short ACTION_CREATE = 3;

    protected static final short ACTION_UPDATE = 4;

    private static final int[] LOCK = new int[0];

    private static int _idcount = 0;
    
    private int _id;

    /** The object being locked. */
    private Object[] _object;

    /** The object's OID. */
    private OID _oid;

    /** Write lock on this object. Refers to the transaction that has
     *  acquired the write lock. Read and write locks are mutually
     *  exclusive. */
    private TransactionContext _writeLock;

    /** Read locks on this object. A LinkedTx list of all transactions
     *  that have acquired a read lock. Read and write locks are
     *  mutually exclusive. */
    private LinkedTx _readLock;

    /** List of all transactions waiting for a read lock. Attempts to
     *  acquire read lock while object has write lock will be recorded
     *  here. When write lock is released, all read locks will acquire. */
    private LinkedTx _readWaiting;

    private int _waitCount;

    /** List of all transactions waiting for a write lock (including
     *  waiting for upgrade from read lock). Attempts to acquire a
     *  write lock while object has a read lock will be recorded here.
     *  When read lock is released, the first write lock will acquire. */
    private LinkedTx _writeWaiting;

    private TransactionContext _confirmWaiting;

    private short _confirmWaitingAction;

    /** Number of transactions which are interested to invoke method on this lock.
     *  If the number is zero, and the lock isFree(), then it is safe dispose this lock. */
    private int _gateCount;

    /**
     * The object's version.
     */
    private long _version;

    private boolean _deleted;

    private boolean _invalidated;

    private boolean _isExpired;
    
    private Object[] _expiredObject;

    /**
     * Create a new lock for the specified object. Must not create two
     * locks for the same object. This will be the object returned from
     * a successful call to one of the <tt>acquire</tt>.
     *
     * @param oid The object to create a lock for
     */
    public ObjectLock(final OID oid) {
        _oid = oid;

        // give each instance of ObjectLock an id, for debug only
        synchronized (LOCK) {
            _id = _idcount;
            _idcount++;
        }
    }
    
    public ObjectLock(final OID oid, final Object[] object, final long version) {
        this(oid);
        
        _object = object;
        _version = version;
    }
    
    /**
     * Return the object's OID.
     */
    public OID getOID() {
        return _oid;
    }

    /** 
     * Set OID of this lock to new value.
     *
     */
    void setOID(final OID oid) {
        _oid = oid;
    }

    /**
     * Indicate that a transaction is interested in this lock.
     * A transaction should call this method if it is going to
     * change the state of this lock (by calling acquire, update 
     * or relase.) It method should be synchronized externally 
     * to avoid race condition. enter and leave should be called 
     * exactly the same number of time.
     */
    void enter() {
        _gateCount++;
    }

    /**
     * Indicate that a transaction is not interested to change the 
     * state of this lock anymore. (ie, will not call either acquire
     * update, release or delete.) 
     * It method should be synchronized externally.
     */
    void leave() {
        _gateCount--;
    }

    /**
     * Return true if there is any transaction called {@link #enter},
     * but not yet called {@link #leave}.
     */ 
    boolean isEntered() {
        return _gateCount != 0;
    }

    /**
     * Return true if this object can be safely disposed. An ObjectLock
     * can be safely disposed if and only if the no transaction is 
     * holding any lock, nor any transaction isEntered.
     */
    boolean isDisposable() {
        return _gateCount == 0 && isFree() && _waitCount == 0;
    }

    /**
     * Returns true if the transaction holds a read or write lock on
     * the object. This method is an efficient mean to determine whether
     * a lock is required, or if the object is owned by the transaction.
     *
     * @param tx The transaction
     * @param write True if must have a write lock
     * @return True if the transaction has a lock on this object
     */
    boolean hasLock(final TransactionContext tx, final boolean write) {
        LinkedTx read;

        if (_writeLock == tx) {
            return true;
        }

        if (_confirmWaiting == tx) {
            if ((_confirmWaitingAction == ACTION_WRITE)
                    || (_confirmWaitingAction == ACTION_CREATE)) {
                return true;
            } else if (!write && _confirmWaitingAction == ACTION_READ) {
                return true;
            }
            return false;
        }

        if (write) {
            return false;
        }
        read = _readLock;
        while (read != null) {
            if (read._tx == tx) {
                return true;
            }
            read = read._next;
        }

        return false;
    }

    /**
     * Return true if and only if this lock can be safely disposed.
     *
     * @return True if no lock and no waiting.
     */
    boolean isFree() {
        return ((_writeLock == null) && (_readLock == null)
                && (_writeWaiting == null) && (_readWaiting == null)
                && (_confirmWaiting == null) && (_waitCount == 0));
    }

    boolean isExclusivelyOwned(final TransactionContext tx) {
        if ((_writeLock == null) && (_readLock == null)) {
            return false;
        }

        if ((_writeLock == null) && (_readLock._tx == tx) && (_readLock._next._tx == null)) {
            return true;
        }

        if ((_writeLock == tx) && (_readLock == null)) {
            return true;
        }

        return false;
    }

    
    /**
     * Return true if this entry has been expired from the cache.
     */
    boolean isExpired() {
        return _isExpired; 
    }

    public Object[] getObject() { 
        if ((_expiredObject != null) && (_object == null)) {
            return _expiredObject;
        }
        return _object; 
    }
   
    /**
     * Indicate that object needs to be expired from the cache.
     */
    public void expire() {
        _isExpired = true; 
    }
   
    /**
     * Indicate that object has been removed from the cache. Perform any 
     * post expiration cleanup.  In particular, remove the reference to any
     * saved cached objects.
     */
    public void expired() { 
        _isExpired = false; 
        _expiredObject = null;
    }

    synchronized void acquireLoadLock(final TransactionContext tx, final boolean write,
            final int timeout) throws LockNotGrantedException {
        int internalTimeout = timeout;
        long endtime = (internalTimeout > 0)
                     ? System.currentTimeMillis() + internalTimeout * 1000
                     : Long.MAX_VALUE;
        while (true) {
            try {
                // cases to consider:
                // 3/ waitingForConfirmation exist
                //      then, we wait
                // 4/ need a read, and objectLock has something
                //      then, we return and wait for confirmation
                // 5/ need a read, and objectLock has nothing
                //      then, we return and wait for confirmation
                // 6/ need a write
                //      then, we return and wait for confirmation
                // 7/ we're in some kind of lock, or waiting, exception
                // 1/ write exist 
                //      then, put it tx into read/write waiting
                // 2/ read exist
                //      then, put it read, or write waiting
                if (_deleted) {
                    throw new ObjectDeletedWaitingForLockException("Object deleted");
                } else if (_confirmWaiting != null) {
                    // other thread is loading or creating object and haven't finished
                    try {
                        _waitCount++;
                        wait();                        
                    } catch (InterruptedException e) {
                        throw new LockNotGrantedException("Thread interrupted acquiring lock!", e);
                    } finally {
                        _waitCount--;
                    }
                } else if (_writeLock == tx) {
                    //throw new IllegalStateException(
                    //        "Transaction: " + tx + " has already hold the write lock on "
                    //        + _oid + " Acquire shouldn't be called twice");
                    return;
                } else if ((_readLock == null) && (_writeLock == null) && write) {
                    // no transaction hold any lock,
                    _confirmWaiting = tx;
                    _confirmWaitingAction = ACTION_WRITE;
                    return;
                } else if ((_readLock == null) && (_writeLock == null) && !write) {
                    // no transaction hold any lock, 
                    if (_object == null) {
                        _confirmWaiting = tx;
                        _confirmWaitingAction = ACTION_READ;
                        return;
                    }
                    _readLock = new LinkedTx(tx, null);
                    return;
                } else if ((_readLock != null) && !write) {
                    // already a transaction holding read lock, can acquire read lock
                    LinkedTx linked = _readLock;
                    while (linked != null) {
                        if (linked._tx == tx) {
                            throw new IllegalStateException("Transaction: " + tx
                                    + " has already hold the write lock on " + _oid
                                    + " Acquire shouldn't be called twice");
                        }
                        linked = linked._next;
                    }
                   
                    // if not already in readLock
                    _readLock = new LinkedTx(tx, _readLock);
                    return;
                } else {
                    // other transaction holding writeLock, waits for write
                    // or, other transaction holding readLock, waiting for read
                    if (internalTimeout == 0) {
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("Timeout on " + this.toString() + " by " + tx);
                        }
                        throw new LockNotGrantedException(
                                (write ? "persist.writeLockTimeout" : "persist.readLockTimeout")
                                + _oid + "/" + _id + " by " + tx);
                    }
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Waiting on " + this.toString() + " by " + tx);
                    }
                    // Detect possibility of dead-lock. Must remain in wait-on-lock
                    // position until lock is granted or exception thrown.
                    tx.setWaitOnLock(this);
                    detectDeadlock(tx, 10);
                    
                    // Must wait for lock and then attempt to reacquire
                    if (write) {
                        _writeWaiting = new LinkedTx(tx, _writeWaiting);
                    } else {
                        _readWaiting = new LinkedTx(tx, _readWaiting);
                    }
                    
                    // Wait until notified or timeout elapses. Must detect
                    // when notified but object deleted (i.e. locks released)
                    // All waiting transactions are notified at once, but once
                    // notified a race condition starts to acquire new lock
                    try {
                        long waittime = endtime - System.currentTimeMillis();
                        wait((waittime < 0) ? 0 : waittime);
                    } catch (InterruptedException except) {
                        // If the thread is interrupted, come out with the proper message
                        throw new LockNotGrantedException(
                                (write ? "persist.writeLockTimeout" : "persist.readLockTimeout")
                                + _oid + "/" + _id + " by " + tx, except);
                    }

                    if (_deleted) {
                        // If object has been deleted while waiting for lock, report deletion.
                        throw new ObjectDeletedWaitingForLockException(
                                "object deleted" + _oid + "/" + _id + " by " + tx);
                    }

                    // Try to re-acquire lock, this time less timeout,
                    // eventually timeout of zero will either succeed or fail
                    // without blocking.
                    if (System.currentTimeMillis() > endtime) {
                        internalTimeout = 0;
                    }

                    removeWaiting(tx);
                    tx.setWaitOnLock(null);
                }
            } finally {
                removeWaiting(tx);       
                tx.setWaitOnLock(null);
            }
        }
    }

    synchronized void acquireCreateLock(final TransactionContext tx)
    throws LockNotGrantedException {
        while (true) {
            // cases to consider:
            // 1/ waitingForConfirmation exist
            // 2/ lock can't be granted, throw LockNotGrantedException
            // 3/ lock can be granted
            //      then, we return and wait for confirmation
            if (_deleted || (_confirmWaiting != null)) {
                // other thread is loading or creating object and haven't finished
                try {
                    _waitCount++;
                    wait();
                    while (_deleted) {
                        wait();
                    }
                } catch (InterruptedException e) {
                    throw new LockNotGrantedException("Thread interrupted acquiring lock!", e);
                } finally {
                    _waitCount--;
                }
            } else if ((_readLock != null) || (_writeLock != null)) {
                throw new LockNotGrantedException("Lock already exist!");
            } else {
                _confirmWaiting = tx;
                _confirmWaitingAction = ACTION_CREATE;
                return;
            }
        }
    }

    // probaraly we just don't need update....
    synchronized void acquireUpdateLock(final TransactionContext tx, final int timeout)
    throws LockNotGrantedException {
        int internalTimeout = timeout;
        long endtime = (internalTimeout > 0)
                     ? System.currentTimeMillis() + internalTimeout * 1000
                     : Long.MAX_VALUE;
        while (true) {
            try {
                // case to consider:
                // 1/ waitingForConfirmation exist
                // 2/ lock can be granted, and _object is not empty
                //      then, we return and wait for confirmation
                // 3/ lock can not granted, wait

                if (_deleted || (_confirmWaiting != null)) {
                    try {
                        _waitCount++;
                        wait();
                        /*
                        if ( _deleted ) {
                            throw new ObjectDeletedWaitingForLockException("Object deleted!");
                        }*/
                    } catch (InterruptedException e) {
                        throw new LockNotGrantedException("Thread interrupted acquiring lock!", e);
                    } finally {
                        _waitCount--;
                    }
                } else if (_writeLock == tx) {
                    return;
                } else if ((_writeLock == null) && (_readLock == null)) {
                    // can get the lock now
                    _confirmWaiting = tx;
                    _confirmWaitingAction = ACTION_UPDATE;
                    return;
                } else {
                    if (internalTimeout == 0) {
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("Timeout on " + this.toString() + " by " + tx);
                        }
                        throw new LockNotGrantedException(Messages.message(
                                "persist.writeLockTimeout"));
                    }
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Waiting on " + this.toString() + " by " + tx);
                    }
                    // Detect possibility of dead-lock. Must remain in wait-on-lock
                    // position until lock is granted or exception thrown.
                    tx.setWaitOnLock(this);
                    detectDeadlock(tx, 10);
                    
                    // Must wait for lock and then attempt to reacquire
                    _writeWaiting = new LinkedTx(tx, _writeWaiting);
                    
                    // Wait until notified or timeout elapses. Must detect
                    // when notified but object deleted (i.e. locks released)
                    // All waiting transactions are notified at once, but once
                    // notified a race condition starts to acquire new lock
                    try {
                        long waittime = endtime - System.currentTimeMillis();
                        wait((waittime < 0) ? 0 : waittime);
                    } catch (InterruptedException except) {
                        // If the thread is interrupted, come out with the proper message
                        throw new LockNotGrantedException(Messages.message(
                                "persist.writeLockTimeout") + _oid + "/" + _id
                                + " by " + tx, except);
                    }

                    if (_deleted) {
                        // If object has been deleted while waiting for lock, report deletion.
                        throw new ObjectDeletedWaitingForLockException(
                                "Object deleted " + _oid + "/" + _id + " by " + tx);
                    }

                    // Try to re-acquire lock, this time less timeout,
                    // eventually timeout of zero will either succeed or fail
                    // without blocking.
                    if (System.currentTimeMillis() > endtime) {
                        internalTimeout = 0;
                    }

                    removeWaiting(tx);
                    tx.setWaitOnLock(null);
                }
            } finally {
                removeWaiting(tx);
                tx.setWaitOnLock(null);                
            }
        }
    }

    public synchronized void setObject(final TransactionContext tx,
            final Object[] object, final long version) {

        _isExpired = false; // initialize cache expiration flag to false
        _expiredObject = null;

        if ((_confirmWaiting != null) && (_confirmWaiting == tx)) {
            _version = version;
            _object = object;
            if (_confirmWaitingAction == ACTION_READ) {
                _readLock = new LinkedTx(tx, null);
            } else {
                _writeLock = tx;
            }
            _confirmWaiting = null;
            notifyAll();
        } else if ((_writeLock != null) && (_writeLock == tx)) {
            _version = version;
            _object = object;
        } else {
            throw new IllegalArgumentException(
                    "Transaction tx does not own this lock, " + toString() + "!");
        }
    }

    public synchronized Object[] getObject(final TransactionContext tx) {
        if ((_confirmWaiting != null) && (_confirmWaiting == tx)) {
            return _object;
        } else if ((_writeLock != null) && (_writeLock == tx)) {
            return _object;
        } else {
            LinkedTx link = _readLock;
            while (link != null) {
                if (link._tx == tx) {
                    return _object;
                }
                link = link._next;
            }
            throw new IllegalArgumentException("Transaction tx does not own this lock!");
        }
    }

    public synchronized long getVersion() {
        return _version;
    }
    
    public synchronized void setVersion(long version) {
        this._version = version;
    }
    
    synchronized void confirm(final TransactionContext tx, final boolean succeed) {

        // cases to consider:
        // 1/ not in waitingForConfirmation
        // 2/ load_read,
        //         downgrade the lock
        // 3/ else
        //         move confirmation and
        //         notify()
        if (_confirmWaiting == tx) {
            if (succeed) {
                if (_confirmWaitingAction == ACTION_READ) {
                    if (_readLock == null) {
                        _readLock = new LinkedTx(tx, null);
                    }
                } else {
                    _writeLock = tx;
                }
            }
            _confirmWaiting = null;
            notifyAll();
        } else if (_confirmWaiting == null) {
            if (!succeed) {
                // remove it from readLock
                if (_writeLock != null) {
                    // same as delete the lock
                    _deleted = true;
                    _object = null;
                    _version =  System.currentTimeMillis();
                    //_writeLock = null;
                    notifyAll();
                } else if (_readLock != null) {
                    if (_readLock._tx == tx) {
                        _readLock = _readLock._next;
                    } else {
                        LinkedTx link = _readLock;
                        while (link != null) {
                            if ((link._next != null) && (link._next._tx == tx)) {
                                link._next = link._next._next;
                                notifyAll();
                                return;
                            }
                            link = link._next;

                        }
                    }
                }
            }
            notifyAll();
        } else {
            throw new IllegalStateException(
                    "Confirm transaction does not match the locked transaction");        
        }
    }

    /**
     * Acquires a lock on the object on behalf of the specified
     * transaction. A write lock will be acquired only if there are no
     * read/write locks on the object; only one write lock may be in
     * effect.  A read lock will be acquired only if there is no write
     * lock on the object; multiple read locks are allowed. If the
     * lock cannot be acquired immediately, the thread will block
     * until the lock is made available or the timeout has elapsed.
     * If the timeout has elapsed or a dead lock has been detected,
     * a {@link LockNotGrantedException} is thrown. If the object has
     * been deleted while waiting for a lock, a {@link
     * ObjectDeletedException} is thrown. To prevent dead locks, a
     * transaction must only call this method for any given object
     * from a single thread and must mark the lock it is trying to
     * acquire and return it from a call to {@link
     * TransactionContext#getWaitOnLock} if the call to this method
     * has not returned yet. If a read lock is available for the
     * transaction and a write lock is requested, the read lock is
     * cancelled whether or not the write is acquired.
     *
     * @param tx The transaction requesting the lock
     * @param timeout Timeout waiting to acquire lock (in milliseconds),
     *  zero for no waiting
     * @throws LockNotGrantedException Lock could not be granted in
     *  the specified timeout or a dead lock has been detected
     * @throws ObjectDeletedWaitingForLockException The object has
     *  been deleted while waiting for the lock
     */
    synchronized void upgrade(final TransactionContext tx, final int timeout)
    throws LockNotGrantedException {
        int internalTimeout = timeout;
        // Note: This method must succeed even if an exception is thrown
        // in the middle. An exception may be thrown by a Thread.stop().
        // Must make sure not to lose consistency.

        if (_confirmWaiting != null) {
            IllegalStateException e = new IllegalStateException(
                    "Internal error: acquire when confirmWaiting is not null");
            throw e;
        }
        if (!hasLock(tx, false)) {
            IllegalStateException e = new IllegalStateException(
                    "Transaction didn't previously acquire this lock");
            throw e;
        }

        long endtime = (internalTimeout > 0)
                     ? System.currentTimeMillis() + internalTimeout * 1000
                     : Long.MAX_VALUE;
        while (true) {
            // Repeat forever until lock is acquired or timeout
            try {

                if (_writeLock == tx) {
                    // Already have write lock, can acquire object
                    return;
                } else if ((_writeLock == null)
                        && (_readLock._tx == tx) && (_readLock._next == null)) {
                    // Upgrading from read to write, no other locks, can upgrade
                    // Order is important in case thread is stopped in the middle
                    //_readLock = null;
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Acquired on " + this.toString() + " by " + tx);
                    }
                    _writeLock = tx;
                    _readLock = null;
                    return;
                } else {
                    // Don't wait if timeout is zero
                    if (internalTimeout == 0) {
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("Timeout on " + this.toString() + " by " + tx);
                        }
                        throw new LockNotGrantedException(
                                "persist.writeTimeout" + _oid + "/" + _id + " by " + tx);
                    }
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Waiting on " + this.toString() + " by " + tx);
                    }
                    // Detect possibility of dead-lock. Must remain in wait-on-lock
                    // position until lock is granted or exception thrown.

                    tx.setWaitOnLock(this);
                    detectDeadlock(tx, 10);
                    
                    // Must wait for lock and then attempt to reacquire
                    _writeWaiting = new LinkedTx(tx, _writeWaiting);
                    
                    // Wait until notified or timeout elapses. Must detect
                    // when notified but object deleted (i.e. locks released)
                    // All waiting transactions are notified at once, but once
                    // notified a race condition starts to acquire new lock
                    try {
                        long waittime = endtime - System.currentTimeMillis();
                        wait((waittime < 0) ? 0 : waittime);
                    } catch (InterruptedException except) {
                        // If the thread is interrupted, come out with the proper message
                        throw new LockNotGrantedException("persist.writeLockTimeout", except);
                    }

                    if (_deleted) {
                        // object should not be deleted, as we got lock on it
                        throw new IllegalStateException(
                                "internal error: object deleted" + _oid + "/" + _id + " by " + tx);
                    }

                    // Try to re-acquire lock, this time less timeout,
                    // eventually timeout of zero will either succeed or fail
                    // without blocking.
                    if (System.currentTimeMillis() > endtime) {
                        internalTimeout = 0;
                    }
                    removeWaiting(tx);
                    tx.setWaitOnLock(null);
                }
            } finally {
                // Must always remove waiting transaction.
                removeWaiting(tx);
                tx.setWaitOnLock(null);
            }
        } 
    }


    /**
     * Releases a lock on the object previously acquired. 
     * A write lock cannot be downgraded into a read lock
     * and the transaction loses its lock on the object. Other
     * transactions are allowed to acquire a read/write lock on the
     * object.
     *
     * @param tx The transaction that holds the lock
     */
    synchronized void release(final TransactionContext tx) {

        if (LOG.isDebugEnabled()) {
            LOG.debug("Release " + this.toString() + " by " + tx);
        }

        try {
            tx.setWaitOnLock(null);
            if (_writeLock == tx) {
                _writeLock = null;
                if (_invalidated || _deleted) {
                    _version = System.currentTimeMillis();
                    // save a copy of the expired objects contents;
                    // this will be used to expire all contained objects
                    if (_isExpired) {
                        _expiredObject = _object;
                    }
                    _object = null;
                }
                _deleted = false;
                _invalidated = false;
            } else if (_readLock != null) {
                if (_readLock._tx == tx) {
                    _readLock = _readLock._next;
                } else {
                    LinkedTx read = _readLock;
                    while (read != null) {
                        if (read._next != null && read._next._tx == tx) {
                            read._next = read._next._next;
                            break;
                        } 
                        read = read._next;
                    }
                    if (read == null) {
                        throw new IllegalStateException(Messages.message(
                                "persist.notOwnerLock") + _oid + "/" + _id + " by " + tx);
                    }
                }
            } else {
                throw new IllegalStateException(Messages.message(
                        "persist.notOwnerLock") + _oid + "/" + _id + " by " + tx);
            }

            // Notify all waiting transactions that they may attempt to
            // acquire lock. First one to succeed wins (or multiple if
            // waiting for read lock).
            notifyAll();
        } catch (ThreadDeath death) {
            // This operation must never fail, not even in the
            // event of a thread death
            release(tx);
            throw death;
        }
    }

    /**
     * Informs the lock that the object has been deleted by the
     * transaction holding the write lock. The lock on the object is
     * released and all transactions waiting for a lock will
     * terminate with an {@link ObjectDeletedException}.
     *
     * @param tx The transaction that holds the lock
     * @throws RuntimeException Attempt to delete object without
     *   acquiring a write lock
     */
    synchronized void delete(final TransactionContext tx) {

        if (tx != _writeLock) {
            throw new IllegalStateException(Messages.message(
                    "persist.notOwnerLock") + " oid:" + _oid + "/" + _id + " by " + tx);
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug ("Delete " + this.toString() + " by " + tx);
        }

        try {
            // Mark lock as unlocked and deleted, notify all waiting transactions
            _deleted = true;
            //_writeLock = null;
            _object = null;
            notifyAll();
        } catch (ThreadDeath death) {
            // Delete operation must never fail, not even in the
            // event of a thread death
            release(tx);
            throw death;
        }
    }

    synchronized void invalidate(final TransactionContext tx) {
        
        if (tx != _writeLock) {
            throw new IllegalStateException(Messages.message(
                    "persist.notOwnerLock") + " oid:" + _oid + "/" + _id + " by " + tx);
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug ("Delete " + this.toString() + " by " + tx);
        }

        _invalidated = true;
    }


    /**
     * Detects a possible dead lock involving the transaction waiting
     * to acquire this lock. If the lock is locked (read or write) by
     * any transaction waiting for a lock on <tt>waitingTx</tt>, a
     * dead lock is detected and {@link LockNotGrantedException}
     * thrown.
     *
     * @param waitingTx The transaction waiting to acquire this lock
     */
    private void detectDeadlock(final TransactionContext waitingTx, final int numOfRec)
    throws LockNotGrantedException {
        ObjectLock waitOn;

        if (numOfRec <= 0) {
            return;
        }

        // Inspect write lock and all read locks (the two are mutually exclusive).

        // For each lock look at all the waiting transactions( waitOn) and
        // determine whether they are currently waiting for a lock. A transaction
        // is waiting for a lock if it has called acquire() and has not
        // returned from the call.

        // If one of these locks is locked (read or write) by this transaction,
        // a dead lock has been detected. Recursion is necessary to prevent
        // indirect dead locks (A locked by B, B locked by C, C acquires lock on A)

        // Only the last lock attempt in a dead-lock situation will cancel.

        if (_writeLock != null) {
            // _writeLock is the blocking transaction. We are only interested in
            // a blocked transacrtion.
            waitOn = _writeLock.getWaitOnLock();
            if (waitOn != null) {
                LinkedTx read;

                // Is the blocked transaction blocked by the transaction locking
                // this object? This is a deadlock.
                if (waitOn._writeLock == waitingTx) {
                    throw new LockNotGrantedException(Messages.message("persist.deadlock"));
                }
                read = waitOn._readLock;
                while (read != null) {
                    if (read._tx == waitingTx) {
                        throw new LockNotGrantedException(Messages.message("persist.deadlock"));
                    }
                    read = read._next;
                }
                waitOn.detectDeadlock(waitingTx, numOfRec - 1);
            }
        } else {
            LinkedTx lock;

            lock = _readLock;
            while (lock != null) {
                // T1 trying to acquire lock on O1, which is locked by T2
                // T2 trying to acauire lock on O1, T1 is waiting on O1

                // lock is the blocking transaction. We are only interested in
                // a blocked transacrtion.
                waitOn = lock._tx.getWaitOnLock();
                if ((waitOn != null) && (lock._tx != waitingTx)) {
                    LinkedTx read;

                    if (waitOn._writeLock == waitingTx) {
                        throw new LockNotGrantedException(Messages.message("persist.deadlock"));
                    }
                    read = waitOn._readLock;
                    while (read != null) {
                        if (read._tx == waitingTx) {
                            throw new LockNotGrantedException(Messages.message("persist.deadlock"));
                        }
                        read = read._next;
                    }
                    waitOn.detectDeadlock(waitingTx, numOfRec - 1);
                }
                lock = lock._next;
            }
        }
    }


    /**
     * Remove the transaction from the waiting list (both read and write).
     */
    private void removeWaiting(final TransactionContext tx) {

        try {
            if (_writeWaiting != null) {
                if (_writeWaiting._tx == tx) {
                    _writeWaiting = _writeWaiting._next;
                } else {
                    LinkedTx wait;
                    
                    wait = _writeWaiting;
                    while (wait._next != null) {
                        if (wait._next._tx == tx) {
                            wait._next = wait._next._next;
                            break;
                        }
                        wait = wait._next;
                    }
                }
            }
            if (_readWaiting != null) {
                if (_readWaiting._tx == tx) {
                    _readWaiting = _readWaiting._next;
                } else {
                    LinkedTx wait;
                    
                    wait = _readWaiting;
                    while (wait._next != null) {
                        if (wait._next._tx == tx) {
                            wait._next = wait._next._next;
                            break;
                        }
                        wait = wait._next;
                    }
                }
            }
            if (_deleted && (_readWaiting == null) && (_writeWaiting == null)
                    && (_confirmWaiting == null)) {
                _deleted = false;
            }
        } catch (ThreadDeath death) {
            // This operation must never fail, not even in the
            // event of a thread death
            removeWaiting(tx);
            throw death;
        }
    }


    public String toString() {
        return _oid.toString() + "/" + _id + " "
                + (((_readLock == null) ? "-" : "R") + "/"
                + ((_writeLock == null) ? "-" : "W"));
    }


    /**
     * Object uses to hold a linked list of transactions holding
     * write locks or waiting for a read/write lock.
     */
    static class LinkedTx {
        private TransactionContext _tx;

        private LinkedTx _next;

        LinkedTx(final TransactionContext tx, final LinkedTx next) {
            _tx = tx;
            _next = next;
        }
    }
}

