/*
 * Copyright 2009 New Atlanta Communications, LLC
 *
 * 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.
 */
package com.newatlanta.appengine.locks;

import com.google.appengine.api.memcache.Expiration;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.appengine.api.memcache.MemcacheServiceFactory;
import com.google.appengine.api.memcache.MemcacheService.SetPolicy;

/**
 * Implements an exclusive lock based on the GAE <code>MemcacheService</code> API,
 * specifically, the <code>put()</code> method specifying
 * <code>SetPolicy.ADD_ONLY_IF_NOT_PRESENT</code>. If the specified key can be
 * put successfully based on this policy, then the lock has been acquired. The
 * lock is released by deleting the key.
 * 
 * The GAE documentation says that requests timeout after "around 30 seconds,"
 * which has two implications:
 * 
 *   1) concerns about reliability of the <code>MemcacheService</code> are
 * minimized, since it seems unlikely that a value will be evicted within the
 * request processing limit; and,
 *      
 *   2) keys (locks) can be set to expire at the same interval as the request
 * timeout, insuring that a lock is not held indefinitely due to programming errors.
 * 
 * This class supports reentrant locks, but requires a matching unlock for every
 * lock in order to release the lock.
 * 
 * @author <a href="mailto:vbonfanti@gmail.com">Vince Bonfanti</a>
 */
public class ExclusiveLock extends AbstractLock {

    private static MemcacheService memcache = MemcacheServiceFactory.getMemcacheService();
    private static Expiration EXPIRATION = Expiration.byDeltaSeconds( 30 );

    private String key;
    private Thread owner;
    private long holdCount;

    public ExclusiveLock( String lockName ) {
        key = lockName;
    }
    
    /**
     * Returns the thread that currently owns this lock; or, <code>null</code>
     * if not owned by a thread running within this JVM instance.
     */
    public Thread getOwner() {
        return owner;
    }
    
    public boolean isHeldByCurrentThread() {
        return ( Thread.currentThread() == owner );
    }

    /**
     * Acquires the lock only if it is free at the time of invocation.
     */
    public boolean tryLock() {
        if ( !isHeldByCurrentThread() && !acquireLock() ) {
            return false; // lock not owned by this thread and put failed
        }
        // put succeeded or this thread already owns the lock
        owner = Thread.currentThread();
        holdCount++;
        return true;
    }
    
    protected boolean acquireLock() {
        // value of -1 causes MemcacheService.increment() to fail, which is what we want
        return memcache.put( key, (long)-1, EXPIRATION, SetPolicy.ADD_ONLY_IF_NOT_PRESENT );
    }

    /**
     * Only the owner thread may unlock.
     * 
     * @throws IllegalStateException
     *         If an unlock attempt is made by a non-owner.
     */
    public void unlock() {
        if ( !isHeldByCurrentThread() ) {
            throw new IllegalStateException( "Attempted unlock by non-owner" );
        }
        if ( --holdCount == 0 ) {
            memcache.delete( key );
            owner = null;
        }
    }
}

@@ -2210,35 +2210,8 @@
 y;%0D%0A
-    private Thread owner;%0D%0A
     
@@ -2330,221 +2330,109 @@
     
-/**%0D%0A     * Returns the thread that currently owns this lock; or, %3Ccode%3Enull%3C/code%3E%0D%0A     * if not owned by a thread running within this JVM instance.%0D%0A     */%0D%0A    public Thread getOwner() %7B%0D%0A        return owner
+public boolean isHeldByCurrentThread() %7B%0D%0A        return isOwner( Thread.currentThread().hashCode() )
 ;%0D%0A 
@@ -2440,37 +2440,40 @@
   %7D%0D%0A    %0D%0A    p
-ublic
+rotected
  boolean isHeldB
@@ -2467,36 +2467,36 @@
 olean is
-HeldByCurrentThread(
+Owner( int hashCode 
 ) %7B%0D%0A   
@@ -2492,28 +2492,25 @@
 de ) %7B%0D%0A    
-    
+%09
 return ( Thr
@@ -2510,47 +2510,46 @@
 n ( 
-Thread.currentThread() == owner
+hashCode == getOwnerHashCode()
  );%0D%0A   
  %7D%0D%0A
@@ -2536,33 +2536,33 @@
 shCode() );%0D%0A   
- 
+%09
 %7D%0D%0A%0D%0A    /**%0D%0A  
@@ -2680,58 +2680,120 @@
     
-    if ( !isHeldByCurrentThread() && !acquireLock(
+%09int hashCode = Thread.currentThread().hashCode();%0D%0A        if ( !acquireLock( hashCode ) && !isOwner( hashCode 
 ) ) 
@@ -2823,16 +2823,31 @@
 alse; //
+ put failed and
  lock no
@@ -2872,23 +2872,8 @@
 read
- and put failed
 %0D%0A  
@@ -2948,49 +2948,8 @@
 ck%0D%0A
-        owner = Thread.currentThread();%0D%0A
     
@@ -3039,101 +3039,25 @@
 ock(
-) %7B%0D%0A        // value of -1 causes MemcacheService.increment() to fail, which is what we want
+ int hashCode ) %7B
 %0D%0A  
@@ -3092,16 +3092,16 @@
 ey, 
-(long)-1
+hashCode
 , EX
@@ -3572,36 +3572,400 @@
     
+%7D%0D%0A
     
-owner = null;%0D%0A        %7D
+%7D%0D%0A%0D%0A    /**%0D%0A     * Gets the hash code of the owner of the lock.%0D%0A     * %0D%0A     * @return the hash code of the thread that owns the lock, or 0 if there%0D%0A     *         is no owner (the lock doesn't exist in memcache)%0D%0A     */%0D%0A    public int getOwnerHashCode() %7B%0D%0A        Integer hashCode = (Integer)memcache.get( key );%0D%0A        return ( hashCode != null ? hashCode.intValue() : 0 );
 %0D%0A  

revert to previous implementation of storing thread hashcode with the lock; this will allow threads to re-acquire the lock (or know if it's been stolen) in case the lock is evicted from memcache