Package org.jboss.ejb3.timerservice.mk2

Source Code of org.jboss.ejb3.timerservice.mk2.TimerServiceImpl

/*
c * JBoss, Home of Professional Open Source.
* Copyright 2009, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.ejb3.timerservice.mk2;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.ejb.EJBException;
import javax.ejb.ScheduleExpression;
import javax.ejb.Timer;
import javax.ejb.TimerConfig;
import javax.ejb.TimerHandle;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Query;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;

import org.jboss.ejb3.context.CurrentInvocationContext;
import org.jboss.ejb3.context.spi.InvocationContext;
import org.jboss.ejb3.timer.schedule.CalendarBasedTimeout;
import org.jboss.ejb3.timerservice.extension.TimerService;
import org.jboss.ejb3.timerservice.mk2.persistence.CalendarTimerEntity;
import org.jboss.ejb3.timerservice.mk2.persistence.TimeoutMethod;
import org.jboss.ejb3.timerservice.mk2.persistence.TimerEntity;
import org.jboss.ejb3.timerservice.mk2.task.TimerTask;
import org.jboss.ejb3.timerservice.spi.TimedObjectInvoker;
import org.jboss.ejb3.timerservice.spi.TimerServiceInvocationContext;
import org.jboss.logging.Logger;



/**
* MK2 implementation of EJB3.1 {@link TimerService}
*
* @author <a href="mailto:cdewolf@redhat.com">Carlo de Wolf</a>
* @version $Revision: $
*/
public class TimerServiceImpl implements TimerService
{
   /**
    * Logger
    */
   private static Logger logger = Logger.getLogger(TimerServiceImpl.class);

   /**
    * The {@link TimedObjectInvoker} which is responsible for invoking the timeout
    * method
    */
   private TimedObjectInvoker invoker;

   /**
    * Used for persistent timers
    */
   private EntityManagerFactory emf;

   /**
    * Transaction manager
    */
   private TransactionManager transactionManager;

   /**
    * For scheduling timeout tasks
    */
   private ScheduledExecutorService executor;

   /**
    * All non-persistent timers which were created by this {@link TimerService}
    *
    */
   private Map<TimerHandle, TimerImpl> nonPersistentTimers = new ConcurrentHashMap<TimerHandle, TimerImpl>();

   private Map<TimerHandle, TimerImpl> persistentWaitingOnTxCompletionTimers = new ConcurrentHashMap<TimerHandle, TimerImpl>();

   private ThreadLocal<EntityManager> transactionScopedEntityManager = new ThreadLocal<EntityManager>();
  
   /**
    * Holds the {@link Future} of each of the timer tasks that have been scheduled
    */
   private Map<TimerHandle, Future<?>> scheduledTimerFutures = new ConcurrentHashMap<TimerHandle, Future<?>>();

   /**
    * Creates a {@link TimerServiceImpl}
    *
    * @param invoker The {@link TimedObjectInvoker} responsible for invoking the timeout method
    * @param emf Entity manager factory responsible for JPA persistence management
    * @param transactionManager Transaction manager responsible for managing the transactional timer service
    * @param executor Executor service responsible for creating scheduled timer tasks
    * @throws IllegalArgumentException If either of the passed param is null
    */
   public TimerServiceImpl(TimedObjectInvoker invoker, EntityManagerFactory emf, TransactionManager transactionManager,
         ScheduledExecutorService executor)
   {
      if (invoker == null)
      {
         throw new IllegalArgumentException("Invoker cannot be null");
      }
      if (emf == null)
      {
         throw new IllegalArgumentException("EntityManagerFactory cannot be null");
      }
      if (transactionManager == null)
      {
         throw new IllegalArgumentException("Transaction manager cannot be null");
      }
      if (executor == null)
      {
         throw new IllegalArgumentException("Executor cannot be null");
      }

      this.invoker = invoker;
      this.emf = emf;
      this.transactionManager = transactionManager;
      this.executor = executor;
   }

   /**
    *
    * {@inheritDoc}
    */
   @Override
   public Timer createCalendarTimer(ScheduleExpression schedule) throws IllegalArgumentException,
         IllegalStateException, EJBException
   {
      return this.createCalendarTimer(schedule, null);
   }

   /**
    * {@inheritDoc}
    */
   @Override
   public Timer createCalendarTimer(ScheduleExpression schedule, TimerConfig timerConfig)
         throws IllegalArgumentException, IllegalStateException, EJBException
   {
      Serializable info = timerConfig == null ? null : timerConfig.getInfo();
      boolean persistent = timerConfig == null ? true : timerConfig.isPersistent();
      return this.createCalendarTimer(schedule, info, persistent, null);
   }

