/**
   * Return an iterator on events in the CalendarEventVector. The order in which the events will be
   * found in the iteration is by event start date.
   *
   * @param range A time range to limit the iterated events. May be null; all events will be
   *     returned.
   * @return an iterator on CalendarEvent objects in the CalendarEventVector (may be empty).
   */
  public Iterator getEvents(TimeRange range) {
    // pull the range of events from vector
    Vector events = new Vector();
    Iterator it = iterator();
    while (it.hasNext()) {
      CalendarEvent test = (CalendarEvent) it.next();
      if (range.overlaps(test.getRange())) {
        events.add(test);
      }
      // %%% if test is past range, we can stop now...
    }

    return events.iterator();
  } // getEvents
  /**
   * Send a single digest message
   *
   * @param id The use id to send the message to.
   * @param msgs The List (DigestMessage) of message to digest.
   * @param period The time period of the digested messages.
   */
  protected void send(String id, List msgs, TimeRange period) {
    // sanity check
    if (msgs.size() == 0) return;

    try {
      String to = userDirectoryService().getUser(id).getEmail();

      // if use has no email address we can't send it
      if ((to == null) || (to.length() == 0)) return;

      String from = "postmaster@" + serverConfigurationService().getServerName();
      String subject =
          serverConfigurationService().getString("ui.service", "Sakai")
              + " "
              + rb.getString("notif")
              + " "
              + period.firstTime().toStringLocalDate();

      StringBuilder body = new StringBuilder();
      body.append(subject);
      body.append("\n\n");

      // toc
      int count = 1;
      for (Iterator iMsgs = msgs.iterator(); iMsgs.hasNext(); ) {
        DigestMessage msg = (DigestMessage) iMsgs.next();

        body.append(Integer.toString(count));
        body.append(".  ");
        body.append(msg.getSubject());
        body.append("\n");
        count++;
      }
      body.append("\n----------------------\n\n");

      // for each msg
      count = 1;
      for (Iterator iMsgs = msgs.iterator(); iMsgs.hasNext(); ) {
        DigestMessage msg = (DigestMessage) iMsgs.next();

        // repeate toc entry
        body.append(Integer.toString(count));
        body.append(".  ");
        body.append(msg.getSubject());
        body.append("\n\n");

        // message body
        body.append(msg.getBody());

        body.append("\n----------------------\n\n");
        count++;
      }

      // tag
      body.append(
          rb.getString("thiaut")
              + " "
              + serverConfigurationService().getString("ui.service", "Sakai")
              + " "
              + "("
              + serverConfigurationService().getServerUrl()
              + ")"
              + "\n"
              + rb.getString("youcan")
              + "\n");

      if (M_log.isDebugEnabled()) M_log.debug(this + " sending digest email to: " + to);

      emailService().send(from, to, subject, body.toString(), to, null, null);
    } catch (Throwable any) {
      M_log.warn(".send: digest to: " + id + " not sent: " + any.toString());
    }
  }
  /**
   * If it's time, send out any digested messages. Send once daily, after a certiain time of day
   * (local time).
   */
  protected void sendDigests() {
    if (M_log.isDebugEnabled()) M_log.debug("checking for sending digests");

    // compute the current period
    String curPeriod = computeRange(timeService().newTime()).toString();

    // if we are in a new period, start sending again
    if (!curPeriod.equals(m_lastSendPeriod)) {
      m_sendDigests = true;

      // remember this period for next check
      m_lastSendPeriod = curPeriod;
    }

    // if we are not sending, early out
    if (!m_sendDigests) return;

    M_log.info("Preparing to send the mail digests for " + curPeriod);

    // count send candidate digests
    int count = 0;

    // process each digest
    List digests = getDigests();
    for (Iterator iDigests = digests.iterator(); iDigests.hasNext(); ) {
      Digest digest = (Digest) iDigests.next();

      // see if this one has any prior periods
      List periods = digest.getPeriods();
      if (periods.size() == 0) continue;

      boolean found = false;
      for (Iterator iPeriods = periods.iterator(); iPeriods.hasNext(); ) {
        String period = (String) iPeriods.next();
        if (!curPeriod.equals(period)) {
          found = true;
          break;
        }
      }
      if (!found) {
        continue;
      }

      // this digest is a send candidate
      count++;

      // get a lock
      DigestEdit edit = null;
      try {
        boolean changed = false;
        edit = edit(digest.getId());

        // process each non-current period
        for (Iterator iPeriods = edit.getPeriods().iterator(); iPeriods.hasNext(); ) {
          String period = (String) iPeriods.next();

          // process if it's not the current period
          if (!curPeriod.equals(period)) {
            TimeRange periodRange = timeService().newTimeRange(period);
            Time timeInPeriod = periodRange.firstTime();

            // any messages?
            List msgs = edit.getMessages(timeInPeriod);
            if (msgs.size() > 0) {
              // send this one
              send(edit.getId(), msgs, periodRange);
            }

            // clear this period
            edit.clear(timeInPeriod);

            changed = true;
          }
        }

        // commit, release the lock
        if (changed) {
          // delete it if empty
          if (edit.getPeriods().size() == 0) {
            remove(edit);
          } else {
            commit(edit);
          }
          edit = null;
        } else {
          cancel(edit);
          edit = null;
        }
      }
      // if in use, missing, whatever, skip on
      catch (Throwable any) {
      } finally {
        if (edit != null) {
          cancel(edit);
          edit = null;
        }
      }
    } // for (Iterator iDigests = digests.iterator(); iDigests.hasNext();)

    // if we didn't see any send candidates, we will stop sending till next period
    if (count == 0) {
      m_sendDigests = false;
    }
  }
 /**
  * Helper to extract the endDate of a TimeRange into a java.util.Calendar object.
  *
  * @param range
  * @return
  */
 private java.util.Calendar getEndDate(TimeRange range) {
   java.util.Calendar c = new GregorianCalendar();
   c.setTimeInMillis(range.lastTime().getTime());
   return c;
 }