package org.fluxtream.connectors.bodymedia; import java.util.ArrayList; import java.util.List; import java.util.TimeZone; import org.fluxtream.core.ApiData; import org.fluxtream.core.TimezoneMap; import org.fluxtream.core.connectors.ObjectType; import org.fluxtream.core.connectors.updaters.UpdateInfo; import org.fluxtream.core.domain.AbstractFacet; import org.fluxtream.core.facets.extractors.AbstractFacetExtractor; import org.fluxtream.core.services.ConnectorUpdateService; import org.fluxtream.core.services.MetadataService; import org.fluxtream.core.utils.TimeUtils; import net.sf.json.JSONArray; import net.sf.json.JSONException; import net.sf.json.JSONObject; import org.fluxtream.core.aspects.FlxLogger; import org.joda.time.DateTime; import org.joda.time.DateTimeConstants; import org.joda.time.LocalDate; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; /** * Extracts information from the apicall and creates a facet */ @Component public class BodymediaSleepFacetExtractor extends AbstractFacetExtractor { //Logs various transactions FlxLogger logger = FlxLogger.getLogger(BodymediaSleepFacetExtractor.class); @Qualifier("connectorUpdateServiceImpl") @Autowired ConnectorUpdateService connectorUpdateService; DateTimeFormatter form = DateTimeFormat.forPattern("yyyyMMdd'T'HHmmssZ"); DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyyMMdd"); @Qualifier("metadataServiceImpl") @Autowired MetadataService metadataService; @Override public List<AbstractFacet> extractFacets(final UpdateInfo updateInfo, final ApiData apiData, final ObjectType objectType) throws Exception { logger.info("guestId=" + updateInfo.getGuestId() + " connector=bodymedia action=extractFacets objectType=" + objectType.getName()); ArrayList<AbstractFacet> facets; String name = objectType.getName(); if(name.equals("sleep")) { facets = extractSleepFacets(updateInfo, apiData); } else //If the facet to be extracted wasn't a step facet { throw new RuntimeException("Sleep extractor called with illegal ObjectType"); } return facets; } /** * Extracts Data from the Sleep api. * @param apiData The data returned by bodymedia * @return a list containing a single BodymediaSleepFacet for the current day */ private ArrayList<AbstractFacet> extractSleepFacets(final UpdateInfo updateInfo, final ApiData apiData) { ArrayList<AbstractFacet> facets = new ArrayList<AbstractFacet>(); /* burnJson is a JSONArray that contains a seperate JSONArray and calorie counts for each day */ JSONObject bodymediaResponse = JSONObject.fromObject(apiData.json); if(bodymediaResponse.has("Failed")) { BodymediaSleepFacet sleep = new BodymediaSleepFacet(updateInfo.apiKey.getId()); sleep.date = bodymediaResponse.getString("Date"); } else { 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); for(Object o : daysArray) { if(o instanceof JSONObject) { JSONObject day = (JSONObject) o; BodymediaSleepFacet sleep = new BodymediaSleepFacet(updateInfo.apiKey.getId()); super.extractCommonFacetData(sleep, apiData); sleep.efficiency = day.getDouble("efficiency"); sleep.totalLying = day.getInt("totalLying"); sleep.totalSleeping = day.getInt("totalSleep"); sleep.json = day.getString("sleepPeriods"); sleep.lastSync = d.getMillis(); //https://developer.bodymedia.com/docs/read/api_reference_v2/Sleep_Service // sleep data is from noon the previous day to noon the current day, // so subtract MILLIS_IN_DAY/2 from midnight long MILLIS_IN_DAY = 86400000l; DateTime date = formatter.parseDateTime(day.getString("date")); if(tzMap!=null) { // Create a LocalDate object which just captures the date without any // timezone assumptions LocalDate ld = new LocalDate(date.getYear(),date.getMonthOfYear(),date.getDayOfMonth()); // Use tzMap to convert date into a datetime with timezone information DateTime realDateStart = tzMap.getStartOfDate(ld); // Set the start and end times for the facet. The start time is the leading midnight // of burn.date according to BodyMedia's idea of what timezone you were in then. // End should, I think, be start + the number of minutes in the minutes array * // the number of milliseconds in a minute. sleep.date = TimeUtils.dateFormatter.print(realDateStart.getMillis()); sleep.start = realDateStart.getMillis() - DateTimeConstants.MILLIS_PER_DAY/2; sleep.end = realDateStart.getMillis() + DateTimeConstants.MILLIS_PER_DAY/2; } else { sleep.date = TimeUtils.dateFormatter.print(date.getMillis()); TimeZone timeZone = metadataService.getTimeZone(updateInfo.getGuestId(), date.getMillis()); long fromNoon = TimeUtils.fromMidnight(date.getMillis(), timeZone) - MILLIS_IN_DAY / 2; long toNoon = TimeUtils.toMidnight(date.getMillis(), timeZone) - MILLIS_IN_DAY / 2; sleep.start = fromNoon; sleep.end = toNoon; } facets.add(sleep); } else throw new JSONException("Days array is not a proper JSONObject"); } } } return facets; } }