public final class ServletTimerImpl implements ServletTimer, Runnable {
  private static final Logger LOG =
      OSGiLogger.getLogger(ServletTimer.class, BundleLogger.getStaticLogger());

  private static final int UUID_LENGTH = 40;

  private RunnableTimerTask timerTask;
  private final ApplicationSessionImpl appSession;
  private final String id;
  private final Serializable info;
  private final boolean repeatingTimer;
  private long scheduledExecutionTime;
  private long delay;
  private long period;
  private long numInvocations;
  private boolean fixedDelay;
  private long firstExecution;
  private TimerListener listener;

  /**
   * Constructor for non-repeating timer.
   *
   * @param info Information about the timer
   * @param delay Delay until execution
   * @param listener Listener that will get timeout events.
   */
  public ServletTimerImpl(
      Serializable info, long delay, TimerListener listener, ApplicationSessionImpl appSession) {
    this(info, delay, false, 0, listener, appSession);
  }

  /**
   * Constructor for repeating times
   *
   * @param info Information about the timer
   * @param delay Delay until first execution
   * @param fixedDelay Whether fixed delay mode should be used
   * @param period Period between execution
   * @param listener Listener that will get timeout events.
   */
  public ServletTimerImpl(
      Serializable info,
      long delay,
      boolean fixedDelay,
      long period,
      TimerListener listener,
      ApplicationSessionImpl appSession) {
    this.id = UUID.randomUUID(UUID_LENGTH);
    this.info = info;
    this.delay = delay;
    this.scheduledExecutionTime = delay + System.currentTimeMillis();
    this.fixedDelay = fixedDelay;
    this.period = period;
    this.listener = listener;
    this.appSession = appSession;
    this.numInvocations = 0;
    this.firstExecution = 0;
    this.repeatingTimer = (period > 0);
    this.timerTask = new RunnableTimerTask(this);
  }

  public void cancel() {
    LOG.debug("removeServletTimer");
    appSession.removeServletTimer(this);
    LOG.debug("cancel timerTask");
    timerTask.cancel();
  }

  public ApplicationSession getApplicationSession() {
    return appSession;
  }

  public String getId() {
    return id;
  }

  public Serializable getInfo() {
    return info;
  }

  public synchronized long getTimeRemaining() {
    return scheduledExecutionTime - System.currentTimeMillis();
  }

  public synchronized long scheduledExecutionTime() {
    return this.scheduledExecutionTime;
  }

  public RunnableTimerTask getServletTimerTask() {
    return timerTask;
  }

  public String toString() {
    StringBuffer sb = new StringBuffer();
    sb.append("Info = ")
        .append(info)
        .append(System.getProperty("line.separator"))
        .append("Scheduled execution time = ");
    synchronized (this) {
      sb.append(scheduledExecutionTime);
    }
    sb.append(System.getProperty("line.separator"));
    sb.append("Time now = ")
        .append(System.currentTimeMillis())
        .append(System.getProperty("line.separator"));
    sb.append("ApplicationSession = ")
        .append(appSession)
        .append(System.getProperty("line.separator"));
    sb.append("Delay = ").append(delay).append(System.getProperty("line.separator"));
    return sb.toString();
  }

  /** Helper to calculate when next execution time is. */
  private synchronized void estimateNextExecution() {
    if (fixedDelay) {
      scheduledExecutionTime = period + System.currentTimeMillis();
    } else {
      if (firstExecution == 0) {
        // save timestamp of first execution
        firstExecution = scheduledExecutionTime;
      }
      long now = System.currentTimeMillis();
      long executedTime = (numInvocations * period);
      numInvocations++;
      scheduledExecutionTime = firstExecution + executedTime;
      if (LOG.isDebugEnabled()) {
        LOG.debug("next execution estimated to run at " + scheduledExecutionTime);
        LOG.debug("current time is " + now);
      }
    }
  }

  public void run() {
    long run = System.currentTimeMillis();
    // PORTAGE chgt de classLoader pour le currentThread
    // ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
    try {
      // ClassLoader cl = listener.getClass().getClassLoader();
      // Thread.currentThread().setContextClassLoader(cl);
      LOG.debug("Call timeout");
      listener.timeout(this);
    } catch (Throwable e) {
      LOG.error("An unexpected exception happened in the timer callback", e);
    } finally {
      // Thread.currentThread().setContextClassLoader(oldClassLoader);
      if (repeatingTimer) {
        LOG.debug("Reset repeating servlet timer");
        estimateNextExecution();
      } else {
        LOG.debug("Cancel servlet timer");
        // this non-repeating timer is now "ready"
        // and should not be included in the list of active timers
        // The application may already have canceled() the timer though
        cancel(); // dont bother about return value....
      }
    }
    run = System.currentTimeMillis() - run;
    if (run > 500) {
      LOG.error(
          appSession.getId()
              + ": The servlet timer has taken more than 500 ms to be handled: "
              + run
              + " ms");
    }
  }
}
public final class TimerServiceImpl extends Timer implements TimerService {
  private static final Logger LOG =
      OSGiLogger.getLogger(TimerService.class, BundleLogger.getStaticLogger());

