Java tutorial
/* * Copyright 2015 Open mHealth * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.openmhealth.shim.jawbone.mapper; import com.fasterxml.jackson.databind.JsonNode; import org.openmhealth.schema.domain.omh.DurationUnitValue; import org.openmhealth.schema.domain.omh.KcalUnitValue; import org.openmhealth.schema.domain.omh.LengthUnitValue; import org.openmhealth.schema.domain.omh.PhysicalActivity; import org.openmhealth.schema.domain.omh.PhysicalActivity.SelfReportedIntensity; import javax.annotation.Nullable; import java.math.BigDecimal; import java.time.OffsetDateTime; import java.time.ZoneId; import java.util.HashMap; import java.util.Map; import java.util.Optional; import static com.google.common.base.Preconditions.checkNotNull; import static java.lang.String.format; import static java.time.Instant.ofEpochSecond; import static java.time.OffsetDateTime.ofInstant; import static org.openmhealth.schema.domain.omh.DurationUnit.SECOND; import static org.openmhealth.schema.domain.omh.KcalUnit.KILOCALORIE; import static org.openmhealth.schema.domain.omh.LengthUnit.METER; import static org.openmhealth.schema.domain.omh.PhysicalActivity.SelfReportedIntensity.*; import static org.openmhealth.schema.domain.omh.TimeInterval.ofEndDateTimeAndDuration; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*; /** * A mapper from Jawbone UP API /workouts responses to {@link PhysicalActivity} objects. * * @author Emerson Farrugia * @author Danilo Bonilla * @author Chris Schaefbauer * @see <a href="https://jawbone.com/up/developer/endpoints/workouts">API documentation</a> */ public class JawbonePhysicalActivityDataPointMapper extends JawboneDataPointMapper<PhysicalActivity> { private Map<Integer, String> activityNameByWorkoutType = new HashMap<>(); public JawbonePhysicalActivityDataPointMapper() { // initialize activity names activityNameByWorkoutType.put(1, "walk"); activityNameByWorkoutType.put(2, "run"); activityNameByWorkoutType.put(3, "lift weights"); activityNameByWorkoutType.put(4, "cross train"); activityNameByWorkoutType.put(5, "nike training"); activityNameByWorkoutType.put(6, "yoga"); activityNameByWorkoutType.put(7, "pilates"); activityNameByWorkoutType.put(8, "body weight exercise"); activityNameByWorkoutType.put(9, "crossfit"); activityNameByWorkoutType.put(10, "p90x"); activityNameByWorkoutType.put(11, "zumba"); activityNameByWorkoutType.put(12, "trx"); activityNameByWorkoutType.put(13, "swim"); activityNameByWorkoutType.put(14, "bike"); activityNameByWorkoutType.put(15, "elliptical"); activityNameByWorkoutType.put(16, "bar method"); activityNameByWorkoutType.put(17, "kinect exercises"); activityNameByWorkoutType.put(18, "tennis"); activityNameByWorkoutType.put(19, "basketball"); activityNameByWorkoutType.put(20, "golf"); activityNameByWorkoutType.put(21, "soccer"); activityNameByWorkoutType.put(22, "ski snowboard"); activityNameByWorkoutType.put(23, "dance"); activityNameByWorkoutType.put(24, "hike"); activityNameByWorkoutType.put(25, "cross country skiing"); activityNameByWorkoutType.put(26, "stationary bike"); activityNameByWorkoutType.put(27, "cardio"); activityNameByWorkoutType.put(28, "game"); activityNameByWorkoutType.put(29, "other"); } @Override protected Optional<PhysicalActivity> getMeasure(JsonNode workoutNode) { checkNotNull(workoutNode); // assume that the title and workout type are optional since the documentation isn't clear Optional<String> title = asOptionalString(workoutNode, "title"); Optional<Integer> workoutType = asOptionalInteger(workoutNode, "sub_type"); String activityName = getActivityName(title.orElse(null), workoutType.orElse(null)); PhysicalActivity.Builder builder = new PhysicalActivity.Builder(activityName); asOptionalBigDecimal(workoutNode, "details.meters") .ifPresent(distance -> builder.setDistance(new LengthUnitValue(METER, distance))); Optional<Long> endTimestamp = asOptionalLong(workoutNode, "time_completed"); Optional<Long> durationInSec = asOptionalLong(workoutNode, "details.time"); Optional<ZoneId> timeZoneId = asOptionalZoneId(workoutNode, "details.tz"); if (endTimestamp.isPresent() && durationInSec.isPresent() && timeZoneId.isPresent()) { OffsetDateTime endDateTime = ofInstant(ofEpochSecond(endTimestamp.get()), getTimeZoneForTimestamp(workoutNode, endTimestamp.get())); builder.setEffectiveTimeFrame( ofEndDateTimeAndDuration(endDateTime, new DurationUnitValue(SECOND, durationInSec.get()))); } Optional<BigDecimal> totalCalories = asOptionalBigDecimal(workoutNode, "details.calories"); if (totalCalories.isPresent()) { asOptionalBigDecimal(workoutNode, "details.bmr_calories").ifPresent(bmrCalories -> { BigDecimal caloriesBurned = totalCalories.get().subtract(bmrCalories); builder.setCaloriesBurned(new KcalUnitValue(KILOCALORIE, caloriesBurned)); }); } asOptionalInteger(workoutNode, "details.intensity") .ifPresent(intensity -> builder.setReportedActivityIntensity(asSelfReportedIntensity(intensity))); return Optional.of(builder.build()); } /** * TODO confirm that we want to make titles trump types * * @param title the title of the workout, if any * @param workoutType the type of the workout, if specified * @return the name of the activity */ public String getActivityName(@Nullable String title, @Nullable Integer workoutType) { if (title != null) { return title; } if (workoutType == null) { return "workout"; } String description = activityNameByWorkoutType.get(workoutType); if (description.equals("other")) { return "workout"; } return description; } /** * @param intensityValue the workout intensity value in the payload * @return the corresponding {@link SelfReportedIntensity} */ public SelfReportedIntensity asSelfReportedIntensity(int intensityValue) { switch (intensityValue) { case 1: return LIGHT; case 2: case 3: return MODERATE; case 4: case 5: return VIGOROUS; default: throw new IllegalArgumentException(format("The intensity value '%d' isn't supported.", intensityValue)); } } @Override protected boolean isSensed(JsonNode workoutNode) { Optional<Integer> steps = asOptionalInteger(workoutNode, "details.steps"); // Jawbone API documentation states that steps is only included if the activity was sensed // by a Jawbone wearable device return steps.isPresent() && steps.get() > 0; } }