   /**
    * {@inheritDoc}
    */
   @Override
   public Timer createIntervalTimer(Date initialExpiration, long intervalDuration, TimerConfig timerConfig)
         throws IllegalArgumentException, IllegalStateException, EJBException
   {
      if (initialExpiration == null)
      {
         throw new IllegalArgumentException("initialExpiration cannot be null while creating a timer");
      }
      if (initialExpiration.getTime() < 0)
      {
         throw new IllegalArgumentException("initialExpiration.getTime() cannot be negative while creating a timer");
      }
      if (intervalDuration < 0)
      {
         throw new IllegalArgumentException("intervalDuration cannot be negative while creating a timer");
      }
      return this.createTimer(initialExpiration, intervalDuration, timerConfig.getInfo(), timerConfig.isPersistent());
   }

   /**
    * {@inheritDoc}
    */
   @Override
   public Timer createIntervalTimer(long initialDuration, long intervalDuration, TimerConfig timerConfig)
         throws IllegalArgumentException, IllegalStateException, EJBException
   {
      if (initialDuration < 0)
      {
         throw new IllegalArgumentException("initialDuration cannot be negative while creating interval timer");
      }
      if (intervalDuration < 0)
      {
         throw new IllegalArgumentException("intervalDuration cannot be negative while creating interval timer");
      }
     
      return this.createIntervalTimer(new Date(System.currentTimeMillis() + initialDuration), intervalDuration, timerConfig);
   }

   /**
    * {@inheritDoc}
    */
   @Override
   public Timer createSingleActionTimer(Date expiration, TimerConfig timerConfig) throws IllegalArgumentException,
         IllegalStateException, EJBException
   {
      if (expiration == null)
      {
         throw new IllegalArgumentException("expiration cannot be null while creating a single action timer");
      }
      if (expiration.getTime() < 0)
      {
         throw new IllegalArgumentException(
               "expiration.getTime() cannot be negative while creating a single action timer");
      }
      return this.createTimer(expiration, 0, timerConfig.getInfo(), timerConfig.isPersistent());
   }

   /**
    * {@inheritDoc}
    */
   @Override
   public Timer createSingleActionTimer(long duration, TimerConfig timerConfig) throws IllegalArgumentException,
         IllegalStateException, EJBException
   {
      if (duration < 0)
         throw new IllegalArgumentException("duration cannot be negative while creating single action timer");

      return createTimer(new Date(System.currentTimeMillis() + duration), 0, timerConfig.getInfo(), timerConfig
            .isPersistent());
   }

   /**
    * {@inheritDoc}
    */
   @Override
   public Timer createTimer(long duration, Serializable info) throws IllegalArgumentException, IllegalStateException,
         EJBException
   {
      if (duration < 0)
         throw new IllegalArgumentException("Duration cannot negative while creating the timer");

      return createTimer(new Date(System.currentTimeMillis() + duration), 0, info, true);
   }

   /**
    * {@inheritDoc}
    */
   @Override
   public Timer createTimer(Date expiration, Serializable info) throws IllegalArgumentException, IllegalStateException,
         EJBException
   {
      if (expiration == null)
      {
         throw new IllegalArgumentException("Expiration date cannot be null while creating a timer");
      }
      if (expiration.getTime() < 0)
      {
         throw new IllegalArgumentException("expiration.getTime() cannot be negative while creating a timer");
      }
      return this.createTimer(expiration, 0, info, true);
   }

   /**
    * {@inheritDoc}
    */
   @Override
   public Timer createTimer(long initialDuration, long intervalDuration, Serializable info)
         throws IllegalArgumentException, IllegalStateException, EJBException
   {
      if (initialDuration < 0)
      {
         throw new IllegalArgumentException("Initial duration cannot be negative while creating timer");
      }
      if (intervalDuration < 0)
      {
         throw new IllegalArgumentException("Interval cannot be negative while creating timer");
      }
      return this.createTimer(new Date(System.currentTimeMillis() + initialDuration), intervalDuration, info, true);

   }

   /**
    * {@inheritDoc}
    */
   @Override
   public Timer createTimer(Date initialExpiration, long intervalDuration, Serializable info)
         throws IllegalArgumentException, IllegalStateException, EJBException
   {
      if (initialExpiration == null)
      {
         throw new IllegalArgumentException("intial expiration date cannot be null while creating a timer");
      }
      if (initialExpiration.getTime() < 0)
      {
         throw new IllegalArgumentException("expiration.getTime() cannot be negative while creating a timer");
      }
      if (intervalDuration < 0)
      {
         throw new IllegalArgumentException("interval duration cannot be negative while creating timer");
      }
      return this.createTimer(initialExpiration, intervalDuration, info, true);
   }

   @Override
   public org.jboss.ejb3.timerservice.extension.Timer getAutoTimer(ScheduleExpression schedule, Method timeoutMethod)
   {
      return this.createCalendarTimer(schedule, null, true, timeoutMethod);
   }

   @Override
   public org.jboss.ejb3.timerservice.extension.Timer getAutoTimer(ScheduleExpression schedule,
         TimerConfig timerConfig, Method timeoutMethod)
   {
      return this.createCalendarTimer(schedule, timerConfig.getInfo(), timerConfig.isPersistent(), timeoutMethod);
   }