  public ServletTimer createTimer(
      ServletContext context, long delay, boolean isPersistent, Serializable info) {
    throw new UnsupportedOperationException();
  }

  public ServletTimer createTimer(
      ServletContext context,
      long delay,
      long period,
      boolean fixedDelay,
      boolean isPersistent,
      Serializable info) {
    throw new UnsupportedOperationException();
  }

  public ServletTimer createTimer(
      ApplicationSession appSession, long delay, boolean isPersistent, Serializable info) {
    if (LOG.isDebugEnabled()) {
      LOG.debug(
          "Create timer for application "
              + appSession.getApplicationName()
              + " (delay="
              + delay
              + ", persistent="
              + isPersistent
              + ")");
    }
    ApplicationSessionImpl applicationSessionImpl = (ApplicationSessionImpl) appSession;

    applicationSessionImpl.checkValid();

    if (!applicationSessionImpl.hasTimerListener()) {
      throw new IllegalStateException(
          "No Timer listeners have been configured for this application ");
    }
    TimerListener listener = applicationSessionImpl.getServletContextInternal().getTimerListener();
    ServletTimerImpl servletTimer =
        createTimerLocally(listener, delay, isPersistent, info, applicationSessionImpl);

    return servletTimer;
  }

  public ServletTimer createTimer(
      ApplicationSession appSession,
      long delay,
      long period,
      boolean fixedDelay,
      boolean isPersistent,
      Serializable info) {
    if (period < 1) {
      throw new IllegalArgumentException("Period should be greater than 0");
    }
    if (LOG.isDebugEnabled()) {
      LOG.debug(
          "Create timer for application "
              + appSession.getApplicationName()
              + " (delay="
              + delay
              + ", period="
              + period
              + ", fixedDelay="
              + fixedDelay
              + ", persistent="
              + isPersistent
              + ")");
    }
    ApplicationSessionImpl applicationSessionImpl = (ApplicationSessionImpl) appSession;

    applicationSessionImpl.checkValid();

    if (applicationSessionImpl.getServletContextInternal().getTimerListener() == null) {
      throw new IllegalStateException(
          "No Timer listeners have been configured for this application ");
    }
    TimerListener timerListener =
        applicationSessionImpl.getServletContextInternal().getTimerListener();
    ServletTimerImpl servletTimer =
        createTimerLocally(
            timerListener, delay, period, fixedDelay, isPersistent, info, applicationSessionImpl);

    return servletTimer;
  }

  /**
   * @param listener
   * @param delay
   * @param isPersistent
   * @param info
   * @param applicationSession
   * @return
   */
  private ServletTimerImpl createTimerLocally(
      TimerListener listener,
      long delay,
      boolean isPersistent,
      Serializable info,
      ApplicationSessionImpl applicationSession) {
    ServletTimerImpl servletTimer = new ServletTimerImpl(info, delay, listener, applicationSession);
    super.schedule(servletTimer.getServletTimerTask(), delay);
    applicationSession.addServletTimer(servletTimer);
    if (isPersistent) {
      persist(servletTimer);
    }
    return servletTimer;
  }

  /**
   * @param listener
   * @param delay
   * @param period
   * @param fixedDelay
   * @param isPersistent
   * @param info
   * @param applicationSession
   * @return
   */
  private ServletTimerImpl createTimerLocally(
      TimerListener listener,
      long delay,
      long period,
      boolean fixedDelay,
      boolean isPersistent,
      Serializable info,
      ApplicationSessionImpl applicationSession) {
    final ServletTimerImpl servletTimer =
        new ServletTimerImpl(info, delay, fixedDelay, period, listener, applicationSession);
    if (fixedDelay) {
      super.schedule(servletTimer.getServletTimerTask(), delay, period);
    } else {
      super.scheduleAtFixedRate(servletTimer.getServletTimerTask(), delay, period);
    }
    applicationSession.addServletTimer(servletTimer);
    if (isPersistent) {
      persist(servletTimer);
    }
    return servletTimer;
  }

  /** @param st */
  private void persist(ServletTimerImpl st) {
    // TODO - implement persistance

  }

  public void stop() {
    super.cancel();
    if (LOG.isDebugEnabled()) {
      LOG.debug("Stopped timer service " + this);
    }
  }

  public void start() {
    if (LOG.isDebugEnabled()) {
      LOG.debug("Started timer service " + this);
    }
  }
}