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.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); }