// Update the start date for next time.  The parameter the updateProgressDate is the date
  // of that retrieveHistory had gotten to when it completed or gave up.
  // If lastSync is set and is < updateProgressDate we will use that, and otherwise use
  // updateProgressDate.
  void updateStartDate(UpdateInfo updateInfo, ObjectType ot, DateTime updateProgressTime) {
    DateTimeComparator comparator = DateTimeComparator.getDateOnlyInstance();

    // Calculate the name of the key in the ApiAttributes table
    // where the next start of update for this object type is
    // stored and retrieve the stored value.  This stored value
    // may potentially be null if something happened to the attributes table
    String updateKeyName = "BodyMedia." + ot.getName() + ".updateStartDate";
    String storedUpdateStartDate =
        guestService.getApiKeyAttribute(updateInfo.apiKey, updateKeyName);

    // Retrieve the lastSync date if it has been added to the
    // updateInfo context by an extractor
    DateTime lastSync = (DateTime) updateInfo.getContext("lastSync");

    // Check which is earlier: the lastSync time returned from Bodymedia or the
    // point we were in the update that just ended.  Store the lesser of the two
    // in nextUpdateStartDate
    String nextUpdateStartDate = storedUpdateStartDate;
    if (lastSync != null) {
      if (comparator.compare(updateProgressTime, lastSync) > 0) {
        // lastSync from Bodymedia is less than the update progress
        nextUpdateStartDate = lastSync.toString(formatter);
      } else {
        // the update progress is less than lastSync from Bodymedia
        nextUpdateStartDate = updateProgressTime.toString(formatter);
      }
    } else {
      // Last sync is null, just leave the stored updateTime
      // alone since it's better to get some extra data next
      // time than to skip data from dates that potentially changed
    }

    // Store the new value if it's different than what's stored in ApiKeyAttributes
    if (storedUpdateStartDate == null || !nextUpdateStartDate.equals(storedUpdateStartDate)) {
      guestService.setApiKeyAttribute(updateInfo.apiKey, updateKeyName, nextUpdateStartDate);
    }
  }
  /**
   * Retrieves that history for the given facet from the start date to the end date. It peforms the
   * api calls in reverse order starting from the end date. This is so that the most recent
   * information is retrieved first.
   *
   * @param updateInfo The api's info
   * @param ot The ObjectType that represents the facet to be updated
   * @param start The earliest date for which the burn history is retrieved. This date is included
   *     in the update.
   * @param end The latest date for which the burn history is retrieved. This date is also included
   *     in the update.
   * @throws Exception If either storing the data fails or if the rate limit is reached on
   *     Bodymedia's api
   */
  private void retrieveHistory(UpdateInfo updateInfo, ObjectType ot, DateTime start, DateTime end)
      throws Exception {
    final String urlExtension = url.get(ot);
    final int increment = maxIncrement.get(ot);
    DateTimeComparator comparator = DateTimeComparator.getDateOnlyInstance();
    DateTime current = start;

    // Setup the rate delay if we haven't already
    Long rateDelay = getRateDelay(updateInfo);

    try {
      //  Loop from start to end, incrementing by the max number of days you can
      //  specify for a given type of query.  This is 1 for burn and sleep, and 31 for steps.
      // @ loop_invariant date.compareTo(userRegistrationDate) >= 0;
      while (comparator.compare(current, end) < 0) {
        if (guestService.getApiKey(updateInfo.apiKey.getId()) == null) {
          logger.info("Not updating BodyMedia connector instance with a deleted apiKeyId");
          return;
        }
        String startPeriod = current.toString(formatter);
        String endPeriod = current.plusDays(increment - 1).toString(formatter);
        String minutesUrl =
            "http://api.bodymedia.com/v2/json/"
                + urlExtension
                + startPeriod
                + "/"
                + endPeriod
                + "?api_key="
                + guestService.getApiKeyAttribute(updateInfo.apiKey, "bodymediaConsumerKey");
        // The following call may fail due to bodymedia's api. That is expected behavior
        enforceRateLimits(rateDelay);
        String json = signpostHelper.makeRestCall(updateInfo.apiKey, ot.value(), minutesUrl);
        guestService.setApiKeyAttribute(
            updateInfo.apiKey, "timeOfLastCall", String.valueOf(System.currentTimeMillis()));
        JSONObject bodymediaResponse = JSONObject.fromObject(json);
        JSONArray daysArray = bodymediaResponse.getJSONArray("days");
        if (bodymediaResponse.has("lastSync")) {
          DateTime d =
              form.parseDateTime(bodymediaResponse.getJSONObject("lastSync").getString("dateTime"));

          // Get timezone map from UpdateInfo context
          TimezoneMap tzMap = (TimezoneMap) updateInfo.getContext("tzMap");

          // Insert lastSync into the updateInfo context so it's accessible to the updater
          updateInfo.setContext("lastSync", d);
          List<AbstractFacet> newFacets = new ArrayList<AbstractFacet>();
          for (Object o : daysArray) {
            if (o instanceof JSONObject) {
              if (ot == ObjectType.getObjectType(connector(), "steps"))
                newFacets.add(createOrUpdateStepsFacet((JSONObject) o, updateInfo, d, tzMap));
              else if (ot == ObjectType.getObjectType(connector(), "burn"))
                newFacets.add(createOrUpdateBurnFacet((JSONObject) o, updateInfo, d, tzMap));
              else newFacets.add(createOrUpdateSleepFacet((JSONObject) o, updateInfo, d, tzMap));
            }
          }
          bodyTrackStorageService.storeApiData(updateInfo.getGuestId(), newFacets);
        }

        current = current.plusDays(increment);

        // Update the stored value that controls when we will start updating next time
        updateStartDate(updateInfo, ot, current);
      }

    } catch (Exception e) {
      StringBuilder sb =
          new StringBuilder(
                  "module=updateQueue component=updater action=BodymediaUpdater.retrieveHistory")
              .append(" message=\"exception while retrieving history\" connector=")
              .append(updateInfo.apiKey.getConnector().toString())
              .append(" guestId=")
              .append(updateInfo.apiKey.getGuestId())
              .append(" updatingDate=")
              .append(current);
      logger.info(sb.toString());

      // Update the stored value that controls when we will start updating next time
      updateStartDate(updateInfo, ot, current);

      // Rethrow the error so that this task gets rescheduled
      throw e;
    }
  }