org.openmhealth.shim.withings.mapper.WithingsIntradayCaloriesBurnedDataPointMapper.java Source code

Java tutorial

Introduction

Here is the source code for org.openmhealth.shim.withings.mapper.WithingsIntradayCaloriesBurnedDataPointMapper.java

Source

/*
 * 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.withings.mapper;

import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.openmhealth.schema.domain.omh.*;

import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.util.*;

import static com.google.common.base.Preconditions.checkNotNull;
import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*;

/**
 * A mapper from Withings Intraday Activity endpoint responses (/measure?action=getactivity) to {@link CaloriesBurned}
 * objects.
 * <p>
 * <p>This mapper handles responses from an API request that requires special permissions from Withings. This special
 * activation can be requested from their <a href="http://oauth.withings
 * .com/api/doc#api-Measure-get_intraday_measure">API
 * Documentation website</a></p>
 *
 * @author Chris Schaefbauer
 * @see <a href="http://oauth.withings.com/api/doc#api-Measure-get_intraday_measure">Intrday Activity Measures API
 * documentation</a>
 */
public class WithingsIntradayCaloriesBurnedDataPointMapper extends WithingsDataPointMapper<CaloriesBurned> {

    /**
     * Maps JSON response nodes from the intraday activities endpoint (measure?action=getintradayactivity) in the
     * Withings API into a list of {@link CaloriesBurned} {@link DataPoint} objects.
     *
     * @param responseNodes a list of a single JSON node containing the entire response from the intraday activities
     * endpoint
     * @return a list of DataPoint objects of type {@link CaloriesBurned} with the appropriate values mapped from the
     * input
     * JSON
     */
    @Override
    public List<DataPoint<CaloriesBurned>> asDataPoints(List<JsonNode> responseNodes) {

        checkNotNull(responseNodes);
        checkNotNull(responseNodes.size() == 1, "A single response node is allowed per call.");

        JsonNode bodyNode = asRequiredNode(responseNodes.get(0), "body");
        JsonNode seriesNode = asRequiredNode(bodyNode, "series");

        Iterator<Map.Entry<String, JsonNode>> fieldsIterator = seriesNode.fields();
        Map<Long, JsonNode> nodesWithCalories = getNodesWithCalories(fieldsIterator);
        List<Long> startDateTimesInUnixEpochSeconds = Lists.newArrayList(nodesWithCalories.keySet());

        //ensure the datapoints are in order of passing time (data points that are earlier in time come before data
        // points that are later)
        Collections.sort(startDateTimesInUnixEpochSeconds);
        ArrayList<DataPoint<CaloriesBurned>> dataPoints = Lists.newArrayList();
        for (Long startDateTime : startDateTimesInUnixEpochSeconds) {
            asDataPoint(nodesWithCalories.get(startDateTime), startDateTime).ifPresent(dataPoints::add);
        }

        return dataPoints;

    }

    /**
     * Maps an individual list node from the array in the Withings activity measure endpoint response into a {@link
     * CaloriesBurned} data point.
     *
     * @param nodeWithCalorie activity node from the array "activites" contained in the "body" of the endpoint response
     * that has a calories field
     * @return a {@link DataPoint} object containing a {@link CaloriesBurned} measure with the appropriate values from
     * the JSON node parameter, wrapped as an {@link Optional}
     */
    private Optional<DataPoint<CaloriesBurned>> asDataPoint(JsonNode nodeWithCalorie,
            Long startDateTimeInUnixEpochSeconds) {

        Long caloriesBurnedValue = asRequiredLong(nodeWithCalorie, "calories");
        CaloriesBurned.Builder caloriesBurnedBuilder = new CaloriesBurned.Builder(
                new KcalUnitValue(KcalUnit.KILOCALORIE, caloriesBurnedValue));

        Optional<Long> duration = asOptionalLong(nodeWithCalorie, "duration");
        if (duration.isPresent()) {
            OffsetDateTime offsetDateTime = OffsetDateTime
                    .ofInstant(Instant.ofEpochSecond(startDateTimeInUnixEpochSeconds), ZoneId.of("Z"));
            caloriesBurnedBuilder.setEffectiveTimeFrame(TimeInterval.ofStartDateTimeAndDuration(offsetDateTime,
                    new DurationUnitValue(DurationUnit.SECOND, duration.get())));
        }

        Optional<String> userComment = asOptionalString(nodeWithCalorie, "comment");
        if (userComment.isPresent()) {
            caloriesBurnedBuilder.setUserNotes(userComment.get());
        }

        CaloriesBurned calorieBurned = caloriesBurnedBuilder.build();
        return Optional.of(newDataPoint(calorieBurned, null, true, null));

    }

    /**
     * Creates a hashmap that contains only the entries from the intraday activities dictionary that have calories
     * burned counts.
     *
     * @param fieldsIterator an iterator of map entries containing the key-value pairs related to each intraday
     * activity event
     * @return a hashmap with keys as the start datetime (in unix epoch seconds) of each activity event, and values as
     * the information related to the activity event starting at the key datetime
     */
    private Map<Long, JsonNode> getNodesWithCalories(Iterator<Map.Entry<String, JsonNode>> fieldsIterator) {

        HashMap<Long, JsonNode> nodesWithCalories = Maps.newHashMap();
        fieldsIterator.forEachRemaining(n -> addNodesIfHasCalories(nodesWithCalories, n));
        return nodesWithCalories;

    }

    /**
     * Adds a key-value entry into the nodesWithCalories hashmap if it has a calories value.
     *
     * @param nodesWithCalories pass by reference hashmap to which the key-value pair should be added if a calories
     * value exists
     * @param intradayActivityEventEntry an entry from the intraday activity series dictionary, the key is a string
     * representing the state datetime for the acivity period (in unix epoch seconds) and the value is the JSON object
     * holding data related to that activity
     */
    private void addNodesIfHasCalories(HashMap<Long, JsonNode> nodesWithCalories,
            Map.Entry<String, JsonNode> intradayActivityEventEntry) {

        if (intradayActivityEventEntry.getValue().has("calories")) {
            nodesWithCalories.put(Long.parseLong(intradayActivityEventEntry.getKey()),
                    intradayActivityEventEntry.getValue());
        }

    }

}