/*
 * JBoss, Home of Professional Open Source
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */

package org.jboss.cache.pojo.interceptors;

import org.jboss.aop.joinpoint.Invocation;
import org.jboss.aop.joinpoint.MethodInvocation;
import org.jboss.cache.Cache;
import org.jboss.cache.CacheException;
import org.jboss.cache.CacheSPI;
import org.jboss.cache.Fqn;
import org.jboss.cache.lock.UpgradeException;
import org.jboss.cache.pojo.PojoCacheException;
import org.jboss.cache.pojo.impl.InternalConstant;

/**
 * Interceptor that handles the parent node lock associated with transaction.
 *
 * @author Ben Wang
 * @version $Id: PojoTxLockInterceptor.java 4081 2007-06-28 00:56:07Z jgreene $
 */
public class PojoTxLockInterceptor extends AbstractInterceptor
{
   public static final String LOCK_KEY = InternalConstant.POJOCACHE_KEY_PREFIX + "LOCK";
   // retry times for lockPojo just in case there is upgrade exception during concurrent access.
   private final int RETRY = 5;

   public Object invoke(Invocation in) throws Throwable
   {
      MethodInvocation invocation = (MethodInvocation) in;
      try
      {
         handleLock(invocation);
         return invocation.invokeNext(); // proceed to next advice or actual call
      }
      finally
      {
//         handleUnLock(invocation);
      }
   }

   private void handleLock(MethodInvocation invocation) throws Throwable
   {
      Fqn id = (Fqn) invocation.getArguments()[0];
      Cache<Object, Object> cache = getCache(invocation);
//      Object owner = cache.getLockOwner();
      Object owner = null;
      if (!lockPojo(owner, id, cache))
      {
         throw new PojoCacheException("PojoCache.removeObject(): Can't obtain the pojo lock under id: " + id);
      }
   }

   private boolean lockPojo(Object owner, Fqn id, Cache<Object, Object> cache) throws CacheException
   {
      if (log.isDebugEnabled())
      {
         log.debug("lockPojo(): id:" + id + " Owner: " + owner);
      }

      boolean isNeeded = true;
      int retry = 0;
      // If this is an internal id and also it has three levels, we are saying this is
      // Collection, and we need to lock the parent as well.
      // TODO Still a bit ad hoc.
      Fqn realId = id;
      if (id.isChildOrEquals(InternalConstant.JBOSS_INTERNAL) && id.size() > 2)
      {
         realId = id.getParent();
         if (log.isDebugEnabled())
         {
            log.debug("lockPojo(): will lock parent id instead:" + realId);
         }
      }

      while (isNeeded)
      {
         try
         {
            cache.put(realId, LOCK_KEY, "LOCK");
            isNeeded = false;
         }
         catch (UpgradeException upe)
         {
            log.warn("lockPojo(): can't upgrade the lock during lockPojo. Will re-try. id: " + realId
                     + " retry times: " + retry);
// TODO is this really ok to comment out??
//            cache.get(realId).release(owner);
            if (retry++ > RETRY)
            {
               return false;
            }
            // try to sleep a little as well.
            try
            {
               Thread.sleep(10);
            }
            catch (InterruptedException e)
            {
               ;
            }
         }
      }

      return true;
   }

}
