org.openmhealth.shim.runkeeper.mapper.RunkeeperDataPointMapper.java Source code

Java tutorial

Introduction

Here is the source code for org.openmhealth.shim.runkeeper.mapper.RunkeeperDataPointMapper.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.runkeeper.mapper;

import com.fasterxml.jackson.databind.JsonNode;
import org.openmhealth.schema.domain.omh.*;
import org.openmhealth.shim.common.mapper.JsonNodeDataPointMapper;

import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.UUID.randomUUID;
import static org.openmhealth.schema.domain.omh.DurationUnit.SECOND;
import static org.openmhealth.schema.domain.omh.TimeInterval.ofStartDateTimeAndDuration;
import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*;

/**
 * An abstract mapper for building RunKeeper data points.
 *
 * @author Emerson Farrugia
 */
public abstract class RunkeeperDataPointMapper<T extends SchemaSupport> implements JsonNodeDataPointMapper<T> {

    public static final String RESOURCE_API_SOURCE_NAME = "Runkeeper HealthGraph API";
    public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter
            .ofPattern("EEE, d MMM yyyy HH:mm:ss");

    @Override
    public List<DataPoint<T>> asDataPoints(List<JsonNode> responseNodes) {

        // all mapped RunKeeper responses only require a single endpoint response
        checkNotNull(responseNodes);
        checkNotNull(responseNodes.size() == 1, "A single response node is allowed per call.");

        // all mapped RunKeeper responses contain a single list
        JsonNode listNode = asRequiredNode(responseNodes.get(0), getListNodeName());

        List<DataPoint<T>> dataPoints = new ArrayList<>();

        for (JsonNode listEntryNode : listNode) {

            // The Runkeeper HealthGraph API does not allow 3rd parties to write the utc_offset property in their
            // posts, so we filter out data in the HealthGraph API that does not come directly from Runkeeper. This
            // ensures that we can establish a time frame for each activity because we have utc_offset information.
            if (!listEntryNode.has("utc_offset")) {
                continue;
            }

            asDataPoint(listEntryNode).ifPresent(dataPoints::add);
        }

        return dataPoints;
    }

    /**
     * @return the name of the list node used by this mapper
     */
    protected String getListNodeName() {
        return "items";
    }

    /**
     * @return a {@link DataPointHeader} for data points created from Runkeeper HealthGraph API responses
     */
    protected DataPointHeader getDataPointHeader(JsonNode itemNode, Measure measure) {

        DataPointAcquisitionProvenance.Builder provenanceBuilder = new DataPointAcquisitionProvenance.Builder(
                RESOURCE_API_SOURCE_NAME);

        getModality(itemNode).ifPresent(provenanceBuilder::setModality);

        DataPointAcquisitionProvenance provenance = provenanceBuilder.build();

        asOptionalString(itemNode, "uri")
                .ifPresent(externalId -> provenance.setAdditionalProperty("external_id", externalId));

        DataPointHeader.Builder headerBuilder = new DataPointHeader.Builder(randomUUID().toString(),
                measure.getSchemaId()).setAcquisitionProvenance(provenance);

        asOptionalInteger(itemNode, "userId").ifPresent(userId -> headerBuilder.setUserId(userId.toString()));

        return headerBuilder.build();

    }

    /**
     * @see <a href="http://billday.com/2013/04/09/validating-tracked-versus-manual-fitness-activities-using-the
     * -health-graph-api/">article on modality</a>
     */
    // TODO clarify source checks
    public Optional<DataPointModality> getModality(JsonNode itemNode) {

        String source = asOptionalString(itemNode, "source").orElse(null);
        String entryMode = asOptionalString(itemNode, "entry_mode").orElse(null);
        Boolean hasPath = asOptionalBoolean(itemNode, "has_path").orElse(null);

        if (entryMode != null && entryMode.equals("Web") && source != null
                && source.equalsIgnoreCase("RunKeeper")) {

            return Optional.of(DataPointModality.SELF_REPORTED);
        }

        if (source != null && source.equalsIgnoreCase("RunKeeper") && entryMode != null && entryMode.equals("API")
                && hasPath != null && hasPath) {

            return Optional.of(DataPointModality.SENSED);
        }

        return Optional.empty();

    }

    /**
     * Sets the effective time frame property for a measure builder.
     *
     * @param itemNode an individual datapoint from the list of datapoints returned in the API response
     * @param builder the measure builder to have the effective date property set
     */
    protected void setEffectiveTimeFrameIfPresent(JsonNode itemNode, Measure.Builder builder) {

        Optional<LocalDateTime> localStartDateTime = asOptionalLocalDateTime(itemNode, "start_time",
                DATE_TIME_FORMATTER);

        // RunKeeper doesn't support fractional time zones
        Optional<Integer> utcOffset = asOptionalInteger(itemNode, "utc_offset");
        Optional<Double> durationInS = asOptionalDouble(itemNode, "duration");

        if (localStartDateTime.isPresent() && utcOffset.isPresent() && durationInS.isPresent()) {

            OffsetDateTime startDateTime = localStartDateTime.get().atOffset(ZoneOffset.ofHours(utcOffset.get()));
            DurationUnitValue duration = new DurationUnitValue(SECOND, durationInS.get());

            builder.setEffectiveTimeFrame(ofStartDateTimeAndDuration(startDateTime, duration));
        }
    }

    /**
     * @param listEntryNode the list entry node
     * @return the data point mapped to from that entry, unless skipped
     */
    protected abstract Optional<DataPoint<T>> asDataPoint(JsonNode listEntryNode);
}