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.googlefit.mapper; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.Lists; import org.openmhealth.schema.domain.omh.*; import org.openmhealth.shim.common.mapper.JsonNodeDataPointMapper; import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneId; import java.util.List; import java.util.Optional; import java.util.UUID; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asOptionalNode; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asOptionalString; /** * A base class for mappers that translate Google Fit API responses into {@link Measure} objects. * * @author Chris Schaefbauer */ public abstract class GoogleFitDataPointMapper<T extends Measure> implements JsonNodeDataPointMapper<T> { public static final String RESOURCE_API_SOURCE_NAME = "Google Fit API"; /** * Maps a JSON response from the Google Fit API containing a JSON array of data points to a list of {@link * DataPoint} objects of the appropriate measure type. Splits individual nodes based on the name of the list node, * "point," and then iteratively maps the nodes in the list. * * @param responseNodes the response body from a Google Fit endpoint, contained in a list of a single JSON node * @return a list of DataPoint objects of type T with the appropriate values mapped from the input JSON; because * these JSON objects are contained within an array in the input response, each object in that JSON array will map * to an item in the returned list */ public List<DataPoint<T>> asDataPoints(List<JsonNode> responseNodes) { checkNotNull(responseNodes); checkArgument(responseNodes.size() == 1, "Only one response should be input to the mapper"); List<DataPoint<T>> dataPoints = Lists.newArrayList(); Optional<JsonNode> listNodes = asOptionalNode(responseNodes.get(0), getListNodeName()); if (listNodes.isPresent()) { for (JsonNode listNode : listNodes.get()) { asDataPoint(listNode).ifPresent(dataPoints::add); } } return dataPoints; } /** * Maps a JSON response node from the Google Fit API into a {@link Measure} object of the appropriate type. * * @param listNode an individual datapoint from the array contained in the Google Fit response * @return a {@link DataPoint} object containing the target measure with the appropriate values from the JSON node * parameter, wrapped as an {@link Optional} */ protected abstract Optional<DataPoint<T>> asDataPoint(JsonNode listNode); /** * Creates a complete {@link DataPoint} object with the measure parameter and the appropriate header and header * information. * * @param measure the {@link Measure} of type T to be wrapped as a {@link DataPoint} * @param fitDataSourceId the origin data source from the Google Fit API, contained in the originDataSourceId * property; refers to the originating source that brought the data into Google Fit */ public DataPoint<T> newDataPoint(T measure, String fitDataSourceId) { DataPointAcquisitionProvenance.Builder acquisitionProvenanceBuilder = new DataPointAcquisitionProvenance.Builder( RESOURCE_API_SOURCE_NAME); // For data from the Google Fit API that has an origin data source id ending with "user_input" we know that // the data point is self-reported from the user through the Google Fit app or web interface if (fitDataSourceId != null && fitDataSourceId.endsWith("user_input")) { acquisitionProvenanceBuilder.setModality(DataPointModality.SELF_REPORTED); } // Although there is limited standardization in this information, we decided to pass it through as an // additional property to prevent information loss and in case someone were to leverage this information in // some way DataPointAcquisitionProvenance acquisitionProvenance = acquisitionProvenanceBuilder.build(); if (fitDataSourceId != null) { acquisitionProvenance.setAdditionalProperty("source_origin_id", fitDataSourceId); } DataPointHeader header = new DataPointHeader.Builder(UUID.randomUUID().toString(), measure.getSchemaId()) .setAcquisitionProvenance(acquisitionProvenance).build(); return new DataPoint<>(header, measure); } /** * Converts a nanosecond timestamp from the Google Fit API into an offset datetime value. * * @param unixEpochNanosString the timestamp directly from the Google JSON document * @return an offset datetime object representing the input timestamp */ public OffsetDateTime convertGoogleNanosToOffsetDateTime(String unixEpochNanosString) { return OffsetDateTime.ofInstant(Instant.ofEpochSecond(0, Long.parseLong(unixEpochNanosString)), ZoneId.of("Z")); } /** * @param builder a measure builder of type T * @param listNode the JSON node representing an individual datapoint, which contains the start and end time * properties, from within the response array */ public void setEffectiveTimeFrameIfPresent(T.Builder builder, JsonNode listNode) { Optional<String> startTimeNanosString = asOptionalString(listNode, "startTimeNanos"); Optional<String> endTimeNanosString = asOptionalString(listNode, "endTimeNanos"); // When the start and end times are identical, such as for a single body weight measure, then we only need to // create an effective time frame with a single date time value if (startTimeNanosString.isPresent() && endTimeNanosString.isPresent()) { if (startTimeNanosString.equals(endTimeNanosString)) { builder.setEffectiveTimeFrame(convertGoogleNanosToOffsetDateTime(startTimeNanosString.get())); } else { builder.setEffectiveTimeFrame(TimeInterval.ofStartDateTimeAndEndDateTime( convertGoogleNanosToOffsetDateTime(startTimeNanosString.get()), convertGoogleNanosToOffsetDateTime(endTimeNanosString.get()))); } } } /** * The name of the list that contains the datapoints associated with the request. */ protected String getListNodeName() { return "point"; } /** * The name of the list node contained within each datapoint that contains the target value. */ protected String getValueListNodeName() { return "value"; } }