   /**
    * {@inheritDoc}
    */
   @Override
   public Collection<Timer> getTimers() throws IllegalStateException, EJBException
   {
      if (this.isLifecycleCallbackInvocation() && this.isSingletonBeanInvocation() == false)
      {
         throw new IllegalStateException(
               "getTimers() method invocation is not allowed during lifecycle callback of non-singleton EJBs");
      }

      Set<Timer> activeTimers = new HashSet<Timer>();
      // get all active non-persistent timers for this timerservice
      for (TimerImpl timer : this.nonPersistentTimers.values())
      {
         if (timer != null && timer.isActive())
         {
            activeTimers.add(timer);
         }
      }
      // get all active timers which are persistent, but haven't yet been
      // persisted (waiting for tx to complete)
      for (TimerImpl timer : this.persistentWaitingOnTxCompletionTimers.values())
      {
         if (timer != null && timer.isActive())
         {
            activeTimers.add(timer);
         }
      }

      // now get all active persistent timers for this timerservice
      activeTimers.addAll(this.getActiveTimers());
      return activeTimers;
   }

   /**
    * Create a {@link Timer}
    *
    * @param initialExpiration The {@link Date} at which the first timeout should occur.
    *                       <p>If the date is in the past, then the timeout is triggered immediately
    *                       when the timer moves to {@link TimerState#ACTIVE}</p>
    * @param intervalDuration The interval (in milli seconds) between consecutive timeouts for the newly created timer.
    *                           <p>Cannot be a negative value. A value of 0 indicates a single timeout action</p>
    * @param info {@link Serializable} info that will be made available through the newly created timer's {@link Timer#getInfo()} method
    * @param persistent True if the newly created timer has to be persistent
    * @return Returns the newly created timer
    * @throws IllegalArgumentException If <code>initialExpiration</code> is null or <code>intervalDuration</code> is negative
    * @throws IllegalStateException If this method was invoked during a lifecycle callback on the EJB
    */
   private Timer createTimer(Date initialExpiration, long intervalDuration, Serializable info, boolean persistent)
   {
      if (this.isLifecycleCallbackInvocation() && this.isSingletonBeanInvocation() == false)
      {
         throw new IllegalStateException("Creation of timers is not allowed during lifecycle callback of non-singleton EJBs");
      }
      if (initialExpiration == null)
      {
         throw new IllegalArgumentException("initial expiration is null");
      }
      if (intervalDuration < 0)
      {
         throw new IllegalArgumentException("interval duration is negative");
      }

      // create an id for the new timer instance
      UUID uuid = UUID.randomUUID();
      // create the timer
      TimerImpl timer = new TimerImpl(uuid.toString(), this, initialExpiration, intervalDuration, info, persistent);
      // if it's persistent, then save it
      if (persistent)
      {
         this.persistTimer(timer);
      }
      // now "start" the timer. This involves, moving the timer to an ACTIVE state
      // and scheduling the timer task
      this.startTimer(timer);

      this.addTimer(timer);
      // return the newly created timer
      return timer;
   }

   /**
    * Creates a calendar based {@link Timer}
    *
    * @param schedule The {@link ScheduleExpression} which will be used for creating scheduled timer tasks
    *               for a calendar based timer
    * @param info {@link Serializable} info that will be made available through the newly created timer's {@link Timer#getInfo()} method
    * @param persistent True if the newly created timer has to be persistent
    * @return Returns the newly created timer
    * @throws IllegalArgumentException If the passed <code>schedule</code> is null
    * @throws IllegalStateException If this method was invoked during a lifecycle callback on the EJB
    */
   private org.jboss.ejb3.timerservice.extension.Timer createCalendarTimer(ScheduleExpression schedule,
         Serializable info, boolean persistent, Method timeoutMethod)
   {
      if (this.isLifecycleCallbackInvocation() && this.isSingletonBeanInvocation() == false)
      {
         throw new IllegalStateException("Creation of timers is not allowed during lifecycle callback of non-singleton EJBs");
      }
      if (schedule == null)
      {
         throw new IllegalArgumentException("schedule is null");
      }
      // parse the passed schedule and create the calendar based timeout
      CalendarBasedTimeout calendarTimeout = new CalendarBasedTimeout(schedule);
      // generate a id for the timer
      UUID uuid = UUID.randomUUID();
      // create the timer
      TimerImpl timer = new CalendarTimer(uuid.toString(), this, calendarTimeout, info, persistent, timeoutMethod);

      if (persistent)
      {
         this.persistTimer(timer);
      }
     
      // now "start" the timer. This involves, moving the timer to an ACTIVE state
      // and scheduling the timer task
      this.startTimer(timer);

      this.addTimer(timer);
      // return the timer
      return timer;
   }

