public BodymediaUpdater() {
   super();
   ObjectType burn = ObjectType.getObjectType(connector(), "burn");
   ObjectType sleep = ObjectType.getObjectType(connector(), "sleep");
   ObjectType steps = ObjectType.getObjectType(connector(), "steps");
   url.put(burn, "burn/day/minute/intensity/");
   url.put(sleep, "sleep/day/period/");
   url.put(steps, "step/day/hour/");
   maxIncrement.put(burn, 1);
   maxIncrement.put(sleep, 1);
   maxIncrement.put(steps, 31);
 }
  public DateTime getStartDate(UpdateInfo updateInfo, ObjectType ot) throws Exception {
    ApiKey apiKey = updateInfo.apiKey;

    // The updateStartDate for a given object type is stored in the apiKeyAttributes
    // as BodyMedia.<objectName>.updateStartDate.  In the case of a failure the updater will store
    // the date
    // that failed and start there next time.  In the case of a successfully completed update it
    // will store
    // the lastSync date returned from BodyMedia along with each API call.
    String updateKeyName = "BodyMedia." + ot.getName() + ".updateStartDate";
    String updateStartDate = guestService.getApiKeyAttribute(apiKey, updateKeyName);

    // The first time we do this there won't be an apiKeyAttribute yet.  In that case get the
    // registration date for the user and store that.

    if (updateStartDate == null) {
      OAuthConsumer consumer = setupConsumer(updateInfo.apiKey);
      String api_key = guestService.getApiKeyAttribute(updateInfo.apiKey, "bodymediaConsumerKey");
      updateStartDate = getUserRegistrationDate(updateInfo, api_key, consumer);

      // Store in the apiKeyAttribute for next time
      guestService.setApiKeyAttribute(updateInfo.apiKey, updateKeyName, updateStartDate);
    }
    try {
      return formatter.parseDateTime(updateStartDate);
    } catch (IllegalArgumentException e) {
      return TimeUtils.dateFormatter.parseDateTime(updateStartDate);
    }
  }
 private long getLatestData(ApiKey apiKey) {
   if (!apiKey.getConnector().hasFacets()) return Long.MAX_VALUE;
   final ObjectType[] objectTypes = apiKey.getConnector().objectTypes();
   if (objectTypes == null || objectTypes.length == 0) {
     final String maxTimeAtt =
         guestService.getApiKeyAttribute(apiKey, ApiKeyAttribute.MAX_TIME_KEY);
     // only return the ApiKey's maxTime if we have it cached as an attribute
     if (maxTimeAtt != null && StringUtils.isNotEmpty(maxTimeAtt)) {
       final DateTime dateTime =
           ISODateTimeFormat.dateHourMinuteSecondFraction()
               .withZoneUTC()
               .parseDateTime(maxTimeAtt);
       return dateTime.getMillis();
     }
   } else {
     long maxTime = Long.MIN_VALUE;
     for (ObjectType objectType : objectTypes) {
       final String maxTimeAtt =
           guestService.getApiKeyAttribute(
               apiKey, objectType.getApiKeyAttributeName(ApiKeyAttribute.MAX_TIME_KEY));
       if (maxTimeAtt != null && StringUtils.isNotEmpty(maxTimeAtt)) {
         final DateTime dateTime =
             ISODateTimeFormat.dateHourMinuteSecondFraction()
                 .withZoneUTC()
                 .parseDateTime(maxTimeAtt);
         final long maxObjectTypeTime = dateTime.getMillis();
         if (maxObjectTypeTime > maxTime) maxTime = maxObjectTypeTime;
       }
     }
     // only return the ApiKey's maxTime if we have it cached as an attribute for any its
     // connector's objectTypes
     if (maxTime > Long.MIN_VALUE) return maxTime;
   }
   // fall back to old method of querying the facets table
   AbstractFacet facet = null; // apiDataService.getLatestApiDataFacet(apiKey, null);
   return facet == null ? Long.MAX_VALUE : facet.end;
 }
  // 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;
    }
  }
  @GET
  @Path("/installed")
  @Produces({MediaType.APPLICATION_JSON})
  public String getInstalledConnectors() {
    Guest guest = AuthHelper.getGuest();
    // If no guest is logged in, return empty array
    if (guest == null) return "[]";
    ResourceBundle res = ResourceBundle.getBundle("messages/connectors");
    try {
      List<ConnectorInfo> connectors = sysService.getConnectors();
      JSONArray connectorsArray = new JSONArray();
      for (int i = 0; i < connectors.size(); i++) {
        final ConnectorInfo connectorInfo = connectors.get(i);
        final Connector api = connectorInfo.getApi();
        if (api == null) {
          StringBuilder sb =
              new StringBuilder(
                  "module=API component=connectorStore action=getInstalledConnectors ");
          logger.warn("message=\"null connector for " + connectorInfo.getName() + "\"");
          continue;
        }
        if (!guestService.hasApiKey(guest.getId(), api)
            || api.getName().equals("facebook") /*HACK*/) {
          connectors.remove(i--);
        } else {
          ConnectorInfo connector = connectorInfo;
          JSONObject connectorJson = new JSONObject();
          Connector conn = Connector.fromValue(connector.api);
          ApiKey apiKey = guestService.getApiKey(guest.getId(), conn);

          connectorJson.accumulate("prettyName", conn.prettyName());
          List<String> facetTypes = new ArrayList<String>();
          ObjectType[] objTypes = conn.objectTypes();
          if (objTypes != null) {
            for (ObjectType obj : objTypes) {
              facetTypes.add(connector.connectorName + "-" + obj.getName());
            }
          }
          connectorJson.accumulate("facetTypes", facetTypes);
          connectorJson.accumulate(
              "status", apiKey.status != null ? apiKey.status.toString() : "NA");
          connectorJson.accumulate("name", connector.name);
          connectorJson.accumulate("connectUrl", connector.connectUrl);
          connectorJson.accumulate("image", connector.image);
          connectorJson.accumulate("connectorName", connector.connectorName);
          connectorJson.accumulate("enabled", connector.enabled);
          connectorJson.accumulate("manageable", connector.manageable);
          connectorJson.accumulate("text", connector.text);
          connectorJson.accumulate("api", connector.api);
          connectorJson.accumulate("apiKeyId", apiKey.getId());
          connectorJson.accumulate(
              "lastSync", connector.supportsSync ? getLastSync(apiKey) : Long.MAX_VALUE);
          connectorJson.accumulate("latestData", getLatestData(apiKey));
          final String auditTrail = checkForErrors(apiKey);
          connectorJson.accumulate("errors", auditTrail != null);
          connectorJson.accumulate("auditTrail", auditTrail != null ? auditTrail : "");
          connectorJson.accumulate("syncing", checkIfSyncInProgress(guest.getId(), conn));
          connectorJson.accumulate(
              "channels", settingsService.getChannelsForConnector(guest.getId(), conn));
          connectorJson.accumulate("sticky", connector.connectorName.equals("fluxtream_capture"));
          connectorJson.accumulate("supportsRenewToken", connector.supportsRenewTokens);
          connectorJson.accumulate("supportsSync", connector.supportsSync);
          connectorJson.accumulate("supportsFileUpload", connector.supportsFileUpload);
          connectorJson.accumulate("prettyName", conn.prettyName());
          final String uploadMessageKey = conn.getName() + ".upload";
          if (res.containsKey(uploadMessageKey)) {
            final String uploadMessage = res.getString(uploadMessageKey);
            connectorJson.accumulate("uploadMessage", uploadMessage);
          }
          connectorsArray.add(connectorJson);
        }
      }
      StringBuilder sb =
          new StringBuilder("module=API component=connectorStore action=getInstalledConnectors")
              .append(" guestId=")
              .append(guest.getId());
      logger.info(sb.toString());
      return connectorsArray.toString();
    } catch (Exception e) {
      StringBuilder sb =
          new StringBuilder("module=API component=connectorStore action=getInstalledConnectors")
              .append(" guestId=")
              .append(guest.getId())
              .append(" stackTrace=<![CDATA[")
              .append(Utils.stackTrace(e))
              .append("]]>");
      System.out.println(sb.toString());
      logger.warn(sb.toString());
      return gson.toJson(
          new StatusModel(false, "Failed to get installed connectors: " + e.getMessage()));
    }
  }