/*
 * 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 an interval slightly longer than
 * the request timeout, insuring that a lock is not held indefinitely due to
 * programming errors.
 * 
 * This class does not support reentrant locks.
 * 
 * @author <a href="mailto:vbonfanti@gmail.com">Vince Bonfanti</a>
 */
public class ExclusiveLock extends AbstractLock {
	
	private static MemcacheService memcache = MemcacheServiceFactory.getMemcacheService();
	private static Expiration REQUEST_TIMEOUT = Expiration.byDeltaSeconds( 40 );
	
	private String key;
	
	public ExclusiveLock( String lockName ) {
		key = lockName;
	}
	
	/**
	 * Acquires the lock only if it is free at the time of invocation.
	 */
	public boolean tryLock() {
		return memcache.put( key, null, REQUEST_TIMEOUT, SetPolicy.ADD_ONLY_IF_NOT_PRESENT );
	}

	public void unlock() {
		memcache.delete( key );
	}
}

@@ -1792,24 +1792,16 @@
 ass 
-does not 
 support
+s
  ree
@@ -1944,20 +1944,22 @@
 Lock %7B%0D%0A
-%09
 %0D%0A
-%09
+    
 private 
@@ -2038,17 +2038,20 @@
 ice();%0D%0A
-%09
+    
 private 
@@ -2068,31 +2068,26 @@
 iration 
-REQUEST_TIMEOUT
+EXPIRATION
  = Expir
@@ -2115,20 +2115,22 @@
  40 );%0D%0A
-%09
 %0D%0A
-%09
+    
 private 
@@ -2142,20 +2142,51 @@
 g key;%0D%0A
-%09%0D%0A%09
+    private long lockCount;%0D%0A%0D%0A    
 public E
@@ -2220,18 +2220,24 @@
 me ) %7B%0D%0A
-%09%09
+        
 key = lo
@@ -2249,22 +2249,30 @@
 e;%0D%0A
-%09
+    
 %7D%0D%0A
-%09
 %0D%0A
-%09
+    
 /**%0D%0A
-%09
+    
  * A
@@ -2339,15 +2339,21 @@
 n.%0D%0A
-%09
+    
  */%0D%0A
-%09
+    
 publ
@@ -2380,156 +2380,1195 @@
  %7B%0D%0A
-%09%09return memcache.put( key, null, REQUEST_TIMEOUT, SetPolicy.ADD_ONLY_IF_NOT_PRESENT );%0D%0A%09%7D%0D%0A%0D%0A%09public void unlock() %7B%0D%0A%09%09memcache.delete( key );%0D%0A%09
+        // thread id may not be unique across JVMs, so use hash code%0D%0A        int hashCode = Thread.currentThread().hashCode();%0D%0A        if ( !memcache.put( key, hashCode, EXPIRATION, SetPolicy.ADD_ONLY_IF_NOT_PRESENT )%0D%0A                && ( hashCode != getOwnerHashCode() ) ) %7B%0D%0A            return false;%0D%0A        %7D%0D%0A        lockCount++;%0D%0A        return true;%0D%0A    %7D%0D%0A%0D%0A    /**%0D%0A     * Only the owner thread may release the lock.%0D%0A     */%0D%0A    public void unlock() %7B%0D%0A        int ownerHashCode = getOwnerHashCode();%0D%0A        if ( ownerHashCode == 0 ) %7B // no owner%0D%0A            return;%0D%0A        %7D%0D%0A        if ( ownerHashCode != Thread.currentThread().hashCode() ) %7B%0D%0A            throw new IllegalStateException( %22Attempt to unlock unowned lock%22 );%0D%0A        %7D%0D%0A        if ( --lockCount == 0 ) %7B%0D%0A            memcache.delete( key );%0D%0A        %7D%0D%0A    %7D%0D%0A%0D%0A    /**%0D%0A     * Gets the owner of the lock.%0D%0A     * %0D%0A     * @return the identifier of the thread that owns the lock, or 0 if there%0D%0A     *         is no owner %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    
 %7D%0D%0A%7D

support re-entrant locks; only allow the owner to unlock