   /**
    * TODO: Rethink about this method. Do we really need this?
    * Adds the timer instance to an internal {@link TimerHandle} to {@link TimerImpl} map.
    *
    * @param timer Timer instance
    */
   protected void addTimer(TimerImpl timer)
   {
      if (timer.persistent == false)
      {
         synchronized (nonPersistentTimers)
         {
            nonPersistentTimers.put(timer.getTimerHandle(), timer);
         }
      }
      else
      {
         synchronized (this.persistentWaitingOnTxCompletionTimers)
         {
            this.persistentWaitingOnTxCompletionTimers.put(timer.getTimerHandle(), timer);
         }
      }
   }

   /**
    * Returns the {@link ScheduledExecutorService} used for scheduling timer tasks
    * @return
    */
   protected ScheduledExecutorService getExecutor()
   {
      return executor;
   }

   /**
    * Returns the {@link TimedObjectInvoker} to which this timer service belongs
    * @return
    */
   public TimedObjectInvoker getInvoker()
   {
      return invoker;
   }

   /**
    * Returns the {@link Timer} corresponding to the passed {@link TimerHandle}
    *
    * @param handle The {@link TimerHandle} for which the {@link Timer} is being looked for
    * @return
    * TODO: Rethink about this method. Looks brittle.
    */
   public org.jboss.ejb3.timerservice.extension.Timer getTimer(TimerHandle handle)
   {
      // TODO: Why do we even have to maintain a map of timers.
      // Looks needless and could perhaps lead to inconsistencies.
      // Let's instead just rely on DB? But then again what about non-persistent timers.
      // Overall, needs a bit of thinking
      TimerImpl timer = nonPersistentTimers.get(handle);
      if (timer != null)
      {
         return timer;
      }
      timer = this.persistentWaitingOnTxCompletionTimers.get(handle);
      if (timer != null)
      {
         return timer;
      }
      TimerHandleImpl timerHandle = (TimerHandleImpl) handle;
      return this.getPersistedTimer(timerHandle);

   }

   /**
    * TODO: Rethink this method.
    * @return Returns the current transaction, if any. Else returns null.
    * @throws EJBException If there is any system level exception
    */
   protected Transaction getTransaction()
   {
      try
      {
         return transactionManager.getTransaction();
      }
      catch (SystemException e)
      {
         throw new EJBException(e);
      }
   }

   /**
    * Remove a txtimer from the list of active timers
    */
   void removeTimer(TimerImpl timer)
   {
      if (timer.persistent == false)
      {
         synchronized (nonPersistentTimers)
         {
            nonPersistentTimers.remove(timer.getTimerHandle());
         }
      }
      else
      {
         synchronized (this.persistentWaitingOnTxCompletionTimers)
         {
            this.persistentWaitingOnTxCompletionTimers.remove(timer.getTimerHandle());
         }
      }
      // TODO: I don't really like the idea of removing/deleting
      // persisted timers from the persistent store. This method
      // is currently called when the timer expires or is cancelled.
      // In either case, the state of the timer is changed appropriately
      // and persisted. I guess that should be enough.
      // If there's a good reason for deleting the entries, then uncomment
      // the following (untested) code

      //         try
      //         {
      //            Transaction currentTx = transactionManager.getTransaction();
      //            if (currentTx == null)
      //               transactionManager.begin();
      //            try
      //            {
      //               Query query = em.createQuery("DELETE FROM TimerEntity WHERE id = ?");
      //               query.setParameter(1, txtimer.getId());
      //               query.executeUpdate();
      //               timers.remove(txtimer.getHandle());
      //            }
      //            finally
      //            {
      //               if (currentTx == null)
      //                  transactionManager.commit();
      //            }
      //         }
      //         catch (HeuristicMixedException e)
      //         {
      //            throw new EJBException(e);
      //         }
      //         catch (HeuristicRollbackException e)
      //         {
      //            throw new EJBException(e);
      //         }
      //         catch (NotSupportedException e)
      //         {
      //            throw new EJBException(e);
      //         }
      //         catch (RollbackException e)
      //         {
      //            // TODO: what now?
      //            throw new EJBException(e);
      //         }
      //         catch (SystemException e)
      //         {
      //            throw new EJBException(e);
      //         }

   }

   /**
    * TODO: Not yet implemented
    * @param txtimer
    */
   void retryTimeout(TimerImpl txtimer)
   {
      try
      {
         //retryPolicy.retryTimeout(timedObjectInvoker, txtimer);
         logger.warn("retryTimeout is NYI");
         throw new RuntimeException("NYI");
      }
      catch (Exception e)
      {
         logger.error("Retry timeout failed for timer: " + txtimer, e);
      }
   }

