/*
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 *
 */
package org.jboss.resource.work;

import javax.resource.spi.work.ExecutionContext;
import javax.resource.spi.work.Work;
import javax.resource.spi.work.WorkCompletedException;
import javax.resource.spi.work.WorkEvent;
import javax.resource.spi.work.WorkException;
import javax.resource.spi.work.WorkListener;
import javax.resource.spi.work.WorkRejectedException;

import org.jboss.logging.Logger;
import org.jboss.util.NestedRuntimeException;
import org.jboss.util.threadpool.BasicTaskWrapper;
import org.jboss.util.threadpool.StartTimeoutException;
import org.jboss.util.threadpool.Task;

/**
 * Wraps the resource adapter's work.
 *
 * @author <a href="mailto:adrian@jboss.org">Adrian Brock</a>
 * @version $Revision: 1.8 $
 */
public class WorkWrapper extends BasicTaskWrapper implements Task
{
   // Constants -----------------------------------------------------

   /** The log */
   private static final Logger log = Logger.getLogger(WorkWrapper.class);

   // Attributes ----------------------------------------------------

   /** Whether we are tracing */
   private boolean trace = log.isTraceEnabled();
   
   /** The work */
   private Work work;

   /** The execution context */
   private ExecutionContext executionContext;

   /** the work listener */
   private WorkListener workListener;

   /** The start timeout */
   private long startTimeout;

   /** The work manager */
   private JBossWorkManager workManager;

   /** The wait type */
   private int waitType;

   /** The blocked time */
   private long blockedTime;

   /** Any exception */
   private WorkException exception;
   
   // Static --------------------------------------------------------

   // Constructors --------------------------------------------------

   /**
    * Create a new WorkWrapper
    *
    * @param workManager the work manager
    * @param work the work
    * @param waitType the waitType
    * @param executionContext the execution context
    * @param listener the WorkListener
    * @throws IllegalArgumentException for null work, execution context or a negative start timeout
    */
   public WorkWrapper(JBossWorkManager workManager, Work work, int waitType, long startTimeout, ExecutionContext executionContext, WorkListener workListener)
   {
      super();

      if (work == null)
         throw new IllegalArgumentException("Null work");
      if (executionContext == null)
         throw new IllegalArgumentException("Null execution context");
      if (startTimeout < 0)
         throw new IllegalArgumentException("Illegal start timeout: " + startTimeout);

      this.workManager = workManager;
      this.work = work;
      this.waitType = waitType;
      this.startTimeout = startTimeout;
      this.executionContext = executionContext;
      this.workListener = workListener;

      setTask(this);
   }

   // Public --------------------------------------------------------

   /**
    * Get the work manager
    *
    * @return the work manager
    */
   public JBossWorkManager getWorkManager()
   {
      return workManager;
   }

   /**
    * Retrieve the work
    *
    * @return the work
    */
   public Work getWork()
   {
      return work;
   }

   /**
    * Retrieve the work listener
    *
    * @return the WorkListener
    */
   public WorkListener getWorkListener()
   {
      return workListener;
   }

   /**
    * Retrieve the exection context
    *
    * @return the execution context
    */
   public ExecutionContext getExecutionContext()
   {
      return executionContext;
   }

   /**
    * Retrieve the time blocked
    *
    * @return the blocked time
    */
   public long getBlockedElapsed()
   {
      return blockedTime;
   }
   
   /**
    * Get any exception
    * 
    * @return the exception or null if there is none
    */
   public WorkException getWorkException()
   {
      return exception;
   }

   // Task implementation -------------------------------------------

   public int getWaitType()
   {
      return waitType;
   }

   public int getPriority()
   {
      return Thread.NORM_PRIORITY;
   }

   public long getStartTimeout()
   {
      return startTimeout;
   }

   public long getCompletionTimeout()
   {
      return executionContext.getTransactionTimeout();
   }

   public void execute()
   {
      if (trace)
         log.trace("Executing work " + this);
      try
      {
         workManager.startWork(this);
      }
      catch (WorkException e)
      {
         taskRejected(new NestedRuntimeException(e));
         return;
      }
      try
      {
         work.run();
      }
      finally
      {
         workManager.endWork(this);
      }
      if (trace)
         log.trace("Executed work " + this);
   }

   public void stop()
   {
      if (trace)
         log.trace("Stopping work " + this);

      work.release();
   }

   public void accepted(long time)
   {
      blockedTime = time;

      if (trace)
         log.trace("Accepted work " + this);

      if (workListener != null)
      {
         WorkEvent event = new WorkEvent(workManager, WorkEvent.WORK_ACCEPTED, work, null);
         workListener.workAccepted(event);
      }
   }

   public void rejected(long time, Throwable throwable)
   {
      blockedTime = time;

      if (trace)
      {
         if (throwable != null)
            log.trace("Rejecting work " + this, throwable);
         else
            log.trace("Rejecting work " + this);
      }

      if (throwable != null)
      {
         exception = new WorkRejectedException(throwable);
         if (throwable instanceof StartTimeoutException)
            exception.setErrorCode(WorkRejectedException.START_TIMED_OUT);
      }
      
      workManager.cancelWork(this);
      
      if (workListener != null)
      {
         WorkEvent event = new WorkEvent(workManager, WorkEvent.WORK_ACCEPTED, work, exception);
         workListener.workRejected(event);
      }
   }

   public void started(long time)
   {
      if (waitType != WAIT_NONE)
         blockedTime = time;

      if (workListener != null)
      {
         WorkEvent event = new WorkEvent(workManager, WorkEvent.WORK_STARTED, work, null);
         workListener.workStarted(event);
      }
   }

   public void completed(long time, Throwable throwable)
   {
      if (waitType == WAIT_FOR_COMPLETE)
         blockedTime = time;

      if (throwable != null)
         exception = new WorkCompletedException(throwable);

      if (trace)
         log.trace("Completed work " + this);

      if (workListener != null)
      {
         WorkEvent event = new WorkEvent(workManager, WorkEvent.WORK_COMPLETED, work, exception);
         workListener.workCompleted(event);
      }
   }

   // Object overrides ----------------------------------------------
   
   public String toString()
   {
      StringBuffer buffer = new StringBuffer(100);
      buffer.append("WorkWrapper@").append(Integer.toHexString(System.identityHashCode(this)));
      buffer.append("[workManger=").append(workManager);
      buffer.append(" work=").append(work);
      if (executionContext != null && executionContext.getXid() != null)
      {
         buffer.append(" xid=").append(executionContext.getXid());
         buffer.append(" txTimeout=").append(executionContext.getTransactionTimeout());
      }
      buffer.append(" waitType=" + waitType);
      buffer.append(" startTimeout=" + startTimeout);
      if (blockedTime != 0)
         buffer.append(" blockTime=" + blockedTime);
      if (workListener != null)
         buffer.append(" workListener=" + workListener);
      if (exception != null)
         buffer.append(" exception=" + exception);
      buffer.append("]");
      return buffer.toString();
   }

   // Package protected ---------------------------------------------

   // Protected -----------------------------------------------------

   // Private -------------------------------------------------------

   // Inner classes -------------------------------------------------
}