package org.fluxtream.connectors.up;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
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.impl.client.BasicResponseHandler;
import org.apache.log4j.Logger;
import org.fluxtream.core.TimezoneMap;
import org.fluxtream.core.connectors.ObjectType;
import org.fluxtream.core.connectors.annotations.Updater;
import org.fluxtream.core.connectors.location.LocationFacet;
import org.fluxtream.core.connectors.updaters.AbstractUpdater;
import org.fluxtream.core.connectors.updaters.AuthExpiredException;
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.domain.ChannelMapping;
import org.fluxtream.core.services.ApiDataService;
import org.fluxtream.core.services.JPADaoService;
import org.fluxtream.core.services.MetadataService;
import org.fluxtream.core.services.impl.BodyTrackHelper;
import org.fluxtream.core.utils.HttpUtils;
import org.fluxtream.core.utils.TimespanSegment;
import org.fluxtream.core.utils.UnexpectedHttpResponseCodeException;
import org.joda.time.DateTimeConstants;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
import org.joda.time.format.ISODateTimeFormat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.*;
/**
* User: candide
* Date: 26/01/14
* Time: 09:56
*/
@Component
@Updater(prettyName = "Jawbone_UP", value = 1999, objectTypes = {LocationFacet.class, JawboneUpMovesFacet.class,
JawboneUpSleepFacet.class, JawboneUpMealFacet.class,
JawboneUpServingFacet.class, JawboneUpWorkoutFacet.class},
defaultChannels = {"Jawbone_UP.intensity", "Jawbone_UP.sleep"},
deviceNickname = "Jawbone_UP",
deleteOrder= {1, 2, 4, 8, 32, 16}, bodytrackResponder = JawboneUpBodytrackResponder.class)
public class JawboneUpUpdater extends AbstractUpdater {
Logger logger = Logger.getLogger(JawboneUpUpdater.class);
static final Map<Integer,String> endpointDict = new java.util.Hashtable<Integer,String>();
static {
endpointDict.put(ObjectType.getObjectTypeValue(JawboneUpMovesFacet.class),
"https://jawbone.com/nudge/api/v.1.0/users/@me/moves");
endpointDict.put(ObjectType.getObjectTypeValue(JawboneUpSleepFacet.class),
"https://jawbone.com/nudge/api/v.1.0/users/@me/sleeps");
endpointDict.put(ObjectType.getObjectTypeValue(JawboneUpMealFacet.class),
"https://jawbone.com/nudge/api/v.1.0/users/@me/meals");
endpointDict.put(ObjectType.getObjectTypeValue(JawboneUpWorkoutFacet.class),
"https://jawbone.com/nudge/api/v.1.0/users/@me/workouts");
}
private static final String MOVES_LAST_SYNC_TIME = "lastSyncTime/moves";
private static final String SLEEPS_LAST_SYNC_TIME = "lastSyncTime/sleeps";
private static final String MEALS_LAST_SYNC_TIME = "lastSyncTime/meals";
private static final String WORKOUTS_LAST_SYNC_TIME = "lastSyncTime/workouts";
@Autowired
JPADaoService jpaDaoService;
@Autowired
MetadataService metadataService;
@Autowired
@Qualifier("AsyncWorker")
ThreadPoolTaskExecutor executor;
@Autowired
BodyTrackHelper bodyTrackHelper;
@Override
protected void updateConnectorDataHistory(final UpdateInfo updateInfo) throws Exception {
getUserXid(updateInfo);
guestService.removeApiKeyAttribute(updateInfo.apiKey.getId(), MOVES_LAST_SYNC_TIME);
guestService.removeApiKeyAttribute(updateInfo.apiKey.getId(), SLEEPS_LAST_SYNC_TIME);
guestService.removeApiKeyAttribute(updateInfo.apiKey.getId(), MEALS_LAST_SYNC_TIME);
guestService.removeApiKeyAttribute(updateInfo.apiKey.getId(), WORKOUTS_LAST_SYNC_TIME);
updateConnectorData(updateInfo);
}
@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 = "#fff";
channelStyle.timespanStyles.defaultStyle.borderColor = "#fff";
channelStyle.timespanStyles.defaultStyle.borderWidth = 2;
channelStyle.timespanStyles.defaultStyle.top = 0.0;
channelStyle.timespanStyles.defaultStyle.bottom = 1.0;
channelStyle.timespanStyles.values = new HashMap<String, BodyTrackHelper.TimespanStyle>();
BodyTrackHelper.TimespanStyle stylePart = new BodyTrackHelper.TimespanStyle();
stylePart.top = .0;
stylePart.bottom = 0.9;
stylePart.fillColor = "#1196ef";
stylePart.borderColor = "#1196ef";
channelStyle.timespanStyles.values.put("deep",stylePart);
stylePart = new BodyTrackHelper.TimespanStyle();
stylePart.top = .0;
stylePart.bottom = 0.6;
stylePart.fillColor = "#00d2ff";
stylePart.borderColor = "#00d2ff";
channelStyle.timespanStyles.values.put("light",stylePart);
stylePart = new BodyTrackHelper.TimespanStyle();
stylePart.top = .0;
stylePart.bottom = 0.1;
stylePart.fillColor = "#f87d04";
stylePart.borderColor = "#f87d04";
channelStyle.timespanStyles.values.put("wake",stylePart);
bodyTrackHelper.setBuiltinDefaultStyle(apiKey.getGuestId(), apiKey.getConnector().getName(), "sleep", channelStyle);
}
@Override
protected void updateConnectorData(final UpdateInfo updateInfo) throws Exception {
getUserXid(updateInfo);
updateInfo.setContext("accessToken", guestService.getApiKeyAttribute(updateInfo.apiKey, "accessToken"));
updateJawboneUpDataSince(updateInfo, getLastSyncTime(updateInfo, MOVES_LAST_SYNC_TIME), ObjectType.getObjectTypeValue(JawboneUpMovesFacet.class));
guestService.setApiKeyAttribute(updateInfo.apiKey, MOVES_LAST_SYNC_TIME, String.valueOf(System.currentTimeMillis()));
updateJawboneUpDataSince(updateInfo, getLastSyncTime(updateInfo, SLEEPS_LAST_SYNC_TIME), ObjectType.getObjectTypeValue(JawboneUpSleepFacet.class));
guestService.setApiKeyAttribute(updateInfo.apiKey, SLEEPS_LAST_SYNC_TIME, String.valueOf(System.currentTimeMillis()));
updateJawboneUpDataSince(updateInfo, getLastSyncTime(updateInfo, MEALS_LAST_SYNC_TIME), ObjectType.getObjectTypeValue(JawboneUpMealFacet.class));
guestService.setApiKeyAttribute(updateInfo.apiKey, MEALS_LAST_SYNC_TIME, String.valueOf(System.currentTimeMillis()));
updateJawboneUpDataSince(updateInfo, getLastSyncTime(updateInfo, WORKOUTS_LAST_SYNC_TIME), ObjectType.getObjectTypeValue(JawboneUpWorkoutFacet.class));
guestService.setApiKeyAttribute(updateInfo.apiKey, WORKOUTS_LAST_SYNC_TIME, String.valueOf(System.currentTimeMillis()));
// not updating moods because the API doesn't allow getting updated_after values...
}
/**
* Retrieve Jawbone's unique identifier for the user, so that we can check that data removal requests
* have been properly executed
* @param updateInfo
* @throws Exception
*/
private void getUserXid(UpdateInfo updateInfo) throws Exception {
String user_xid = guestService.getApiKeyAttribute(updateInfo.apiKey, "user_xid");
if (user_xid == null) {
String meJson = callJawboneAPI(updateInfo, "https://jawbone.com/nudge/api/v.1.1/users/@me");
JSONObject jsonObject = JSONObject.fromObject(meJson);
JSONObject meJsonData = jsonObject.getJSONObject("data");
user_xid = meJsonData.getString("xid");
guestService.setApiKeyAttribute(updateInfo.apiKey, "user_xid", user_xid);
}
}
private long getLastSyncTime(final UpdateInfo updateInfo, final String lastSyncTimeAttKey) {
long lastSyncTime = getBeginningOfTime();
final String lastSyncTimeAtt = guestService.getApiKeyAttribute(updateInfo.apiKey, lastSyncTimeAttKey);
if (lastSyncTimeAtt!=null)
lastSyncTime = Long.valueOf(lastSyncTimeAtt);
return lastSyncTime;
}
private long getBeginningOfTime() {
return ISODateTimeFormat.basicDate().withZoneUTC().parseMillis("20100101");
}
private void updateJawboneUpDataSince(final UpdateInfo updateInfo, long lastSyncTime, int objectTypeValue) throws Exception {
final HttpClient client = env.getHttpClient();
try {
// get moves since lastSyncTime
String endpoint = endpointDict.get(objectTypeValue);
String url = getBeginningOfTime()==lastSyncTime
? endpoint + "?start_time=" + getBeginningOfTime()/1000
: endpoint + "?start_time=" + getBeginningOfTime()/1000 + "&updated_after=" + lastSyncTime/1000;
final String upJson = callJawboneAPI(updateInfo, url);
createOrUpdateFacets(updateInfo, upJson, objectTypeValue);
}
finally {
client.getConnectionManager().shutdown();
}
}
private void createOrUpdateMovesFacet(final JSONObject jsonObject, final UpdateInfo updateInfo) throws Exception {
final String xid = jsonObject.getString("xid");
final ApiDataService.FacetQuery facetQuery = new ApiDataService.FacetQuery("e.apiKeyId=? AND e.xid=?",
updateInfo.apiKey.getId(), xid);
final ApiDataService.FacetModifier<JawboneUpMovesFacet> facetModifier = new ApiDataService.FacetModifier<JawboneUpMovesFacet>() {
@Override
public JawboneUpMovesFacet createOrModify(JawboneUpMovesFacet origFacet, final Long apiKeyId) {
try {
JawboneUpMovesFacet facet = origFacet;
if (facet==null) {
facet = new JawboneUpMovesFacet(updateInfo.apiKey.getId());
facet.xid = xid;
extractCommonFacetData(facet, updateInfo);
}
facet.time_created = jsonObject.getLong("time_created");
facet.time_completed = jsonObject.getLong("time_completed");
facet.time_updated = jsonObject.getLong("time_updated");
facet.start = facet.time_created*1000;
facet.end = facet.time_completed*1000;
if (jsonObject.has("title"))
facet.title = jsonObject.getString("title");
if (jsonObject.has("snapshot_image")) {
facet.snapshot_image = jsonObject.getString("snapshot_image");
cacheImage(updateInfo, facet.snapshot_image);
}
String dateString = jsonObject.getString("date");
JSONObject details = jsonObject.getJSONObject("details");
facet.tz = details.getString("tz");
facet.tzs = details.getJSONArray("tzs").toString();
TimeZone defaultTimeZone = TimeZone.getTimeZone(facet.tz);
TimezoneMap tzMap = getTimeZoneMap(facet.tzs);
final LocalDate localDate = ISODateTimeFormat.basicDate().withZoneUTC().parseLocalDate(dateString);
facet.date = ISODateTimeFormat.date().withZoneUTC().print(localDate);
if (details.has("distance"))
facet.distance = details.getInt("distance");
if (details.has("km"))
facet.km = details.getDouble("km");
if (details.has("steps"))
facet.steps = details.getInt("steps");
if (details.has("active_time"))
facet.active_time = details.getInt("active_time");
if (details.has("longest_active"))
facet.longest_active = details.getInt("longest_active");
if (details.has("inactive_time"))
facet.inactive_time = details.getInt("inactive_time");
if (details.has("longest_idle"))
facet.longest_idle = details.getInt("longest_idle");
if (details.has("calories"))
facet.calories = details.getDouble("calories");
if (details.has("bmr"))
facet.bmr = details.getDouble("bmr");
if (details.has("bmr_day"))
facet.bmr_day = details.getDouble("bmr_day");
if (details.has("bg_calories"))
facet.bg_calories = details.getDouble("bg_calories");
if (details.has("wo_calories"))
facet.wo_calories = details.getDouble("wo_calories");
if (details.has("wo_time"))
facet.wo_time = details.getInt("wo_time");
if (details.has("wo_active_time"))
facet.wo_active_time = details.getInt("wo_active_time");
if (details.has("wo_count"))
facet.wo_count = details.getInt("wo_count");
if (details.has("wo_longest"))
facet.wo_longest = details.getInt("wo_longest");
if (details.has("hourly_totals")) {
final JSONObject hourlyTotals = details.getJSONObject("hourly_totals");
JSONArray names = hourlyTotals.names();
for (int i=0; i<names.size(); i++) {
String hour_of_day = names.getString(i);
long start = getTimeOfDay(hour_of_day, tzMap, defaultTimeZone);
JawboneUpMovesHourlyTotals totals = new JawboneUpMovesHourlyTotals();
totals.start = start;
JSONObject totalsJson = hourlyTotals.getJSONObject(hour_of_day);
if (totalsJson.has("distance"))
totals.distance = totalsJson.getInt("distance");
if (totalsJson.has("calories"))
totals.calories = totalsJson.getDouble("calories");
if (totalsJson.has("steps"))
totals.steps = totalsJson.getInt("steps");
if (totalsJson.has("active_time"))
totals.active_time = totalsJson.getInt("active_time");
if (totalsJson.has("inactive_time"))
totals.inactive_time = totalsJson.getInt("inactive_time");
if (totalsJson.has("longest_active_time"))
totals.longest_active_time = totalsJson.getInt("longest_active_time");
if (totalsJson.has("longest_idle_time"))
totals.longest_idle_time = totalsJson.getInt("longest_idle_time");
facet.addHourlyTotals(totals);
}
}
try {
String intensityJSON = callJawboneAPI(updateInfo, String.format("https://jawbone.com/nudge/api/moves/%s/snapshot", xid));
JSONObject intensity = JSONObject.fromObject(intensityJSON);
JSONArray intensityData = intensity.getJSONArray("data");
facet.intensityStorage = intensityData.toString();
} catch (Throwable t) {
logger.warn("could not import Jawbone UP moves intensity records: " + t.getMessage());
}
return facet;
} catch (Throwable t) {
logger.warn("could not import a Jawbone UP moves record: " + t.getMessage());
t.printStackTrace();
return origFacet;
}
}
};
final JawboneUpMovesFacet newFacet = apiDataService.createOrReadModifyWrite(JawboneUpMovesFacet.class, facetQuery, facetModifier, updateInfo.apiKey.getId());
if (newFacet!=null) {
List<AbstractFacet> newFacets = new ArrayList<AbstractFacet>();
newFacets.add(newFacet);
bodyTrackStorageService.storeApiData(updateInfo.apiKey, newFacets);
}
}
private long getTimeOfDay(final String hour_of_day, final TimezoneMap tzMap, final TimeZone defaultTimeZone) {
final String dateString = hour_of_day.substring(0, 8);
final int millis_since_midnight_offset = Integer.valueOf(hour_of_day.substring(8))*DateTimeConstants.MILLIS_PER_HOUR;
final TreeSet<TimespanSegment<DateTimeZone>> spans = tzMap.spans;
for (TimespanSegment<DateTimeZone> span : spans) {
long day_gmt = ISODateTimeFormat.basicDate().withZone(span.getValue()).parseDateTime(dateString).getMillis();
long possibleTime = day_gmt + millis_since_midnight_offset;
if (span.getStart()<possibleTime&&span.getEnd()>possibleTime)
return possibleTime;
}
long day_gmt = ISODateTimeFormat.basicDate().withZone(DateTimeZone.forTimeZone(defaultTimeZone)).parseDateTime(dateString).getMillis();
return day_gmt + millis_since_midnight_offset;
}
/**
* Retrieve the snapshot_image and cache it in the user's resources directory
* @param updateInfo
* @param uri
*/
private void cacheImage(final UpdateInfo updateInfo, final String uri) {
final String devKvsLocation = env.get("btdatastore.db.location");
executor.execute(new Runnable() {
public void run() {
try {
String url = "https://jawbone.com" + uri;
HttpClient client = env.getHttpClient();
HttpGet request = new HttpGet(url);
HttpResponse response = null;
response = client.execute(request);
BufferedInputStream rd = new BufferedInputStream(
response.getEntity().getContent());
File f = new File(new StringBuilder(devKvsLocation).append(File.separator)
.append(updateInfo.getGuestId())
.append(File.separator)
.append(connector().prettyName())
.append(File.separator)
.append(updateInfo.apiKey.getId())
.append(uri).toString());
f.getParentFile().mkdirs();
IOUtils.copy(rd, new FileOutputStream(f));
}
catch (IOException e) {
e.printStackTrace();
}
}
});
}
TimezoneMap getTimeZoneMap(final String tzs) {
TimezoneMap map = new TimezoneMap();
if (tzs==null) return map;
JSONArray timezoneArray = JSONArray.fromObject(tzs);
for (int i=0; i<timezoneArray.size(); i++) {
JSONArray timezoneInfo = timezoneArray.getJSONArray(i);
long start = timezoneInfo.getLong(0)*1000;
// allow some padding for the last element in the map
long end = start + DateTimeConstants.MILLIS_PER_DAY;
if (timezoneArray.size()>=i+2)
end = timezoneArray.getJSONArray(i+1).getLong(0)*1000;
String ID = timezoneInfo.getString(1);
map.add(start, end, DateTimeZone.forID(ID));
}
//add some padding for the first element in the map
if (map.spans.size()>0) {
final TimespanSegment<DateTimeZone> first = map.spans.first();
first.setStart(first.getStart()-DateTimeConstants.MILLIS_PER_DAY);
}
return map;
}
private void createOrUpdateFacets(final UpdateInfo updateInfo, final String upJson, int objectTypeValue) throws Exception {
JSONObject jsonObject = JSONObject.fromObject(upJson);
JSONObject data = jsonObject.getJSONObject("data");
JSONArray items = data.getJSONArray("items");
for (int i=0; i<items.size(); i++) {
JSONObject json = items.getJSONObject(i);
if (objectTypeValue==ObjectType.getObjectTypeValue(JawboneUpSleepFacet.class))
createOrUpdateSleepFacet(json, updateInfo);
else if (objectTypeValue==ObjectType.getObjectTypeValue(JawboneUpMovesFacet.class))
createOrUpdateMovesFacet(json, updateInfo);
else if (objectTypeValue==ObjectType.getObjectTypeValue(JawboneUpMealFacet.class))
createOrUpdateMealFacet(json, updateInfo);
else if (objectTypeValue==ObjectType.getObjectTypeValue(JawboneUpWorkoutFacet.class))
createOrUpdateWorkoutFacet(json, updateInfo);
}
if (data.has("links")) {
JSONObject links = data.getJSONObject("links");
if (links.has("next")) {
String next = links.getString("next");
getNextBatchOfFacets(updateInfo, next, objectTypeValue);
}
}
}
private void getNextBatchOfFacets(final UpdateInfo updateInfo, String url, final int objectTypeValue) throws Exception {
url = url.replaceAll("v.None", "v.1.0");
logger.info(String.format("getting next batch of jawbone facets (objectType value: %s): %s", objectTypeValue, url));
final String jawboneAPIJson = callJawboneAPI(updateInfo, "https://jawbone.com" + url);
createOrUpdateFacets(updateInfo, jawboneAPIJson, objectTypeValue);
}
private void createOrUpdateSleepFacet(final JSONObject jsonObject, final UpdateInfo updateInfo) throws Exception {
final String xid = jsonObject.getString("xid");
final ApiDataService.FacetQuery facetQuery = new ApiDataService.FacetQuery("e.apiKeyId=? AND e.xid=?",
updateInfo.apiKey.getId(), xid);
final ApiDataService.FacetModifier<JawboneUpSleepFacet> facetModifier = new ApiDataService.FacetModifier<JawboneUpSleepFacet>() {
@Override
public JawboneUpSleepFacet createOrModify(JawboneUpSleepFacet origFacet, final Long apiKeyId) {
try {
JawboneUpSleepFacet facet = origFacet;
if (facet==null) {
facet = new JawboneUpSleepFacet(updateInfo.apiKey.getId());
facet.xid = xid;
extractCommonFacetData(facet, updateInfo);
}
facet.time_created = jsonObject.getLong("time_created");
facet.time_completed = jsonObject.getLong("time_completed");
facet.start = facet.time_created*1000;
facet.end = facet.time_completed*1000;
JSONObject details = jsonObject.getJSONObject("details");
String dateString = jsonObject.getString("date");
final LocalDate localDate = ISODateTimeFormat.basicDate().withZoneUTC().parseLocalDate(dateString);
facet.date = ISODateTimeFormat.date().withZoneUTC().print(localDate);
facet.tz = details.getString("tz");
if (jsonObject.has("title"))
facet.title = jsonObject.getString("title");
if (jsonObject.has("snapshot_image")) {
facet.snapshot_image = jsonObject.getString("snapshot_image");
cacheImage(updateInfo, facet.snapshot_image);
}
extractGPSData(facet, jsonObject, updateInfo, xid);
if (details.has("smart_alarm_fire"))
facet.smart_alarm_fire = details.getLong("smart_alarm_fire");
if (details.has("awake_time"))
facet.awake_time = details.getLong("awake_time");
if (details.has("asleep_time"))
facet.asleep_time = details.getLong("asleep_time");
if (details.has("awakenings"))
facet.awakenings = details.getInt("awakenings");
if (details.has("rem"))
facet.rem = details.getInt("rem");
if (details.has("light"))
facet.light = details.getInt("light");
if (details.has("deep"))
facet.deep = details.getInt("deep");
if (details.has("awake"))
facet.awake = details.getInt("awake");
if (details.has("duration"))
facet.duration = details.getInt("duration");
if (details.has("quality"))
facet.quality = details.getInt("quality");
try {
String phasesJSON = callJawboneAPI(updateInfo, String.format("https://jawbone.com/nudge/api/sleeps/%s/snapshot", xid));
JSONObject phases = JSONObject.fromObject(phasesJSON);
JSONArray phasesData = phases.getJSONArray("data");
facet.phasesStorage = phasesData.toString();
} catch (Throwable t) {
logger.warn("could not import Jawbone UP sleep phases records: " + t.getMessage());
}
return facet;
} catch (Throwable t) {
logger.warn("could not import a Jawbone UP sleep record: " + t.getMessage());
t.printStackTrace();
return origFacet;
}
}
};
final JawboneUpSleepFacet newFacet = apiDataService.createOrReadModifyWrite(JawboneUpSleepFacet.class, facetQuery, facetModifier, updateInfo.apiKey.getId());
if (newFacet!=null) {
List<AbstractFacet> newFacets = new ArrayList<AbstractFacet>();
newFacets.add(newFacet);
bodyTrackStorageService.storeApiData(updateInfo.apiKey, newFacets);
}
}
public static void main(final String[] args) {
String place_lat_string = "";
System.out.println("not blank? " + StringUtils.isNotBlank(place_lat_string));
System.out.println("numeric? " + StringUtils.isNumeric(place_lat_string));
}
private void extractGPSData(final JawboneUpGeoFacet facet, final JSONObject jsonObject, final UpdateInfo updateInfo, final String xid) {
if (jsonObject.has("place_lat")&&
jsonObject.has("place_lon")&&
!(jsonObject.getString("place_lat").equals(""))&&
!(jsonObject.getString("place_lon").equals("")))
{
double place_lat, place_lon;
try {
place_lat = jsonObject.getDouble("place_lat");
place_lon = jsonObject.getDouble("place_lon");
} catch (Throwable t) {
System.out.println("Couldn't get latitude/longitude from strings: " + jsonObject.getString("place_lat") + "/" + jsonObject.getString("place_lon"));
return;
}
facet.place_lat = place_lat;
facet.place_lon = place_lon;
if (jsonObject.has("place_name"))
facet.place_name = jsonObject.getString("place_name");
LocationFacet locationFacet = new LocationFacet(updateInfo.apiKey.getId());
if (jsonObject.has("place_acc")&&StringUtils.isNotEmpty("place_acc")) {
String placeAccuracy = jsonObject.getString("place_acc");
try {
int place_acc = Integer.valueOf(placeAccuracy);
locationFacet.accuracy = jsonObject.getInt("place_acc");
facet.place_acc = place_acc;
} catch(Throwable t) {
locationFacet.accuracy = -1;
facet.place_acc = -1;
}
} else {
locationFacet.accuracy = -1;
facet.place_acc = -1;
}
locationFacet.latitude = (float) place_lat;
locationFacet.longitude = (float) place_lon;
locationFacet.timestampMs = facet.start;
locationFacet.start = locationFacet.timestampMs;
locationFacet.end = locationFacet.timestampMs;
locationFacet.source = LocationFacet.Source.JAWBONE_UP;
locationFacet.apiKeyId = updateInfo.apiKey.getId();
locationFacet.api = connector().value();
locationFacet.uri = xid;
apiDataService.addGuestLocation(updateInfo.getGuestId(), locationFacet);
}
}
private void createOrUpdateMealFacet(final JSONObject jsonObject, final UpdateInfo updateInfo) throws Exception {
final String xid = jsonObject.getString("xid");
final ApiDataService.FacetQuery facetQuery = new ApiDataService.FacetQuery("e.apiKeyId=? AND e.xid=?",
updateInfo.apiKey.getId(), xid);
final ApiDataService.FacetModifier<JawboneUpMealFacet> facetModifier = new ApiDataService.FacetModifier<JawboneUpMealFacet>() {
@Override
public JawboneUpMealFacet createOrModify(JawboneUpMealFacet origFacet, final Long apiKeyId) {
try {
JawboneUpMealFacet facet = origFacet;
if (facet==null) {
facet = new JawboneUpMealFacet(updateInfo.apiKey.getId());
facet.xid = xid;
extractCommonFacetData(facet, updateInfo);
}
facet.time_created = jsonObject.getLong("time_created");
facet.time_updated = jsonObject.getLong("time_updated");
facet.time_completed = jsonObject.getLong("time_completed");
facet.start = facet.time_created*1000;
facet.end = facet.time_completed*1000;
JSONObject details = jsonObject.getJSONObject("details");
String dateString = jsonObject.getString("date");
final LocalDate localDate = ISODateTimeFormat.basicDate().withZoneUTC().parseLocalDate(dateString);
facet.date = ISODateTimeFormat.date().withZoneUTC().print(localDate);
facet.tz = details.getString("tz");
facet.mealDetails = details.toString();
extractGPSData(facet, jsonObject, updateInfo, xid);
// now fetch the servings for this meal...
final String servingsJSONStr = callJawboneAPI(updateInfo, "https://jawbone.com/nudge/api/v.1.0/meals/" + xid);
JSONObject servingsJSON = JSONObject.fromObject(servingsJSONStr);
JSONObject data = servingsJSON.getJSONObject("data");
JSONObject itemsObject = data.getJSONObject("items");
JSONArray items = itemsObject.getJSONArray("items");
List<JawboneUpServingFacet> servingFacets = new ArrayList<JawboneUpServingFacet>();
for (int i=0; i<items.size(); i++) {
JSONObject servingJSON = items.getJSONObject(i);
// pass in start- and end- times of the parent meal facet
final JawboneUpServingFacet servingFacet = createOrUpdateServingFacet(servingJSON, updateInfo, facet);
servingFacets.add(servingFacet);
}
facet.setServings(servingFacets);
return facet;
} catch (Throwable t) {
logger.warn("could not import a Jawbone UP meal record: " + t.getMessage());
t.printStackTrace();
return origFacet;
}
}
};
apiDataService.createOrReadModifyWrite(JawboneUpMealFacet.class, facetQuery, facetModifier, updateInfo.apiKey.getId());
}
private JawboneUpServingFacet createOrUpdateServingFacet(final JSONObject jsonObject, final UpdateInfo updateInfo, final JawboneUpMealFacet meal) throws Exception {
final String xid = jsonObject.getString("xid");
final ApiDataService.FacetQuery facetQuery = new ApiDataService.FacetQuery("e.apiKeyId=? AND e.xid=?",
updateInfo.apiKey.getId(), xid);
final ApiDataService.FacetModifier<JawboneUpServingFacet> facetModifier = new ApiDataService.FacetModifier<JawboneUpServingFacet>() {
@Override
public JawboneUpServingFacet createOrModify(JawboneUpServingFacet origFacet, final Long apiKeyId) {
try {
JawboneUpServingFacet facet = origFacet;
if (facet==null) {
facet = new JawboneUpServingFacet(updateInfo.apiKey.getId());
facet.xid = xid;
extractCommonFacetData(facet, updateInfo);
}
facet.date = meal.date;
facet.tz = meal.tz;
facet.start = meal.start;
facet.end = meal.end;
if (jsonObject.has("image")&&StringUtils.isNotEmpty(jsonObject.getString("image"))) {
facet.image = jsonObject.getString("image");
cacheImage(updateInfo, facet.image);
}
facet.servingDetails = jsonObject.toString();
facet.meal = meal;
return facet;
} catch (Throwable t) {
logger.warn("could not import a Jawbone UP meal record: " + t.getMessage());
t.printStackTrace();
return origFacet;
}
}
};
final JawboneUpServingFacet newFacet = apiDataService.createOrReadModifyWrite(JawboneUpServingFacet.class, facetQuery, facetModifier, updateInfo.apiKey.getId());
return newFacet;
}
private void createOrUpdateWorkoutFacet(final JSONObject jsonObject, final UpdateInfo updateInfo) throws Exception {
final String xid = jsonObject.getString("xid");
final ApiDataService.FacetQuery facetQuery = new ApiDataService.FacetQuery("e.apiKeyId=? AND e.xid=?",
updateInfo.apiKey.getId(), xid);
final ApiDataService.FacetModifier<JawboneUpWorkoutFacet> facetModifier = new ApiDataService.FacetModifier<JawboneUpWorkoutFacet>() {
@Override
public JawboneUpWorkoutFacet createOrModify(JawboneUpWorkoutFacet origFacet, final Long apiKeyId) {
try {
JawboneUpWorkoutFacet facet = origFacet;
if (facet==null) {
facet = new JawboneUpWorkoutFacet(updateInfo.apiKey.getId());
facet.xid = xid;
extractCommonFacetData(facet, updateInfo);
}
facet.time_created = jsonObject.getLong("time_created");
facet.time_updated = jsonObject.getLong("time_updated");
facet.time_completed = jsonObject.getLong("time_completed");
facet.start = facet.time_created*1000;
facet.end = facet.time_completed*1000;
String dateString = jsonObject.getString("date");
final LocalDate localDate = ISODateTimeFormat.basicDate().withZoneUTC().parseLocalDate(dateString);
facet.date = ISODateTimeFormat.date().withZoneUTC().print(localDate);
JSONObject details = jsonObject.getJSONObject("details");
facet.tz = details.getString("tz");
facet.sub_type = jsonObject.getInt("sub_type");
facet.workoutDetails = details.toString();
if (details.has("steps"))
facet.steps = details.getInt("steps");
if (details.has("time"))
facet.duration = details.getInt("time");
if (details.has("bg_active_time"))
facet.bg_active_time = details.getInt("bg_active_time");
if (details.has("meters"))
facet.meters = details.getDouble("meters");
if (details.has("km"))
facet.km = details.getDouble("km");
if (details.has("intensity"))
facet.intensity = details.getInt("intensity");
if (details.has("calories"))
facet.calories = details.getDouble("calories");
if (details.has("bmr"))
facet.bmr = details.getDouble("bmr");
if (details.has("bg_calories"))
facet.bg_calories = details.getDouble("bg_calories");
if (details.has("bmr_calories"))
facet.bmr_calories = details.getDouble("bmr_calories");
if (jsonObject.has("image")&&!jsonObject.getString("image").equals("")) {
facet.image = jsonObject.getString("image");
cacheImage(updateInfo, facet.image);
}
if (jsonObject.has("image")&&!jsonObject.getString("snapshot_image").equals("")) {
facet.snapshot_image = jsonObject.getString("snapshot_image");
cacheImage(updateInfo, facet.snapshot_image);
}
if (jsonObject.has("route")&&!jsonObject.getString("route").equals("")) {
facet.route = jsonObject.getString("route");
cacheImage(updateInfo, facet.route);
}
extractGPSData(facet, jsonObject, updateInfo, xid);
return facet;
} catch (Throwable t) {
logger.warn("could not import a Jawbone UP workout record: " + t.getMessage());
t.printStackTrace();
return origFacet;
}
}
};
apiDataService.createOrReadModifyWrite(JawboneUpWorkoutFacet.class, facetQuery, facetModifier, updateInfo.apiKey.getId());
}
private String callJawboneAPI(final UpdateInfo updateInfo, final String url) throws Exception {
final HttpClient client = env.getHttpClient();
final long then = System.currentTimeMillis();
try {
long tokenExpires = Long.valueOf(guestService.getApiKeyAttribute(updateInfo.apiKey, "tokenExpires"));
if (tokenExpires<System.currentTimeMillis())
refreshToken(updateInfo);
HttpGet get = new HttpGet(url);
get.setHeader("Authorization", "Bearer " + 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 {
handleErrors(statusCode, response, "Could not update Jawbone Up Moves Data");
}
}
finally {
client.getConnectionManager().shutdown();
}
throw new RuntimeException("Error calling Jawbone API: this statement should have never been reached");
}
public String refreshToken(ApiKey apiKey) throws IOException, UnexpectedHttpResponseCodeException, UpdateFailedException {
String refreshToken = guestService.getApiKeyAttribute(apiKey, "refreshToken");
Map<String,String> parameters = new HashMap<String,String>();
parameters.put("grant_type", "refresh_token");
parameters.put("refresh_token", refreshToken);
parameters.put("client_id", guestService.getApiKeyAttribute(apiKey, "jawboneUp.client.id"));
parameters.put("client_secret", guestService.getApiKeyAttribute(apiKey, "jawboneUp.client.secret"));
final String json = HttpUtils.fetch("https://jawbone.com/auth/oauth2/token", parameters);
JSONObject token = JSONObject.fromObject(json);
if (!token.has("access_token")) {
final String message = "Couldn't renew access token (no \"access_token\" field in JSON response)";
throw new UpdateFailedException(message, new Exception(), true, ApiKey.PermanentFailReason.unknownReason(message));
}
final String accessToken = token.getString("access_token");
// store the new secret
guestService.setApiKeyAttribute(apiKey,
"jawboneUp.client.secret", env.get("jawboneUp.client.secret"));
guestService.setApiKeyAttribute(apiKey,
"accessToken", accessToken);
long expiresIn = token.getLong("expires_in");
guestService.setApiKeyAttribute(apiKey,
"tokenExpires", String.valueOf(new BigInteger(String.valueOf(System.currentTimeMillis())).
add(new BigInteger(String.valueOf(expiresIn*1000)))));
guestService.setApiKeyAttribute(apiKey,
"refreshToken", token.getString("refresh_token"));
return accessToken;
}
private void refreshToken(UpdateInfo updateInfo) throws IOException, UnexpectedHttpResponseCodeException, UpdateFailedException {
String accessToken = refreshToken(updateInfo.apiKey);
updateInfo.setContext("accessToken", accessToken);
}
private void handleErrors(final int statusCode, final HttpResponse response, final String message) throws Exception {
// try to extract more information from the response
try {
ResponseHandler<String> responseHandler = new BasicResponseHandler();
String content = responseHandler.handleResponse(response);
JSONObject errorJson = JSONObject.fromObject(content);
if (errorJson.has("meta")) {
JSONObject meta = errorJson.getJSONObject("meta");
if (meta.has("error_type")) {
String details = meta.has("error_detail") ? meta.getString("error_details") : "Unknown Error (no details provided)";
throw new UpdateFailedException(message + " - " + details, true, ApiKey.PermanentFailReason.unknownReason(details));
}
}
} catch (Throwable t) {
// just ignore any potential problems here
}
if (statusCode==401)
throw new AuthExpiredException();
if (statusCode>=400&&statusCode<500) {
throw new UpdateFailedException("Unexpected response code: " + statusCode, new Exception(), true,
ApiKey.PermanentFailReason.clientError(statusCode, message));
} else
throw new UpdateFailedException("Unexpected response code: " + statusCode);
}
}