   /**
    * Persists the passed <code>timer</code>.
    *
    * <p>
    *   If the passed timer is null or is non-persistent (i.e. {@link Timer#isPersistent()} returns false),
    *   then this method acts as a no-op
    * </p>
    * @param timer
    */
   public void persistTimer(TimerImpl timer)
   {
      // if not persistent, then do nothing
      if (timer == null || timer.persistent == false)
      {
         return;
      }

      // get the persistent entity from the timer
      TimerEntity timerEntity = timer.getPersistentState();

      // TODO: Now all that bolierplate for tx management (which
      // needs to go once we have the timer service "managed")
      Transaction previousTx = null;
      boolean newTxStarted = false;
      try
      {
         previousTx = this.transactionManager.getTransaction();
         // we persist with REQUIRED tx semantics
         // if there's no current tx in progress, then create a new one
         if (previousTx == null)
         {
            this.startNewTx();
            newTxStarted = true;
         }

         EntityManager em = this.getCurrentEntityManager();
         // merge the state
         TimerEntity mergedTimerEntity = em.merge(timerEntity);

         // do the actual persistence
         em.persist(mergedTimerEntity);

      }
      catch (Throwable t)
      {
         // TODO: Again the boilerplate tx management code
         this.setRollbackOnly();
         throw new RuntimeException(t);
      }
      finally
      {
         // since we started a new tx, end it (either commit or rollback) ourselves
         if (newTxStarted)
         {
            this.endTx();
         }
      }

   }

   /**
    * Suspends any currently scheduled tasks for {@link Timer}s
    * <p>
    *   Note that, suspend does <b>not</b> cancel the {@link Timer}. Instead,
    *   it just cancels the <b>next scheduled timeout</b>. So once the {@link Timer}
    *   is restored (whenever that happens), the {@link Timer} will continue to
    *   timeout at appropriate times.
    * </p>
    */
   public void suspendTimers()
   {
      // get all active timers (persistent/non-persistent inclusive)
      Collection<Timer> timers = this.getTimers();
      for (Timer timer : timers)
      {
         if (timer instanceof TimerImpl == false)
         {
            continue;
         }
         // suspend the timer
         ((TimerImpl) timer).suspend();
      }
   }

   /**
    * Restores persisted timers, corresponding to this timerservice, which are eligible for any new timeouts.
    * <p>
    * This includes timers whose {@link TimerState} is <b>neither</b> of the following:
    * <ul>
    *   <li>{@link TimerState#CANCELED}</li>
    *   <li>{@link TimerState#EXPIRED}</li>
    * </ul>
    * </p>
    * <p>
    *   All such restored timers will be schedule for their next timeouts.
    * </p>   
    */
   public void restoreTimers()
   {
      // get the persisted timers which are considered active
      List<TimerImpl> restorableTimers = this.getActiveTimers();

      logger.debug("Found " + restorableTimers.size() + " active timers for timedObjectId: "
            + this.invoker.getTimedObjectId());
      // now "start" each of the restorable timer. This involves, moving the timer to an ACTIVE state
      // and scheduling the timer task
      for (TimerImpl activeTimer : restorableTimers)
      {
         this.startTimer(activeTimer);
         logger.debug("Started timer: " + activeTimer);
         // save any changes to the state (that will have happened on call to startTimer)
         this.persistTimer(activeTimer);
      }

   }

   /**
    * Registers a timer with a transaction (if any in progress) and then moves
    * the timer to a active state, so that it becomes eligible for timeouts
    */
   protected void startTimer(TimerImpl timer)
   {
      registerTimerWithTx(timer);

      // the timer will actually go ACTIVE on tx commit
      startInTx(timer);
   }

   /**
    * Registers the timer with any active transaction so that appropriate action on the timer can be
    * carried out on transaction lifecycle events, through the use of {@link Synchronization}
    * callbacks.
    * <p>
    *   If there is no transaction in progress, when this method is called, then
    *   this method is effectively a no-op.
    * </p>
    * @param timer
    */
   protected void registerTimerWithTx(TimerImpl timer)
   {
      // get the current transaction
      Transaction tx = this.getTransaction();
      if (tx != null)
      {
         try
         {
            // register for lifecycle events of transaction
            tx.registerSynchronization(new TimerCreationTransactionSynchronization(timer));
         }
         catch (RollbackException e)
         {
            // TODO: throw the right exception
            throw new EJBException(e);
         }
         catch (SystemException e)
         {
            throw new EJBException(e);
         }
      }
   }

   /**
    * Moves the timer to either {@link TimerState#STARTED_IN_TX} or {@link TimerState#ACTIVE}
    * depending on whether there's any transaction active currently.
    * <p>
    *   If there's no transaction currently active, then this method creates and schedules a timer task.
    *   Else, it just changes the state of the timer to {@link TimerState#STARTED_IN_TX} and waits
    *   for the transaction to commit, to schedule the timer task.
    * </p>
    * @param timer
    */
   protected void startInTx(TimerImpl timer)
   {
      timer.setTimerState(TimerState.ACTIVE);
      this.persistTimer(timer);
     
      // if there's no transaction, then trigger a schedule immidiately.
      // Else, the timer will be scheduled on tx synchronization callback
      if (this.getTransaction() == null)
      {
         // create and schedule a timer task
         timer.scheduleTimeout();
      }

   }

