package org.jgroups.protocols;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;

import org.jgroups.Address;
import org.jgroups.Event;
import org.jgroups.Header;
import org.jgroups.Message;
import org.jgroups.View;
import org.jgroups.annotations.MBean;
import org.jgroups.annotations.ManagedAttribute;
import org.jgroups.annotations.ManagedOperation;
import org.jgroups.annotations.Property;
import org.jgroups.blocks.locking.AwaitInfo;
import org.jgroups.blocks.locking.LockInfo;
import org.jgroups.blocks.locking.LockNotification;
import org.jgroups.blocks.locking.Owner;
import org.jgroups.stack.Protocol;
import org.jgroups.util.Streamable;
import org.jgroups.util.Util;



/**
 * Base locking protocol, handling most of the protocol communication with other instances. To use distributed locking,
 * {@link org.jgroups.blocks.locking.LockService} is placed on a channel. LockService talks to a subclass of Locking
 * via events.
 * @author Bela Ban
 * @since 2.12
 * @see org.jgroups.protocols.CENTRAL_LOCK
 * @see org.jgroups.protocols.PEER_LOCK
 */
@MBean(description="Based class for locking functionality")
abstract public class Locking extends Protocol {

    @Property(description="bypasses message bundling if set")
    protected boolean bypass_bundling=true;


    protected Address local_addr;

    protected View view;

    // server side locks
    protected final ConcurrentMap<String,ServerLock> server_locks=Util.createConcurrentMap(20);

    // client side locks
    protected final Map<String,Map<Owner,ClientLock>> client_locks=new HashMap<String,Map<Owner,ClientLock>>();

    protected final Set<LockNotification> lock_listeners=new HashSet<LockNotification>();
    


    protected static enum Type {
        GRANT_LOCK,        // request to acquire a lock
        LOCK_GRANTED,      // response to sender of GRANT_LOCK on succcessful lock acquisition
        LOCK_DENIED,       // response to sender of GRANT_LOCK on unsuccessful lock acquisition (e.g. on tryLock())
        RELEASE_LOCK,      // request to release a lock
        CREATE_LOCK,       // request to create a server lock (sent by coordinator to backups). Used by CentralLockService
        DELETE_LOCK,       // request to delete a server lock (sent by coordinator to backups). Used by CentralLockService
        LOCK_AWAIT,        // request to await until condition is signaled
        COND_SIG,          // request to signal awaiting thread
        COND_SIG_ALL,      // request to signal all awaiting threads
        SIG_RET,           // response to alert of signal
        DELETE_LOCK_AWAIT, // request to delete a waiter
        CREATE_AWAITER,    // request to create a server lock await (sent by coordinator to backups). Used by CentralLockService
        DELETE_AWAITER     // request to delete a server lock await (sent by coordinator to backups). Used by CentralLockService
    }



    public Locking() {
    }


    public boolean getBypassBundling() {
        return bypass_bundling;
    }

    public void setBypassBundling(boolean bypass_bundling) {
        this.bypass_bundling=bypass_bundling;
    }

    public void addLockListener(LockNotification listener) {
        if(listener != null)
            lock_listeners.add(listener);
    }

    public void removeLockListener(LockNotification listener) {
        if(listener != null)
            lock_listeners.remove(listener);
    }

    @ManagedAttribute
    public String getAddress() {
        return local_addr != null? local_addr.toString() : null;
    }

    @ManagedAttribute
    public String getView() {
        return view != null? view.toString() : null;
    }
   


