package org.fluxtream.connectors.beddit; import com.google.gdata.util.common.base.Pair; import net.sf.json.JSONArray; import net.sf.json.JSONObject; import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.HttpClient; import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.HttpGet; import org.apache.http.conn.params.ConnRoutePNames; import org.apache.http.impl.client.BasicResponseHandler; import org.fluxtream.core.connectors.annotations.Updater; import org.fluxtream.core.connectors.updaters.AbstractUpdater; import org.fluxtream.core.connectors.updaters.UpdateFailedException; import org.fluxtream.core.connectors.updaters.UpdateInfo; import org.fluxtream.core.domain.AbstractFacet; import org.fluxtream.core.domain.ApiKey; import org.fluxtream.core.services.ApiDataService; import org.fluxtream.core.services.impl.BodyTrackHelper; import org.fluxtream.core.utils.HttpUtils; import org.fluxtream.core.utils.UnexpectedHttpResponseCodeException; import org.joda.time.DateTimeZone; import org.scribe.builder.ServiceBuilder; import org.scribe.model.OAuthRequest; import org.scribe.model.Response; import org.scribe.model.Token; import org.scribe.model.Verb; import org.scribe.oauth.OAuthService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @Component @Updater(prettyName = "Beddit", value = 352, objectTypes={SleepFacet.class}, bodytrackResponder=BedditBodytrackResponder.class, defaultChannels = {"Beddit.sleepStages", "Beddit.snoringEpisodes","Beddit.sleepCycles", "Beddit.heartRate"}) public class BedditUpdater extends AbstractUpdater { @Autowired BodyTrackHelper bodyTrackHelper; //note: all timestamps but the updated timestamp are in local time, use the provided timezone ID to deal with this @Override protected void updateConnectorDataHistory(UpdateInfo updateInfo) throws Exception { updateConnectorData(updateInfo); } @Override protected void updateConnectorData(UpdateInfo updateInfo) throws Exception { long userId = Long.parseLong(guestService.getApiKeyAttribute(updateInfo.apiKey, "userid")); String accessToken = guestService.getApiKeyAttribute(updateInfo.apiKey, "access_token"); long then = System.currentTimeMillis(); Double latestData = getLatestFacetTime(updateInfo); String url = "https://cloudapi.beddit.com/api/v1/user/" + userId + "/sleep"; if (latestData != null){ url += "?updated_after=" + latestData; } int statusCode; String content = null; // support both oauth2- and token -based authorization if (guestService.getApiKeyAttribute(updateInfo.apiKey, "accessToken")!=null) { final String oauthAccessToken = guestService.getApiKeyAttribute(updateInfo.apiKey, "accessToken"); updateInfo.setContext("accessToken", oauthAccessToken); try { content = callBedditAPI(updateInfo, url); statusCode = HttpStatus.SC_OK; } catch (UnexpectedHttpResponseCodeException exc) { statusCode = exc.getHttpResponseCode(); } } else { HttpGet get = new HttpGet(url); get.setHeader("Authorization", "UserToken " + accessToken); HttpClient httpClient = env.getHttpClient(); if (env.get("development") != null && env.get("development").equals("true")) httpClient = HttpUtils.httpClientTrustingAllSSLCerts(); HttpResponse response = httpClient.execute(get); statusCode = response.getStatusLine().getStatusCode(); ResponseHandler<String> responseHandler = new BasicResponseHandler(); content = responseHandler.handleResponse(response); } if (statusCode != HttpStatus.SC_OK) { throw new UpdateFailedException("Got status code " + statusCode); } try{ Double newLatestTime = createOrUpdateFacets(updateInfo,content); if (newLatestTime != null && (latestData == null || newLatestTime > latestData)){ updateLatestFacetTime(updateInfo, newLatestTime); } countSuccessfulApiCall(updateInfo.apiKey, updateInfo.objectTypes, then, url); } catch (Exception e){ countFailedApiCall(updateInfo.apiKey,updateInfo.objectTypes,then,url, ExceptionUtils.getStackTrace(e), statusCode, null); e.printStackTrace(); throw new UpdateFailedException(e.getMessage()); } } private String callBedditAPI(final UpdateInfo updateInfo, final String url) throws Exception { HttpClient client = env.getHttpClient(); if (env.get("development") != null && env.get("development").equals("true")) { // HttpHost proxy = new HttpHost("127.0.0.1", 8888, "http"); // client.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY,proxy); client = HttpUtils.httpClientTrustingAllSSLCerts(); } final long then = System.currentTimeMillis(); try { HttpGet get = new HttpGet(url); get.setHeader("Authorization", "UserToken " + updateInfo.getContext("accessToken")); HttpResponse response = client.execute(get); int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == HttpStatus.SC_OK) { ResponseHandler<String> responseHandler = new BasicResponseHandler(); String content = responseHandler.handleResponse(response); countSuccessfulApiCall(updateInfo.apiKey, updateInfo.objectTypes, then, url); return content; } else { throw new UnexpectedHttpResponseCodeException(statusCode, response.getStatusLine().getReasonPhrase()); } } finally { client.getConnectionManager().shutdown(); } } @Override public void setDefaultChannelStyles(ApiKey apiKey) { BodyTrackHelper.ChannelStyle channelStyle = new BodyTrackHelper.ChannelStyle(); channelStyle.timespanStyles = new BodyTrackHelper.MainTimespanStyle(); channelStyle.timespanStyles.defaultStyle = new BodyTrackHelper.TimespanStyle(); channelStyle.timespanStyles.defaultStyle.fillColor = "#A3A3A3"; channelStyle.timespanStyles.defaultStyle.borderColor = "#A3A3A3"; channelStyle.timespanStyles.defaultStyle.borderWidth = 0; channelStyle.timespanStyles.defaultStyle.top = 1.00; channelStyle.timespanStyles.defaultStyle.bottom = 0.67; channelStyle.timespanStyles.values = new HashMap(); BodyTrackHelper.TimespanStyle stylePart = new BodyTrackHelper.TimespanStyle(); stylePart.top = 0.67; stylePart.bottom = 1.00; stylePart.fillColor = "#A3A3A3"; stylePart.borderColor = "#A3A3A3"; channelStyle.timespanStyles.values.put("away",stylePart); stylePart = new BodyTrackHelper.TimespanStyle(); stylePart.top = 0.33; stylePart.bottom = 0.67; stylePart.fillColor = "#85D3EF"; stylePart.borderColor = "#85D3EF"; channelStyle.timespanStyles.values.put("awake",stylePart); stylePart = new BodyTrackHelper.TimespanStyle(); stylePart.top = 0.00; stylePart.bottom = 0.33; stylePart.fillColor = "#2EA3CE"; stylePart.borderColor = "#2EA3CE"; channelStyle.timespanStyles.values.put("asleep",stylePart); bodyTrackHelper.setBuiltinDefaultStyle(apiKey.getGuestId(), "beddit", "sleepStages", channelStyle); channelStyle = new BodyTrackHelper.ChannelStyle(); channelStyle.timespanStyles = new BodyTrackHelper.MainTimespanStyle(); channelStyle.timespanStyles.defaultStyle = new BodyTrackHelper.TimespanStyle(); channelStyle.timespanStyles.defaultStyle.fillColor = "#33b5e5"; channelStyle.timespanStyles.defaultStyle.borderColor = "#33b5e5"; channelStyle.timespanStyles.defaultStyle.borderWidth = 0; channelStyle.timespanStyles.defaultStyle.top = 1.00; channelStyle.timespanStyles.defaultStyle.bottom = 0.0; channelStyle.timespanStyles.values = new HashMap(); bodyTrackHelper.setBuiltinDefaultStyle(apiKey.getGuestId(), "beddit", "snoringEpisodes", channelStyle); } private Double createOrUpdateFacets(UpdateInfo updateInfo, String json) throws Exception { JSONArray sleepsArray = JSONArray.fromObject(json); Double oldestFacetTime = null; List<AbstractFacet> newFacets = new LinkedList<AbstractFacet>(); for (int i = 0; i < sleepsArray.size(); i++){ SleepFacet newFacet = createOrUpdateFacet(updateInfo, sleepsArray.getJSONObject(i)); oldestFacetTime = oldestFacetTime == null ? newFacet.updatedTime : Math.max(oldestFacetTime, newFacet.updatedTime); newFacets.add(newFacet); } bodyTrackStorageService.storeApiData(updateInfo.apiKey, newFacets); return oldestFacetTime; } private SleepFacet createOrUpdateFacet(final UpdateInfo updateInfo, final JSONObject sleepObject) throws Exception { final DateTimeZone timezone = DateTimeZone.forID(sleepObject.getString("timezone")); return apiDataService.createOrReadModifyWrite(SleepFacet.class,new ApiDataService.FacetQuery( "e.apiKeyId = ? AND e.date = ?", updateInfo.apiKey.getId(), sleepObject.getString("date")), new ApiDataService.FacetModifier<SleepFacet>() { @Override public SleepFacet createOrModify(SleepFacet facet, Long apiKeyId) { if (facet == null){ facet = new SleepFacet(updateInfo.apiKey.getId()); facet.api = updateInfo.apiKey.getConnector().value(); facet.date = sleepObject.getString("date"); } facet.updatedTime = sleepObject.getDouble("updated"); facet.start = (long) sleepObject.getDouble("start_timestamp")*1000; facet.end = (long) sleepObject.getDouble("end_timestamp")*1000; JSONObject propertiesObject = sleepObject.getJSONObject("properties"); facet.sleepTimeTarget = propertiesObject.getDouble("sleep_time_target"); facet.snoringAmount = propertiesObject.getDouble("total_snoring_episode_duration"); if (propertiesObject.has("resting_heart_rate")) { Object o = propertiesObject.get("resting_heart-rate"); if (o instanceof Number) facet.restingHeartRate = propertiesObject.getDouble("resting_heart_rate"); } facet.respirationRate = propertiesObject.has("average_respiration_rate") ? propertiesObject.getDouble("average_respiration_rate") : null; facet.timeToFallAsleep = propertiesObject.has("sleep_latency") ? propertiesObject.getDouble("sleep_latency") : null; facet.awayCount = (int) propertiesObject.getDouble("away_episode_count"); facet.totalAwayTime = propertiesObject.getDouble("stage_duration_A"); facet.totalSleepTime = propertiesObject.getDouble("stage_duration_S"); facet.totalWakeTime = propertiesObject.getDouble("stage_duration_W"); facet.totalTimeNoSignal = propertiesObject.getDouble("stage_duration_G"); facet.scoreBedExits = propertiesObject.getDouble("score_bed_exits"); facet.scoreSleepAmount = propertiesObject.getDouble("score_amount_of_sleep"); facet.scoreSnoring = propertiesObject.getDouble("score_snoring"); facet.scoreFallAsleepTime = propertiesObject.getDouble("score_sleep_latency"); facet.scoreSleepEfficiency = propertiesObject.getDouble("score_sleep_efficiency"); facet.scoreAwakenings = propertiesObject.getDouble("score_awakenings"); JSONArray sleepTags = sleepObject.getJSONArray("tags"); List<String> sleepTagsData = new ArrayList<String>(); for (int i = 0; i < sleepTags.size(); i++) { sleepTagsData.add(sleepTags.getString(i)); } facet.setSleepTags(sleepTagsData); JSONObject timeValueTracksObject = sleepObject.getJSONObject("time_value_tracks"); if (timeValueTracksObject.has("sleep_cycles")) { JSONObject sleep_cycles = timeValueTracksObject.getJSONObject("sleep_cycles"); if (sleep_cycles!=null) { JSONArray sleepCycles = sleep_cycles.getJSONArray("items"); List<Pair<Long, Double>> sleepCycleData = new ArrayList<Pair<Long, Double>>(); for (int i = 0; i < sleepCycles.size(); i++) { sleepCycleData.add(new Pair<Long, Double>((long)sleepCycles.getJSONArray(i).getDouble(0)*1000, sleepCycles.getJSONArray(i).getDouble(1))); } facet.setSleepCycles(sleepCycleData); } } if (timeValueTracksObject.has("heart_rate_curve")) { JSONObject heart_rate_curve = timeValueTracksObject.getJSONObject("heart_rate_curve"); if (heart_rate_curve!=null) { JSONArray heartRateCurve = heart_rate_curve.getJSONArray("items"); List<Pair<Long,Double>> heartRateCurveData = new ArrayList<Pair<Long,Double>>(); for (int i = 0; i < heartRateCurve.size(); i++) { heartRateCurveData.add(new Pair<Long, Double>((long)heartRateCurve.getJSONArray(i).getDouble(0)*1000, heartRateCurve.getJSONArray(i).getDouble(1))); } facet.setHeartRateCurve(heartRateCurveData); } } if (timeValueTracksObject.has("sleep_stages")) { JSONObject sleep_stages = timeValueTracksObject.getJSONObject("sleep_stages"); if (sleep_stages!=null) { JSONArray sleepStages = sleep_stages.getJSONArray("items"); List<Pair<Long,Integer>> sleepStagesData = new ArrayList<Pair<Long, Integer>>(); for (int i = 0 ; i < sleepStages.size(); i++) { sleepStagesData.add(new Pair<Long, Integer>((long)sleepStages.getJSONArray(i).getDouble(0)*1000, sleepStages.getJSONArray(i).getInt(1))); } facet.setSleepStages(sleepStagesData); } } if (timeValueTracksObject.has("snoring_episodes")) { JSONObject snoring_episodes = timeValueTracksObject.getJSONObject("snoring_episodes"); if (snoring_episodes!=null) { JSONArray snoringEpisodes = snoring_episodes.getJSONArray("items"); List<Pair<Long,Double>> snoringEpisodesData = new ArrayList<Pair<Long, Double>>(); for (int i = 0; i < snoringEpisodes.size(); i++) { snoringEpisodesData.add(new Pair<Long,Double>((long)snoringEpisodes.getJSONArray(i).getDouble(0)*1000, snoringEpisodes.getJSONArray(i).getDouble(1))); } facet.setSnoringEpisodes(snoringEpisodesData); } } return facet; } },updateInfo.apiKey.getId()); } private Double getLatestFacetTime(UpdateInfo updateInfo){ ApiKey apiKey = updateInfo.apiKey; String updateKeyName = "SleepAsAndroid.latestData"; String latestDataString = guestService.getApiKeyAttribute(apiKey, updateKeyName); return latestDataString == null ? null : Double.parseDouble(latestDataString); } private void updateLatestFacetTime(UpdateInfo updateInfo, double timestamp){ String updateKeyName = "SleepAsAndroid.latestData"; guestService.setApiKeyAttribute(updateInfo.apiKey, updateKeyName, Double.toString(timestamp)); } }