   /**
    * Returns true if the {@link CurrentInvocationContext} represents a lifecycle
    * callback invocation. Else returns false.
    * <p>
    *   This method internally relies on {@link CurrentInvocationContext#get()} to obtain
    *   the current invocation context.
    *   <ul>
    *       <li>If the context is available then it looks for the method that was invoked.
    *       The absence of a method indicates a lifecycle callback.</li>
    *       <li>If the context is <i>not</i> available, then this method returns false (i.e.
    *       it doesn't consider the current invocation as a lifecycle callback). This is
    *       for convenience, to allow the invocation of {@link javax.ejb.TimerService} methods
    *       in the absence of {@link CurrentInvocationContext}</li>
    *   </ul>   
    *      
    * </p>
    * @return
    */
   protected boolean isLifecycleCallbackInvocation()
   {
      InvocationContext currentInvocationContext = null;
      try
      {
         currentInvocationContext = CurrentInvocationContext.get();
      }
      catch (IllegalStateException ise)
      {
         // no context info available so return false
         return false;
      }
      // If the method in current invocation context is null,
      // then it represents a lifecycle callback invocation
      Method invokedMethod = currentInvocationContext.getMethod();
      if (invokedMethod == null)
      {
         // it's a lifecycle callback
         return true;
      }
      // not an lifecycle callback
      return false;
   }
  
   /**
    * Creates and schedules a {@link TimerTask} for the next timeout of the passed <code>timer</code>
    */
   protected void scheduleTimeout(TimerImpl timer)
   {
      Date nextExpiration = timer.getNextExpiration();
      if (nextExpiration == null)
      {
         logger.info("Next expiration is null. No tasks will be scheduled for timer " + timer);
         return;
      }
      // create the timer task
      Runnable timerTask = timer.getTimerTask();
      // find out how long is it away from now
      long delay = nextExpiration.getTime() - System.currentTimeMillis();
      // if in past, then trigger immediately
      if (delay < 0)
      {
         delay = 0;
      }
      long intervalDuration = timer.getInterval();
      if (intervalDuration > 0)
      {
         logger.debug("Scheduling timer " + timer + " at fixed rate, starting at " + delay
               + " milli seconds from now with repeated interval=" + intervalDuration);
         // schedule the task
         Future<?> future = this.executor.scheduleAtFixedRate(timerTask, delay, intervalDuration, TimeUnit.MILLISECONDS);
         // maintain it in timerservice for future use (like cancellation)
         this.scheduledTimerFutures.put(timer.getTimerHandle(), future);
      }
      else
      {
         logger.debug("Scheduling a single action timer " + timer + " starting at " + delay + " milli seconds from now");
         // schedule the task
         Future<?> future = this.executor.schedule(timerTask, delay, TimeUnit.MILLISECONDS);
         // maintain it in timerservice for future use (like cancellation)
         this.scheduledTimerFutures.put(timer.getTimerHandle(), future);
      }
   }
  
   /**
    * Cancels any scheduled {@link Future} corresponding to the passed <code>timer</code>
    * @param timer
    */
   protected void cancelTimeout(TimerImpl timer)
   {
      TimerHandle handle = timer.getTimerHandle();
      Future<?> scheduleFuture = this.scheduledTimerFutures.get(handle);
      if (scheduleFuture != null)
      {
         scheduleFuture.cancel(false);
      }
     
   }
  
   private boolean isSingletonBeanInvocation()
   {
      InvocationContext currentInvocationContext = null;
      try
      {
         currentInvocationContext = CurrentInvocationContext.get();
        
         if (currentInvocationContext instanceof TimerServiceInvocationContext)
         {
            TimerServiceInvocationContext timerserviceInvocationCtx = (TimerServiceInvocationContext) currentInvocationContext;
            return timerserviceInvocationCtx.isSingleton();
         }
         return false;
      }
      catch (IllegalStateException ise)
      {
         // no context info available so return false
         return false;
      }
   }

   private TimerImpl getPersistedTimer(TimerHandleImpl timerHandle)
   {
      String id = timerHandle.getId();
      String timedObjectId = timerHandle.getTimedObjectId();
      EntityManager em = this.emf.createEntityManager();
      Query query = em.createQuery("from TimerEntity t where t.id = :id and t.timedObjectId = :timedObjectId");
      query.setParameter("id", id);
      query.setParameter("timedObjectId", timedObjectId);

      List<TimerEntity> timers = query.getResultList();
      if (timers == null || timers.isEmpty())
      {
         return null;
      }
      if (timers.size() > 1)
      {
         throw new EJBException("More than one timer found for TimerHandle: " + timerHandle);
      }
      TimerEntity timerEntity = timers.get(0);
      if (timerEntity.isCalendarTimer())
      {
         return new CalendarTimer((CalendarTimerEntity) timerEntity, this);
      }
      return new TimerImpl(timerEntity, this);

   }