    public Object down(Event evt) {
        switch(evt.getType()) {
            case Event.LOCK:
                LockInfo info=(LockInfo)evt.getArg();
                ClientLock lock=getLock(info.getName());
                if(!info.isTrylock()) {
                    if(info.isLockInterruptibly()) {
                        try {
                            lock.lockInterruptibly();
                        }
                        catch(InterruptedException e) {
                            Thread.currentThread().interrupt(); // has to be checked by caller who has to rethrow ...
                        }
                    }
                    else
                        lock.lock();
                }
                else {
                    if(info.isUseTimeout()) {
                        try {
                            return lock.tryLock(info.getTimeout(), info.getTimeUnit());
                        }
                        catch(InterruptedException e) {
                            Thread.currentThread().interrupt();
                        }
                    }
                    else {
                        return lock.tryLock();
                    }
                }
                return null;


            case Event.UNLOCK:
                info=(LockInfo)evt.getArg();
                lock=getLock(info.getName(), false);
                if(lock != null)
                    lock.unlock();
                return null;

            case Event.UNLOCK_ALL:
                unlockAll();
                return null;
            case Event.LOCK_AWAIT:
                info=(LockInfo)evt.getArg();
                lock=getLock(info.getName(), false);
                if (lock == null || !lock.acquired) {
                    throw new IllegalMonitorStateException();
                }
                Condition condition = lock.newCondition();
                if (info.isUseTimeout()) {
                    try {
                        return condition.awaitNanos(info.getTimeUnit().toNanos(
                            info.getTimeout()));
                    }
                    catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
                else if (info.isLockInterruptibly()) {
                    try {
                        condition.await();
                    }
                    catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
                else {
                    condition.awaitUninterruptibly();
                }
                break;
            case Event.LOCK_SIGNAL:
                AwaitInfo awaitInfo = (AwaitInfo)evt.getArg();
                lock=getLock(awaitInfo.getName(), false);
                if (lock == null || !lock.acquired) {
                    throw new IllegalMonitorStateException();
                }
                sendSignalConditionRequest(awaitInfo.getName(), 
                    awaitInfo.isAll());
                break;
            case Event.SET_LOCAL_ADDRESS:
                local_addr=(Address)evt.getArg();
                break;

            case Event.VIEW_CHANGE:
                handleView((View)evt.getArg());
                break;
        }
        return down_prot.down(evt);
    }

    public Object up(Event evt) {
        switch(evt.getType()) {
            case Event.MSG:
                Message msg=(Message)evt.getArg();
                LockingHeader hdr=(LockingHeader)msg.getHeader(id);
                if(hdr == null)
                    break;

                Request req=(Request)msg.getObject();
                if(log.isTraceEnabled())
                    log.trace("[" + local_addr + "] <-- [" + msg.getSrc() + "] " + req);
                switch(req.type) {
                    case GRANT_LOCK:
                    case RELEASE_LOCK:
                        handleLockRequest(req);
                        break;
                    case LOCK_GRANTED:
                        handleLockGrantedResponse(req.lock_name, req.owner, msg.getSrc());
                        break;
                    case LOCK_DENIED:
                        handleLockDeniedResponse(req.lock_name, req.owner);
                        break;
                    case CREATE_LOCK:
                        handleCreateLockRequest(req.lock_name, req.owner);
                        break;
                    case DELETE_LOCK:
                        handleDeleteLockRequest(req.lock_name);
                        break;
                    case COND_SIG:
                    case COND_SIG_ALL:
                        handleSignalRequest(req);
                        break;
                    case LOCK_AWAIT:
                        handleAwaitRequest(req.lock_name, req.owner);
                        handleLockRequest(req);
                        break;
                    case DELETE_LOCK_AWAIT:
                        handleDeleteAwaitRequest(req.lock_name, req.owner);
                        break;
                    case SIG_RET:
                        handleSignalResponse(req.lock_name, req.owner);
                        break;
                    case CREATE_AWAITER:
                        handleCreateAwaitingRequest(req.lock_name, req.owner);
                        break;
                    case DELETE_AWAITER:
                        handleDeleteAwaitingRequest(req.lock_name, req.owner);
                        break;
                    default:
                        log.error("Request of type " + req.type + " not known");
                        break;
                }
                return null;

            case Event.VIEW_CHANGE:
                handleView((View)evt.getArg());
                break;
        }
        return up_prot.up(evt);
    }

    protected ClientLock getLock(String name) {
        return getLock(name, getOwner(), true);
    }

    protected ClientLock getLock(String name, boolean create_if_absent) {
        return getLock(name, getOwner(), create_if_absent);
    }

    @ManagedOperation(description="Unlocks all currently held locks")
    public void unlockAll() {
        List<ClientLock> locks=new ArrayList<ClientLock>();
        synchronized(client_locks) {
            Collection<Map<Owner,ClientLock>> maps=client_locks.values();
            for(Map<Owner,ClientLock> map: maps) {
                locks.addAll(map.values());
            }
        }
        for(ClientLock lock: locks)
            lock.unlock();
    }


    @ManagedOperation(description="Dumps all locks")
    public String printLocks() {
        StringBuilder sb=new StringBuilder();
        sb.append("server locks:\n");
        for(Map.Entry<String,ServerLock> entry: server_locks.entrySet()) {
            sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
        }

        sb.append("\nmy locks: ");
        synchronized(client_locks) {
            boolean first_element=true;
            for(Map.Entry<String,Map<Owner,ClientLock>> entry: client_locks.entrySet()) {
                if(first_element)
                    first_element=false;
                else
                    sb.append(", ");
                sb.append(entry.getKey()).append(" (");
                Map<Owner,ClientLock> owners=entry.getValue();
                boolean first=true;
                for(Map.Entry<Owner,ClientLock> entry2: owners.entrySet()) {
                    if(first)
                        first=false;
                    else
                        sb.append(", ");
                    sb.append(entry2.getKey());
                    ClientLock cl=entry2.getValue();
                    if(!cl.acquired || cl.denied)
                        sb.append(", unlocked");
                }
                sb.append(")");
            }
        }
        return sb.toString();
    }

    protected void handleView(View view) {
        this.view=view;
        if(log.isDebugEnabled())
            log.debug("view=" + view);
        List<Address> members=view.getMembers();
        for(Map.Entry<String,ServerLock> entry: server_locks.entrySet()) {
            entry.getValue().handleView(members);
        }
        for(Map.Entry<String,ServerLock> entry: server_locks.entrySet()) {
            ServerLock lock=entry.getValue();
            if(lock.isEmpty() && lock.current_owner == null)
                server_locks.remove(entry.getKey());
        }
    }



    protected ClientLock createLock(String lock_name) {
        return new ClientLock(lock_name);
    }

    protected Owner getOwner() {
        return new Owner(local_addr, Thread.currentThread().getId());
    }

    abstract protected void sendGrantLockRequest(String lock_name, Owner owner, long timeout, boolean is_trylock);
    abstract protected void sendReleaseLockRequest(String lock_name, Owner owner);
    abstract protected void sendAwaitConditionRequest(String lock_name, Owner owner);
    abstract protected void sendSignalConditionRequest(String lock_name, boolean all);
    abstract protected void sendDeleteAwaitConditionRequest(String lock_name, Owner owner);


    protected void sendRequest(Address dest, Type type, String lock_name, Owner owner, long timeout, boolean is_trylock) {
        Request req=new Request(type, lock_name, owner, timeout, is_trylock);
        Message msg=new Message(dest, null, req);
        msg.putHeader(id, new LockingHeader());
        if(bypass_bundling)
            msg.setFlag(Message.DONT_BUNDLE);
        if(log.isTraceEnabled())
            log.trace("[" + local_addr + "] --> [" + (dest == null? "ALL" : dest) + "] " + req);
        try {
            down_prot.down(new Event(Event.MSG, msg));
        }
        catch(Exception ex) {
            log.error("failed sending " + type + " request: " + ex);
        }
    }


    protected void sendLockResponse(Type type, Owner dest, String lock_name) {
        Request rsp=new Request(type, lock_name, dest, 0);
        Message lock_granted_rsp=new Message(dest.getAddress(), null, rsp);
        lock_granted_rsp.putHeader(id, new LockingHeader());
        if(bypass_bundling)
            lock_granted_rsp.setFlag(Message.DONT_BUNDLE);

        if(log.isTraceEnabled())
            log.trace("[" + local_addr + "] --> [" + dest.getAddress() + "] " + rsp);

        try {
            down_prot.down(new Event(Event.MSG, lock_granted_rsp));
        }
        catch(Exception ex) {
            log.error("failed sending " + type + " message to " + dest + ": " + ex);
        }
    }


    protected void sendSignalResponse(Owner dest, String lock_name) {
        Request rsp=new Request(Type.SIG_RET, lock_name, dest, 0);
        Message lock_granted_rsp=new Message(dest.getAddress(), null, rsp);
        lock_granted_rsp.putHeader(id, new LockingHeader());
        if(bypass_bundling)
            lock_granted_rsp.setFlag(Message.DONT_BUNDLE);

        if(log.isTraceEnabled())
            log.trace("[" + local_addr + "] --> [" + dest.getAddress() + "] " + rsp);

        try {
            down_prot.down(new Event(Event.MSG, lock_granted_rsp));
        }
        catch(Exception ex) {
            log.error("failed sending " + Type.SIG_RET + " message to " + dest + ": " + ex);
        }
    }


    protected void handleLockRequest(Request req) {
        ServerLock lock=server_locks.get(req.lock_name);
        if(lock == null) {
            lock=new ServerLock(req.lock_name);
            ServerLock tmp=server_locks.putIfAbsent(req.lock_name, lock);
            if(tmp != null)
                lock=tmp;
            else {
                notifyLockCreated(req.lock_name);
            }
        }
        lock.handleRequest(req);
        // We remove the lock if there is no waiters or owner
        if(lock.isEmpty() && lock.current_owner == null && lock.condition.queue.isEmpty()) {
            server_locks.remove(req.lock_name);
        }
    }


    protected void handleLockGrantedResponse(String lock_name, Owner owner, Address sender) {
        ClientLock lock=getLock(lock_name, owner, false);
        if(lock != null)
            lock.handleLockGrantedResponse(owner, sender);
    }

    protected void handleLockDeniedResponse(String lock_name, Owner owner) {
         ClientLock lock=getLock(lock_name, owner, false);
         if(lock != null)
             lock.lockDenied();
    }
    
    protected void handleAwaitRequest(String lock_name, Owner owner) {
        ServerLock lock=server_locks.get(lock_name);
        if (lock != null) {
            lock.condition.addWaiter(owner);
        }
        else {
            log.error("Condition await was received but lock was not created.  Waiter may block forever");
        }
    }
    
    protected void handleDeleteAwaitRequest(String lock_name, Owner owner) {
        ServerLock lock=server_locks.get(lock_name);
        if (lock != null) {
            lock.condition.removeWaiter(owner);
        }
        else {
            log.error("Condition await delete was received, but lock was gone");
        }
    }
    
    protected void handleSignalResponse(String lock_name, Owner owner) {
        ClientLock lock=getLock(lock_name, owner, false);
        if(lock != null) {
            synchronized (lock.condition) {
                lock.condition.signaled();
            }
        }
        else {
            log.error("Condition response was client lock was not present.  Ignored signal.");
        }
    }
    
    protected void handleSignalRequest(Request req) {
        ServerLock lock=server_locks.get(req.lock_name);
        if (lock != null) {
            lock.handleRequest(req);
        }
        else {
            log.error("Condition signal was received but lock was not created.  Couldn't notify anyone.");
        }
    }
    
    protected void handleCreateLockRequest(String lock_name, Owner owner) {
        synchronized(server_locks) {
            server_locks.put(lock_name, new ServerLock(lock_name, owner));
        }
    }


    protected void handleDeleteLockRequest(String lock_name) {
        synchronized(server_locks) {
            ServerLock lock = server_locks.get(lock_name);
            synchronized (lock.condition) {
                if (lock.condition.queue.isEmpty()) {
                    server_locks.remove(lock_name);
                }
                else {
                    lock.current_owner = null;
                }
            }
        }
    }


    protected void handleCreateAwaitingRequest(String lock_name, Owner owner) {
        synchronized(server_locks) {
            ServerLock lock = server_locks.get(lock_name);
            if (lock == null) {
                lock = new ServerLock(lock_name);
            }
            lock.condition.queue.add(owner);
        }
    }


    protected void handleDeleteAwaitingRequest(String lock_name, Owner owner) {
        synchronized(server_locks) {
            ServerLock lock = server_locks.get(lock_name);
            if (lock != null) {
                synchronized (lock.condition) {
                    lock.condition.queue.remove(owner);
                    if (lock.condition.queue.isEmpty() && lock.current_owner == null) {
                        server_locks.remove(lock_name);
                    }
                }
            }
        }
    }


    protected ClientLock getLock(String name, Owner owner, boolean create_if_absent) {
        synchronized(client_locks) {
            Map<Owner,ClientLock> owners=client_locks.get(name);
            if(owners == null) {
                if(!create_if_absent)
                    return null;
                owners=new HashMap<Owner,ClientLock>();
                client_locks.put(name, owners);
            }
            ClientLock lock=owners.get(owner);
            if(lock == null) {
                if(!create_if_absent)
                    return null;
                lock=createLock(name);
                owners.put(owner, lock);
            }
            return lock;
        }
    }

    protected void removeClientLock(String lock_name, Owner owner) {
        synchronized(client_locks) {
            Map<Owner,ClientLock> owners=client_locks.get(lock_name);
            if(owners != null) {
                ClientLock lock=owners.remove(owner);
                if(lock != null) {
                    if(owners.isEmpty())
                        client_locks.remove(lock_name);
                }
            }
        }
    }


    protected void notifyLockCreated(String lock_name) {
        for(LockNotification listener: lock_listeners) {
            try {
                listener.lockCreated(lock_name);
            }
            catch(Throwable t) {
                log.error("failed notifying " + listener, t);
            }
        }
    }

    protected void notifyLockDeleted(String lock_name) {
        for(LockNotification listener: lock_listeners) {
            try {
                listener.lockDeleted(lock_name);
            }
            catch(Throwable t) {
                log.error("failed notifying " + listener, t);
            }
        }
    }

    protected void notifyLocked(String lock_name, Owner owner) {
        for(LockNotification listener: lock_listeners) {
            try {
                listener.locked(lock_name, owner);
            }
            catch(Throwable t) {
                log.error("failed notifying " + listener, t);
            }
        }
    }

    protected void notifyUnlocked(String lock_name, Owner owner) {
        for(LockNotification listener: lock_listeners) {
            try {
                listener.unlocked(lock_name, owner);
            }
            catch(Throwable t) {
                log.error("failed notifying " + listener, t);
            }
        }
    }

    protected void notifyAwaiting(String lock_name, Owner owner) {
        for(LockNotification listener: lock_listeners) {
            try {
                listener.awaiting(lock_name, owner);
            }
            catch(Throwable t) {
                log.error("failed notifying " + listener, t);
            }
        }
    }

    protected void notifyAwaited(String lock_name, Owner owner) {
        for(LockNotification listener: lock_listeners) {
            try {
                listener.awaited(lock_name, owner);
            }
            catch(Throwable t) {
                log.error("failed notifying " + listener, t);
            }
        }
    }



    /**
     * Server side queue for handling of lock requests (lock, release).
     * @author Bela Ban
     */
    protected class ServerLock {
        protected final String lock_name;
        protected Owner current_owner;
        protected final List<Request> queue=new ArrayList<Request>();
        protected final ServerCondition condition;

        public ServerLock(String lock_name) {
            this.lock_name=lock_name;
            this.condition=new ServerCondition(this);
        }

        protected ServerLock(String lock_name, Owner owner) {
            this.lock_name=lock_name;
            this.current_owner=owner;
            this.condition=new ServerCondition(this);
        }

        protected synchronized void handleRequest(Request req) {
            switch(req.type) {
                case GRANT_LOCK:
                    if(current_owner == null) {
                        setOwner(req.owner);
                        sendLockResponse(Type.LOCK_GRANTED, req.owner, req.lock_name);
                    }
                    else {
                        if(current_owner.equals(req.owner)) {
                            sendLockResponse(Type.LOCK_GRANTED, req.owner, req.lock_name);
                        }
                        else {
                            if(req.is_trylock && req.timeout <= 0)
                                sendLockResponse(Type.LOCK_DENIED, req.owner, req.lock_name);
                            else
                                addToQueue(req);
                        }
                    }
                    break;
                case RELEASE_LOCK:
                case LOCK_AWAIT:
                    if(current_owner == null)
                        break;
                    if(current_owner.equals(req.owner))
                        setOwner(null);
                    else
                        addToQueue(req);
                    break;
                case COND_SIG:
                    condition.signal(false);
                    break;
                case COND_SIG_ALL:
                    condition.signal(true);
                    break;
                default:
                    throw new IllegalArgumentException("type " + req.type + " is invalid here");
            }

            processQueue();
        }

        protected synchronized void handleView(List<Address> members) {
            if(current_owner != null && !members.contains(current_owner.getAddress())) {
                Owner tmp=current_owner;
                setOwner(null);
                if(log.isDebugEnabled())
                    log.debug("unlocked \"" + lock_name + "\" because owner " + tmp + " left");
            }

            for(Iterator<Request> it=queue.iterator(); it.hasNext();) {
                Request req=it.next();
                if(!members.contains(req.owner.getAddress()))
                    it.remove();
            }
            
            for(Iterator<Owner> it=condition.queue.iterator(); it.hasNext();) {
                Owner own=it.next();
                if(!members.contains(own.getAddress())) {
                    it.remove();
                }
            }

            processQueue();
        }


        protected void addToQueue(Request req) {
            if(queue.isEmpty()) {
                if(req.type == Type.GRANT_LOCK)
                    queue.add(req);
                return; // RELEASE_LOCK is discarded on an empty queue
            }

            // at this point the queue is not empty
            switch(req.type) {

                // If there is already a lock request from the same owner, discard the new lock request
                case GRANT_LOCK:
                    if(!isRequestPresent(Type.GRANT_LOCK, req.owner))
                        queue.add(req);
                    break;

                case RELEASE_LOCK:
                    // Release the lock request from the same owner already in the queue
                    // If there is no lock request, discard the unlock request
                    removeRequest(Type.GRANT_LOCK, req.owner);
                    break;
            }
        }

        /** Checks if a certain request from a given owner is already in the queue */
        protected boolean isRequestPresent(Type type, Owner owner) {
            for(Request req: queue)
                if(req.type == type && req.owner.equals(owner))
                    return true;
            return false;
        }

        protected void removeRequest(Type type, Owner owner) {
            for(Iterator<Request> it=queue.iterator(); it.hasNext();) {
                Request req=it.next();
                if(req.type == type && req.owner.equals(owner))
                    it.remove();
            }
        }


        protected void processQueue() {
            if(current_owner == null) {
                while(!queue.isEmpty()) {
                    Request req=queue.remove(0);
                    if(req.type == Type.GRANT_LOCK) {
                        setOwner(req.owner);
                        sendLockResponse(Type.LOCK_GRANTED, req.owner, req.lock_name);
                        break;
                    }
                }
            }
        }

        protected void setOwner(Owner owner) {
            if(owner == null) {
                if(current_owner != null) {
                    Owner tmp=current_owner;
                    current_owner=null;
                    notifyUnlocked(lock_name, tmp);
                }
            }
            else {
                current_owner=owner;
                notifyLocked(lock_name, owner);
            }
        }

        public boolean isEmpty() {return queue.isEmpty();}

        public String toString() {
            StringBuilder sb=new StringBuilder();
            sb.append(current_owner);
            if(!queue.isEmpty()) {
                sb.append(", queue: ");
                for(Request req: queue) {
                    sb.append(req.toStringShort()).append(" ");
                }
            }
            return sb.toString();
        }
    }

    protected class ServerCondition {
        protected final ServerLock lock;
        protected final Queue<Owner> queue=new ArrayDeque<Owner>();
        
        public ServerCondition(ServerLock lock) {
            this.lock = lock;
        }
        
        public synchronized void addWaiter(Owner waiter) {
            notifyAwaiting(lock.lock_name, waiter);
            if (log.isTraceEnabled()) {
                log.trace("Waiter [" + waiter + "] was added for " + lock.lock_name);
            }
            queue.add(waiter);
        }
        
        public synchronized void removeWaiter(Owner waiter) {
            notifyAwaited(lock.lock_name, waiter);
            if (log.isTraceEnabled()) {
                log.trace("Waiter [" + waiter + "] was removed for " + lock.lock_name);
            }
            queue.remove(waiter);
        }
        
        public synchronized void signal(boolean all) {
            if (queue.isEmpty()) {
                if (log.isTraceEnabled()) {
                    log.trace("Signal for [" + lock.lock_name + 
                        "] ignored since, no one is waiting in queue.");
                }
            }
            
            Owner entry;
            if (all) {
                while ((entry = queue.poll()) != null) {
                    notifyAwaited(lock.lock_name, entry);
                    if (log.isTraceEnabled()) {
                        log.trace("Signalled " + entry + " for " + lock.lock_name);
                    }
                    sendSignalResponse(entry, lock.lock_name);
                }
            }
            else {
                entry = queue.poll();
                if (entry != null) {
                    notifyAwaited(lock.lock_name, entry);
                    if (log.isTraceEnabled()) {
                        log.trace("Signalled " + entry + " for " + lock.lock_name);
                    }
                    sendSignalResponse(entry, lock.lock_name);
                }
            }
        }
    }



    protected class ClientLock implements Lock {
        protected final String      name;
        protected Owner             owner;
        protected volatile boolean  acquired;
        protected volatile boolean  denied;
        protected volatile boolean  is_trylock;
        protected long              timeout;
        
        protected final ClientCondition condition;

        public ClientLock(String name) {
            this.name=name;
            this.condition = new ClientCondition(this);
        }

        public void lock() {
            try {
                acquire(false);
            }
            catch(InterruptedException e) {
                // This should never happen
            }
        }

        public void lockInterruptibly() throws InterruptedException {
            acquire(true);
        }

        public boolean tryLock() {
            try {
                return acquireTryLock(0, false);
            }
            catch(InterruptedException e) {
                return false;
            }
        }

        public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
            return acquireTryLock(TimeUnit.MILLISECONDS.convert(time, unit), true);
        }

        public synchronized void unlock() {
            _unlock(false);
        }

        public Condition newCondition() {
            // Currently only 1 condition per Lock is supported
            return condition;
        }

        public String toString() {
            return name + " (locked=" + acquired +")";
        }

        protected synchronized void lockGranted() {
            acquired=true;
            this.notifyAll();
        }

        protected synchronized void lockDenied() {
            denied=true;
            this.notifyAll();
        }

        protected void handleLockGrantedResponse(Owner owner, Address sender) {
            lockGranted();
        }

        protected synchronized void acquire(boolean throwInterrupt) throws InterruptedException {
            if(!acquired) {
                owner=getOwner();
                sendGrantLockRequest(name, owner, 0, false);
                boolean interrupted=false;
                while(!acquired) {
                    try {
                        this.wait();
                    }
                    catch (InterruptedException e) {
                        // If we haven't acquired the lock yet and were interrupted, then we have to clean up the lock
                        // request and throw the exception
                        if (throwInterrupt && !acquired) {
                            _unlock(true);
                            throw e;
                        }
                        // If we did get the lock then we will return with the lock and interrupt status.
                        // If we don't throw exceptions then we just set the interrupt flag and let it loop around
                        interrupted=true;
                    }
                }
                if(interrupted)
                    Thread.currentThread().interrupt();
            }
        }

        protected synchronized void _unlock(boolean force) {
            if(!acquired && !denied && !force)
                return;
            this.timeout=0;
            this.is_trylock=false;
            sendReleaseLockRequest(name, owner);
            acquired=denied=false;
            notifyAll();

            removeClientLock(name, owner);
            notifyLockDeleted(name);
            owner=null;
        }

        protected synchronized boolean acquireTryLock(long timeout, boolean use_timeout) throws InterruptedException {
            if(denied)
                return false;
            if(!acquired) {
                is_trylock=true;
                this.timeout=timeout;
                owner=getOwner();
                sendGrantLockRequest(name, owner, timeout, true);

                long target_time=use_timeout? System.currentTimeMillis() + timeout : 0;
                boolean interrupted = false;
                while(!acquired && !denied) {
                    if(use_timeout) {
                        long wait_time=target_time - System.currentTimeMillis();
                        if(wait_time <= 0)
                            break;
                        else {
                            this.timeout=wait_time;
                            try {
                                this.wait(wait_time);
                            }
                            catch (InterruptedException e) {
                                // If we were interrupted and haven't received a response yet then we try to
                                // clean up the lock request and throw the exception
                                if (!acquired && !denied) {
                                    _unlock(true);
                                    throw e;
                                }
                                // In the case that we were told if we acquired or denied the lock then return that, but
                                // make sure we set the interrupt status
                                interrupted = true;
                            }
                        }
                    }
                    else {
                        try {
                            this.wait();
                        }
                        catch(InterruptedException e) {
                            interrupted = true;
                        }
                    }
                }
                if(interrupted)
                    Thread.currentThread().interrupt();
            }
            if(!acquired || denied)
                _unlock(true);
            return acquired && !denied;
        }
    }
    
    protected class ClientCondition implements Condition {

        protected final ClientLock lock;
        protected final AtomicBoolean signaled = new AtomicBoolean(false);
        /**
         * This is okay only having 1 since a client condition is 1 per 
         * lock_name, thread id combination.
         */
        protected volatile AtomicReference<Thread> parker=new AtomicReference<Thread>();
        
        public ClientCondition(ClientLock lock) {
            this.lock = lock;
        }
        
        @Override
        public void await() throws InterruptedException {
            InterruptedException ex = null;
            try {
                await(true);
            }
            catch (InterruptedException e) {
                ex = e;
                throw ex;
            }
            finally {
                lock.lock();
                
                // If we are throwing an InterruptedException
                // then clear the interrupt state as well.
                if (ex != null) {
                    Thread.interrupted();
                }
            }
        }

        @Override
        public void awaitUninterruptibly() {
            try {
                await(false);
            }
            catch(InterruptedException e) {
                // This should never happen
            }
            finally {
                lock.lock();
            }
        }

        @Override
        public long awaitNanos(long nanosTimeout) throws InterruptedException {
            long beforeLock;
            InterruptedException ex = null;
            try {
                beforeLock = await(nanosTimeout) + System.nanoTime();
            }
            catch (InterruptedException e) {
                ex = e;
                throw ex;
            }
            finally {
                lock.lock();
                
                // If we are throwing an InterruptedException
                // then clear the interrupt state as well.
                if (ex != null) {
                    Thread.interrupted();
                }
            }
            
            return beforeLock - System.nanoTime();
        }

        /**
         * Note this wait will only work correctly if the converted value is less
         * than 292 years.  This is due to the limitation in System.nano and long
         * values that can only store up to 292 years (22<sup>63</sup> nanoseconds).
         * 
         * For more information please see {@link System#nanoTime()}
         */
        @Override
        public boolean await(long time, TimeUnit unit)
                throws InterruptedException {
            return awaitNanos(unit.toNanos(time)) > 0;
        }

        @Override
        public boolean awaitUntil(Date deadline) throws InterruptedException {
            long waitUntilTime = deadline.getTime();
            long currentTime = System.currentTimeMillis();
            
            long waitTime = waitUntilTime - currentTime;
            if (waitTime > 0) {
                return await(waitTime, TimeUnit.MILLISECONDS);
            }
            else {
                return false;
            }
        }
        
        protected void await(boolean throwInterrupt) throws InterruptedException {
            if(!signaled.get()) {
                lock.acquired = false;
                sendAwaitConditionRequest(lock.name, lock.owner);
                boolean interrupted=false;
                while(!signaled.get()) {
                    parker.set(Thread.currentThread());
                    LockSupport.park(this);
                    
                    if (Thread.interrupted()) {
                        // If we were interrupted and haven't received a response yet then we try to
                        // clean up the lock request and throw the exception
                        if (!signaled.get()) {
                            sendDeleteAwaitConditionRequest(lock.name, lock.owner);
                            throw new InterruptedException();
                        }
                        // In the case that we were signaled and interrupted
                        // we want to return the signal but still interrupt
                        // our thread
                        interrupted = true; 
                    }
                }
                if(interrupted)
                    Thread.currentThread().interrupt();
            }
            
            // We set as if this signal was no released.  This way if the
            // condition is reused again, but the client condition isn't lost
            // we won't think we were signaled immediately
            signaled.set(false);
        }
        
        protected long await(long nanoSeconds) throws InterruptedException {
            long target_nano=System.nanoTime() + nanoSeconds;
            
            if(!signaled.get()) {
                // We release the lock at the same time as waiting on the
                // condition
                lock.acquired = false;
                sendAwaitConditionRequest(lock.name, lock.owner);
                
                boolean interrupted = false;
                while(!signaled.get()) {
                    long wait_nano=target_nano - System.nanoTime();
                    // If we waited max time break out
                    if(wait_nano > 0) {
                        parker.set(Thread.currentThread());
                        LockSupport.parkNanos(this, wait_nano);
                        
                        if (Thread.interrupted()) {
                            // If we were interrupted and haven't received a response yet then we try to
                            // clean up the lock request and throw the exception
                            if (!signaled.get()) {
                                sendDeleteAwaitConditionRequest(lock.name, lock.owner);
                                throw new InterruptedException();
                            }
                            // In the case that we were signaled and interrupted
                            // we want to return the signal but still interrupt
                            // our thread
                            interrupted = true; 
                        }
                    }
                    else {
                        break;
                    }
                }
                if(interrupted)
                    Thread.currentThread().interrupt();
            }
            
            // We set as if this signal was no released.  This way if the
            // condition is reused again, but the client condition isn't lost
            // we won't think we were signaled immediately
            // If we weren't signaled then delete our request
            if (!signaled.getAndSet(false)) {
                sendDeleteAwaitConditionRequest(lock.name, lock.owner);
            }
            return target_nano - System.nanoTime();
        }

        @Override
        public void signal() {
            sendSignalConditionRequest(lock.name, false);
        }

        @Override
        public void signalAll() {
            sendSignalConditionRequest(lock.name, true);
        }
        
        protected void signaled() {
            signaled.set(true);
            Thread thread = parker.getAndSet(null);
            if (thread != null)
                LockSupport.unpark(thread);
        }
    }


    protected static class Request implements Streamable {
        protected Type    type;
        protected String  lock_name;
        protected Owner   owner;
        protected long    timeout=0;
        protected boolean is_trylock;


        public Request() {
        }

        public Request(Type type, String lock_name, Owner owner, long timeout) {
            this.type=type;
            this.lock_name=lock_name;
            this.owner=owner;
            this.timeout=timeout;
        }

        public Request(Type type, String lock_name, Owner owner, long timeout, boolean is_trylock) {
            this(type, lock_name, owner, timeout);
            this.is_trylock=is_trylock;
        }

        public void writeTo(DataOutputStream out) throws IOException {
            out.writeByte(type.ordinal());
            Util.writeString(lock_name, out);
            Util.writeStreamable(owner, out);
            out.writeLong(timeout);
            out.writeBoolean(is_trylock);
        }

        public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException {
            type=Type.values()[in.readByte()];
            lock_name=Util.readString(in);
            owner=(Owner)Util.readStreamable(Owner.class, in);
            timeout=in.readLong();
            is_trylock=in.readBoolean();
        }

        public String toString() {
            return type.name() + " [" + lock_name + ", owner=" + owner + (is_trylock? ", trylock " : " ") +
              (timeout > 0? "(timeout=" + timeout + ")" : "" + "]");
        }

        public String toStringShort() {
            StringBuilder sb=new StringBuilder();
            switch(type) {
                case RELEASE_LOCK:
                    sb.append("U");
                    break;
                case GRANT_LOCK:
                    sb.append(is_trylock? "TL" : "L");
                    break;
                default:
                    sb.append("N/A");
                    break;
            }
            sb.append("(").append(lock_name).append(",").append(owner);
            if(timeout > 0)
                sb.append(",").append(timeout);
            sb.append(")");
            return sb.toString();
        }
    }


    public static class LockingHeader extends Header {

        public LockingHeader() {
        }

        public int size() {
            return 0;
        }

        public void writeTo(DataOutputStream out) throws IOException {
        }

        public void readFrom(DataInputStream in) throws IOException, IllegalAccessException, InstantiationException {
        }
    }

}