   private List<TimerImpl> getActiveTimers()
   {
      // we need only those timers which correspond to the
      // timed object invoker to which this timer service belongs. So
      // first get hold of the timed object id
      String timedObjectId = this.getInvoker().getTimedObjectId();

      // timer states which do *not* represent an active timer
      Set<TimerState> ineligibleTimerStates = new HashSet<TimerState>();
      ineligibleTimerStates.add(TimerState.CANCELED);
      ineligibleTimerStates.add(TimerState.EXPIRED);

      EntityManager em = this.emf.createEntityManager();

      Query activeTimersQuery = em
            .createQuery("from TimerEntity t where t.timedObjectId = :timedObjectId and t.timerState not in (:timerStates)");
      activeTimersQuery.setParameter("timedObjectId", timedObjectId);
      activeTimersQuery.setParameter("timerStates", ineligibleTimerStates);

      List<TimerEntity> persistedTimers = activeTimersQuery.getResultList();
      List<TimerImpl> activeTimers = new ArrayList<TimerImpl>();
      for (TimerEntity persistedTimer : persistedTimers)
      {
         TimerImpl activeTimer = null;
         if (persistedTimer.isCalendarTimer())
         {
            CalendarTimerEntity calendarTimerEntity = (CalendarTimerEntity) persistedTimer;
            // create a timer instance from the persisted calendar timer
            activeTimer = new CalendarTimer(calendarTimerEntity, this);
         }
         else
         {
            // create the timer instance from the persisted state
            activeTimer = new TimerImpl(persistedTimer, this);
         }
         // add it to the list of timers which will be restored
         activeTimers.add(activeTimer);
      }

      return activeTimers;
   }

   private Serializable clone(Serializable info) throws Exception
   {
      ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
      ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
      objectOutputStream.writeObject(info);

      ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
      ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
      Object clonedInfo = objectInputStream.readObject();

      return (Serializable) clonedInfo;
   }

   private org.jboss.ejb3.timerservice.extension.Timer getExistingAutoTimer(ScheduleExpression schedule,
         TimerConfig timerConfig, String timeoutMethodName, String[] methodParams)
   {
      //      if (timerConfig != null && timerConfig.isPersistent() == false)
      //      {
      //         return null;
      //      }
      //      // we need to restore only those timers which correspond to the
      //      // timed object invoker to which this timer service belongs. So
      //      // first get hold of the timed object id
      //      String timedObjectId = this.getInvoker().getTimedObjectId();
      //
      //      // TODO: Again the boilerplate transaction management code
      //      // (which will go, once the timer service is "managed")
      //      boolean thisMethodStartedTx = this.startTxIfNone();
      //
      //      try
      //      {
      //
      //         // join the transaction, since the entity manager was created
      //         // outside the transaction context
      //         this.em.joinTransaction();
      //
      //         Query autoTimersQuery = this.em
      //               .createQuery("from CalendarTimerEntity t where t.timedObjectId = :timedObjectId and t.autoTimer is true");
      //         autoTimersQuery.setParameter("timedObjectId", timedObjectId);
      //
      //         List<CalendarTimerEntity> autoTimers = autoTimersQuery.getResultList();
      //         for (CalendarTimerEntity autoTimer : autoTimers)
      //         {
      //            TimeoutMethod timeoutMethod = autoTimer.getTimeoutMethod();
      //            if (this.doesTimeoutMethodMatch(timeoutMethod, timeoutMethodName, methodParams) == false)
      //            {
      //               continue;
      //            }
      //            if (timerConfig != null)
      //            {
      //               Serializable info = timerConfig.getInfo();
      //               if (info != null)
      //               {
      //                  if (info.equals(autoTimer.getInfo()) == false)
      //                  {
      //                     continue;
      //                  }
      //               }
      //               else
      //               {
      //                  if (autoTimer.getInfo() != null)
      //                  {
      //                     continue;
      //                  }
      //               }
      //            }
      //            // now onto schedule
      //            ScheduleExpression autoTimerSchedule = autoTimer.getScheduleExpression();
      //            if (this.doSchedulesMatch(autoTimerSchedule, schedule))
      //            {
      //               return new CalendarTimer(autoTimer, this);
      //            }
      //           
      //         }
      //      }
      //      catch (Throwable t)
      //      {
      //         // TODO: Again the tx management boilerplate
      //         this.setRollbackOnly();
      //         throw new RuntimeException(t);
      //      }
      //      finally
      //      {
      //         // TODO: Remove this once the timer service implementation
      //         // becomes "managed"
      //         if (thisMethodStartedTx)
      //         {
      //            this.endTransaction();
      //         }
      //      }
      return null;
   }

   private boolean doSchedulesMatch(ScheduleExpression schedule, ScheduleExpression otherScheduleExpression)
   {
      return true;
   }

   private boolean doesTimeoutMethodMatch(TimeoutMethod timeoutMethod, String timeoutMethodName, String[] methodParams)
   {
      if (timeoutMethod.getMethodName().equals(timeoutMethodName) == false)
      {
         return false;
      }
      String[] timeoutMethodParams = timeoutMethod.getMethodParams();
      if (timeoutMethodParams == null && methodParams == null)
      {
         return true;
      }
      return this.methodParamsMatch(timeoutMethodParams, methodParams);
   }

   private boolean doesTimerConfigMatch(TimerConfig timerConfig)
   {
      return true;
   }

   private boolean isEitherParamNull(Object param1, Object param2)
   {
      if (param1 != null && param2 == null)
      {
         return true;
      }
      if (param2 != null && param1 == null)
      {
         return true;
      }
      return false;
   }

   private boolean methodParamsMatch(String[] methodParams, String[] otherMethodParams)
   {
      if (this.isEitherParamNull(methodParams, otherMethodParams))
      {
         return false;
      }

      if (methodParams.length != otherMethodParams.length)
      {
         return false;
      }
      for (int i = 0; i < methodParams.length; i++)
      {
         if (methodParams[i].equals(otherMethodParams[i]) == false)
         {
            return false;
         }
      }
      return true;
   }

   /**
    * Marks the transaction for rollback
    * NOTE: This method will soon be removed, once this timer service
    * implementation becomes "managed"
    * 
    */
   private void setRollbackOnly()
   {
      try
      {
         Transaction tx = this.transactionManager.getTransaction();
         if (tx != null)
         {
            tx.setRollbackOnly();
         }
      }
      catch (IllegalStateException ise)
      {
         logger.error("Ignoring exception during setRollbackOnly: ", ise);
      }
      catch (SystemException se)
      {
         logger.error("Ignoring exception during setRollbackOnly: ", se);
      }
   }

   private void startNewTx()
   {
      try
      {
         this.transactionManager.begin();
      }
      catch (Throwable t)
      {
         throw new RuntimeException("Could not start transaction", t);
      }
   }

   private void endTx()
   {
      try
      {
         Transaction tx = this.transactionManager.getTransaction();
         if (tx == null)
         {
            throw new IllegalStateException("Transaction cannot be ended since no transaction is in progress");
         }
         if (tx.getStatus() == Status.STATUS_MARKED_ROLLBACK)
         {
            this.transactionManager.rollback();
         }
         else if (tx.getStatus() == Status.STATUS_ACTIVE)
         {
            // Commit tx
            this.transactionManager.commit();
         }
      }
      catch (Exception e)
      {
         throw new RuntimeException("Could not end transaction", e);
      }
   }

   private class TimerCreationTransactionSynchronization implements Synchronization
   {
      /**
       * The timer being managed in the transaction
       */
      private TimerImpl timer;

      public TimerCreationTransactionSynchronization(TimerImpl timer)
      {
         if (timer == null)
         {
            throw new IllegalStateException("Timer cannot be null");
         }
         this.timer = timer;
      }

      /**
       * {@inheritDoc}
       */
      @Override
      public void afterCompletion(int status)
      {
         if (this.timer.persistent)
         {
            synchronized (TimerServiceImpl.this.persistentWaitingOnTxCompletionTimers)
            {
               TimerServiceImpl.this.persistentWaitingOnTxCompletionTimers.remove(this.timer.getTimerHandle());
            }
         }
         if (status == Status.STATUS_COMMITTED)
         {
            logger.debug("commit timer creation: " + this.timer);

            TimerState timerState = this.timer.getState();
            switch (timerState)
            {
               case ACTIVE :
                  // the timer was started/activated in a tx.
                  // now it's time to schedule the task
                  this.timer.scheduleTimeout();
                  break;
            }
         }
         else if (status == Status.STATUS_ROLLEDBACK)
         {
            logger.debug("Rolling back timer creation: " + this.timer);

            TimerState timerState = this.timer.getState();
            switch (timerState)
            {
               case ACTIVE:
                  // TODO: I think the timer state should be FAILED instead of
                  // CANCELLED. Let's rethink this later
                  this.timer.setTimerState(TimerState.CANCELED);
                  break;
            }
           
         }
        
      }

      @Override
      public void beforeCompletion()
      {
         // TODO Auto-generated method stub

      }

   }
  


   private class EntityManagerTransactionSynchronization implements Synchronization
   {

      @Override
      public void afterCompletion(int status)
      {
         EntityManager em = TimerServiceImpl.this.transactionScopedEntityManager.get();
         TimerServiceImpl.this.transactionScopedEntityManager.remove();
         if (em != null)
         {
            try
            {
               em.close();
            }
            catch (Exception e)
            {
               logger.debug("Ignoring exception during entity manager close: ", e);
            }
         }

      }

      @Override
      public void beforeCompletion()
      {
         // TODO Auto-generated method stub

      }

   }

   private EntityManager getCurrentEntityManager() throws Exception
   {
      EntityManager em = this.transactionScopedEntityManager.get();
      if (em != null)
      {
         return em;
      }
      Transaction tx = this.transactionManager.getTransaction();
      if (tx == null)
      {
         throw new IllegalStateException("No transaction in progress. Cannot create an entity manager");
      }
      em = this.emf.createEntityManager();
      em.joinTransaction();
      this.transactionScopedEntityManager.set(em);
      tx.registerSynchronization(new EntityManagerTransactionSynchronization());

      return em;
   }
}
TOP

Related Classes of org.jboss.ejb3.timerservice.mk2.TimerServiceImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.