From 79db8011e05a0603e59f8b9e9a11d7abc54cafc5 Mon Sep 17 00:00:00 2001 From: Emerson Farrugia Date: Fri, 11 Sep 2015 17:59:25 +0200 Subject: [PATCH 01/68] Fix version in Gradle build script --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c50d9b4d..552a25cc 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ subprojects { ext { javaVersion = 1.8 - shimmerVersion = '0.3.0.SNAPSHOT' + shimmerVersion = '0.3.0' omhSchemaSdkVersion = '1.0.3' } From d05067c35ae80ad959e34459b5eef5f9592673fa Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Mon, 28 Sep 2015 13:53:34 -0600 Subject: [PATCH 02/68] Add base and blood pressure mappers for iHealth --- .../IHealthBloodPressureDataPointMapper.java | 100 +++++++++++++ .../mapper/IHealthDataPointMapper.java | 130 +++++++++++++++++ ...BloodPressureDataPointMapperUnitTests.java | 134 ++++++++++++++++++ .../mapper/ihealth-blood-pressure.json | 41 ++++++ 4 files changed, 405 insertions(+) create mode 100644 shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java create mode 100644 shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java create mode 100644 shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java create mode 100644 shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java new file mode 100644 index 00000000..ec7b4700 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java @@ -0,0 +1,100 @@ +/* + * 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.ihealth.mapper; + +import com.fasterxml.jackson.databind.JsonNode; +import org.openmhealth.schema.domain.omh.*; + +import java.util.Optional; + +import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredDouble; + + +/** + * @author Chris Schaefbauer + */ +public class IHealthBloodPressureDataPointMapper extends IHealthDataPointMapper{ + + static final double KPA_TO_MMHG_CONVERSION_RATE = 7.500617; + + @Override + protected String getListNodeName() { + return "BPDataList"; + } + + @Override + protected String getUnitPropertyNameForMeasure() { return "BPUnit"; } + + @Override + protected Optional> asDataPoint(JsonNode listNode, int bloodPressureUnit) { + + BloodPressureUnitType bloodPressureUnitType = BloodPressureUnitType.fromIntegerValue(bloodPressureUnit); + + double systolicValue = getBloodPressureValueInMmHg(asRequiredDouble(listNode, "HP"), bloodPressureUnitType); + SystolicBloodPressure systolicBloodPressure = new SystolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY,systolicValue); + + double diastolicValue = getBloodPressureValueInMmHg(asRequiredDouble(listNode,"LP"), bloodPressureUnitType); + DiastolicBloodPressure diastolicBloodPressure = new DiastolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY,diastolicValue); + + + BloodPressure.Builder bloodPressureBuilder = new BloodPressure.Builder(systolicBloodPressure, diastolicBloodPressure); + + + setEffectiveTimeFrameIfExists(listNode,bloodPressureBuilder); + setUserNoteIfExists(listNode,bloodPressureBuilder); + + BloodPressure bloodPressure = bloodPressureBuilder.build(); + return Optional.of(new DataPoint<>(createDataPointHeader(listNode,bloodPressure), bloodPressure)); + } + + protected double getBloodPressureValueInMmHg(double rawBpValue, BloodPressureUnitType bloodPressureUnit) { + + switch(bloodPressureUnit){ + case mmHg: + return rawBpValue; + case KPa: + return rawBpValue * KPA_TO_MMHG_CONVERSION_RATE; + default: + throw new UnsupportedOperationException(); + } + } + + protected enum BloodPressureUnitType { + + mmHg(0), + KPa(1); + + private int value; + + BloodPressureUnitType(int value) { + this.value = value; + } + + protected int getValue(){ + return value; + } + + public static BloodPressureUnitType fromIntegerValue(int bpIntValue) { + for (BloodPressureUnitType type : values() ){ + if (type.getValue() == bpIntValue) return type; + } + throw new UnsupportedOperationException(); + } + } + + +} diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java new file mode 100644 index 00000000..a649f016 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java @@ -0,0 +1,130 @@ +/* + * 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.ihealth.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.DataPointMapper; + +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 org.openmhealth.schema.domain.omh.DataPointModality.*; +import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*; + + +/** + * @author Chris Schaefbauer + */ +public abstract class IHealthDataPointMapper implements DataPointMapper { + + public static final String RESOURCE_API_SOURCE_NAME = "iHealth Resource API"; + public static final String DATA_SOURCE_MANUAL = "Manual"; + public static final String DATA_SOURCE_FROM_DEVICE = "FromDevice"; + + @Override + public List> asDataPoints(List responseNodes) { + + JsonNode responseNode = responseNodes.get(0); + + List> dataPoints = Lists.newArrayList(); + + int measureUnit = asRequiredInteger(responseNode, getUnitPropertyNameForMeasure()); + + for (JsonNode listNode : asRequiredNode(responseNode, getListNodeName())) { + + asDataPoint(listNode, measureUnit).ifPresent(dp -> dataPoints.add(dp)); + } + + return dataPoints; + // return Collections.singletonList(asDataPoint(responseNode,measureUnit)); + } + + protected DataPointHeader createDataPointHeader(JsonNode listNode, Measure measure) { + + DataPointAcquisitionProvenance.Builder acquisitionProvenanceBuilder = + new DataPointAcquisitionProvenance.Builder(RESOURCE_API_SOURCE_NAME); + + asOptionalString(listNode, "DataSource").ifPresent( + dataSource -> setAppropriateModality(dataSource, acquisitionProvenanceBuilder)); + + DataPointAcquisitionProvenance acquisitionProvenance = acquisitionProvenanceBuilder.build(); + + asOptionalString(listNode, "DataID") + .ifPresent(externalId -> acquisitionProvenance.setAdditionalProperty("external_id", + externalId)); + + asOptionalLong(listNode, "LastChangeTime").ifPresent( + lastUpdatedInUnixSecs -> acquisitionProvenance.setAdditionalProperty("source_updated_date_time", + OffsetDateTime.ofInstant(Instant.ofEpochSecond(lastUpdatedInUnixSecs), ZoneId.of("Z")))); + + DataPointHeader dataPointHeader = + new DataPointHeader.Builder(UUID.randomUUID().toString(), measure.getSchemaId()) + .setAcquisitionProvenance(acquisitionProvenance) + .build(); + + return dataPointHeader; + } + + protected void setEffectiveTimeFrameIfExists(JsonNode listNode, Measure.Builder builder){ + + Optional optionalOffsetDateTime = asOptionalLong(listNode, "MDate"); + + if(optionalOffsetDateTime.isPresent()){ + + asOptionalString(listNode,"TimeZone").ifPresent(timezoneOffsetString -> builder + .setEffectiveTimeFrame( + OffsetDateTime.ofInstant( + Instant.ofEpochSecond(optionalOffsetDateTime.get()), + ZoneId.of(timezoneOffsetString)))); + + } + } + + protected void setUserNoteIfExists(JsonNode listNode, Measure.Builder builder){ + + Optional note = asOptionalString(listNode, "Note"); + + if(note.isPresent() && !note.get().isEmpty()){ + + builder.setUserNotes(note.get()); + } + + + } + + private void setAppropriateModality(String dataSourceValue, DataPointAcquisitionProvenance.Builder builder) { + + if (dataSourceValue.equals(DATA_SOURCE_FROM_DEVICE)) { + builder.setModality(SENSED); + } + else if (dataSourceValue.equals(DATA_SOURCE_MANUAL)) { + builder.setModality(SELF_REPORTED); + } + } + + protected abstract String getListNodeName(); + + protected abstract String getUnitPropertyNameForMeasure(); + + protected abstract Optional> asDataPoint(JsonNode jsonNode, int measureUnit); +} diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java new file mode 100644 index 00000000..b35da70a --- /dev/null +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java @@ -0,0 +1,134 @@ +/* + * 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.ihealth.mapper; + +import com.fasterxml.jackson.databind.JsonNode; +import org.openmhealth.schema.domain.omh.*; +import org.openmhealth.shim.common.mapper.DataPointMapperUnitTests; +import org.springframework.core.io.ClassPathResource; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.time.OffsetDateTime; +import java.util.List; + +import static java.util.Collections.singletonList; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.openmhealth.shim.ihealth.mapper.IHealthBloodPressureDataPointMapper.*; + + +/** + * @author Chris Schaefbauer + */ +public class IHealthBloodPressureDataPointMapperUnitTests extends DataPointMapperUnitTests { + + private JsonNode responseNode; + private IHealthBloodPressureDataPointMapper mapper = new IHealthBloodPressureDataPointMapper(); + + @BeforeTest + public void initializeResponseNode() throws IOException { + + ClassPathResource resource = + new ClassPathResource("org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json"); + responseNode = objectMapper.readTree(resource.getInputStream()); + } + + @Test + public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + assertThat(dataPoints.size(), equalTo(2)); + } + + @Test + public void asDataPointsShouldReturnCorrectSensedDataPoints() { + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + + BloodPressure expectedBloodPressure = new BloodPressure.Builder( + new SystolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, 120), + new DiastolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, 90)) + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T04:04:23-08:00")) + .build(); + + assertThat(dataPoints.get(0).getBody(), equalTo(expectedBloodPressure)); + + DataPointHeader testHeader = dataPoints.get(0).getHeader(); + + assertThat(testHeader.getBodySchemaId(), equalTo(BloodPressure.SCHEMA_ID)); + assertThat(testHeader.getAcquisitionProvenance().getModality(), equalTo(DataPointModality.SENSED)); + assertThat(testHeader.getAcquisitionProvenance().getSourceName(), + equalTo(IHealthDataPointMapper.RESOURCE_API_SOURCE_NAME)); + assertThat(testHeader.getAcquisitionProvenance().getAdditionalProperties().get("external_id"), equalTo( + "c62b84d9d4b7480a8ff2aef1465aa454")); + assertThat(testHeader.getAcquisitionProvenance().getAdditionalProperties().get("source_updated_date_time"), + equalTo(OffsetDateTime.parse("2015-09-17T20:04:30Z"))); + + } + + @Test + public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + + BloodPressure expectedBloodPressure = new BloodPressure.Builder( + new SystolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, 130), + new DiastolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, 95)) + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-25T08:03:57-06:00")) + .setUserNotes("BP on the up and up.") + .build(); + + assertThat(dataPoints.get(1).getBody(), equalTo(expectedBloodPressure)); + + DataPointHeader testHeader = dataPoints.get(1).getHeader(); + + assertThat(testHeader.getAcquisitionProvenance().getModality(), equalTo(DataPointModality.SELF_REPORTED)); + + } + + @Test + public void asDataPointsShouldReturnCorrectUserNotesWithDataPoints() { + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + + assertThat(dataPoints.get(0).getBody().getUserNotes(),nullValue()); + assertThat(dataPoints.get(1).getBody().getUserNotes(),equalTo("BP on the up and up.")); + } + + + @Test + public void getBloodPressureValueInMmHgReturnsCorrectValueForMmHgUnit() { + + double bpValueFromMmHg = mapper.getBloodPressureValueInMmHg(120, BloodPressureUnitType.mmHg); + assertThat(bpValueFromMmHg, equalTo(120.0)); + + double bpValueFromKpa = mapper.getBloodPressureValueInMmHg(16, BloodPressureUnitType.KPa); + assertThat(bpValueFromKpa, equalTo(120.009872)); + + } + + @Test(expectedExceptions = UnsupportedOperationException.class) + public void getBloodPressureValueShouldThrowExceptionForInvalidEnum(){ + + mapper.getBloodPressureValueInMmHg(12,BloodPressureUnitType.fromIntegerValue(12)); + } + + +} diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json new file mode 100644 index 00000000..ab0048da --- /dev/null +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json @@ -0,0 +1,41 @@ +{ + "BPDataList": [ + { + "BPL": 3, + "DataID": "c62b84d9d4b7480a8ff2aef1465aa454", + "DataSource": "FromDevice", + "HP": 120, + "HR": 100, + "IsArr": -1, + "LP": 90, + "LastChangeTime": 1442520270, + "Lat": -1, + "Lon": -1, + "MDate": 1442491463, + "Note": "", + "TimeZone": "-0800" + }, + { + "BPL": 3, + "DataID": "47262ccffec747a4b21befb04b322424", + "DataSource": "Manual", + "HP": 130, + "HR": 75, + "IsArr": -1, + "LP": 95, + "LastChangeTime": 1443211477, + "Lat": -1, + "Lon": -1, + "MDate": 1443189837, + "Note": "BP on the up and up.", + "TimeZone": "-0600" + } + ], + "BPUnit": 0, + "CurrentRecordCount": 5, + "NextPageUrl": "", + "PageLength": 50, + "PageNumber": 1, + "PrevPageUrl": "", + "RecordCount": 5 +} \ No newline at end of file From bf4799738bcc9859cdf478941c6754c00717053a Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Mon, 28 Sep 2015 16:14:42 -0600 Subject: [PATCH 03/68] Refactor iHealth header tests into parent class --- ...BloodPressureDataPointMapperUnitTests.java | 30 +++++------- .../IHealthDataPointMapperUnitTests.java | 47 +++++++++++++++++++ 2 files changed, 60 insertions(+), 17 deletions(-) create mode 100644 shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java index b35da70a..1932a5ac 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java @@ -18,7 +18,6 @@ import com.fasterxml.jackson.databind.JsonNode; import org.openmhealth.schema.domain.omh.*; -import org.openmhealth.shim.common.mapper.DataPointMapperUnitTests; import org.springframework.core.io.ClassPathResource; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; @@ -31,13 +30,14 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; -import static org.openmhealth.shim.ihealth.mapper.IHealthBloodPressureDataPointMapper.*; +import static org.openmhealth.schema.domain.omh.DataPointModality.*; +import static org.openmhealth.shim.ihealth.mapper.IHealthBloodPressureDataPointMapper.BloodPressureUnitType; /** * @author Chris Schaefbauer */ -public class IHealthBloodPressureDataPointMapperUnitTests extends DataPointMapperUnitTests { +public class IHealthBloodPressureDataPointMapperUnitTests extends IHealthDataPointMapperUnitTests { private JsonNode responseNode; private IHealthBloodPressureDataPointMapper mapper = new IHealthBloodPressureDataPointMapper(); @@ -50,6 +50,8 @@ public void initializeResponseNode() throws IOException { responseNode = objectMapper.readTree(resource.getInputStream()); } + // TODO: Test/handle datapoints that have zero values for BP (awaiting response from iHealth) + @Test public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { @@ -72,14 +74,8 @@ public void asDataPointsShouldReturnCorrectSensedDataPoints() { DataPointHeader testHeader = dataPoints.get(0).getHeader(); - assertThat(testHeader.getBodySchemaId(), equalTo(BloodPressure.SCHEMA_ID)); - assertThat(testHeader.getAcquisitionProvenance().getModality(), equalTo(DataPointModality.SENSED)); - assertThat(testHeader.getAcquisitionProvenance().getSourceName(), - equalTo(IHealthDataPointMapper.RESOURCE_API_SOURCE_NAME)); - assertThat(testHeader.getAcquisitionProvenance().getAdditionalProperties().get("external_id"), equalTo( - "c62b84d9d4b7480a8ff2aef1465aa454")); - assertThat(testHeader.getAcquisitionProvenance().getAdditionalProperties().get("source_updated_date_time"), - equalTo(OffsetDateTime.parse("2015-09-17T20:04:30Z"))); + testDataPointHeader(testHeader, BloodPressure.SCHEMA_ID, SENSED, "c62b84d9d4b7480a8ff2aef1465aa454", + OffsetDateTime.parse("2015-09-17T20:04:30Z")); } @@ -98,8 +94,8 @@ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { assertThat(dataPoints.get(1).getBody(), equalTo(expectedBloodPressure)); DataPointHeader testHeader = dataPoints.get(1).getHeader(); - - assertThat(testHeader.getAcquisitionProvenance().getModality(), equalTo(DataPointModality.SELF_REPORTED)); + + assertThat(testHeader.getAcquisitionProvenance().getModality(), equalTo(SELF_REPORTED)); } @@ -108,8 +104,8 @@ public void asDataPointsShouldReturnCorrectUserNotesWithDataPoints() { List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - assertThat(dataPoints.get(0).getBody().getUserNotes(),nullValue()); - assertThat(dataPoints.get(1).getBody().getUserNotes(),equalTo("BP on the up and up.")); + assertThat(dataPoints.get(0).getBody().getUserNotes(), nullValue()); + assertThat(dataPoints.get(1).getBody().getUserNotes(), equalTo("BP on the up and up.")); } @@ -125,9 +121,9 @@ public void getBloodPressureValueInMmHgReturnsCorrectValueForMmHgUnit() { } @Test(expectedExceptions = UnsupportedOperationException.class) - public void getBloodPressureValueShouldThrowExceptionForInvalidEnum(){ + public void getBloodPressureValueShouldThrowExceptionForInvalidEnum() { - mapper.getBloodPressureValueInMmHg(12,BloodPressureUnitType.fromIntegerValue(12)); + mapper.getBloodPressureValueInMmHg(12, BloodPressureUnitType.fromIntegerValue(12)); } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java new file mode 100644 index 00000000..b5f95c0e --- /dev/null +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java @@ -0,0 +1,47 @@ +/* + * 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.ihealth.mapper; + +import org.openmhealth.schema.domain.omh.DataPointHeader; +import org.openmhealth.schema.domain.omh.DataPointModality; +import org.openmhealth.schema.domain.omh.SchemaId; +import org.openmhealth.shim.common.mapper.DataPointMapperUnitTests; + +import java.time.OffsetDateTime; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; + + +/** + * @author Chris Schaefbauer + */ +public class IHealthDataPointMapperUnitTests extends DataPointMapperUnitTests { + + protected void testDataPointHeader(DataPointHeader testHeader, SchemaId schemaId, DataPointModality modality, String externalId, OffsetDateTime updatedDateTime){ + + assertThat(testHeader.getBodySchemaId(), equalTo(schemaId)); + assertThat(testHeader.getAcquisitionProvenance().getModality(), equalTo(modality)); + assertThat(testHeader.getAcquisitionProvenance().getSourceName(), + equalTo(IHealthDataPointMapper.RESOURCE_API_SOURCE_NAME)); + assertThat(testHeader.getAcquisitionProvenance().getAdditionalProperties().get("external_id"), equalTo( + externalId)); + assertThat(testHeader.getAcquisitionProvenance().getAdditionalProperties().get("source_updated_date_time"), + equalTo(updatedDateTime)); + } + +} From 73e3d22db1b0a9c8e9ff493098271d424b06e798 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Mon, 28 Sep 2015 16:54:56 -0600 Subject: [PATCH 04/68] Add iHealth body weight mapper and tests --- .../IHealthBloodPressureDataPointMapper.java | 31 ++-- .../IHealthBodyWeightDataPointMapper.java | 129 +++++++++++++++ .../mapper/IHealthDataPointMapper.java | 10 +- ...BloodPressureDataPointMapperUnitTests.java | 2 +- ...lthBodyWeightDataPointMapperUnitTests.java | 154 ++++++++++++++++++ .../mapper/ihealth-blood-pressure.json | 2 +- .../ihealth/mapper/ihealth-body-weight.json | 41 +++++ .../ihealth-empty-body-weight-list.json | 10 ++ .../ihealth-missing-body-weight-value.json | 26 +++ 9 files changed, 384 insertions(+), 21 deletions(-) create mode 100644 shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java create mode 100644 shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java create mode 100644 shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-body-weight.json create mode 100644 shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight-list.json create mode 100644 shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-missing-body-weight-value.json diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java index ec7b4700..6f00e05b 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java @@ -27,7 +27,7 @@ /** * @author Chris Schaefbauer */ -public class IHealthBloodPressureDataPointMapper extends IHealthDataPointMapper{ +public class IHealthBloodPressureDataPointMapper extends IHealthDataPointMapper { static final double KPA_TO_MMHG_CONVERSION_RATE = 7.500617; @@ -45,25 +45,26 @@ protected Optional> asDataPoint(JsonNode listNode, int BloodPressureUnitType bloodPressureUnitType = BloodPressureUnitType.fromIntegerValue(bloodPressureUnit); double systolicValue = getBloodPressureValueInMmHg(asRequiredDouble(listNode, "HP"), bloodPressureUnitType); - SystolicBloodPressure systolicBloodPressure = new SystolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY,systolicValue); + SystolicBloodPressure systolicBloodPressure = + new SystolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, systolicValue); - double diastolicValue = getBloodPressureValueInMmHg(asRequiredDouble(listNode,"LP"), bloodPressureUnitType); - DiastolicBloodPressure diastolicBloodPressure = new DiastolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY,diastolicValue); + double diastolicValue = getBloodPressureValueInMmHg(asRequiredDouble(listNode, "LP"), bloodPressureUnitType); + DiastolicBloodPressure diastolicBloodPressure = + new DiastolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, diastolicValue); + BloodPressure.Builder bloodPressureBuilder = + new BloodPressure.Builder(systolicBloodPressure, diastolicBloodPressure); - BloodPressure.Builder bloodPressureBuilder = new BloodPressure.Builder(systolicBloodPressure, diastolicBloodPressure); - - - setEffectiveTimeFrameIfExists(listNode,bloodPressureBuilder); - setUserNoteIfExists(listNode,bloodPressureBuilder); + setEffectiveTimeFrameIfExists(listNode, bloodPressureBuilder); + setUserNoteIfExists(listNode, bloodPressureBuilder); BloodPressure bloodPressure = bloodPressureBuilder.build(); - return Optional.of(new DataPoint<>(createDataPointHeader(listNode,bloodPressure), bloodPressure)); + return Optional.of(new DataPoint<>(createDataPointHeader(listNode, bloodPressure), bloodPressure)); } protected double getBloodPressureValueInMmHg(double rawBpValue, BloodPressureUnitType bloodPressureUnit) { - switch(bloodPressureUnit){ + switch ( bloodPressureUnit ) { case mmHg: return rawBpValue; case KPa: @@ -84,13 +85,15 @@ protected enum BloodPressureUnitType { this.value = value; } - protected int getValue(){ + protected int getValue() { return value; } public static BloodPressureUnitType fromIntegerValue(int bpIntValue) { - for (BloodPressureUnitType type : values() ){ - if (type.getValue() == bpIntValue) return type; + for (BloodPressureUnitType type : values()) { + if (type.getValue() == bpIntValue) { + return type; + } } throw new UnsupportedOperationException(); } diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java new file mode 100644 index 00000000..aa8141b2 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java @@ -0,0 +1,129 @@ +/* + * 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.ihealth.mapper; + +import com.fasterxml.jackson.databind.JsonNode; +import org.openmhealth.schema.domain.omh.BodyWeight; +import org.openmhealth.schema.domain.omh.DataPoint; +import org.openmhealth.schema.domain.omh.MassUnit; +import org.openmhealth.schema.domain.omh.MassUnitValue; + +import java.util.Optional; + +import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredDouble; + + +/** + * @author Chris Schaefbauer + */ +public class IHealthBodyWeightDataPointMapper extends IHealthDataPointMapper { + + // Reference: https://en.wikipedia.org/wiki/Stone_(unit) + private static final double STONE_TO_KG_FACTOR = 6.3503; + + @Override + protected String getListNodeName() { + return "WeightDataList"; + } + + @Override + protected String getUnitPropertyNameForMeasure() { + return "WeightUnit"; + } + + @Override + protected Optional> asDataPoint(JsonNode listNode, int measureUnit) { + + BodyWeightUnitType bodyWeightUnitType = BodyWeightUnitType.fromIntegerValue(measureUnit); + MassUnit bodyWeightUnit = bodyWeightUnitType.getOmhUnit(); + + double bodyWeightValue = getBodyWeightValueForUnitType(listNode, bodyWeightUnitType); + + if (bodyWeightValue == 0) { + + return Optional.empty(); + } + + BodyWeight.Builder bodyWeightBuilder = + new BodyWeight.Builder(new MassUnitValue(bodyWeightUnit, bodyWeightValue)); + + setEffectiveTimeFrameIfExists(listNode,bodyWeightBuilder); + setUserNoteIfExists(listNode,bodyWeightBuilder); + + BodyWeight bodyWeight = bodyWeightBuilder.build(); + + return Optional.of(new DataPoint<>(createDataPointHeader(listNode, bodyWeight), bodyWeight)); + } + + protected double getBodyWeightValueForUnitType(JsonNode listNode, + BodyWeightUnitType bodyWeightUnitType) { + + Double weightValueFromApi = asRequiredDouble(listNode, "WeightValue"); + return getBodyWeightValueForUnitType(weightValueFromApi, bodyWeightUnitType); + } + + protected double getBodyWeightValueForUnitType(double bodyWeightValue, BodyWeightUnitType bodyWeightUnitType) { + + return bodyWeightValue * bodyWeightUnitType.getConversionFactorToOmh(); + } + + enum BodyWeightUnitType { + + /* + The conversion factor handles conversions from unsupported mass units (currently the 'Stone' unit) to omh + supported units such that: + + ValueIntoSchema = ValueFromApi * Conversion + + We map stone into kg because it is the SI unit for mass and the most widely accepted for measuring human + body weight in a clinical/scientific context. + */ + kg(0, MassUnit.KILOGRAM, 1), + lb(1, MassUnit.POUND, 1), + stone(2, MassUnit.KILOGRAM, STONE_TO_KG_FACTOR); + + private final MassUnit omhUnit; + private final double conversionFactorToOmh; + private int magicNumber; + + BodyWeightUnitType(int magicNumber, MassUnit omhUnit, double conversionFactor) { + this.omhUnit = omhUnit; + this.conversionFactorToOmh = conversionFactor; + this.magicNumber = magicNumber; + } + + public MassUnit getOmhUnit() { + return omhUnit; + } + + public double getConversionFactorToOmh() { + return conversionFactorToOmh; + } + + public static BodyWeightUnitType fromIntegerValue(int unitValueFromApi) { + + for (BodyWeightUnitType type : values()) { + if (type.magicNumber == unitValueFromApi) { + return type; + } + } + + throw new UnsupportedOperationException(); + } + + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java index a649f016..d6638fcc 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java @@ -85,13 +85,13 @@ protected DataPointHeader createDataPointHeader(JsonNode listNode, Measure measu return dataPointHeader; } - protected void setEffectiveTimeFrameIfExists(JsonNode listNode, Measure.Builder builder){ + protected void setEffectiveTimeFrameIfExists(JsonNode listNode, Measure.Builder builder) { Optional optionalOffsetDateTime = asOptionalLong(listNode, "MDate"); - if(optionalOffsetDateTime.isPresent()){ + if (optionalOffsetDateTime.isPresent()) { - asOptionalString(listNode,"TimeZone").ifPresent(timezoneOffsetString -> builder + asOptionalString(listNode, "TimeZone").ifPresent(timezoneOffsetString -> builder .setEffectiveTimeFrame( OffsetDateTime.ofInstant( Instant.ofEpochSecond(optionalOffsetDateTime.get()), @@ -100,11 +100,11 @@ protected void setEffectiveTimeFrameIfExists(JsonNode listNode, Measure.Builder } } - protected void setUserNoteIfExists(JsonNode listNode, Measure.Builder builder){ + protected void setUserNoteIfExists(JsonNode listNode, Measure.Builder builder) { Optional note = asOptionalString(listNode, "Note"); - if(note.isPresent() && !note.get().isEmpty()){ + if (note.isPresent() && !note.get().isEmpty()) { builder.setUserNotes(note.get()); } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java index 1932a5ac..59f14510 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java @@ -94,7 +94,7 @@ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { assertThat(dataPoints.get(1).getBody(), equalTo(expectedBloodPressure)); DataPointHeader testHeader = dataPoints.get(1).getHeader(); - + assertThat(testHeader.getAcquisitionProvenance().getModality(), equalTo(SELF_REPORTED)); } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java new file mode 100644 index 00000000..0a7fd377 --- /dev/null +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java @@ -0,0 +1,154 @@ +/* + * 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.ihealth.mapper; + +import com.fasterxml.jackson.databind.JsonNode; +import org.openmhealth.schema.domain.omh.*; +import org.springframework.core.io.ClassPathResource; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.time.OffsetDateTime; +import java.util.List; + +import static java.util.Collections.singletonList; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; +import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; +import static org.openmhealth.shim.ihealth.mapper.IHealthBodyWeightDataPointMapper.BodyWeightUnitType; + + +/** + * @author Chris Schaefbauer + */ +public class IHealthBodyWeightDataPointMapperUnitTests extends IHealthDataPointMapperUnitTests { + + protected JsonNode responseNode; + IHealthBodyWeightDataPointMapper mapper = new IHealthBodyWeightDataPointMapper(); + + @BeforeTest + public void initializeResponseNode() throws IOException { + + ClassPathResource resource = + new ClassPathResource("org/openmhealth/shim/ihealth/mapper/ihealth-body-weight.json"); + responseNode = objectMapper.readTree(resource.getInputStream()); + } + + @Test + public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + assertThat(dataPoints.size(), equalTo(2)); + } + + @Test + public void asDataPointsShouldReturnCorrectSensedDataPoints() { + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + + BodyWeight.Builder expectedBodyWeightBuilder = new BodyWeight.Builder( + new MassUnitValue(MassUnit.KILOGRAM, 77.5643875134944)) + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T04:04:09-08:00")); + + assertThat(dataPoints.get(0).getBody(), equalTo(expectedBodyWeightBuilder.build())); + + + DataPointHeader dataPointHeader = dataPoints.get(0).getHeader(); + testDataPointHeader(dataPointHeader, BodyWeight.SCHEMA_ID, SENSED, "5fe5893c418b48cd8da7954f8b6c2f36", + OffsetDateTime.parse("2015-09-17T20:04:17Z")); + + } + + @Test + public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + + BodyWeight.Builder expectedBodyWeightBuilder = + new BodyWeight.Builder(new MassUnitValue(MassUnit.KILOGRAM, 77.56438446044922)) + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T08:07:57-06:00")) + .setUserNotes("Weight so good, look at me now"); + + assertThat(dataPoints.get(1).getBody(), equalTo(expectedBodyWeightBuilder.build())); + + testDataPointHeader(dataPoints.get(1).getHeader(), BodyWeight.SCHEMA_ID, SELF_REPORTED, + "b702a3a5e998f2fca268df6daaa69871", OffsetDateTime.parse("2015-09-17T20:08:00Z")); + + } + + @Test + public void asDataPointsShouldReturnCorrectUserNotes() { + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + + assertThat(dataPoints.get(0).getBody().getUserNotes(),nullValue()); + assertThat(dataPoints.get(1).getBody().getUserNotes(), equalTo("Weight so good, look at me now")); + } + + @Test + public void asDataPointsShouldReturnNoDataPointsWhenWeightValueEqualsZero() throws IOException { + + ClassPathResource resource = + new ClassPathResource("org/openmhealth/shim/ihealth/mapper/ihealth-missing-body-weight-value.json"); + JsonNode zeroValueNode = objectMapper.readTree(resource.getInputStream()); + + List> dataPoints = mapper.asDataPoints(singletonList(zeroValueNode)); + assertThat(dataPoints.size(),equalTo(0)); + } + + @Test + public void asDataPointsShouldReturnNoDataPointsWhenWeightListIsEmpty() throws IOException { + + ClassPathResource resource = + new ClassPathResource("org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight-list.json"); + JsonNode emptyListNode = objectMapper.readTree(resource.getInputStream()); + + List> dataPoints = mapper.asDataPoints(singletonList(emptyListNode)); + assertThat(dataPoints.size(),equalTo(0)); + } + + @Test + public void getBodyWeightValueForUnitTypeShouldReturnCorrectValueForOmhCompatibleTypes() { + + double bodyWeightValueForUnitType = mapper.getBodyWeightValueForUnitType(66.3, BodyWeightUnitType.kg); + assertThat(bodyWeightValueForUnitType, equalTo(66.3)); + + bodyWeightValueForUnitType = mapper.getBodyWeightValueForUnitType(100.5, BodyWeightUnitType.lb); + assertThat(bodyWeightValueForUnitType, equalTo(100.5)); + } + + @Test + public void getBodyWeightValueForUnitTypeShouldReturnCorrectValueForOmhIncompatibleTypes() { + + double bodyWeightValueForUnitType = mapper.getBodyWeightValueForUnitType(12.4, BodyWeightUnitType.stone); + assertThat(bodyWeightValueForUnitType, equalTo(78.74372)); + + } + + @Test + public void getOmhUnitInBodyWeightUnitTypeShouldReturnCorrectMassUnits() { + + assertThat(BodyWeightUnitType.kg.getOmhUnit(), equalTo(MassUnit.KILOGRAM)); + assertThat(BodyWeightUnitType.lb.getOmhUnit(), equalTo(MassUnit.POUND)); + assertThat(BodyWeightUnitType.stone.getOmhUnit(), equalTo(MassUnit.KILOGRAM)); + } + + +} diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json index ab0048da..83d4f579 100644 --- a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json @@ -37,5 +37,5 @@ "PageLength": 50, "PageNumber": 1, "PrevPageUrl": "", - "RecordCount": 5 + "RecordCount": 2 } \ No newline at end of file diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-body-weight.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-body-weight.json new file mode 100644 index 00000000..452fbf9f --- /dev/null +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-body-weight.json @@ -0,0 +1,41 @@ +{ + "CurrentRecordCount": 4, + "NextPageUrl": "", + "PageLength": 50, + "PageNumber": 1, + "PrevPageUrl": "", + "RecordCount": 2, + "WeightDataList": [ + { + "BMI": 22.56052563257619, + "BoneValue": 0, + "DCI": 2035.774872767189, + "DataID": "5fe5893c418b48cd8da7954f8b6c2f36", + "DataSource": "FromDevice", + "FatValue": 10, + "LastChangeTime": 1442520257, + "MDate": 1442491449, + "MuscaleValue": 0, + "Note": "", + "TimeZone": "-0800", + "WaterValue": 25, + "WeightValue": 77.5643875134944 + }, + { + "BMI": 22.56052398681641, + "BoneValue": 0, + "DCI": 0, + "DataID": "b702a3a5e998f2fca268df6daaa69871", + "DataSource": "Manual", + "FatValue": 0, + "LastChangeTime": 1442520480, + "MDate": 1442498877, + "MuscaleValue": 0, + "Note": "Weight so good, look at me now", + "TimeZone": "-0600", + "WaterValue": 0, + "WeightValue": 77.56438446044922 + } + ], + "WeightUnit": 0 +} \ No newline at end of file diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight-list.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight-list.json new file mode 100644 index 00000000..765b8503 --- /dev/null +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight-list.json @@ -0,0 +1,10 @@ +{ + "CurrentRecordCount": 0, + "NextPageUrl": "", + "PageLength": 50, + "PageNumber": 1, + "PrevPageUrl": "", + "RecordCount": 0, + "WeightDataList": [], + "WeightUnit": 0 +} \ No newline at end of file diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-missing-body-weight-value.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-missing-body-weight-value.json new file mode 100644 index 00000000..b02b709a --- /dev/null +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-missing-body-weight-value.json @@ -0,0 +1,26 @@ +{ + "CurrentRecordCount": 4, + "NextPageUrl": "", + "PageLength": 50, + "PageNumber": 1, + "PrevPageUrl": "", + "RecordCount": 1, + "WeightDataList": [ + { + "BMI": 22.56052398681641, + "BoneValue": 0, + "DCI": 0, + "DataID": "b702a3a5e998f2fca268df6daaa69871", + "DataSource": "Manual", + "FatValue": 0, + "LastChangeTime": 1442520480, + "MDate": 1442498877, + "MuscaleValue": 0, + "Note": "Weight so good, look at me meow", + "TimeZone": "-0600", + "WaterValue": 0, + "WeightValue": 0 + } + ], + "WeightUnit": 0 +} \ No newline at end of file From ee6fc8a78c07870ebab060e215f44764f73e0675 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Mon, 28 Sep 2015 17:19:04 -0600 Subject: [PATCH 05/68] Add iHealth BMI mapper and tests --- .../IHealthBodyMassIndexDataPointMapper.java | 64 ++++++++++ ...BodyMassIndexDataPointMapperUnitTests.java | 112 ++++++++++++++++++ .../ihealth-missing-body-weight-value.json | 2 +- 3 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java create mode 100644 shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java new file mode 100644 index 00000000..dd4fde35 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java @@ -0,0 +1,64 @@ +/* + * 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.ihealth.mapper; + +import com.fasterxml.jackson.databind.JsonNode; +import org.openmhealth.schema.domain.omh.BodyMassIndex; +import org.openmhealth.schema.domain.omh.DataPoint; +import org.openmhealth.schema.domain.omh.TypedUnitValue; + +import java.util.Optional; + +import static org.openmhealth.schema.domain.omh.BodyMassIndexUnit.KILOGRAMS_PER_SQUARE_METER; +import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredDouble; + + +/** + * @author Chris Schaefbauer + */ +public class IHealthBodyMassIndexDataPointMapper extends IHealthDataPointMapper{ + + @Override + protected String getListNodeName() { + return "WeightDataList"; + } + + @Override + protected String getUnitPropertyNameForMeasure() { + return "WeightUnit"; + } + + @Override + protected Optional> asDataPoint(JsonNode listNode, int measureUnit) { + + Double bmiValue = asRequiredDouble(listNode, "BMI"); + + if(bmiValue == 0){ + return Optional.empty(); + } + + BodyMassIndex.Builder bodyMassIndexBuilder = new BodyMassIndex.Builder(new TypedUnitValue<>(KILOGRAMS_PER_SQUARE_METER, + bmiValue)); + + setEffectiveTimeFrameIfExists(listNode,bodyMassIndexBuilder); + setUserNoteIfExists(listNode,bodyMassIndexBuilder); + + BodyMassIndex bodyMassIndex = bodyMassIndexBuilder.build(); + return Optional.of(new DataPoint<>(createDataPointHeader(listNode,bodyMassIndex),bodyMassIndex)); + + } +} diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java new file mode 100644 index 00000000..f3f75ac1 --- /dev/null +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java @@ -0,0 +1,112 @@ +/* + * 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.ihealth.mapper; + +import com.fasterxml.jackson.databind.JsonNode; +import org.openmhealth.schema.domain.omh.*; +import org.springframework.core.io.ClassPathResource; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.time.OffsetDateTime; +import java.util.List; + +import static java.util.Collections.singletonList; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.openmhealth.schema.domain.omh.BodyMassIndex.*; +import static org.openmhealth.schema.domain.omh.DataPointModality.*; + + +/** + * @author Chris Schaefbauer + */ +public class IHealthBodyMassIndexDataPointMapperUnitTests extends IHealthDataPointMapperUnitTests { + + private JsonNode responseNode; + private IHealthBodyMassIndexDataPointMapper mapper = new IHealthBodyMassIndexDataPointMapper(); + + @BeforeTest + public void initializeResponse() throws IOException { + + ClassPathResource resource = + new ClassPathResource("org/openmhealth/shim/ihealth/mapper/ihealth-body-weight.json"); + responseNode = objectMapper.readTree(resource.getInputStream()); + } + + @Test + public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + assertThat(dataPoints.size(), equalTo(2)); + } + + @Test + public void asDataPointsShouldReturnCorrectSensedDataPoints() { + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + + BodyMassIndex.Builder expectedBodyMassIndexBuilder = new BodyMassIndex.Builder(new TypedUnitValue<>( + BodyMassIndexUnit.KILOGRAMS_PER_SQUARE_METER, 22.56052563257619)) + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T04:04:09-08:00")); + + assertThat(dataPoints.get(0).getBody(), equalTo(expectedBodyMassIndexBuilder.build())); + + testDataPointHeader(dataPoints.get(0).getHeader(), SCHEMA_ID, SENSED, "5fe5893c418b48cd8da7954f8b6c2f36", + OffsetDateTime.parse("2015-09-17T20:04:17Z")); + } + + @Test + public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + + BodyMassIndex.Builder expectedBodyMassIndexBuilder = new BodyMassIndex.Builder( + new TypedUnitValue<>(BodyMassIndexUnit.KILOGRAMS_PER_SQUARE_METER, 22.56052398681641)) + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T08:07:57-06:00")) + .setUserNotes("Weight so good, look at me now"); + + assertThat(dataPoints.get(1).getBody(), equalTo(expectedBodyMassIndexBuilder.build())); + + testDataPointHeader(dataPoints.get(1).getHeader(), SCHEMA_ID, SELF_REPORTED, + "b702a3a5e998f2fca268df6daaa69871", OffsetDateTime.parse("2015-09-17T20:08:00Z")); + } + + @Test + public void asDataPointsShouldReturnNoDataPointsWhenBodyMassIndexValueIsZero() throws IOException { + + ClassPathResource resource = + new ClassPathResource("org/openmhealth/shim/ihealth/mapper/ihealth-missing-body-weight-value.json"); + JsonNode zeroValueNode = objectMapper.readTree(resource.getInputStream()); + + List> dataPoints = mapper.asDataPoints(singletonList(zeroValueNode)); + assertThat(dataPoints.size(), equalTo(0)); + } + + @Test + public void asDataPointsShouldReturnNoDataPointsWhenWeightListIsEmpty() throws IOException { + + ClassPathResource resource = + new ClassPathResource("org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight-list.json"); + JsonNode emptyListNode = objectMapper.readTree(resource.getInputStream()); + + List> dataPoints = mapper.asDataPoints(singletonList(emptyListNode)); + assertThat(dataPoints.size(),equalTo(0)); + } + +} diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-missing-body-weight-value.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-missing-body-weight-value.json index b02b709a..89955bb3 100644 --- a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-missing-body-weight-value.json +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-missing-body-weight-value.json @@ -7,7 +7,7 @@ "RecordCount": 1, "WeightDataList": [ { - "BMI": 22.56052398681641, + "BMI": 0, "BoneValue": 0, "DCI": 0, "DataID": "b702a3a5e998f2fca268df6daaa69871", From 2aa7bdb9796853d7863f8c510e93615f2746108f Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Tue, 29 Sep 2015 13:21:52 -0600 Subject: [PATCH 06/68] Add iHealth blood glucose mapper and tests --- .../IHealthBloodGlucoseDataPointMapper.java | 140 ++++++++++++++++++ .../IHealthBloodPressureDataPointMapper.java | 14 +- .../IHealthBodyWeightDataPointMapper.java | 14 +- .../mapper/IHealthDataPointMapper.java | 3 +- ...hBloodGlucoseDataPointMapperUnitTests.java | 127 ++++++++++++++++ ...BloodPressureDataPointMapperUnitTests.java | 8 +- ...BodyMassIndexDataPointMapperUnitTests.java | 2 +- ...lthBodyWeightDataPointMapperUnitTests.java | 16 +- .../ihealth-blood-glucose-empty-list.json | 10 ++ .../ihealth/mapper/ihealth-blood-glucose.json | 37 +++++ ...on => ihealth-body-weight-empty-list.json} | 0 11 files changed, 343 insertions(+), 28 deletions(-) create mode 100644 shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java create mode 100644 shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java create mode 100644 shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose-empty-list.json create mode 100644 shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose.json rename shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/{ihealth-empty-body-weight-list.json => ihealth-body-weight-empty-list.json} (100%) diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java new file mode 100644 index 00000000..2a7aa078 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java @@ -0,0 +1,140 @@ +/* + * 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.ihealth.mapper; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.ImmutableMap; +import org.openmhealth.schema.domain.omh.*; + +import java.util.Optional; + +import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asOptionalString; +import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredDouble; + + +/** + * @author Chris Schaefbauer + */ +public class IHealthBloodGlucoseDataPointMapper extends IHealthDataPointMapper { + + protected static ImmutableMap iHealthBloodGlucoseRelationshipToMeal; + + @Override + protected String getListNodeName() { + return "BGDataList"; + } + + @Override + protected String getUnitPropertyNameForMeasure() { + return "BGUnit"; + } + + public IHealthBloodGlucoseDataPointMapper() { + + initializeTemporalRelationshipToFoodMap(); + } + + @Override + protected Optional> asDataPoint(JsonNode listNode, int measureUnit) { + + double bloodGlucoseValue = asRequiredDouble(listNode, "BG"); + if (bloodGlucoseValue == 0) { + + return Optional.empty(); + } + + BloodGlucoseUnit bloodGlucoseUnit = + IHealthBloodGlucoseUnit.fromIHealthMagicNumber(measureUnit).getBloodGlucoseUnit(); + + BloodGlucose.Builder bloodGlucoseBuilder = + new BloodGlucose.Builder(new TypedUnitValue<>(bloodGlucoseUnit, bloodGlucoseValue)); + + + Optional dinnerSituation = asOptionalString(listNode, "DinnerSituation"); + + if (dinnerSituation.isPresent()) { + + TemporalRelationshipToMeal temporalRelationshipToMeal = + iHealthBloodGlucoseRelationshipToMeal.get(dinnerSituation.get()); + + if (temporalRelationshipToMeal != null) { + bloodGlucoseBuilder.setTemporalRelationshipToMeal(temporalRelationshipToMeal); + } + } + + setEffectiveTimeFrameIfExists(listNode, bloodGlucoseBuilder); + setUserNoteIfExists(listNode, bloodGlucoseBuilder); + + BloodGlucose bloodGlucose = bloodGlucoseBuilder.build(); + + /* The "temporal_relationship_to_medication" property is not part of the Blood Glucose schema, so its name and + values may change or we may remove support for this property at any time. */ + asOptionalString(listNode, "DrugSituation").ifPresent( + drugSituation -> bloodGlucose + .setAdditionalProperty("temporal_relationship_to_medication", drugSituation)); + + return Optional.of(new DataPoint<>(createDataPointHeader(listNode, bloodGlucose), bloodGlucose)); + } + + private void initializeTemporalRelationshipToFoodMap() { + + ImmutableMap.Builder relationshipToMealMapBuilder = ImmutableMap.builder(); + + relationshipToMealMapBuilder.put("Before_breakfast", TemporalRelationshipToMeal.BEFORE_BREAKFAST) + .put("After_breakfast", TemporalRelationshipToMeal.AFTER_BREAKFAST) + .put("Before_lunch", TemporalRelationshipToMeal.BEFORE_LUNCH) + .put("After_lunch", TemporalRelationshipToMeal.AFTER_LUNCH) + .put("Before_dinner", TemporalRelationshipToMeal.BEFORE_DINNER) + .put("After_dinner", TemporalRelationshipToMeal.AFTER_DINNER) + .put("At_midnight", TemporalRelationshipToMeal.AFTER_DINNER); + + iHealthBloodGlucoseRelationshipToMeal = relationshipToMealMapBuilder.build(); + + } + + protected enum IHealthBloodGlucoseUnit { + + mgPerDl(0, BloodGlucoseUnit.MILLIGRAMS_PER_DECILITER), + mmolPerL(1, BloodGlucoseUnit.MILLIMOLES_PER_LITER); + + private int magicNumber; + private BloodGlucoseUnit bgUnit; + + IHealthBloodGlucoseUnit(int magicNumber, BloodGlucoseUnit bgUnit) { + + this.magicNumber = magicNumber; + this.bgUnit = bgUnit; + } + + protected BloodGlucoseUnit getBloodGlucoseUnit() { + return bgUnit; + } + + public static IHealthBloodGlucoseUnit fromIHealthMagicNumber(int magicNumberFromResponse) { + + for (IHealthBloodGlucoseUnit type : values()) { + if (type.magicNumber == magicNumberFromResponse) { + return type; + } + } + throw new UnsupportedOperationException(); + } + + } + + +} diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java index 6f00e05b..f3bba1e0 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java @@ -42,7 +42,7 @@ protected String getListNodeName() { @Override protected Optional> asDataPoint(JsonNode listNode, int bloodPressureUnit) { - BloodPressureUnitType bloodPressureUnitType = BloodPressureUnitType.fromIntegerValue(bloodPressureUnit); + IHealthBloodPressureUnit bloodPressureUnitType = IHealthBloodPressureUnit.fromIntegerValue(bloodPressureUnit); double systolicValue = getBloodPressureValueInMmHg(asRequiredDouble(listNode, "HP"), bloodPressureUnitType); SystolicBloodPressure systolicBloodPressure = @@ -62,7 +62,7 @@ protected Optional> asDataPoint(JsonNode listNode, int return Optional.of(new DataPoint<>(createDataPointHeader(listNode, bloodPressure), bloodPressure)); } - protected double getBloodPressureValueInMmHg(double rawBpValue, BloodPressureUnitType bloodPressureUnit) { + protected double getBloodPressureValueInMmHg(double rawBpValue, IHealthBloodPressureUnit bloodPressureUnit) { switch ( bloodPressureUnit ) { case mmHg: @@ -74,14 +74,14 @@ protected double getBloodPressureValueInMmHg(double rawBpValue, BloodPressureUni } } - protected enum BloodPressureUnitType { + protected enum IHealthBloodPressureUnit { mmHg(0), KPa(1); private int value; - BloodPressureUnitType(int value) { + IHealthBloodPressureUnit(int value) { this.value = value; } @@ -89,12 +89,14 @@ protected int getValue() { return value; } - public static BloodPressureUnitType fromIntegerValue(int bpIntValue) { - for (BloodPressureUnitType type : values()) { + public static IHealthBloodPressureUnit fromIntegerValue(int bpIntValue) { + + for (IHealthBloodPressureUnit type : values()) { if (type.getValue() == bpIntValue) { return type; } } + throw new UnsupportedOperationException(); } } diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java index aa8141b2..57a15f1f 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java @@ -48,7 +48,7 @@ protected String getUnitPropertyNameForMeasure() { @Override protected Optional> asDataPoint(JsonNode listNode, int measureUnit) { - BodyWeightUnitType bodyWeightUnitType = BodyWeightUnitType.fromIntegerValue(measureUnit); + IHealthBodyWeightUnit bodyWeightUnitType = IHealthBodyWeightUnit.fromIntegerValue(measureUnit); MassUnit bodyWeightUnit = bodyWeightUnitType.getOmhUnit(); double bodyWeightValue = getBodyWeightValueForUnitType(listNode, bodyWeightUnitType); @@ -70,18 +70,18 @@ protected Optional> asDataPoint(JsonNode listNode, int mea } protected double getBodyWeightValueForUnitType(JsonNode listNode, - BodyWeightUnitType bodyWeightUnitType) { + IHealthBodyWeightUnit bodyWeightUnitType) { Double weightValueFromApi = asRequiredDouble(listNode, "WeightValue"); return getBodyWeightValueForUnitType(weightValueFromApi, bodyWeightUnitType); } - protected double getBodyWeightValueForUnitType(double bodyWeightValue, BodyWeightUnitType bodyWeightUnitType) { + protected double getBodyWeightValueForUnitType(double bodyWeightValue, IHealthBodyWeightUnit bodyWeightUnitType) { return bodyWeightValue * bodyWeightUnitType.getConversionFactorToOmh(); } - enum BodyWeightUnitType { + enum IHealthBodyWeightUnit { /* The conversion factor handles conversions from unsupported mass units (currently the 'Stone' unit) to omh @@ -100,7 +100,7 @@ The conversion factor handles conversions from unsupported mass units (currently private final double conversionFactorToOmh; private int magicNumber; - BodyWeightUnitType(int magicNumber, MassUnit omhUnit, double conversionFactor) { + IHealthBodyWeightUnit(int magicNumber, MassUnit omhUnit, double conversionFactor) { this.omhUnit = omhUnit; this.conversionFactorToOmh = conversionFactor; this.magicNumber = magicNumber; @@ -114,9 +114,9 @@ public double getConversionFactorToOmh() { return conversionFactorToOmh; } - public static BodyWeightUnitType fromIntegerValue(int unitValueFromApi) { + public static IHealthBodyWeightUnit fromIntegerValue(int unitValueFromApi) { - for (BodyWeightUnitType type : values()) { + for (IHealthBodyWeightUnit type : values()) { if (type.magicNumber == unitValueFromApi) { return type; } diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java index d6638fcc..4f60a088 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java @@ -52,11 +52,10 @@ public List> asDataPoints(List responseNodes) { for (JsonNode listNode : asRequiredNode(responseNode, getListNodeName())) { - asDataPoint(listNode, measureUnit).ifPresent(dp -> dataPoints.add(dp)); + asDataPoint(listNode, measureUnit).ifPresent(dataPoints::add); } return dataPoints; - // return Collections.singletonList(asDataPoint(responseNode,measureUnit)); } protected DataPointHeader createDataPointHeader(JsonNode listNode, Measure measure) { diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java new file mode 100644 index 00000000..cb038e59 --- /dev/null +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java @@ -0,0 +1,127 @@ +/* + * 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.ihealth.mapper; + +import com.fasterxml.jackson.databind.JsonNode; +import org.openmhealth.schema.domain.omh.*; +import org.springframework.core.io.ClassPathResource; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.time.OffsetDateTime; +import java.util.List; + +import static java.util.Collections.singletonList; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.openmhealth.schema.domain.omh.BloodGlucose.SCHEMA_ID; +import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; +import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; +import static org.openmhealth.shim.ihealth.mapper.IHealthBloodGlucoseDataPointMapper.IHealthBloodGlucoseUnit; + + +/** + * @author Chris Schaefbauer + */ +public class IHealthBloodGlucoseDataPointMapperUnitTests extends IHealthDataPointMapperUnitTests { + + private JsonNode responseNode; + private IHealthBloodGlucoseDataPointMapper mapper = new IHealthBloodGlucoseDataPointMapper(); + + @BeforeTest + public void initializeResponseNode() throws IOException { + + ClassPathResource resource = + new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose.json"); + responseNode = objectMapper.readTree(resource.getInputStream()); + } + + @Test + public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + assertThat(dataPoints.size(), equalTo(2)); + } + + @Test + public void asDataPointsShouldReturnCorrectSensedDataPoints() { + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + + BloodGlucose.Builder expectedBloodGlucoseBuilder = new BloodGlucose.Builder( + new TypedUnitValue<>(BloodGlucoseUnit.MILLIGRAMS_PER_DECILITER, 60)) + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T04:03:27-08:00")) + .setTemporalRelationshipToMeal(TemporalRelationshipToMeal.BEFORE_BREAKFAST) + .setUserNotes("Such glucose, much blood."); + + assertThat(dataPoints.get(0).getBody(), equalTo(expectedBloodGlucoseBuilder.build())); + + assertThat(dataPoints.get(0).getBody().getAdditionalProperty("temporal_relationship_to_medication").get(), + equalTo("Before_taking_pills")); + + testDataPointHeader(dataPoints.get(0).getHeader(), SCHEMA_ID, SENSED, + "f706b6152f684c0e9185b1fa6b7c5148", OffsetDateTime.parse("2015-09-17T20:03:41Z")); + } + + @Test + public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + + BloodGlucose.Builder expectedBloodGlucoseBuilder = + new BloodGlucose.Builder(new TypedUnitValue<>(BloodGlucoseUnit.MILLIGRAMS_PER_DECILITER, 70)) + .setTemporalRelationshipToMeal(TemporalRelationshipToMeal.AFTER_BREAKFAST) + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-24T08:44:40-06:00")); + + assertThat(dataPoints.get(1).getBody(), equalTo(expectedBloodGlucoseBuilder.build())); + + assertThat(dataPoints.get(1).getBody().getAdditionalProperty("temporal_relationship_to_medication").get(), + equalTo("After_taking_pills")); + + assertThat(dataPoints.get(1).getHeader().getAcquisitionProvenance().getModality(), equalTo(SELF_REPORTED)); + } + + @Test + public void asDataPointsShouldReturnNoDataPointsWhenBloodGlucoseListIsEmpty() throws IOException { + + ClassPathResource resource = + new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose-empty-list.json"); + JsonNode emptyListResponseNode = objectMapper.readTree(resource.getInputStream()); + + List> dataPoints = mapper.asDataPoints(singletonList(emptyListResponseNode)); + assertThat(dataPoints.size(), equalTo(0)); + } + + @Test + public void iHealthBloodGlucoseUnitEnumShouldReturnCorrectOmhGlucoseUnit() { + + IHealthBloodGlucoseUnit iHealthBloodGlucoseUnit = + IHealthBloodGlucoseUnit.fromIHealthMagicNumber(0); + assertThat(iHealthBloodGlucoseUnit.getBloodGlucoseUnit(), equalTo(BloodGlucoseUnit.MILLIGRAMS_PER_DECILITER)); + + iHealthBloodGlucoseUnit = IHealthBloodGlucoseUnit.fromIHealthMagicNumber(1); + assertThat(iHealthBloodGlucoseUnit.getBloodGlucoseUnit(), equalTo(BloodGlucoseUnit.MILLIMOLES_PER_LITER)); + } + + @Test(expectedExceptions = UnsupportedOperationException.class) + public void iHealthBloodGlucoseUnitEnumShouldThrowExceptionWhenInvalidMagicNumber() { + + IHealthBloodGlucoseUnit.fromIHealthMagicNumber(5); + } + +} diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java index 59f14510..60d4b0bf 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java @@ -31,7 +31,7 @@ import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.openmhealth.schema.domain.omh.DataPointModality.*; -import static org.openmhealth.shim.ihealth.mapper.IHealthBloodPressureDataPointMapper.BloodPressureUnitType; +import static org.openmhealth.shim.ihealth.mapper.IHealthBloodPressureDataPointMapper.IHealthBloodPressureUnit; /** @@ -112,10 +112,10 @@ public void asDataPointsShouldReturnCorrectUserNotesWithDataPoints() { @Test public void getBloodPressureValueInMmHgReturnsCorrectValueForMmHgUnit() { - double bpValueFromMmHg = mapper.getBloodPressureValueInMmHg(120, BloodPressureUnitType.mmHg); + double bpValueFromMmHg = mapper.getBloodPressureValueInMmHg(120, IHealthBloodPressureUnit.mmHg); assertThat(bpValueFromMmHg, equalTo(120.0)); - double bpValueFromKpa = mapper.getBloodPressureValueInMmHg(16, BloodPressureUnitType.KPa); + double bpValueFromKpa = mapper.getBloodPressureValueInMmHg(16, IHealthBloodPressureUnit.KPa); assertThat(bpValueFromKpa, equalTo(120.009872)); } @@ -123,7 +123,7 @@ public void getBloodPressureValueInMmHgReturnsCorrectValueForMmHgUnit() { @Test(expectedExceptions = UnsupportedOperationException.class) public void getBloodPressureValueShouldThrowExceptionForInvalidEnum() { - mapper.getBloodPressureValueInMmHg(12, BloodPressureUnitType.fromIntegerValue(12)); + mapper.getBloodPressureValueInMmHg(12, IHealthBloodPressureUnit.fromIntegerValue(12)); } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java index f3f75ac1..8c7d56b1 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java @@ -102,7 +102,7 @@ public void asDataPointsShouldReturnNoDataPointsWhenBodyMassIndexValueIsZero() t public void asDataPointsShouldReturnNoDataPointsWhenWeightListIsEmpty() throws IOException { ClassPathResource resource = - new ClassPathResource("org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight-list.json"); + new ClassPathResource("org/openmhealth/shim/ihealth/mapper/ihealth-body-weight-empty-list.json"); JsonNode emptyListNode = objectMapper.readTree(resource.getInputStream()); List> dataPoints = mapper.asDataPoints(singletonList(emptyListNode)); diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java index 0a7fd377..c1c7e215 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java @@ -32,7 +32,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; -import static org.openmhealth.shim.ihealth.mapper.IHealthBodyWeightDataPointMapper.BodyWeightUnitType; +import static org.openmhealth.shim.ihealth.mapper.IHealthBodyWeightDataPointMapper.IHealthBodyWeightUnit; /** @@ -117,7 +117,7 @@ public void asDataPointsShouldReturnNoDataPointsWhenWeightValueEqualsZero() thro public void asDataPointsShouldReturnNoDataPointsWhenWeightListIsEmpty() throws IOException { ClassPathResource resource = - new ClassPathResource("org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight-list.json"); + new ClassPathResource("org/openmhealth/shim/ihealth/mapper/ihealth-body-weight-empty-list.json"); JsonNode emptyListNode = objectMapper.readTree(resource.getInputStream()); List> dataPoints = mapper.asDataPoints(singletonList(emptyListNode)); @@ -127,17 +127,17 @@ public void asDataPointsShouldReturnNoDataPointsWhenWeightListIsEmpty() throws I @Test public void getBodyWeightValueForUnitTypeShouldReturnCorrectValueForOmhCompatibleTypes() { - double bodyWeightValueForUnitType = mapper.getBodyWeightValueForUnitType(66.3, BodyWeightUnitType.kg); + double bodyWeightValueForUnitType = mapper.getBodyWeightValueForUnitType(66.3, IHealthBodyWeightUnit.kg); assertThat(bodyWeightValueForUnitType, equalTo(66.3)); - bodyWeightValueForUnitType = mapper.getBodyWeightValueForUnitType(100.5, BodyWeightUnitType.lb); + bodyWeightValueForUnitType = mapper.getBodyWeightValueForUnitType(100.5, IHealthBodyWeightUnit.lb); assertThat(bodyWeightValueForUnitType, equalTo(100.5)); } @Test public void getBodyWeightValueForUnitTypeShouldReturnCorrectValueForOmhIncompatibleTypes() { - double bodyWeightValueForUnitType = mapper.getBodyWeightValueForUnitType(12.4, BodyWeightUnitType.stone); + double bodyWeightValueForUnitType = mapper.getBodyWeightValueForUnitType(12.4, IHealthBodyWeightUnit.stone); assertThat(bodyWeightValueForUnitType, equalTo(78.74372)); } @@ -145,9 +145,9 @@ public void getBodyWeightValueForUnitTypeShouldReturnCorrectValueForOmhIncompati @Test public void getOmhUnitInBodyWeightUnitTypeShouldReturnCorrectMassUnits() { - assertThat(BodyWeightUnitType.kg.getOmhUnit(), equalTo(MassUnit.KILOGRAM)); - assertThat(BodyWeightUnitType.lb.getOmhUnit(), equalTo(MassUnit.POUND)); - assertThat(BodyWeightUnitType.stone.getOmhUnit(), equalTo(MassUnit.KILOGRAM)); + assertThat(IHealthBodyWeightUnit.kg.getOmhUnit(), equalTo(MassUnit.KILOGRAM)); + assertThat(IHealthBodyWeightUnit.lb.getOmhUnit(), equalTo(MassUnit.POUND)); + assertThat(IHealthBodyWeightUnit.stone.getOmhUnit(), equalTo(MassUnit.KILOGRAM)); } diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose-empty-list.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose-empty-list.json new file mode 100644 index 00000000..1f97ef02 --- /dev/null +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose-empty-list.json @@ -0,0 +1,10 @@ +{ + "BGDataList": [], + "BGUnit": 0, + "CurrentRecordCount": 0, + "NextPageUrl": "", + "PageLength": 50, + "PageNumber": 1, + "PrevPageUrl": "", + "RecordCount": 0 +} \ No newline at end of file diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose.json new file mode 100644 index 00000000..60ba0a06 --- /dev/null +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose.json @@ -0,0 +1,37 @@ +{ + "BGDataList": [ + { + "BG": 60, + "DataID": "f706b6152f684c0e9185b1fa6b7c5148", + "DataSource": "FromDevice", + "DinnerSituation": "Before_breakfast", + "DrugSituation": "Before_taking_pills", + "LastChangeTime": 1442520221, + "Lat": -1, + "Lon": -1, + "MDate": 1442491407, + "Note": "Such glucose, much blood.", + "TimeZone": "-0800" + }, + { + "BG": 70, + "DataID": "b0d61f8df4214f938a8d05d0ac583baa", + "DataSource": "Manual", + "DinnerSituation": "After_breakfast", + "DrugSituation": "After_taking_pills", + "LastChangeTime": 1443127602, + "Lat": -1, + "Lon": -1, + "MDate": 1443105880, + "Note": "", + "TimeZone": "-0600" + } + ], + "BGUnit": 0, + "CurrentRecordCount": 3, + "NextPageUrl": "", + "PageLength": 50, + "PageNumber": 1, + "PrevPageUrl": "", + "RecordCount": 3 +} \ No newline at end of file diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight-list.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-body-weight-empty-list.json similarity index 100% rename from shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight-list.json rename to shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-body-weight-empty-list.json From cc33791d0405890d1c1c8c2b2360501c84ffa58c Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Tue, 29 Sep 2015 19:59:12 -0600 Subject: [PATCH 07/68] Add iHealth physical activity mapper and tests --- .../IHealthBloodGlucoseDataPointMapper.java | 6 +- .../IHealthBloodPressureDataPointMapper.java | 4 +- .../IHealthBodyMassIndexDataPointMapper.java | 6 +- .../IHealthBodyWeightDataPointMapper.java | 6 +- .../mapper/IHealthDataPointMapper.java | 12 ++- ...HealthPhysicalActivityDataPointMapper.java | 81 +++++++++++++++++++ .../ihealth-sports-activity-empty-list.json | 9 +++ .../mapper/ihealth-sports-activity.json | 34 ++++++++ 8 files changed, 143 insertions(+), 15 deletions(-) create mode 100644 shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java create mode 100644 shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity-empty-list.json create mode 100644 shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity.json diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java index 2a7aa078..2c063cc9 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java @@ -39,8 +39,8 @@ protected String getListNodeName() { } @Override - protected String getUnitPropertyNameForMeasure() { - return "BGUnit"; + protected Optional getUnitPropertyNameForMeasure() { + return Optional.of("BGUnit"); } public IHealthBloodGlucoseDataPointMapper() { @@ -49,7 +49,7 @@ public IHealthBloodGlucoseDataPointMapper() { } @Override - protected Optional> asDataPoint(JsonNode listNode, int measureUnit) { + protected Optional> asDataPoint(JsonNode listNode, Integer measureUnit) { double bloodGlucoseValue = asRequiredDouble(listNode, "BG"); if (bloodGlucoseValue == 0) { diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java index f3bba1e0..c90717f0 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java @@ -37,10 +37,10 @@ protected String getListNodeName() { } @Override - protected String getUnitPropertyNameForMeasure() { return "BPUnit"; } + protected Optional getUnitPropertyNameForMeasure() { return Optional.of("BPUnit"); } @Override - protected Optional> asDataPoint(JsonNode listNode, int bloodPressureUnit) { + protected Optional> asDataPoint(JsonNode listNode, Integer bloodPressureUnit) { IHealthBloodPressureUnit bloodPressureUnitType = IHealthBloodPressureUnit.fromIntegerValue(bloodPressureUnit); diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java index dd4fde35..743fccf8 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java @@ -38,12 +38,12 @@ protected String getListNodeName() { } @Override - protected String getUnitPropertyNameForMeasure() { - return "WeightUnit"; + protected Optional getUnitPropertyNameForMeasure() { + return Optional.of("WeightUnit"); } @Override - protected Optional> asDataPoint(JsonNode listNode, int measureUnit) { + protected Optional> asDataPoint(JsonNode listNode, Integer measureUnit) { Double bmiValue = asRequiredDouble(listNode, "BMI"); diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java index 57a15f1f..8ab4503c 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java @@ -41,12 +41,12 @@ protected String getListNodeName() { } @Override - protected String getUnitPropertyNameForMeasure() { - return "WeightUnit"; + protected Optional getUnitPropertyNameForMeasure() { + return Optional.of("WeightUnit"); } @Override - protected Optional> asDataPoint(JsonNode listNode, int measureUnit) { + protected Optional> asDataPoint(JsonNode listNode, Integer measureUnit) { IHealthBodyWeightUnit bodyWeightUnitType = IHealthBodyWeightUnit.fromIntegerValue(measureUnit); MassUnit bodyWeightUnit = bodyWeightUnitType.getOmhUnit(); diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java index 4f60a088..14eeab27 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java @@ -48,11 +48,15 @@ public List> asDataPoints(List responseNodes) { List> dataPoints = Lists.newArrayList(); - int measureUnit = asRequiredInteger(responseNode, getUnitPropertyNameForMeasure()); + Optional measureUnit = Optional.empty(); + if(getUnitPropertyNameForMeasure().isPresent()){ + + measureUnit = asOptionalInteger(responseNode, getUnitPropertyNameForMeasure().get()); + } for (JsonNode listNode : asRequiredNode(responseNode, getListNodeName())) { - asDataPoint(listNode, measureUnit).ifPresent(dataPoints::add); + asDataPoint(listNode, measureUnit.orElse(null)).ifPresent(dataPoints::add); } return dataPoints; @@ -123,7 +127,7 @@ else if (dataSourceValue.equals(DATA_SOURCE_MANUAL)) { protected abstract String getListNodeName(); - protected abstract String getUnitPropertyNameForMeasure(); + protected abstract Optional getUnitPropertyNameForMeasure(); - protected abstract Optional> asDataPoint(JsonNode jsonNode, int measureUnit); + protected abstract Optional> asDataPoint(JsonNode jsonNode, Integer measureUnit); } diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java new file mode 100644 index 00000000..2e9bf732 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java @@ -0,0 +1,81 @@ +/* + * 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.ihealth.mapper; + +import com.fasterxml.jackson.databind.JsonNode; +import org.openmhealth.schema.domain.omh.DataPoint; +import org.openmhealth.schema.domain.omh.PhysicalActivity; +import org.openmhealth.schema.domain.omh.TimeInterval; + +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.util.Optional; + +import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asOptionalInteger; +import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asOptionalLong; +import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredString; + + +/** + * @author Chris Schaefbauer + */ +public class IHealthPhysicalActivityDataPointMapper extends IHealthDataPointMapper { + + @Override + protected String getListNodeName() { + + return "SPORTDataList"; + } + + @Override + protected Optional getUnitPropertyNameForMeasure() { + + return Optional.empty(); + } + + @Override + protected Optional> asDataPoint(JsonNode listNode, Integer measureUnit) { + + String activityName = asRequiredString(listNode, "SportName"); + + if (activityName.isEmpty()) { + return Optional.empty(); + } + + PhysicalActivity.Builder physicalActivityBuilder = new PhysicalActivity.Builder(activityName); + + Optional startTimeUnixEpochSecs = asOptionalLong(listNode, "SportStartTime"); + Optional endTimeUnixEpochSecs = asOptionalLong(listNode, "SportEndTime"); + Optional timeZoneOffset = asOptionalInteger(listNode, "TimeZone"); + + if (startTimeUnixEpochSecs.isPresent() && endTimeUnixEpochSecs.isPresent() && timeZoneOffset.isPresent()) { + + physicalActivityBuilder.setEffectiveTimeFrame(TimeInterval.ofStartDateTimeAndEndDateTime( + OffsetDateTime.ofInstant( + Instant.ofEpochSecond(startTimeUnixEpochSecs.get()), + ZoneId.ofOffset("UTC", ZoneOffset.ofHours(timeZoneOffset.get()))), + OffsetDateTime.ofInstant( + Instant.ofEpochSecond(endTimeUnixEpochSecs.get()), + ZoneId.ofOffset("UTC", ZoneOffset.ofHours(timeZoneOffset.get()))))); + } + + PhysicalActivity physicalActivity = physicalActivityBuilder.build(); + return Optional.of(new DataPoint<>(createDataPointHeader(listNode, physicalActivity), physicalActivity)); + } +} diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity-empty-list.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity-empty-list.json new file mode 100644 index 00000000..33b065b7 --- /dev/null +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity-empty-list.json @@ -0,0 +1,9 @@ +{ + "CurrentRecordCount": 0, + "NextPageUrl": "", + "PageLength": 50, + "PageNumber": 1, + "PrevPageUrl": "", + "RecordCount": 0, + "SPORTDataList": [] +} \ No newline at end of file diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity.json new file mode 100644 index 00000000..8eed28b3 --- /dev/null +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity.json @@ -0,0 +1,34 @@ +{ + "CurrentRecordCount": 2, + "NextPageUrl": "", + "PageLength": 50, + "PageNumber": 1, + "PrevPageUrl": "", + "RecordCount": 2, + "SPORTDataList": [ + { + "Calories": 221.5, + "DataID": "3f8770f51cc84957a57d20f4fee1f34b", + "DataSource": "FromDevice", + "LastChangeTime": 1442520177, + "Lat": -1, + "Lon": -1, + "SportEndTime": 1442521948, + "SportName": "Swimming, breaststroke", + "SportStartTime": 1442520148, + "TimeZone": -8 + }, + { + "Calories": 202.5, + "DataID": "4bd1fdab8bab4e15950b7a95c1fff7ea", + "DataSource": "Manual", + "LastChangeTime": 1443127405, + "Lat": -1, + "Lon": -1, + "SportEndTime": 1442956383, + "SportName": "Running", + "SportStartTime": 1442954583, + "TimeZone": -6 + } + ] +} \ No newline at end of file From 09ef194b542609fb2784d1169b0b3f566e027550 Mon Sep 17 00:00:00 2001 From: Jasper Speicher Date: Thu, 1 Oct 2015 13:28:10 -0700 Subject: [PATCH 08/68] Add a view and controller in the console showing a nice message at the end of the authorization flow. --- shim-server-ui/app/scripts/app.js | 4 ++++ shim-server-ui/app/scripts/controllers/main.js | 5 +++++ shim-server-ui/app/styles/main.css | 6 ++++++ shim-server-ui/app/views/authorizationComplete.html | 6 ++++++ 4 files changed, 21 insertions(+) create mode 100644 shim-server-ui/app/views/authorizationComplete.html diff --git a/shim-server-ui/app/scripts/app.js b/shim-server-ui/app/scripts/app.js index 9e71d1eb..7045674c 100644 --- a/shim-server-ui/app/scripts/app.js +++ b/shim-server-ui/app/scripts/app.js @@ -14,6 +14,10 @@ angular templateUrl: 'views/main.html', controller: 'MainCtrl' }) + .when('/authorizationComplete', { + templateUrl: 'views/authorizationComplete.html', + controller: 'AuthorizationCompleteCtrl' + }) .otherwise({ redirectTo: '/' }); diff --git a/shim-server-ui/app/scripts/controllers/main.js b/shim-server-ui/app/scripts/controllers/main.js index 8f49c8cd..9ef8ce61 100644 --- a/shim-server-ui/app/scripts/controllers/main.js +++ b/shim-server-ui/app/scripts/controllers/main.js @@ -4,6 +4,11 @@ * Simple UI for managing shim data on the shim server. */ angular.module('sandboxConsoleApp') + .controller('AuthorizationCompleteCtrl', ['$scope', '$http', '$window', '$timeout', function ($scope, $http, $window, $timeout) { + $timeout(function() { + $window.close(); + }, 3000); + }]) .controller('MainCtrl', ['$scope', '$http', '$window', function ($scope, $http, $window) { var API_ROOT_URL = "/omh-shims-api"; diff --git a/shim-server-ui/app/styles/main.css b/shim-server-ui/app/styles/main.css index 8211e88b..7eda3640 100644 --- a/shim-server-ui/app/styles/main.css +++ b/shim-server-ui/app/styles/main.css @@ -166,4 +166,10 @@ body { .jumbotron { border-bottom: 0; } + + .authorization-complete-message { + padding: 40px; + text-align: center; + } + } \ No newline at end of file diff --git a/shim-server-ui/app/views/authorizationComplete.html b/shim-server-ui/app/views/authorizationComplete.html new file mode 100644 index 00000000..453e7722 --- /dev/null +++ b/shim-server-ui/app/views/authorizationComplete.html @@ -0,0 +1,6 @@ +
+
+ AuthorizationComplete.
+ This window should close automatically.
+
+
\ No newline at end of file From b7179a22d5ac32df1add8d3a7804d344aecbea01 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Thu, 1 Oct 2015 14:31:50 -0600 Subject: [PATCH 09/68] Add redirect to auth complete route after auth --- .../main/java/org/openmhealth/shim/Application.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/shim-server/src/main/java/org/openmhealth/shim/Application.java b/shim-server/src/main/java/org/openmhealth/shim/Application.java index e8f7ec6d..829bec9e 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/Application.java +++ b/shim-server/src/main/java/org/openmhealth/shim/Application.java @@ -56,6 +56,7 @@ @RestController public class Application extends WebSecurityConfigurerAdapter { + private static final String AUTH_COMPLETE_URL = "/authorizationComplete"; @Autowired private AccessParametersRepo accessParametersRepo; @@ -266,6 +267,15 @@ public AuthorizationResponse approve( } return null; } + + try{ + servletResponse.sendRedirect(AUTH_COMPLETE_URL); + } + catch (IOException e) { + e.printStackTrace(); + throw new ShimException("Error occurred in redirecting to completion URL"); + } + return response; } } From 85f0f0168adb49c3853ad1d6a3c66adfa8f25729 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Thu, 1 Oct 2015 14:45:35 -0600 Subject: [PATCH 10/68] Redirect URI change --- shim-server/src/main/java/org/openmhealth/shim/Application.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/Application.java b/shim-server/src/main/java/org/openmhealth/shim/Application.java index 829bec9e..7248272b 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/Application.java +++ b/shim-server/src/main/java/org/openmhealth/shim/Application.java @@ -56,7 +56,7 @@ @RestController public class Application extends WebSecurityConfigurerAdapter { - private static final String AUTH_COMPLETE_URL = "/authorizationComplete"; + private static final String AUTH_COMPLETE_URL = "/#authorizationComplete"; @Autowired private AccessParametersRepo accessParametersRepo; From 332ab2c00b984499f178ea0bf99fe0cb1d826c0c Mon Sep 17 00:00:00 2001 From: Jasper Speicher Date: Thu, 1 Oct 2015 14:03:08 -0700 Subject: [PATCH 11/68] Add error states to authorization complete page in console --- shim-server-ui/app/scripts/app.js | 2 +- shim-server-ui/app/scripts/controllers/main.js | 3 ++- shim-server-ui/app/views/authorizationComplete.html | 7 +++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/shim-server-ui/app/scripts/app.js b/shim-server-ui/app/scripts/app.js index 7045674c..104ba81e 100644 --- a/shim-server-ui/app/scripts/app.js +++ b/shim-server-ui/app/scripts/app.js @@ -14,7 +14,7 @@ angular templateUrl: 'views/main.html', controller: 'MainCtrl' }) - .when('/authorizationComplete', { + .when('/authorizationComplete/:errorState', { templateUrl: 'views/authorizationComplete.html', controller: 'AuthorizationCompleteCtrl' }) diff --git a/shim-server-ui/app/scripts/controllers/main.js b/shim-server-ui/app/scripts/controllers/main.js index 9ef8ce61..04b382f5 100644 --- a/shim-server-ui/app/scripts/controllers/main.js +++ b/shim-server-ui/app/scripts/controllers/main.js @@ -4,10 +4,11 @@ * Simple UI for managing shim data on the shim server. */ angular.module('sandboxConsoleApp') - .controller('AuthorizationCompleteCtrl', ['$scope', '$http', '$window', '$timeout', function ($scope, $http, $window, $timeout) { + .controller('AuthorizationCompleteCtrl', ['$scope', '$http', '$window', '$timeout','$routeParams', function ($scope, $http, $window, $timeout, $routeParams) { $timeout(function() { $window.close(); }, 3000); + $scope.error = $routeParams.errorState=='failure'; }]) .controller('MainCtrl', ['$scope', '$http', '$window', function ($scope, $http, $window) { diff --git a/shim-server-ui/app/views/authorizationComplete.html b/shim-server-ui/app/views/authorizationComplete.html index 453e7722..405f60c8 100644 --- a/shim-server-ui/app/views/authorizationComplete.html +++ b/shim-server-ui/app/views/authorizationComplete.html @@ -1,6 +1,9 @@
- AuthorizationComplete.
- This window should close automatically.
+ We're sorry, + Great, + authorization was not successful.
+
+ This window should now close automatically.
\ No newline at end of file From 0e06b739e318d2baacd23ee6e5b1161ee0f09215 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Thu, 1 Oct 2015 15:03:52 -0600 Subject: [PATCH 12/68] Add separate redirect for success and failure of auth --- .../main/java/org/openmhealth/shim/Application.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/Application.java b/shim-server/src/main/java/org/openmhealth/shim/Application.java index 7248272b..bcbb247a 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/Application.java +++ b/shim-server/src/main/java/org/openmhealth/shim/Application.java @@ -56,7 +56,8 @@ @RestController public class Application extends WebSecurityConfigurerAdapter { - private static final String AUTH_COMPLETE_URL = "/#authorizationComplete"; + private static final String AUTH_SUCCESS_URL = "/#authorizationComplete/success"; + private static final String AUTH_FAILURE_URL = "/#authorizationComplete/failure"; @Autowired private AccessParametersRepo accessParametersRepo; @@ -268,8 +269,14 @@ public AuthorizationResponse approve( return null; } + String authorizationStatusURL = AUTH_SUCCESS_URL; + if(response.getAccessParameters().getAccessToken()==null){ + + authorizationStatusURL = AUTH_FAILURE_URL; + } + try{ - servletResponse.sendRedirect(AUTH_COMPLETE_URL); + servletResponse.sendRedirect(authorizationStatusURL); } catch (IOException e) { e.printStackTrace(); From 9ab00b3e1f88f3dc282c6e39ead3ec07ec17d0f1 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Thu, 1 Oct 2015 16:00:45 -0600 Subject: [PATCH 13/68] Connect physical activity mapper to iHealth shim --- .../openmhealth/shim/ihealth/IHealthShim.java | 343 +++++++++++++++ .../shim/ihealth/IHealthShim.java.txt | 414 ------------------ .../src/main/resources/application.yaml | 4 +- ...sicalActivityDataPointMapperUnitTests.java | 104 +++++ .../mapper/ihealth-heart-rate-from-bp.json | 56 +++ .../mapper/ihealth-heart-rate-from-spo2.json | 34 ++ 6 files changed, 540 insertions(+), 415 deletions(-) create mode 100644 shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java delete mode 100644 shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java.txt create mode 100644 shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java create mode 100644 shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-bp.json create mode 100644 shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-spo2.json diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java new file mode 100644 index 00000000..16c4a659 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java @@ -0,0 +1,343 @@ +package org.openmhealth.shim.ihealth; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import org.openmhealth.shim.*; +import org.openmhealth.shim.ihealth.mapper.*; +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.security.oauth2.client.OAuth2RestOperations; +import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; +import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException; +import org.springframework.security.oauth2.client.token.AccessTokenRequest; +import org.springframework.security.oauth2.client.token.RequestEnhancer; +import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider; +import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; +import org.springframework.security.oauth2.common.AuthenticationScheme; +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; +import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.util.SerializationUtils; +import org.springframework.stereotype.Component; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.client.ResponseExtractor; +import org.springframework.web.util.UriComponentsBuilder; + +import java.io.IOException; +import java.net.URI; +import java.time.OffsetDateTime; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import static java.util.Collections.singletonList; +import static org.slf4j.LoggerFactory.getLogger; + + +@Component +@ConfigurationProperties(prefix = "openmhealth.shim.ihealth") +public class IHealthShim extends OAuth2ShimBase { + + public static final String SHIM_KEY = "ihealth"; + + private static final String API_URL = "https://api.ihealthlabs.com:8443/openapiv2/"; + + private static final String AUTHORIZE_URL = "https://api.ihealthlabs.com:8443/OpenApiV2/OAuthv2/userauthorization/"; + + private static final String TOKEN_URL = AUTHORIZE_URL; + + public static final List IHEALTH_SCOPES = Arrays.asList("OpenApiActivity", "OpenApiBP", "OpenApiSleep", + "OpenApiWeight", "OpenApiBG", "OpenApiSpO2", "OpenApiUserInfo", "OpenApiFood", "OpenApiSport"); + + private static final Logger logger = getLogger(IHealthShim.class); + + @Autowired + public IHealthShim(ApplicationAccessParametersRepo applicationParametersRepo, + AuthorizationRequestParametersRepo authorizationRequestParametersRepo, + AccessParametersRepo accessParametersRepo, + ShimServerConfig shimServerConfig) { + super(applicationParametersRepo, authorizationRequestParametersRepo, accessParametersRepo, shimServerConfig); + } + + @Override + public String getLabel() { + return "iHealth"; + } + + @Override + public String getShimKey() { + return SHIM_KEY; + } + + @Override + public String getBaseAuthorizeUrl() { + return AUTHORIZE_URL; + } + + @Override + public String getBaseTokenUrl() { + return TOKEN_URL; + } + + @Override + public List getScopes() { + return IHEALTH_SCOPES; + } + + @Override + public AuthorizationCodeAccessTokenProvider getAuthorizationCodeAccessTokenProvider() { + return new IHealthAuthorizationCodeAccessTokenProvider(); + } + + @Override + public ShimDataType[] getShimDataTypes() { + return new ShimDataType[] { + IHealthDataTypes.PHYSICAL_ACTIVITY, + IHealthDataTypes.BLOOD_GLUCOSE, + IHealthDataTypes.BLOOD_PRESSURE, + IHealthDataTypes.BODY_WEIGHT, + IHealthDataTypes.BODY_MASS_INDEX + }; + } + + @Value("${openmhealth.shim.ihealth.sportSC}") + public String sportSC; + @Value("${openmhealth.shim.ihealth.sportSV}") + public String sportSV; + + public enum IHealthDataTypes implements ShimDataType { + + PHYSICAL_ACTIVITY("sport.json"), + BLOOD_GLUCOSE("glucose.json"), + BLOOD_PRESSURE("bp.json"), + BODY_WEIGHT("weight.json"), + //SLEEP("sleep"), + //STEP_COUNT("activity"), + BODY_MASS_INDEX("weight.json"); + + private String endPoint; + + IHealthDataTypes(String endPoint) { + + this.endPoint = endPoint; + } + + public String getEndPoint() { + return endPoint; + } + + } + + @Override + protected ResponseEntity getData(OAuth2RestOperations restTemplate, + ShimDataRequest shimDataRequest) throws ShimException { + + final IHealthDataTypes dataType; + try { + dataType = IHealthDataTypes.valueOf( + shimDataRequest.getDataTypeKey().trim().toUpperCase()); + } + catch (NullPointerException | IllegalArgumentException e) { + throw new ShimException("Null or Invalid data type parameter: " + + shimDataRequest.getDataTypeKey() + + " in shimDataRequest, cannot retrieve data."); + } + + OffsetDateTime now = OffsetDateTime.now(); + OffsetDateTime startDate = shimDataRequest.getStartDateTime() == null ? + now.minusDays(1) : shimDataRequest.getStartDateTime(); + OffsetDateTime endDate = shimDataRequest.getEndDateTime() == null ? + now.plusDays(1) : shimDataRequest.getEndDateTime(); + + UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(API_URL); + String userId = "uk"; + if(shimDataRequest.getAccessParameters()!=null){ + OAuth2AccessToken token = SerializationUtils.deserialize(shimDataRequest.getAccessParameters().getSerializedToken());; + userId = Preconditions.checkNotNull((String) token.getAdditionalInformation().get("UserID")); +// uriBuilder.path("user") +// .path(userId); + uriBuilder.queryParam("access_token", token.getValue()); + } + + String scValue = getScValue(dataType); + String svValue = getSvValue(dataType); + + uriBuilder.path("/user/") + .path(userId+"/") + .path(dataType.getEndPoint()) + .queryParam("client_id", restTemplate.getResource().getClientId()) + .queryParam("client_secret",restTemplate.getResource().getClientSecret()) + .queryParam("start_time", startDate.toEpochSecond()) + .queryParam("end_time",endDate.toEpochSecond()) + .queryParam("locale","default") + .queryParam("sc",scValue) + .queryParam("sv",svValue); + +// String urlRequest = API_URL + "/user/" + userId + "/" + dataType.getEndPoint() + ".json?"; +// +// urlRequest += "&start_time=" + startDate.toEpochSecond(); +// urlRequest += "&end_time=" + endDate.toEpochSecond(); +// urlRequest += "&client_id=" + restTemplate.getResource().getClientId(); +// urlRequest += "&client_secret=" + restTemplate.getResource().getClientSecret(); +// urlRequest += "&access_token=" + token.getValue(); +// urlRequest += "&locale=default"; + + ResponseEntity responseEntity; + try{ + URI url = uriBuilder.build().encode().toUri(); + responseEntity = restTemplate.getForEntity(url, JsonNode.class); + } + catch (HttpClientErrorException | HttpServerErrorException e) { + // FIXME figure out how to handle this + logger.error("A request for iHealth data failed.", e); + throw e; + } + + + //ObjectMapper objectMapper = new ObjectMapper(); + + if (shimDataRequest.getNormalize()) { + // SimpleModule module = new SimpleModule(); + // module.addDeserializer(ShimDataResponse.class, dataType.getNormalizer()); + // objectMapper.registerModule(module); + IHealthDataPointMapper mapper; + + switch ( dataType ) { + + case PHYSICAL_ACTIVITY: + mapper = new IHealthPhysicalActivityDataPointMapper(); + break; + case BLOOD_GLUCOSE: + mapper = new IHealthBloodGlucoseDataPointMapper(); + break; + case BLOOD_PRESSURE: + mapper = new IHealthBloodPressureDataPointMapper(); + break; + case BODY_WEIGHT: + mapper = new IHealthBodyWeightDataPointMapper(); + break; + case BODY_MASS_INDEX: + mapper = new IHealthBloodPressureDataPointMapper(); + break; + default: + throw new UnsupportedOperationException(); + } + + + return ResponseEntity.ok().body( + ShimDataResponse.result(SHIM_KEY, mapper.asDataPoints(singletonList(responseEntity.getBody())))); + // return new ResponseEntity<>( + // objectMapper.readValue(responseEntity.getBody(), ShimDataResponse.class), + // HttpStatus.OK); + } + else { + + return ResponseEntity.ok().body(ShimDataResponse.result(SHIM_KEY, responseEntity.getBody())); + } + } + + private String getScValue(IHealthDataTypes dataType) { + + switch(dataType){ + case PHYSICAL_ACTIVITY: + return sportSC; + default: + throw new UnsupportedOperationException(); + } + } + + private String getSvValue(IHealthDataTypes dataType) { + + switch (dataType){ + + case PHYSICAL_ACTIVITY: + return sportSV; + default: + throw new UnsupportedOperationException(); + } + } + + // @Override +// public void trigger(OAuth2RestOperations restTemplate, ShimDataRequest shimDataRequest) throws ShimException { +// +// +// } + + @Override + public OAuth2ProtectedResourceDetails getResource() { + AuthorizationCodeResourceDetails resource = (AuthorizationCodeResourceDetails) super.getResource(); + resource.setAuthenticationScheme(AuthenticationScheme.none); + return resource; + } + + @Override + protected String getAuthorizationUrl(UserRedirectRequiredException exception) { + final OAuth2ProtectedResourceDetails resource = getResource(); + return exception.getRedirectUri() + + "?client_id=" + resource.getClientId() + + "&response_type=code" + + "&APIName=" + Joiner.on(' ').join(resource.getScope()) + + "&redirect_uri=" + getCallbackUrl() + "?state=" + exception.getStateKey(); + } + + public class IHealthAuthorizationCodeAccessTokenProvider extends AuthorizationCodeAccessTokenProvider { + + public IHealthAuthorizationCodeAccessTokenProvider() { + this.setTokenRequestEnhancer(new RequestEnhancer() { + + @Override + public void enhance(AccessTokenRequest request, + OAuth2ProtectedResourceDetails resource, + MultiValueMap form, HttpHeaders headers) { + + form.set("client_id", resource.getClientId()); + form.set("client_secret", resource.getClientSecret()); + form.set("redirect_uri", getCallbackUrl()); + form.set("state", request.getStateKey()); + } + }); + } + + @Override + protected HttpMethod getHttpMethod() { + return HttpMethod.GET; + } + + @Override + protected ResponseExtractor getResponseExtractor() { + return new ResponseExtractor() { + + @Override + public OAuth2AccessToken extractData(ClientHttpResponse response) throws IOException { + + JsonNode node = new ObjectMapper().readTree(response.getBody()); + String token = Preconditions + .checkNotNull(node.path("AccessToken").textValue(), "Missing access token: %s", node); + String refreshToken = Preconditions + .checkNotNull(node.path("RefreshToken").textValue(), "Missing refresh token: %s" + node); + String userId = + Preconditions.checkNotNull(node.path("UserID").textValue(), "Missing UserID: %s", node); + long expiresIn = node.path("Expires").longValue() * 1000; + Preconditions.checkArgument(expiresIn > 0, "Missing Expires: %s", node); + + DefaultOAuth2AccessToken accessToken = new DefaultOAuth2AccessToken(token); + accessToken.setExpiration(new Date(System.currentTimeMillis() + expiresIn)); + accessToken.setRefreshToken(new DefaultOAuth2RefreshToken(refreshToken)); + accessToken.setAdditionalInformation(ImmutableMap.of("UserID", userId)); + return accessToken; + } + }; + } + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java.txt b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java.txt deleted file mode 100644 index 1815fefe..00000000 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java.txt +++ /dev/null @@ -1,414 +0,0 @@ -package org.openmhealth.shim.ihealth; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; -import org.openmhealth.schema.pojos.Activity; -import org.openmhealth.schema.pojos.BloodGlucose; -import org.openmhealth.schema.pojos.BloodPressure; -import org.openmhealth.schema.pojos.BodyWeight; -import org.openmhealth.schema.pojos.DataPoint; -import org.openmhealth.schema.pojos.HeartRate; -import org.openmhealth.schema.pojos.SleepDuration; -import org.openmhealth.schema.pojos.StepCount; -import org.openmhealth.schema.pojos.build.ActivityBuilder; -import org.openmhealth.schema.pojos.build.BloodGlucoseBuilder; -import org.openmhealth.schema.pojos.build.BloodPressureBuilder; -import org.openmhealth.schema.pojos.build.BodyWeightBuilder; -import org.openmhealth.schema.pojos.build.HeartRateBuilder; -import org.openmhealth.schema.pojos.build.SleepDurationBuilder; -import org.openmhealth.schema.pojos.build.StepCountBuilder; -import org.openmhealth.schema.pojos.generic.DurationUnitValue; -import org.openmhealth.shim.AccessParametersRepo; -import org.openmhealth.shim.ApplicationAccessParametersRepo; -import org.openmhealth.shim.AuthorizationRequestParametersRepo; -import org.openmhealth.shim.OAuth2ShimBase; -import org.openmhealth.shim.ShimDataRequest; -import org.openmhealth.shim.ShimDataResponse; -import org.openmhealth.shim.ShimDataType; -import org.openmhealth.shim.ShimException; -import org.openmhealth.shim.ShimServerConfig; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.security.oauth2.client.OAuth2RestOperations; -import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; -import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException; -import org.springframework.security.oauth2.client.token.AccessTokenRequest; -import org.springframework.security.oauth2.client.token.RequestEnhancer; -import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider; -import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; -import org.springframework.security.oauth2.common.AuthenticationScheme; -import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; -import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken; -import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.common.util.SerializationUtils; -import org.springframework.stereotype.Component; -import org.springframework.util.MultiValueMap; -import org.springframework.web.client.ResponseExtractor; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.google.common.base.Joiner; -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableMap; - -@Component -@ConfigurationProperties(prefix = "openmhealth.shim.ihealth") -public class IHealthShim extends OAuth2ShimBase { - - public static final String SHIM_KEY = "ihealth"; - - private static final String API_URL = "https://api.ihealthlabs.com:8443/openapiv2"; - - private static final String AUTHORIZE_URL = "https://api.ihealthlabs.com:8443/OpenApiV2/OAuthv2/userauthorization/"; - - private static final String TOKEN_URL = AUTHORIZE_URL; - - public static final List IHEALTH_SCOPES = Arrays.asList("OpenApiActivity", "OpenApiBP", "OpenApiSleep", - "OpenApiWeight", "OpenApiBG", "OpenApiSpO2", "OpenApiUserInfo", "OpenApiFood", "OpenApiSport"); - - @Autowired - public IHealthShim(ApplicationAccessParametersRepo applicationParametersRepo, - AuthorizationRequestParametersRepo authorizationRequestParametersRepo, - AccessParametersRepo accessParametersRepo, - ShimServerConfig shimServerConfig) { - super(applicationParametersRepo, authorizationRequestParametersRepo, accessParametersRepo, shimServerConfig); - } - - @Override - public String getLabel() { - return "iHealth"; - } - - @Override - public String getShimKey() { - return SHIM_KEY; - } - - @Override - public String getBaseAuthorizeUrl() { - return AUTHORIZE_URL; - } - - @Override - public String getBaseTokenUrl() { - return TOKEN_URL; - } - - @Override - public List getScopes() { - return IHEALTH_SCOPES; - } - - @Override - public AuthorizationCodeAccessTokenProvider getAuthorizationCodeAccessTokenProvider() { - return new IHealthAuthorizationCodeAccessTokenProvider(); - } - - @Override - public ShimDataType[] getShimDataTypes() { - return new ShimDataType[] { - IHealthDataTypes.ACTIVITY, - IHealthDataTypes.BLOOD_GLUCOSE, - IHealthDataTypes.BLOOD_PRESSURE, - IHealthDataTypes.BODY_WEIGHT, - IHealthDataTypes.SLEEP, - IHealthDataTypes.STEP_COUNT - }; - } - - public enum IHealthDataTypes implements ShimDataType { - - ACTIVITY("sport", new JsonDeserializer() { - @Override - public ShimDataResponse deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) - throws IOException { - - JsonNode responseNode = jsonParser.getCodec().readTree(jsonParser); - if (responseNode.path("SPORTDataList").size() == 0) { - return ShimDataResponse.empty(IHealthShim.SHIM_KEY); - } - List activities = new ArrayList<>(); - for (JsonNode node : responseNode.path("SPORTDataList")) { - activities.add(new ActivityBuilder() - .withStartAndEnd(dateTimeValue(node.path("SportStartTime")), dateTimeValue(node.path("SportEndTime"))) - .setActivityName(textValue(node.path("SportName"))).build()); - } - Map results = new HashMap<>(); - results.put(Activity.SCHEMA_ACTIVITY, activities); - return ShimDataResponse.result(IHealthShim.SHIM_KEY, results); - } - }), - - BLOOD_GLUCOSE("glucose", new JsonDeserializer() { - @Override - public ShimDataResponse deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) - throws IOException { - - JsonNode responseNode = jsonParser.getCodec().readTree(jsonParser); - if (responseNode.path("BGDataList").size() == 0) { - return ShimDataResponse.empty(IHealthShim.SHIM_KEY); - } - List datapoints = new ArrayList<>(); - for (JsonNode node : responseNode.path("BGDataList")) { - datapoints.add(new BloodGlucoseBuilder().setTimeTaken(dateTimeValue(node.path("MDate"))) - .setMgdLValue(node.path("BG").decimalValue()) - .setNotes(textValue(node.path("Note"))).build()); - } - Map results = new HashMap<>(); - results.put(BloodGlucose.SCHEMA_BLOOD_GLUCOSE, datapoints); - return ShimDataResponse.result(IHealthShim.SHIM_KEY, results); - } - }), - - BLOOD_PRESSURE("bp", new JsonDeserializer() { - @Override - public ShimDataResponse deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) - throws IOException { - - JsonNode responseNode = jsonParser.getCodec().readTree(jsonParser); - if (responseNode.path("BPDataList").size() == 0) { - return ShimDataResponse.empty(IHealthShim.SHIM_KEY); - } - List bloodPressure = new ArrayList<>(); - List heartRate = new ArrayList<>(); - for (JsonNode node : responseNode.path("BPDataList")) { - DateTime start = dateTimeValue(node.path("MDate")); - bloodPressure.add(new BloodPressureBuilder().setTimeTaken(start) - .setValues(node.path("HP").decimalValue(), node.path("LP").decimalValue()) - .setNotes(textValue(node.path("Note"))).build()); - heartRate.add(new HeartRateBuilder().withTimeTaken(start) - .withRate(node.path("HR").intValue()).build()); - } - Map results = new HashMap<>(); - results.put(BloodPressure.SCHEMA_BLOOD_PRESSURE, bloodPressure); - results.put(HeartRate.SCHEMA_HEART_RATE, heartRate); - return ShimDataResponse.result(IHealthShim.SHIM_KEY, results); - } - }), - - BODY_WEIGHT("weight", new JsonDeserializer() { - @Override - public ShimDataResponse deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) - throws IOException { - - JsonNode responseNode = jsonParser.getCodec().readTree(jsonParser); - if (responseNode.path("WeightDataList").size() == 0) { - return ShimDataResponse.empty(IHealthShim.SHIM_KEY); - } - List datapoints = new ArrayList<>(); - for (JsonNode node : responseNode.path("WeightDataList")) { - datapoints.add(new BodyWeightBuilder().setTimeTaken(dateTimeValue(node.path("MDate"))) - .setWeight(node.path("WeightValue").asText(), "kg").build()); - } - Map results = new HashMap<>(); - results.put(BodyWeight.SCHEMA_BODY_WEIGHT, datapoints); - return ShimDataResponse.result(IHealthShim.SHIM_KEY, results); - } - }), - - SLEEP("sleep", new JsonDeserializer() { - @Override - public ShimDataResponse deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) - throws IOException { - - JsonNode responseNode = jsonParser.getCodec().readTree(jsonParser); - if (responseNode.path("SRDataList").size() == 0) { - return ShimDataResponse.empty(IHealthShim.SHIM_KEY); - } - List sleepDurations = new ArrayList<>(); - for (JsonNode node : responseNode.path("SRDataList")) { - DateTime start = dateTimeValue(node.path("StartTime")); - DateTime end = dateTimeValue(node.path("EndTime")); - sleepDurations.add(new SleepDurationBuilder().withStartAndEnd(start, end) - .setNotes(textValue(node.path("Note"))).build()); - } - Map results = new HashMap<>(); - results.put(SleepDuration.SCHEMA_SLEEP_DURATION, sleepDurations); - return ShimDataResponse.result(IHealthShim.SHIM_KEY, results); - } - }), - - STEP_COUNT("activity", new JsonDeserializer() { - @Override - public ShimDataResponse deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) - throws IOException { - - JsonNode responseNode = jsonParser.getCodec().readTree(jsonParser); - if (responseNode.path("ARDataList").size() == 0) { - return ShimDataResponse.empty(IHealthShim.SHIM_KEY); - } - List stepCounts = new ArrayList<>(); - for (JsonNode node : responseNode.path("ARDataList")) { - if (node.path("Steps").intValue() > 0) { - DateTime start = dateTimeValue(node.path("MDate")); - stepCounts.add(new StepCountBuilder() - .withStartAndDuration(start, 1.0, DurationUnitValue.DurationUnit.d) - .setSteps(node.path("Steps").intValue()).build()); - } - } - Map results = new HashMap<>(); - results.put(StepCount.SCHEMA_STEP_COUNT, stepCounts); - return ShimDataResponse.result(IHealthShim.SHIM_KEY, results); - } - }); - - private String endPoint; - - private JsonDeserializer normalizer; - - IHealthDataTypes(String endPoint, JsonDeserializer normalizer) { - this.endPoint = endPoint; - this.normalizer = normalizer; - } - - @Override - public JsonDeserializer getNormalizer() { - return normalizer; - } - - public String getEndPoint() { - return endPoint; - } - - private static DateTime dateTimeValue(JsonNode node) { - Preconditions.checkArgument(node.isIntegralNumber()); - return new DateTime(node.longValue() * 1000, DateTimeZone.UTC); - } - - private static String textValue(JsonNode node) { - Preconditions.checkArgument(node.isTextual()); - return Strings.emptyToNull(node.textValue().trim()); - } - } - - @Override - protected ResponseEntity getData(OAuth2RestOperations restTemplate, - ShimDataRequest shimDataRequest) throws ShimException { - - final IHealthDataTypes dataType; - try { - dataType = IHealthDataTypes.valueOf( - shimDataRequest.getDataTypeKey().trim().toUpperCase()); - } catch (NullPointerException | IllegalArgumentException e) { - throw new ShimException("Null or Invalid data type parameter: " - + shimDataRequest.getDataTypeKey() - + " in shimDataRequest, cannot retrieve data."); - } - - OAuth2AccessToken token = SerializationUtils.deserialize(shimDataRequest.getAccessParameters().getSerializedToken()); - String userId = Preconditions.checkNotNull((String) token.getAdditionalInformation().get("UserID")); - String urlRequest = API_URL + "/user/" + userId + "/" + dataType.getEndPoint() + ".json?"; - DateTime now = new DateTime(); - DateTime startDate = shimDataRequest.getStartDate() == null ? - now.minusDays(1) : shimDataRequest.getStartDate(); - DateTime endDate = shimDataRequest.getEndDate() == null ? - now.plusDays(1) : shimDataRequest.getEndDate(); - urlRequest += "&start_time=" + startDate.getMillis() / 1000; - urlRequest += "&end_time=" + endDate.getMillis() / 1000; - urlRequest += "&page_index=1"; - urlRequest += "&client_id=" + restTemplate.getResource().getClientId(); - urlRequest += "&client_secret=" + restTemplate.getResource().getClientSecret(); - urlRequest += "&access_token=" + token.getValue(); - urlRequest += "&locale=default"; - - ResponseEntity responseEntity = restTemplate.getForEntity(urlRequest, byte[].class); - - ObjectMapper objectMapper = new ObjectMapper(); - try { - if (shimDataRequest.getNormalize()) { - SimpleModule module = new SimpleModule(); - module.addDeserializer(ShimDataResponse.class, dataType.getNormalizer()); - objectMapper.registerModule(module); - return new ResponseEntity<>( - objectMapper.readValue(responseEntity.getBody(), ShimDataResponse.class), HttpStatus.OK); - } else { - return new ResponseEntity<>( - ShimDataResponse.result(IHealthShim.SHIM_KEY, objectMapper.readTree(responseEntity.getBody())), HttpStatus.OK); - } - } catch (IOException e) { - e.printStackTrace(); - throw new ShimException("Could not read response data."); - } - } - - @Override - public OAuth2ProtectedResourceDetails getResource() { - AuthorizationCodeResourceDetails resource = (AuthorizationCodeResourceDetails) super.getResource(); - resource.setAuthenticationScheme(AuthenticationScheme.none); - return resource; - } - - @Override - protected String getAuthorizationUrl(UserRedirectRequiredException exception) { - final OAuth2ProtectedResourceDetails resource = getResource(); - return exception.getRedirectUri() - + "?client_id=" + resource.getClientId() - + "&response_type=code" - + "&APIName=" + Joiner.on(' ').join(resource.getScope()) - + "&redirect_uri=" + getCallbackUrl() + "?state=" + exception.getStateKey(); - } - - public class IHealthAuthorizationCodeAccessTokenProvider extends AuthorizationCodeAccessTokenProvider { - - public IHealthAuthorizationCodeAccessTokenProvider() { - this.setTokenRequestEnhancer(new RequestEnhancer() { - - @Override - public void enhance(AccessTokenRequest request, - OAuth2ProtectedResourceDetails resource, - MultiValueMap form, HttpHeaders headers) { - - form.set("client_id", resource.getClientId()); - form.set("client_secret", resource.getClientSecret()); - form.set("redirect_uri", getCallbackUrl()); - form.set("state", request.getStateKey()); - } - }); - } - - @Override - protected HttpMethod getHttpMethod() { - return HttpMethod.GET; - } - - @Override - protected ResponseExtractor getResponseExtractor() { - return new ResponseExtractor() { - - @Override - public OAuth2AccessToken extractData(ClientHttpResponse response) throws IOException { - - JsonNode node = new ObjectMapper().readTree(response.getBody()); - String token = Preconditions.checkNotNull(node.path("AccessToken").textValue(), "Missing access token: %s", node); - String refreshToken = Preconditions.checkNotNull(node.path("RefreshToken").textValue(), "Missing refresh token: %s" + node); - String userId = Preconditions.checkNotNull(node.path("UserID").textValue(), "Missing UserID: %s", node); - long expiresIn = node.path("Expires").longValue() * 1000; - Preconditions.checkArgument(expiresIn > 0, "Missing Expires: %s", node); - - DefaultOAuth2AccessToken accessToken = new DefaultOAuth2AccessToken(token); - accessToken.setExpiration(new Date(System.currentTimeMillis() + expiresIn)); - accessToken.setRefreshToken(new DefaultOAuth2RefreshToken(refreshToken)); - accessToken.setAdditionalInformation(ImmutableMap.of("UserID", userId)); - return accessToken; - } - }; - } - } -} diff --git a/shim-server/src/main/resources/application.yaml b/shim-server/src/main/resources/application.yaml index a613f73d..f441cd3d 100644 --- a/shim-server/src/main/resources/application.yaml +++ b/shim-server/src/main/resources/application.yaml @@ -25,13 +25,15 @@ openmhealth: callbackUrlBase: http://localhost:8083 #NOTE: Un-comment and fill in with your credentials if you're not using the UI + ihealth: + sportSC: "4627a40e37104fb98af95c91ae4201e0" + sportSV: "c343378aba1545bbb5daa0ada0c3f6bd" #fitbit: # clientId: [YOUR_CLIENT_ID] # clientSecret: [YOUR_CLIENT_SECRET] #fatsecret: # clientId: [YOUR_CLIENT_ID] # clientSecret: [YOUR_CLIENT_SECRET] - #ihealth: # clientId: [YOUR_CLIENT_ID] # clientSecret: [YOUR_CLIENT_SECRET] #jawbone: diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java new file mode 100644 index 00000000..bd9362ac --- /dev/null +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java @@ -0,0 +1,104 @@ +/* + * 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.ihealth.mapper; + +import com.fasterxml.jackson.databind.JsonNode; +import org.openmhealth.schema.domain.omh.DataPoint; +import org.openmhealth.schema.domain.omh.PhysicalActivity; +import org.openmhealth.schema.domain.omh.TimeInterval; +import org.springframework.core.io.ClassPathResource; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.time.OffsetDateTime; +import java.util.List; + +import static java.util.Collections.singletonList; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; +import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; +import static org.openmhealth.schema.domain.omh.PhysicalActivity.SCHEMA_ID; + + +/** + * @author Chris Schaefbauer + */ +public class IHealthPhysicalActivityDataPointMapperUnitTests extends IHealthDataPointMapperUnitTests { + + + private JsonNode responseNode; + private IHealthPhysicalActivityDataPointMapper mapper = new IHealthPhysicalActivityDataPointMapper(); + + @BeforeTest + public void initializeResponseNode() throws IOException { + + ClassPathResource resource = new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity.json"); + responseNode = objectMapper.readTree(resource.getInputStream()); + } + + @Test + public void asDataPointsShouldReturnTheCorrectNumberOfDataPoints(){ + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + assertThat(dataPoints.size(),equalTo(2)); + } + + @Test + public void asDataPointsShouldReturnCorrectSensedDataPoints(){ + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + + PhysicalActivity.Builder expectedPhysicalActivityBuilder = new PhysicalActivity.Builder("Swimming, breaststroke") + .setEffectiveTimeFrame(TimeInterval.ofStartDateTimeAndEndDateTime( + OffsetDateTime.parse("2015-09-17T12:02:28-08:00"), + OffsetDateTime.parse("2015-09-17T12:32:28-08:00"))); + assertThat(dataPoints.get(0).getBody(),equalTo(expectedPhysicalActivityBuilder.build())); + + testDataPointHeader(dataPoints.get(0).getHeader(), SCHEMA_ID, SENSED, + "3f8770f51cc84957a57d20f4fee1f34b", OffsetDateTime.parse("2015-09-17T20:02:57Z")); + } + + @Test + public void asDataPointsShouldReturnCorrectSelfReportedDataPoints(){ + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + + + PhysicalActivity.Builder expectedPhysicalActivityBuilder = new PhysicalActivity.Builder("Running") + .setEffectiveTimeFrame( + TimeInterval.ofStartDateTimeAndEndDateTime( + OffsetDateTime.parse("2015-09-22T14:43:03-06:00"), + OffsetDateTime.parse("2015-09-22T15:13:03-06:00"))); + + assertThat(dataPoints.get(1).getBody(),equalTo(expectedPhysicalActivityBuilder.build())); + + assertThat(dataPoints.get(1).getHeader().getAcquisitionProvenance().getModality(), equalTo(SELF_REPORTED)); + } + + @Test + public void asDataPointsReturnsNoDataPointsForAnEmptyList() throws IOException { + + ClassPathResource resource = new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity-empty-list.json"); + JsonNode emptyListResponseNode = objectMapper.readTree(resource.getInputStream()); + + List> dataPoints = mapper.asDataPoints(singletonList(emptyListResponseNode)); + assertThat(dataPoints.size(),equalTo(0)); + } + +} diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-bp.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-bp.json new file mode 100644 index 00000000..ed53558c --- /dev/null +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-bp.json @@ -0,0 +1,56 @@ +{ + "BPDataList": [ + { + "BPL": 3, + "DataID": "c62b84d9d4b7480a8ff2aef1465aa454", + "DataSource": "Manual", + "HP": 120, + "HR": 100, + "IsArr": -1, + "LP": 90, + "LastChangeTime": 1442520270, + "Lat": -1, + "Lon": -1, + "MDate": 1442491463, + "Note": "", + "TimeZone": "-0800" + }, + { + "BPL": 3, + "DataID": "d6c08fb0bde6dc50db5c5f305a76c455", + "DataSource": "Manual", + "HP": 120, + "HR": 100, + "IsArr": 0, + "LP": 90, + "LastChangeTime": 1442520465, + "Lat": 0, + "Lon": 0, + "MDate": 1442498865, + "Note": "", + "TimeZone": "-0600" + }, + { + "BPL": 3, + "DataID": "8695fe79edfdcd9613d8e7509376bd5b", + "DataSource": "Manual", + "HP": 120, + "HR": 100, + "IsArr": 0, + "LP": 90, + "LastChangeTime": 1443044787, + "Lat": 0, + "Lon": 0, + "MDate": 1443023187, + "Note": "", + "TimeZone": "-0600" + } + ], + "BPUnit": 0, + "CurrentRecordCount": 3, + "NextPageUrl": "", + "PageLength": 50, + "PageNumber": 1, + "PrevPageUrl": "", + "RecordCount": 3 +} \ No newline at end of file diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-spo2.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-spo2.json new file mode 100644 index 00000000..7cc5c1eb --- /dev/null +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-spo2.json @@ -0,0 +1,34 @@ +{ + "BODataList": [ + { + "BO": 99, + "DataID": "d7fb9db14b0fc3e8e1635720c28bda64", + "DataSource": "Manual", + "HR": 80, + "LastChangeTime": 1443044760, + "Lat": 0, + "Lon": 0, + "MDate": 1443023160, + "Note": "", + "TimeZone": "-0600" + }, + { + "BO": 90, + "DataID": "217e56a104cf8462f4977fd8bf743ca5", + "DataSource": "Manual", + "HR": 0, + "LastChangeTime": 1443128580, + "Lat": 0, + "Lon": 0, + "MDate": 1443106980, + "Note": "Satch on satch ", + "TimeZone": "-0600" + } + ], + "CurrentRecordCount": 2, + "NextPageUrl": "", + "PageLength": 50, + "PageNumber": 1, + "PrevPageUrl": "", + "RecordCount": 2 +} \ No newline at end of file From 395e0c36878b32c6d0a2d69c2c29407204d048b3 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Thu, 1 Oct 2015 16:03:50 -0600 Subject: [PATCH 14/68] Suppress JsonMapping exceptions in authentication process When getData is being called in the context of OAuth authentication, an issue with data mapping implies a failure of the getData request, which is not necessarily tied to the authentication process. We suppress these errors because they may incorrectly suggest that the authentication failed, when it fact it succeeded, but the subsequent getData request failed for some other reason. There is code in subsequent lines that handles checking for the actual success of the OAuth process, so we rely on that to confirm success. --- .../main/java/org/openmhealth/shim/OAuth2ShimBase.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/OAuth2ShimBase.java b/shim-server/src/main/java/org/openmhealth/shim/OAuth2ShimBase.java index 99bf3ce8..7e9dc346 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/OAuth2ShimBase.java +++ b/shim-server/src/main/java/org/openmhealth/shim/OAuth2ShimBase.java @@ -17,6 +17,7 @@ package org.openmhealth.shim; +import org.openmhealth.shim.common.mapper.JsonNodeMappingException; import org.springframework.data.domain.Sort; import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext; @@ -158,7 +159,13 @@ public AuthorizationResponse handleAuthorizationResponse(HttpServletRequest serv accessParameters.setStateKey(state); accessParametersRepo.save(accessParameters); - trigger(restTemplate, getTriggerDataRequest()); + try{ + trigger(restTemplate, getTriggerDataRequest()); + } + catch(JsonNodeMappingException e){ + // In this case authentication may have succeeded, but the data request may have failed so we + // should not fail. We should check and see if authentication succeeded in subsequent lines. + } /** * By this line we will have an approved access token or From 356e0f18ffeee323d022d669c8f5942666d9fa9b Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Thu, 1 Oct 2015 16:33:17 -0600 Subject: [PATCH 15/68] Use auth response type to determine auth success In processing authentication responses, the authentication response object carries a property indicating whether or not authentication is successful. This property is set to successful if the auth token is set and there are not other exceptions in the process. As this response is returned in the callback endpoint handler, we can then take action on redirecting based on its state. --- .../src/main/java/org/openmhealth/shim/Application.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/Application.java b/shim-server/src/main/java/org/openmhealth/shim/Application.java index bcbb247a..87667330 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/Application.java +++ b/shim-server/src/main/java/org/openmhealth/shim/Application.java @@ -269,10 +269,10 @@ public AuthorizationResponse approve( return null; } - String authorizationStatusURL = AUTH_SUCCESS_URL; - if(response.getAccessParameters().getAccessToken()==null){ + String authorizationStatusURL = AUTH_FAILURE_URL; + if(response.getType().equals(AuthorizationResponse.Type.AUTHORIZED)){ - authorizationStatusURL = AUTH_FAILURE_URL; + authorizationStatusURL = AUTH_SUCCESS_URL; } try{ From 2a6f2c3549f633b7425166042c45288a997cbbb6 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Thu, 1 Oct 2015 21:27:08 -0600 Subject: [PATCH 16/68] Initial commit of iHealth heart rate mapper --- .../openmhealth/shim/ihealth/IHealthShim.java | 126 +++++++++++------- .../IHealthBloodGlucoseDataPointMapper.java | 7 +- .../IHealthBloodPressureDataPointMapper.java | 6 +- .../IHealthBodyMassIndexDataPointMapper.java | 6 +- .../IHealthBodyWeightDataPointMapper.java | 6 +- .../mapper/IHealthDataPointMapper.java | 20 +-- .../IHealthHeartRateDataPointMapper.java | 56 ++++++++ ...HealthPhysicalActivityDataPointMapper.java | 10 +- .../src/main/resources/application.yaml | 6 +- ...BloodPressureDataPointMapperUnitTests.java | 2 +- ...althHeartRateDataPointMapperUnitTests.java | 74 ++++++++++ .../mapper/ihealth-blood-pressure.json | 16 +-- .../mapper/ihealth-heart-rate-from-bp.json | 17 +-- .../mapper/ihealth-heart-rate-from-spo2.json | 2 +- 14 files changed, 259 insertions(+), 95 deletions(-) create mode 100644 shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java create mode 100644 shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapperUnitTests.java diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java index 16c4a659..c66c4037 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java @@ -5,12 +5,14 @@ import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; import org.openmhealth.shim.*; import org.openmhealth.shim.ihealth.mapper.*; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; @@ -40,12 +42,14 @@ import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.Map; import static java.util.Collections.singletonList; import static org.slf4j.LoggerFactory.getLogger; @Component +@EnableConfigurationProperties @ConfigurationProperties(prefix = "openmhealth.shim.ihealth") public class IHealthShim extends OAuth2ShimBase { @@ -107,7 +111,8 @@ public ShimDataType[] getShimDataTypes() { IHealthDataTypes.BLOOD_GLUCOSE, IHealthDataTypes.BLOOD_PRESSURE, IHealthDataTypes.BODY_WEIGHT, - IHealthDataTypes.BODY_MASS_INDEX + IHealthDataTypes.BODY_MASS_INDEX, + IHealthDataTypes.HEART_RATE }; } @@ -115,25 +120,36 @@ public ShimDataType[] getShimDataTypes() { public String sportSC; @Value("${openmhealth.shim.ihealth.sportSV}") public String sportSV; + @Value("${openmhealth.shim.ihealth.bloodPressureSC}") + public String bloodPressureSC; + @Value("${openmhealth.shim.ihealth.bloodPressureSV}") + public String bloodPressureSV; + @Value("${openmhealth.shim.ihealth.spo2SC}") + public String spo2SC; + @Value("${openmhealth.shim.ihealth.spo2SV}") + public String spo2SV; + + public Map serialValues; public enum IHealthDataTypes implements ShimDataType { - PHYSICAL_ACTIVITY("sport.json"), - BLOOD_GLUCOSE("glucose.json"), - BLOOD_PRESSURE("bp.json"), - BODY_WEIGHT("weight.json"), + PHYSICAL_ACTIVITY(singletonList("sport.json")), + BLOOD_GLUCOSE(singletonList("glucose.json")), + BLOOD_PRESSURE(singletonList("bp.json")), + BODY_WEIGHT(singletonList("weight.json")), //SLEEP("sleep"), //STEP_COUNT("activity"), - BODY_MASS_INDEX("weight.json"); + BODY_MASS_INDEX(singletonList("weight.json")), + HEART_RATE(Lists.newArrayList("bp.json","spo2.json")); - private String endPoint; + private List endPoint; - IHealthDataTypes(String endPoint) { + IHealthDataTypes(List endPoint) { this.endPoint = endPoint; } - public String getEndPoint() { + public List getEndPoint() { return endPoint; } @@ -160,29 +176,54 @@ protected ResponseEntity getData(OAuth2RestOperations restTemp OffsetDateTime endDate = shimDataRequest.getEndDateTime() == null ? now.plusDays(1) : shimDataRequest.getEndDateTime(); - UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(API_URL); - String userId = "uk"; - if(shimDataRequest.getAccessParameters()!=null){ - OAuth2AccessToken token = SerializationUtils.deserialize(shimDataRequest.getAccessParameters().getSerializedToken());; - userId = Preconditions.checkNotNull((String) token.getAdditionalInformation().get("UserID")); -// uriBuilder.path("user") -// .path(userId); - uriBuilder.queryParam("access_token", token.getValue()); + + + + List scValues = getScValues(dataType); + List svValues = getSvValues(dataType); + + List responseEntities = Lists.newArrayList(); + int i=0; + for(String endPoint : dataType.getEndPoint()){ + UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(API_URL); + + String userId = "uk"; + if(shimDataRequest.getAccessParameters()!=null){ + OAuth2AccessToken token = SerializationUtils.deserialize(shimDataRequest.getAccessParameters().getSerializedToken());; + userId = Preconditions.checkNotNull((String) token.getAdditionalInformation().get("UserID")); + // uriBuilder.path("user") + // .path(userId); + uriBuilder.queryParam("access_token", token.getValue()); + } + + uriBuilder.path("/user/") + .path(userId+"/") + .path(endPoint) + .queryParam("client_id", restTemplate.getResource().getClientId()) + .queryParam("client_secret",restTemplate.getResource().getClientSecret()) + .queryParam("start_time", startDate.toEpochSecond()) + .queryParam("end_time",endDate.toEpochSecond()) + .queryParam("locale", "default") + .queryParam("sc", scValues.get(i)) + .queryParam("sv", svValues.get(i)); + + + try{ + URI url = uriBuilder.build().encode().toUri(); + responseEntities.add(restTemplate.getForEntity(url, JsonNode.class).getBody()); + } + catch (HttpClientErrorException | HttpServerErrorException e) { + // FIXME figure out how to handle this + logger.error("A request for iHealth data failed.", e); + throw e; + } + + i++; + } - String scValue = getScValue(dataType); - String svValue = getSvValue(dataType); - uriBuilder.path("/user/") - .path(userId+"/") - .path(dataType.getEndPoint()) - .queryParam("client_id", restTemplate.getResource().getClientId()) - .queryParam("client_secret",restTemplate.getResource().getClientSecret()) - .queryParam("start_time", startDate.toEpochSecond()) - .queryParam("end_time",endDate.toEpochSecond()) - .queryParam("locale","default") - .queryParam("sc",scValue) - .queryParam("sv",svValue); + // String urlRequest = API_URL + "/user/" + userId + "/" + dataType.getEndPoint() + ".json?"; // @@ -193,16 +234,7 @@ protected ResponseEntity getData(OAuth2RestOperations restTemp // urlRequest += "&access_token=" + token.getValue(); // urlRequest += "&locale=default"; - ResponseEntity responseEntity; - try{ - URI url = uriBuilder.build().encode().toUri(); - responseEntity = restTemplate.getForEntity(url, JsonNode.class); - } - catch (HttpClientErrorException | HttpServerErrorException e) { - // FIXME figure out how to handle this - logger.error("A request for iHealth data failed.", e); - throw e; - } + //ObjectMapper objectMapper = new ObjectMapper(); @@ -236,33 +268,37 @@ protected ResponseEntity getData(OAuth2RestOperations restTemp return ResponseEntity.ok().body( - ShimDataResponse.result(SHIM_KEY, mapper.asDataPoints(singletonList(responseEntity.getBody())))); + ShimDataResponse.result(SHIM_KEY, mapper.asDataPoints(responseEntities))); // return new ResponseEntity<>( // objectMapper.readValue(responseEntity.getBody(), ShimDataResponse.class), // HttpStatus.OK); } else { - return ResponseEntity.ok().body(ShimDataResponse.result(SHIM_KEY, responseEntity.getBody())); + return ResponseEntity.ok().body(ShimDataResponse.result(SHIM_KEY, responseEntities)); } } - private String getScValue(IHealthDataTypes dataType) { + private List getScValues(IHealthDataTypes dataType) { switch(dataType){ case PHYSICAL_ACTIVITY: - return sportSC; + return singletonList(sportSC); + case HEART_RATE: + return Lists.newArrayList(bloodPressureSC, spo2SC); default: throw new UnsupportedOperationException(); } } - private String getSvValue(IHealthDataTypes dataType) { + private List getSvValues(IHealthDataTypes dataType) { switch (dataType){ case PHYSICAL_ACTIVITY: - return sportSV; + return singletonList(sportSV); + case HEART_RATE: + return Lists.newArrayList(bloodPressureSV, spo2SV); default: throw new UnsupportedOperationException(); } diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java index 2c063cc9..949b7f51 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java @@ -20,8 +20,10 @@ import com.google.common.collect.ImmutableMap; import org.openmhealth.schema.domain.omh.*; +import java.util.List; import java.util.Optional; +import static java.util.Collections.singletonList; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asOptionalString; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredDouble; @@ -34,8 +36,8 @@ public class IHealthBloodGlucoseDataPointMapper extends IHealthDataPointMapper iHealthBloodGlucoseRelationshipToMeal; @Override - protected String getListNodeName() { - return "BGDataList"; + protected List getListNodeNames() { + return singletonList("BGDataList"); } @Override @@ -63,7 +65,6 @@ protected Optional> asDataPoint(JsonNode listNode, Integ BloodGlucose.Builder bloodGlucoseBuilder = new BloodGlucose.Builder(new TypedUnitValue<>(bloodGlucoseUnit, bloodGlucoseValue)); - Optional dinnerSituation = asOptionalString(listNode, "DinnerSituation"); if (dinnerSituation.isPresent()) { diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java index c90717f0..df9808c5 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java @@ -19,8 +19,10 @@ import com.fasterxml.jackson.databind.JsonNode; import org.openmhealth.schema.domain.omh.*; +import java.util.List; import java.util.Optional; +import static java.util.Collections.singletonList; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredDouble; @@ -32,8 +34,8 @@ public class IHealthBloodPressureDataPointMapper extends IHealthDataPointMapper< static final double KPA_TO_MMHG_CONVERSION_RATE = 7.500617; @Override - protected String getListNodeName() { - return "BPDataList"; + protected List getListNodeNames() { + return singletonList("BPDataList"); } @Override diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java index 743fccf8..cb91f535 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java @@ -21,8 +21,10 @@ import org.openmhealth.schema.domain.omh.DataPoint; import org.openmhealth.schema.domain.omh.TypedUnitValue; +import java.util.List; import java.util.Optional; +import static java.util.Collections.singletonList; import static org.openmhealth.schema.domain.omh.BodyMassIndexUnit.KILOGRAMS_PER_SQUARE_METER; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredDouble; @@ -33,8 +35,8 @@ public class IHealthBodyMassIndexDataPointMapper extends IHealthDataPointMapper{ @Override - protected String getListNodeName() { - return "WeightDataList"; + protected List getListNodeNames() { + return singletonList("WeightDataList"); } @Override diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java index 8ab4503c..518f6db8 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java @@ -22,8 +22,10 @@ import org.openmhealth.schema.domain.omh.MassUnit; import org.openmhealth.schema.domain.omh.MassUnitValue; +import java.util.List; import java.util.Optional; +import static java.util.Collections.singletonList; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredDouble; @@ -36,8 +38,8 @@ public class IHealthBodyWeightDataPointMapper extends IHealthDataPointMapper getListNodeNames() { + return singletonList("WeightDataList"); } @Override diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java index 14eeab27..c90b9ad8 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java @@ -44,19 +44,21 @@ public abstract class IHealthDataPointMapper implements DataPointMapper> asDataPoints(List responseNodes) { - JsonNode responseNode = responseNodes.get(0); - List> dataPoints = Lists.newArrayList(); - Optional measureUnit = Optional.empty(); - if(getUnitPropertyNameForMeasure().isPresent()){ + for (int i = 0; i < responseNodes.size(); i++) { - measureUnit = asOptionalInteger(responseNode, getUnitPropertyNameForMeasure().get()); - } + JsonNode responseNode = responseNodes.get(i); + Optional measureUnit = Optional.empty(); + if (getUnitPropertyNameForMeasure().isPresent()) { + + measureUnit = asOptionalInteger(responseNode, getUnitPropertyNameForMeasure().get()); + } - for (JsonNode listNode : asRequiredNode(responseNode, getListNodeName())) { + for (JsonNode listNode : asRequiredNode(responseNode, getListNodeNames().get(i))) { - asDataPoint(listNode, measureUnit.orElse(null)).ifPresent(dataPoints::add); + asDataPoint(listNode, measureUnit.orElse(null)).ifPresent(dataPoints::add); + } } return dataPoints; @@ -125,7 +127,7 @@ else if (dataSourceValue.equals(DATA_SOURCE_MANUAL)) { } } - protected abstract String getListNodeName(); + protected abstract List getListNodeNames(); protected abstract Optional getUnitPropertyNameForMeasure(); diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java new file mode 100644 index 00000000..50937e65 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java @@ -0,0 +1,56 @@ +/* + * 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.ihealth.mapper; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.Lists; +import org.openmhealth.schema.domain.omh.DataPoint; +import org.openmhealth.schema.domain.omh.HeartRate; + +import java.util.List; +import java.util.Optional; + +import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredDouble; + + +/** + * @author Chris Schaefbauer + */ +public class IHealthHeartRateDataPointMapper extends IHealthDataPointMapper{ + + + @Override + protected List getListNodeNames() { + + return Lists.newArrayList("BPDataList","BODataList"); + } + + @Override + protected Optional getUnitPropertyNameForMeasure() { + return Optional.empty(); + } + + @Override + protected Optional> asDataPoint(JsonNode listNode, Integer measureUnit) { + + double heartRateValue = asRequiredDouble(listNode,"HR"); + HeartRate.Builder heartRateBuilder = new HeartRate.Builder(heartRateValue); + setEffectiveTimeFrameIfExists(listNode,heartRateBuilder); + HeartRate heartRate = heartRateBuilder.build(); + return Optional.of(new DataPoint<>(createDataPointHeader(listNode,heartRate),heartRate)); + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java index 2e9bf732..0a95348d 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java @@ -25,11 +25,11 @@ import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.ZoneOffset; +import java.util.List; import java.util.Optional; -import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asOptionalInteger; -import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asOptionalLong; -import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredString; +import static java.util.Collections.singletonList; +import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*; /** @@ -38,9 +38,9 @@ public class IHealthPhysicalActivityDataPointMapper extends IHealthDataPointMapper { @Override - protected String getListNodeName() { + protected List getListNodeNames() { - return "SPORTDataList"; + return singletonList("SPORTDataList"); } @Override diff --git a/shim-server/src/main/resources/application.yaml b/shim-server/src/main/resources/application.yaml index f441cd3d..5bcad6b1 100644 --- a/shim-server/src/main/resources/application.yaml +++ b/shim-server/src/main/resources/application.yaml @@ -7,7 +7,7 @@ spring: enabled: false data: mongodb: - uri: mongodb://mongo:27017/omh_dsu + uri: mongodb://127.0.0.1:27017/omh_dsu jackson: serialization: indent_output: true @@ -28,6 +28,10 @@ openmhealth: ihealth: sportSC: "4627a40e37104fb98af95c91ae4201e0" sportSV: "c343378aba1545bbb5daa0ada0c3f6bd" + bloodPressureSC: "4627a40e37104fb98af95c91ae4201e0" + bloodPressureSV: "3ffcde7a7c6444e79bd4307c117041f8" + spo2SC: "4627a40e37104fb98af95c91ae4201e0" + spo2SV: "3ab71cace61245bc8b2af4f90f329be6" #fitbit: # clientId: [YOUR_CLIENT_ID] # clientSecret: [YOUR_CLIENT_SECRET] diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java index 60d4b0bf..b9483252 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java @@ -87,7 +87,7 @@ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { BloodPressure expectedBloodPressure = new BloodPressure.Builder( new SystolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, 130), new DiastolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, 95)) - .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-25T08:03:57-06:00")) + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T08:07:45-06:00")) .setUserNotes("BP on the up and up.") .build(); diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapperUnitTests.java new file mode 100644 index 00000000..206ad640 --- /dev/null +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapperUnitTests.java @@ -0,0 +1,74 @@ +/* + * 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.ihealth.mapper; + + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.Lists; +import org.openmhealth.schema.domain.omh.DataPoint; +import org.openmhealth.schema.domain.omh.HeartRate; +import org.springframework.core.io.ClassPathResource; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.time.OffsetDateTime; +import java.util.List; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; + + +/** + * @author Chris Schaefbauer + */ +public class IHealthHeartRateDataPointMapperUnitTests extends IHealthDataPointMapperUnitTests { + + JsonNode bpNode; + JsonNode spo2Node; + private IHealthHeartRateDataPointMapper mapper = new IHealthHeartRateDataPointMapper(); + + @BeforeTest + public void initializeResponseNodes() throws IOException { + + ClassPathResource resource = new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-bp.json"); + bpNode = objectMapper.readTree(resource.getInputStream()); + + resource = new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-spo2.json"); + spo2Node = objectMapper.readTree(resource.getInputStream()); + } + + @Test + public void asDataPointsShouldReturnCorrectNumberOfDataPoints(){ + + List> dataPoints = mapper.asDataPoints(Lists.newArrayList(bpNode, spo2Node)); + assertThat(dataPoints.size(),equalTo(4)); + } + + @Test + public void asDataPointsShouldReturnCorrectSensedDataPointsFromBpResponse(){ + + List> dataPoints = mapper.asDataPoints(Lists.newArrayList(bpNode, spo2Node)); + + HeartRate.Builder expectedHeartRateBuilder = new HeartRate.Builder(100) + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T04:04:23-08:00")); + HeartRate expectedSensedHeartRate = expectedHeartRateBuilder.build(); + assertThat(dataPoints.get(0).getBody(),equalTo(expectedSensedHeartRate)); + + } + +} diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json index 83d4f579..42900818 100644 --- a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json @@ -17,16 +17,16 @@ }, { "BPL": 3, - "DataID": "47262ccffec747a4b21befb04b322424", + "DataID": "d6c08fb0bde6dc50db5c5f305a76c455", "DataSource": "Manual", "HP": 130, - "HR": 75, - "IsArr": -1, + "HR": 100, + "IsArr": 0, "LP": 95, - "LastChangeTime": 1443211477, - "Lat": -1, - "Lon": -1, - "MDate": 1443189837, + "LastChangeTime": 1442520465, + "Lat": 0, + "Lon": 0, + "MDate": 1442498865, "Note": "BP on the up and up.", "TimeZone": "-0600" } @@ -37,5 +37,5 @@ "PageLength": 50, "PageNumber": 1, "PrevPageUrl": "", - "RecordCount": 2 + "RecordCount": 5 } \ No newline at end of file diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-bp.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-bp.json index ed53558c..77806516 100644 --- a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-bp.json +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-bp.json @@ -3,7 +3,7 @@ { "BPL": 3, "DataID": "c62b84d9d4b7480a8ff2aef1465aa454", - "DataSource": "Manual", + "DataSource": "FromDevice", "HP": 120, "HR": 100, "IsArr": -1, @@ -29,21 +29,6 @@ "MDate": 1442498865, "Note": "", "TimeZone": "-0600" - }, - { - "BPL": 3, - "DataID": "8695fe79edfdcd9613d8e7509376bd5b", - "DataSource": "Manual", - "HP": 120, - "HR": 100, - "IsArr": 0, - "LP": 90, - "LastChangeTime": 1443044787, - "Lat": 0, - "Lon": 0, - "MDate": 1443023187, - "Note": "", - "TimeZone": "-0600" } ], "BPUnit": 0, diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-spo2.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-spo2.json index 7cc5c1eb..b185b5a2 100644 --- a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-spo2.json +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-spo2.json @@ -3,7 +3,7 @@ { "BO": 99, "DataID": "d7fb9db14b0fc3e8e1635720c28bda64", - "DataSource": "Manual", + "DataSource": "FromDevice", "HR": 80, "LastChangeTime": 1443044760, "Lat": 0, From 432e0fcab4a2ac7f2e55e5560cf061031a3ae77e Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Thu, 1 Oct 2015 21:33:22 -0600 Subject: [PATCH 17/68] Connect heart rate mapper to iHealth shim --- .../main/java/org/openmhealth/shim/ihealth/IHealthShim.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java index c66c4037..82e4706c 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java @@ -262,6 +262,9 @@ protected ResponseEntity getData(OAuth2RestOperations restTemp case BODY_MASS_INDEX: mapper = new IHealthBloodPressureDataPointMapper(); break; + case HEART_RATE: + mapper = new IHealthHeartRateDataPointMapper(); + break; default: throw new UnsupportedOperationException(); } From 5c702ba95b60a9f81f93c2918ba1cfc763af59ea Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Fri, 2 Oct 2015 11:59:38 -0600 Subject: [PATCH 18/68] Hook up remaining mappers to iHealth shim --- .../openmhealth/shim/ihealth/IHealthShim.java | 93 ++++++++++--------- .../src/main/resources/application.yaml | 18 ++-- 2 files changed, 59 insertions(+), 52 deletions(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java index 82e4706c..95507514 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java @@ -10,7 +10,6 @@ import org.openmhealth.shim.ihealth.mapper.*; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.http.HttpHeaders; @@ -116,20 +115,30 @@ public ShimDataType[] getShimDataTypes() { }; } - @Value("${openmhealth.shim.ihealth.sportSC}") - public String sportSC; - @Value("${openmhealth.shim.ihealth.sportSV}") - public String sportSV; - @Value("${openmhealth.shim.ihealth.bloodPressureSC}") - public String bloodPressureSC; - @Value("${openmhealth.shim.ihealth.bloodPressureSV}") - public String bloodPressureSV; - @Value("${openmhealth.shim.ihealth.spo2SC}") - public String spo2SC; - @Value("${openmhealth.shim.ihealth.spo2SV}") - public String spo2SV; - - public Map serialValues; +// @Value("${openmhealth.shim.ihealth.sportSC}") +// public String sportSC; +// @Value("${openmhealth.shim.ihealth.sportSV}") +// public String sportSV; +// @Value("${openmhealth.shim.ihealth.bloodPressureSC}") +// public String bloodPressureSC; +// @Value("${openmhealth.shim.ihealth.bloodPressureSV}") +// public String bloodPressureSV; +// @Value("${openmhealth.shim.ihealth.spo2SC}") +// public String spo2SC; +// @Value("${openmhealth.shim.ihealth.spo2SV}") +// public String spo2SV; + + Map serialValues; + + public Map getSerialValues(){ + + return serialValues; + } + + public void setSerialValues(Map serialValues){ + + this.serialValues = serialValues; + } public enum IHealthDataTypes implements ShimDataType { @@ -177,22 +186,20 @@ protected ResponseEntity getData(OAuth2RestOperations restTemp now.plusDays(1) : shimDataRequest.getEndDateTime(); - - List scValues = getScValues(dataType); List svValues = getSvValues(dataType); List responseEntities = Lists.newArrayList(); + int i=0; for(String endPoint : dataType.getEndPoint()){ UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(API_URL); String userId = "uk"; if(shimDataRequest.getAccessParameters()!=null){ + OAuth2AccessToken token = SerializationUtils.deserialize(shimDataRequest.getAccessParameters().getSerializedToken());; userId = Preconditions.checkNotNull((String) token.getAdditionalInformation().get("UserID")); - // uriBuilder.path("user") - // .path(userId); uriBuilder.queryParam("access_token", token.getValue()); } @@ -222,23 +229,6 @@ protected ResponseEntity getData(OAuth2RestOperations restTemp } - - - -// String urlRequest = API_URL + "/user/" + userId + "/" + dataType.getEndPoint() + ".json?"; -// -// urlRequest += "&start_time=" + startDate.toEpochSecond(); -// urlRequest += "&end_time=" + endDate.toEpochSecond(); -// urlRequest += "&client_id=" + restTemplate.getResource().getClientId(); -// urlRequest += "&client_secret=" + restTemplate.getResource().getClientSecret(); -// urlRequest += "&access_token=" + token.getValue(); -// urlRequest += "&locale=default"; - - - - - //ObjectMapper objectMapper = new ObjectMapper(); - if (shimDataRequest.getNormalize()) { // SimpleModule module = new SimpleModule(); // module.addDeserializer(ShimDataResponse.class, dataType.getNormalizer()); @@ -260,7 +250,7 @@ protected ResponseEntity getData(OAuth2RestOperations restTemp mapper = new IHealthBodyWeightDataPointMapper(); break; case BODY_MASS_INDEX: - mapper = new IHealthBloodPressureDataPointMapper(); + mapper = new IHealthBodyMassIndexDataPointMapper(); break; case HEART_RATE: mapper = new IHealthHeartRateDataPointMapper(); @@ -269,12 +259,8 @@ protected ResponseEntity getData(OAuth2RestOperations restTemp throw new UnsupportedOperationException(); } - return ResponseEntity.ok().body( ShimDataResponse.result(SHIM_KEY, mapper.asDataPoints(responseEntities))); - // return new ResponseEntity<>( - // objectMapper.readValue(responseEntity.getBody(), ShimDataResponse.class), - // HttpStatus.OK); } else { @@ -286,9 +272,17 @@ private List getScValues(IHealthDataTypes dataType) { switch(dataType){ case PHYSICAL_ACTIVITY: - return singletonList(sportSC); + return singletonList(serialValues.get("sportSC")); + case BODY_WEIGHT: + return singletonList(serialValues.get("weightSC")); + case BODY_MASS_INDEX: + return singletonList(serialValues.get("weightSC")); // body mass index comes from the weight endpoint + case BLOOD_PRESSURE: + return singletonList(serialValues.get("bloodPressureSC")); + case BLOOD_GLUCOSE: + return singletonList(serialValues.get("bloodGlucoseSC")); case HEART_RATE: - return Lists.newArrayList(bloodPressureSC, spo2SC); + return Lists.newArrayList(serialValues.get("bloodPressureSC"), serialValues.get("spo2SC")); default: throw new UnsupportedOperationException(); } @@ -297,11 +291,18 @@ private List getScValues(IHealthDataTypes dataType) { private List getSvValues(IHealthDataTypes dataType) { switch (dataType){ - case PHYSICAL_ACTIVITY: - return singletonList(sportSV); + return singletonList(serialValues.get("sportSV")); + case BODY_WEIGHT: + return singletonList(serialValues.get("weightSV")); + case BODY_MASS_INDEX: + return singletonList(serialValues.get("weightSV")); // body mass index comes from the weight endpoint + case BLOOD_PRESSURE: + return singletonList(serialValues.get("bloodPressureSV")); + case BLOOD_GLUCOSE: + return singletonList(serialValues.get("bloodGlucoseSV")); case HEART_RATE: - return Lists.newArrayList(bloodPressureSV, spo2SV); + return Lists.newArrayList(serialValues.get("bloodPressureSV"), serialValues.get("spo2SV")); default: throw new UnsupportedOperationException(); } diff --git a/shim-server/src/main/resources/application.yaml b/shim-server/src/main/resources/application.yaml index 5bcad6b1..0b8d683f 100644 --- a/shim-server/src/main/resources/application.yaml +++ b/shim-server/src/main/resources/application.yaml @@ -26,12 +26,18 @@ openmhealth: #NOTE: Un-comment and fill in with your credentials if you're not using the UI ihealth: - sportSC: "4627a40e37104fb98af95c91ae4201e0" - sportSV: "c343378aba1545bbb5daa0ada0c3f6bd" - bloodPressureSC: "4627a40e37104fb98af95c91ae4201e0" - bloodPressureSV: "3ffcde7a7c6444e79bd4307c117041f8" - spo2SC: "4627a40e37104fb98af95c91ae4201e0" - spo2SV: "3ab71cace61245bc8b2af4f90f329be6" + serialValues: + sportSC: "4627a40e37104fb98af95c91ae4201e0" + sportSV: "c343378aba1545bbb5daa0ada0c3f6bd" + bloodPressureSC: "4627a40e37104fb98af95c91ae4201e0" + bloodPressureSV: "3ffcde7a7c6444e79bd4307c117041f8" + spo2SC: "4627a40e37104fb98af95c91ae4201e0" + spo2SV: "3ab71cace61245bc8b2af4f90f329be6" + weightSC: "4627a40e37104fb98af95c91ae4201e0" + weightSV: "24cbdf8952c44ae1a92939132beedb47" + bloodGlucoseSC: "4627a40e37104fb98af95c91ae4201e0" + bloodGlucoseSV: "cd44c9fd42444b729757b69e88be2eb7" + #fitbit: # clientId: [YOUR_CLIENT_ID] # clientSecret: [YOUR_CLIENT_SECRET] From d859735f2d04eb9f76ffb2ef48cf3cc3171ac053 Mon Sep 17 00:00:00 2001 From: Emerson Farrugia Date: Fri, 2 Oct 2015 22:09:23 +0200 Subject: [PATCH 19/68] Refactor naming in iHealth mappers --- .../IHealthBloodGlucoseDataPointMapper.java | 29 +++++++------- ...xygenEndpointHeartRateDataPointMapper.java | 28 ++++++++++++++ .../IHealthBloodPressureDataPointMapper.java | 10 ++--- ...ssureEndpointHeartRateDataPointMapper.java | 28 ++++++++++++++ .../IHealthBodyMassIndexDataPointMapper.java | 24 ++++++------ .../IHealthBodyWeightDataPointMapper.java | 12 +++--- .../mapper/IHealthDataPointMapper.java | 38 +++++++++++-------- .../IHealthHeartRateDataPointMapper.java | 21 +++------- ...HealthPhysicalActivityDataPointMapper.java | 11 ++---- 9 files changed, 125 insertions(+), 76 deletions(-) create mode 100644 shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapper.java create mode 100644 shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapper.java diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java index 949b7f51..29ed08d0 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java @@ -20,10 +20,9 @@ import com.google.common.collect.ImmutableMap; import org.openmhealth.schema.domain.omh.*; -import java.util.List; import java.util.Optional; -import static java.util.Collections.singletonList; +import static com.google.common.base.Preconditions.checkNotNull; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asOptionalString; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredDouble; @@ -36,12 +35,12 @@ public class IHealthBloodGlucoseDataPointMapper extends IHealthDataPointMapper iHealthBloodGlucoseRelationshipToMeal; @Override - protected List getListNodeNames() { - return singletonList("BGDataList"); + protected String getListNodeName() { + return "BGDataList"; } @Override - protected Optional getUnitPropertyNameForMeasure() { + protected Optional getMeasureUnitNodeName() { return Optional.of("BGUnit"); } @@ -51,21 +50,23 @@ public IHealthBloodGlucoseDataPointMapper() { } @Override - protected Optional> asDataPoint(JsonNode listNode, Integer measureUnit) { + protected Optional> asDataPoint(JsonNode listEntryNode, Integer measureUnitMagicNumber) { - double bloodGlucoseValue = asRequiredDouble(listNode, "BG"); - if (bloodGlucoseValue == 0) { + checkNotNull(measureUnitMagicNumber); + + double bloodGlucoseValue = asRequiredDouble(listEntryNode, "BG"); + if (bloodGlucoseValue == 0) { return Optional.empty(); } BloodGlucoseUnit bloodGlucoseUnit = - IHealthBloodGlucoseUnit.fromIHealthMagicNumber(measureUnit).getBloodGlucoseUnit(); + IHealthBloodGlucoseUnit.fromIHealthMagicNumber(measureUnitMagicNumber).getBloodGlucoseUnit(); BloodGlucose.Builder bloodGlucoseBuilder = new BloodGlucose.Builder(new TypedUnitValue<>(bloodGlucoseUnit, bloodGlucoseValue)); - Optional dinnerSituation = asOptionalString(listNode, "DinnerSituation"); + Optional dinnerSituation = asOptionalString(listEntryNode, "DinnerSituation"); if (dinnerSituation.isPresent()) { @@ -77,18 +78,18 @@ protected Optional> asDataPoint(JsonNode listNode, Integ } } - setEffectiveTimeFrameIfExists(listNode, bloodGlucoseBuilder); - setUserNoteIfExists(listNode, bloodGlucoseBuilder); + setEffectiveTimeFrameIfExists(listEntryNode, bloodGlucoseBuilder); + setUserNoteIfExists(listEntryNode, bloodGlucoseBuilder); BloodGlucose bloodGlucose = bloodGlucoseBuilder.build(); /* The "temporal_relationship_to_medication" property is not part of the Blood Glucose schema, so its name and values may change or we may remove support for this property at any time. */ - asOptionalString(listNode, "DrugSituation").ifPresent( + asOptionalString(listEntryNode, "DrugSituation").ifPresent( drugSituation -> bloodGlucose .setAdditionalProperty("temporal_relationship_to_medication", drugSituation)); - return Optional.of(new DataPoint<>(createDataPointHeader(listNode, bloodGlucose), bloodGlucose)); + return Optional.of(new DataPoint<>(createDataPointHeader(listEntryNode, bloodGlucose), bloodGlucose)); } private void initializeTemporalRelationshipToFoodMap() { diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapper.java new file mode 100644 index 00000000..334a954c --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapper.java @@ -0,0 +1,28 @@ +/* + * 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.ihealth.mapper; + +/** + * @author Emerson Farrugia + */ +public class IHealthBloodOxygenEndpointHeartRateDataPointMapper extends IHealthHeartRateDataPointMapper { + + @Override + protected String getListNodeName() { + return "BODataList"; + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java index df9808c5..089992f8 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java @@ -19,10 +19,8 @@ import com.fasterxml.jackson.databind.JsonNode; import org.openmhealth.schema.domain.omh.*; -import java.util.List; import java.util.Optional; -import static java.util.Collections.singletonList; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredDouble; @@ -34,12 +32,14 @@ public class IHealthBloodPressureDataPointMapper extends IHealthDataPointMapper< static final double KPA_TO_MMHG_CONVERSION_RATE = 7.500617; @Override - protected List getListNodeNames() { - return singletonList("BPDataList"); + protected String getListNodeName() { + return "BPDataList"; } @Override - protected Optional getUnitPropertyNameForMeasure() { return Optional.of("BPUnit"); } + protected Optional getMeasureUnitNodeName() { + return Optional.of("BPUnit"); + } @Override protected Optional> asDataPoint(JsonNode listNode, Integer bloodPressureUnit) { diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapper.java new file mode 100644 index 00000000..508e5697 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapper.java @@ -0,0 +1,28 @@ +/* + * 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.ihealth.mapper; + +/** + * @author Emerson Farrugia + */ +public class IHealthBloodPressureEndpointHeartRateDataPointMapper extends IHealthHeartRateDataPointMapper { + + @Override + protected String getListNodeName() { + return "BPDataList"; + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java index cb91f535..fbf61ddb 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java @@ -21,10 +21,8 @@ import org.openmhealth.schema.domain.omh.DataPoint; import org.openmhealth.schema.domain.omh.TypedUnitValue; -import java.util.List; import java.util.Optional; -import static java.util.Collections.singletonList; import static org.openmhealth.schema.domain.omh.BodyMassIndexUnit.KILOGRAMS_PER_SQUARE_METER; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredDouble; @@ -32,35 +30,35 @@ /** * @author Chris Schaefbauer */ -public class IHealthBodyMassIndexDataPointMapper extends IHealthDataPointMapper{ +public class IHealthBodyMassIndexDataPointMapper extends IHealthDataPointMapper { @Override - protected List getListNodeNames() { - return singletonList("WeightDataList"); + protected String getListNodeName() { + return "WeightDataList"; } @Override - protected Optional getUnitPropertyNameForMeasure() { + protected Optional getMeasureUnitNodeName() { return Optional.of("WeightUnit"); } @Override - protected Optional> asDataPoint(JsonNode listNode, Integer measureUnit) { + protected Optional> asDataPoint(JsonNode listNode, Integer measureUnitMagicNumber) { Double bmiValue = asRequiredDouble(listNode, "BMI"); - if(bmiValue == 0){ + if (bmiValue == 0) { return Optional.empty(); } - BodyMassIndex.Builder bodyMassIndexBuilder = new BodyMassIndex.Builder(new TypedUnitValue<>(KILOGRAMS_PER_SQUARE_METER, - bmiValue)); + BodyMassIndex.Builder bodyMassIndexBuilder = + new BodyMassIndex.Builder(new TypedUnitValue<>(KILOGRAMS_PER_SQUARE_METER, bmiValue)); - setEffectiveTimeFrameIfExists(listNode,bodyMassIndexBuilder); - setUserNoteIfExists(listNode,bodyMassIndexBuilder); + setEffectiveTimeFrameIfExists(listNode, bodyMassIndexBuilder); + setUserNoteIfExists(listNode, bodyMassIndexBuilder); BodyMassIndex bodyMassIndex = bodyMassIndexBuilder.build(); - return Optional.of(new DataPoint<>(createDataPointHeader(listNode,bodyMassIndex),bodyMassIndex)); + return Optional.of(new DataPoint<>(createDataPointHeader(listNode, bodyMassIndex), bodyMassIndex)); } } diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java index 518f6db8..ae90439b 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java @@ -22,10 +22,8 @@ import org.openmhealth.schema.domain.omh.MassUnit; import org.openmhealth.schema.domain.omh.MassUnitValue; -import java.util.List; import java.util.Optional; -import static java.util.Collections.singletonList; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredDouble; @@ -38,19 +36,19 @@ public class IHealthBodyWeightDataPointMapper extends IHealthDataPointMapper getListNodeNames() { - return singletonList("WeightDataList"); + protected String getListNodeName() { + return "WeightDataList"; } @Override - protected Optional getUnitPropertyNameForMeasure() { + protected Optional getMeasureUnitNodeName() { return Optional.of("WeightUnit"); } @Override - protected Optional> asDataPoint(JsonNode listNode, Integer measureUnit) { + protected Optional> asDataPoint(JsonNode listNode, Integer measureUnitMagicNumber) { - IHealthBodyWeightUnit bodyWeightUnitType = IHealthBodyWeightUnit.fromIntegerValue(measureUnit); + IHealthBodyWeightUnit bodyWeightUnitType = IHealthBodyWeightUnit.fromIntegerValue(measureUnitMagicNumber); MassUnit bodyWeightUnit = bodyWeightUnitType.getOmhUnit(); double bodyWeightValue = getBodyWeightValueForUnitType(listNode, bodyWeightUnitType); diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java index c90b9ad8..95258f58 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java @@ -18,7 +18,10 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.Lists; -import org.openmhealth.schema.domain.omh.*; +import org.openmhealth.schema.domain.omh.DataPoint; +import org.openmhealth.schema.domain.omh.DataPointAcquisitionProvenance; +import org.openmhealth.schema.domain.omh.DataPointHeader; +import org.openmhealth.schema.domain.omh.Measure; import org.openmhealth.shim.common.mapper.DataPointMapper; import java.time.Instant; @@ -28,7 +31,9 @@ import java.util.Optional; import java.util.UUID; -import static org.openmhealth.schema.domain.omh.DataPointModality.*; +import static com.google.common.base.Preconditions.checkNotNull; +import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; +import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*; @@ -44,21 +49,23 @@ public abstract class IHealthDataPointMapper implements DataPointMapper> asDataPoints(List responseNodes) { - List> dataPoints = Lists.newArrayList(); + // all mapped iHealth responses only require a single endpoint response + checkNotNull(responseNodes); + checkNotNull(responseNodes.size() == 1, "A single response node is allowed per call."); + + JsonNode responseNode = responseNodes.get(0); - for (int i = 0; i < responseNodes.size(); i++) { + Integer measureUnitMagicNumber = null; - JsonNode responseNode = responseNodes.get(i); - Optional measureUnit = Optional.empty(); - if (getUnitPropertyNameForMeasure().isPresent()) { + if (getMeasureUnitNodeName().isPresent()) { + measureUnitMagicNumber = asRequiredInteger(responseNode, getMeasureUnitNodeName().get()); + } - measureUnit = asOptionalInteger(responseNode, getUnitPropertyNameForMeasure().get()); - } + List> dataPoints = Lists.newArrayList(); - for (JsonNode listNode : asRequiredNode(responseNode, getListNodeNames().get(i))) { + for (JsonNode listEntryNode : asRequiredNode(responseNode, getListNodeName())) { - asDataPoint(listNode, measureUnit.orElse(null)).ifPresent(dataPoints::add); - } + asDataPoint(listEntryNode, measureUnitMagicNumber).ifPresent(dataPoints::add); } return dataPoints; @@ -127,9 +134,10 @@ else if (dataSourceValue.equals(DATA_SOURCE_MANUAL)) { } } - protected abstract List getListNodeNames(); + // TODO add Javadoc + protected abstract String getListNodeName(); - protected abstract Optional getUnitPropertyNameForMeasure(); + protected abstract Optional getMeasureUnitNodeName(); - protected abstract Optional> asDataPoint(JsonNode jsonNode, Integer measureUnit); + protected abstract Optional> asDataPoint(JsonNode listEntryNode, Integer measureUnitMagicNumber); } diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java index 50937e65..da1838a9 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java @@ -17,11 +17,9 @@ package org.openmhealth.shim.ihealth.mapper; import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.collect.Lists; import org.openmhealth.schema.domain.omh.DataPoint; import org.openmhealth.schema.domain.omh.HeartRate; -import java.util.List; import java.util.Optional; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredDouble; @@ -30,27 +28,20 @@ /** * @author Chris Schaefbauer */ -public class IHealthHeartRateDataPointMapper extends IHealthDataPointMapper{ - - - @Override - protected List getListNodeNames() { - - return Lists.newArrayList("BPDataList","BODataList"); - } +public abstract class IHealthHeartRateDataPointMapper extends IHealthDataPointMapper { @Override - protected Optional getUnitPropertyNameForMeasure() { + protected Optional getMeasureUnitNodeName() { return Optional.empty(); } @Override - protected Optional> asDataPoint(JsonNode listNode, Integer measureUnit) { + protected Optional> asDataPoint(JsonNode listNode, Integer measureUnitMagicNumber) { - double heartRateValue = asRequiredDouble(listNode,"HR"); + double heartRateValue = asRequiredDouble(listNode, "HR"); HeartRate.Builder heartRateBuilder = new HeartRate.Builder(heartRateValue); - setEffectiveTimeFrameIfExists(listNode,heartRateBuilder); + setEffectiveTimeFrameIfExists(listNode, heartRateBuilder); HeartRate heartRate = heartRateBuilder.build(); - return Optional.of(new DataPoint<>(createDataPointHeader(listNode,heartRate),heartRate)); + return Optional.of(new DataPoint<>(createDataPointHeader(listNode, heartRate), heartRate)); } } diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java index 0a95348d..ac9b8059 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java @@ -25,10 +25,8 @@ import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.ZoneOffset; -import java.util.List; import java.util.Optional; -import static java.util.Collections.singletonList; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*; @@ -38,19 +36,18 @@ public class IHealthPhysicalActivityDataPointMapper extends IHealthDataPointMapper { @Override - protected List getListNodeNames() { - - return singletonList("SPORTDataList"); + protected String getListNodeName() { + return "SPORTDataList"; } @Override - protected Optional getUnitPropertyNameForMeasure() { + protected Optional getMeasureUnitNodeName() { return Optional.empty(); } @Override - protected Optional> asDataPoint(JsonNode listNode, Integer measureUnit) { + protected Optional> asDataPoint(JsonNode listNode, Integer measureUnitMagicNumber) { String activityName = asRequiredString(listNode, "SportName"); From e96f147912dae3032e5bc57c33c048b61ef6823f Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Mon, 5 Oct 2015 14:33:09 -0600 Subject: [PATCH 20/68] Refactor heart rate mapper to match endpoints Heart rate data comes from two endpoints in iHealth, so instead of using one heart rate data point mapper to handle both endpoints, we use a base heart rate mapper class that handles the mapping, which is the same, then subclass into mappers that match each of the endpoints which have different property names for the lists that need to be addressed. --- .../openmhealth/shim/ihealth/IHealthShim.java | 149 ++++++++++-------- .../IHealthHeartRateDataPointMapper.java | 17 +- ...ointHeartRateDataPointMapperUnitTests.java | 110 +++++++++++++ ...intHeartRateDataPointMapperUnitTests.java} | 25 ++- ...health-blood-glucose-missing-timezone.json | 24 +++ ...ealth-blood-oxygen-missing-heart-rate.json | 22 +++ ...om-spo2.json => ihealth-blood-oxygen.json} | 2 +- ...lth-blood-pressure-missing-heart-rate.json | 26 +++ .../mapper/ihealth-blood-pressure.json | 2 +- .../mapper/ihealth-heart-rate-from-bp.json | 41 ----- 10 files changed, 295 insertions(+), 123 deletions(-) create mode 100644 shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapperUnitTests.java rename shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/{IHealthHeartRateDataPointMapperUnitTests.java => IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java} (69%) create mode 100644 shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose-missing-timezone.json create mode 100644 shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-oxygen-missing-heart-rate.json rename shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/{ihealth-heart-rate-from-spo2.json => ihealth-blood-oxygen.json} (97%) create mode 100644 shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure-missing-heart-rate.json delete mode 100644 shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-bp.json diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java index 95507514..5f52f622 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java @@ -115,27 +115,27 @@ public ShimDataType[] getShimDataTypes() { }; } -// @Value("${openmhealth.shim.ihealth.sportSC}") -// public String sportSC; -// @Value("${openmhealth.shim.ihealth.sportSV}") -// public String sportSV; -// @Value("${openmhealth.shim.ihealth.bloodPressureSC}") -// public String bloodPressureSC; -// @Value("${openmhealth.shim.ihealth.bloodPressureSV}") -// public String bloodPressureSV; -// @Value("${openmhealth.shim.ihealth.spo2SC}") -// public String spo2SC; -// @Value("${openmhealth.shim.ihealth.spo2SV}") -// public String spo2SV; - - Map serialValues; - - public Map getSerialValues(){ + // @Value("${openmhealth.shim.ihealth.sportSC}") + // public String sportSC; + // @Value("${openmhealth.shim.ihealth.sportSV}") + // public String sportSV; + // @Value("${openmhealth.shim.ihealth.bloodPressureSC}") + // public String bloodPressureSC; + // @Value("${openmhealth.shim.ihealth.bloodPressureSV}") + // public String bloodPressureSV; + // @Value("${openmhealth.shim.ihealth.spo2SC}") + // public String spo2SC; + // @Value("${openmhealth.shim.ihealth.spo2SV}") + // public String spo2SV; + + Map serialValues; + + public Map getSerialValues() { return serialValues; } - public void setSerialValues(Map serialValues){ + public void setSerialValues(Map serialValues) { this.serialValues = serialValues; } @@ -149,7 +149,7 @@ public enum IHealthDataTypes implements ShimDataType { //SLEEP("sleep"), //STEP_COUNT("activity"), BODY_MASS_INDEX(singletonList("weight.json")), - HEART_RATE(Lists.newArrayList("bp.json","spo2.json")); + HEART_RATE(Lists.newArrayList("bp.json", "spo2.json")); private List endPoint; @@ -191,33 +191,36 @@ protected ResponseEntity getData(OAuth2RestOperations restTemp List responseEntities = Lists.newArrayList(); - int i=0; - for(String endPoint : dataType.getEndPoint()){ + int i = 0; + for (String endPoint : dataType.getEndPoint()) { UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(API_URL); String userId = "uk"; - if(shimDataRequest.getAccessParameters()!=null){ + if (shimDataRequest.getAccessParameters() != null) { - OAuth2AccessToken token = SerializationUtils.deserialize(shimDataRequest.getAccessParameters().getSerializedToken());; + OAuth2AccessToken token = + SerializationUtils.deserialize(shimDataRequest.getAccessParameters().getSerializedToken()); + ; userId = Preconditions.checkNotNull((String) token.getAdditionalInformation().get("UserID")); uriBuilder.queryParam("access_token", token.getValue()); } uriBuilder.path("/user/") - .path(userId+"/") + .path(userId + "/") .path(endPoint) .queryParam("client_id", restTemplate.getResource().getClientId()) - .queryParam("client_secret",restTemplate.getResource().getClientSecret()) + .queryParam("client_secret", restTemplate.getResource().getClientSecret()) .queryParam("start_time", startDate.toEpochSecond()) - .queryParam("end_time",endDate.toEpochSecond()) + .queryParam("end_time", endDate.toEpochSecond()) .queryParam("locale", "default") .queryParam("sc", scValues.get(i)) .queryParam("sv", svValues.get(i)); - try{ + ResponseEntity responseEntity; + try { URI url = uriBuilder.build().encode().toUri(); - responseEntities.add(restTemplate.getForEntity(url, JsonNode.class).getBody()); + responseEntity = restTemplate.getForEntity(url, JsonNode.class); } catch (HttpClientErrorException | HttpServerErrorException e) { // FIXME figure out how to handle this @@ -225,52 +228,62 @@ protected ResponseEntity getData(OAuth2RestOperations restTemp throw e; } - i++; + if (shimDataRequest.getNormalize()) { + // SimpleModule module = new SimpleModule(); + // module.addDeserializer(ShimDataResponse.class, dataType.getNormalizer()); + // objectMapper.registerModule(module); + IHealthDataPointMapper mapper; + + switch ( dataType ) { + + case PHYSICAL_ACTIVITY: + mapper = new IHealthPhysicalActivityDataPointMapper(); + break; + case BLOOD_GLUCOSE: + mapper = new IHealthBloodGlucoseDataPointMapper(); + break; + case BLOOD_PRESSURE: + mapper = new IHealthBloodPressureDataPointMapper(); + break; + case BODY_WEIGHT: + mapper = new IHealthBodyWeightDataPointMapper(); + break; + case BODY_MASS_INDEX: + mapper = new IHealthBodyMassIndexDataPointMapper(); + break; + case HEART_RATE: + // there are two different mappers for heart rate because the data can come from two endpoints + if(endPoint == "bp.json"){ + mapper = new IHealthBloodPressureEndpointHeartRateDataPointMapper(); + break; + } + else if (endPoint == "spo2.json"){ + mapper = new IHealthBloodOxygenEndpointHeartRateDataPointMapper(); + break; + } + default: + throw new UnsupportedOperationException(); + } - } + responseEntities.addAll(mapper.asDataPoints(singletonList(responseEntity.getBody()))); - if (shimDataRequest.getNormalize()) { - // SimpleModule module = new SimpleModule(); - // module.addDeserializer(ShimDataResponse.class, dataType.getNormalizer()); - // objectMapper.registerModule(module); - IHealthDataPointMapper mapper; - - switch ( dataType ) { - - case PHYSICAL_ACTIVITY: - mapper = new IHealthPhysicalActivityDataPointMapper(); - break; - case BLOOD_GLUCOSE: - mapper = new IHealthBloodGlucoseDataPointMapper(); - break; - case BLOOD_PRESSURE: - mapper = new IHealthBloodPressureDataPointMapper(); - break; - case BODY_WEIGHT: - mapper = new IHealthBodyWeightDataPointMapper(); - break; - case BODY_MASS_INDEX: - mapper = new IHealthBodyMassIndexDataPointMapper(); - break; - case HEART_RATE: - mapper = new IHealthHeartRateDataPointMapper(); - break; - default: - throw new UnsupportedOperationException(); + } + else { + responseEntities.add(responseEntity.getBody()); } - return ResponseEntity.ok().body( - ShimDataResponse.result(SHIM_KEY, mapper.asDataPoints(responseEntities))); - } - else { + i++; - return ResponseEntity.ok().body(ShimDataResponse.result(SHIM_KEY, responseEntities)); } + + return ResponseEntity.ok().body( + ShimDataResponse.result(SHIM_KEY,responseEntities)); + } private List getScValues(IHealthDataTypes dataType) { - switch(dataType){ + switch ( dataType ) { case PHYSICAL_ACTIVITY: return singletonList(serialValues.get("sportSC")); case BODY_WEIGHT: @@ -290,7 +303,7 @@ private List getScValues(IHealthDataTypes dataType) { private List getSvValues(IHealthDataTypes dataType) { - switch (dataType){ + switch ( dataType ) { case PHYSICAL_ACTIVITY: return singletonList(serialValues.get("sportSV")); case BODY_WEIGHT: @@ -309,10 +322,10 @@ private List getSvValues(IHealthDataTypes dataType) { } // @Override -// public void trigger(OAuth2RestOperations restTemplate, ShimDataRequest shimDataRequest) throws ShimException { -// -// -// } + // public void trigger(OAuth2RestOperations restTemplate, ShimDataRequest shimDataRequest) throws ShimException { + // + // + // } @Override public OAuth2ProtectedResourceDetails getResource() { diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java index da1838a9..e2c0bae2 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java @@ -36,12 +36,21 @@ protected Optional getMeasureUnitNodeName() { } @Override - protected Optional> asDataPoint(JsonNode listNode, Integer measureUnitMagicNumber) { + protected Optional> asDataPoint(JsonNode listEntryNode, Integer measureUnitMagicNumber) { + + double heartRateValue = asRequiredDouble(listEntryNode, "HR"); + + if (heartRateValue == 0) { + + return Optional.empty(); + } - double heartRateValue = asRequiredDouble(listNode, "HR"); HeartRate.Builder heartRateBuilder = new HeartRate.Builder(heartRateValue); - setEffectiveTimeFrameIfExists(listNode, heartRateBuilder); + + setEffectiveTimeFrameIfExists(listEntryNode, heartRateBuilder); + setUserNoteIfExists(listEntryNode, heartRateBuilder); + HeartRate heartRate = heartRateBuilder.build(); - return Optional.of(new DataPoint<>(createDataPointHeader(listNode, heartRate), heartRate)); + return Optional.of(new DataPoint<>(createDataPointHeader(listEntryNode, heartRate), heartRate)); } } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapperUnitTests.java new file mode 100644 index 00000000..690f1cdc --- /dev/null +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapperUnitTests.java @@ -0,0 +1,110 @@ +/* + * 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.ihealth.mapper; + +import com.fasterxml.jackson.databind.JsonNode; +import org.openmhealth.schema.domain.omh.DataPoint; +import org.openmhealth.schema.domain.omh.HeartRate; +import org.springframework.core.io.ClassPathResource; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.time.OffsetDateTime; +import java.util.List; + +import static java.util.Collections.singletonList; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; +import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; + + +/** + * @author Chris Schaefbauer + */ +public class IHealthBloodOxygenEndpointHeartRateDataPointMapperUnitTests extends IHealthDataPointMapperUnitTests { + + JsonNode responseNode; + private IHealthBloodOxygenEndpointHeartRateDataPointMapper mapper = + new IHealthBloodOxygenEndpointHeartRateDataPointMapper(); + + @BeforeTest + public void initializeResponseNode() throws IOException { + + ClassPathResource resource = + new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-blood-oxygen.json"); + responseNode = objectMapper.readTree(resource.getInputStream()); + } + + @Test + public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + assertThat(dataPoints.size(), equalTo(2)); + } + + @Test + public void asDataPointsShouldReturnCorrectSensedDataPoints() { + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + + HeartRate.Builder expectedHeartRateBuilder = new HeartRate.Builder(80) + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-23T15:46:00-06:00")); + + assertThat(dataPoints.get(0).getBody(), equalTo(expectedHeartRateBuilder.build())); + + testDataPointHeader(dataPoints.get(0).getHeader(), HeartRate.SCHEMA_ID, SENSED, + "d7fb9db14b0fc3e8e1635720c28bda64", OffsetDateTime.parse("2015-09-23T21:46:00Z")); + } + + @Test + public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + + HeartRate.Builder expectedHeartRateBuilder = new HeartRate.Builder(65) + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-24T15:03:00-06:00")) + .setUserNotes("Satch on satch "); + + assertThat(dataPoints.get(1).getBody(), equalTo(expectedHeartRateBuilder.build())); + + assertThat(dataPoints.get(1).getHeader().getAcquisitionProvenance().getModality(), equalTo(SELF_REPORTED)); + } + + @Test + public void asDataPointsShouldReturnCorrectUserNotesWithDataPoints() { + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + + assertThat(dataPoints.get(0).getBody().getUserNotes(), nullValue()); + assertThat(dataPoints.get(1).getBody().getUserNotes(), equalTo("Satch on satch ")); + } + + @Test + public void asDataPointsShouldReturnNoDataPointWhenHeartRateDataIsNotPresent() throws IOException { + + ClassPathResource resource = new ClassPathResource( + "org/openmhealth/shim/ihealth/mapper/ihealth-blood-oxygen-missing-heart-rate.json"); + JsonNode noHeartRateBloodOxygenNode = objectMapper.readTree(resource.getInputStream()); + + List> dataPoints = mapper.asDataPoints(singletonList(noHeartRateBloodOxygenNode)); + + assertThat(dataPoints.size(), equalTo(0)); + } +} diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java similarity index 69% rename from shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapperUnitTests.java rename to shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java index 206ad640..c3a631ca 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java @@ -27,8 +27,10 @@ import java.io.IOException; import java.time.OffsetDateTime; +import java.util.Collections; import java.util.List; +import static java.util.Collections.*; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; @@ -36,16 +38,22 @@ /** * @author Chris Schaefbauer */ -public class IHealthHeartRateDataPointMapperUnitTests extends IHealthDataPointMapperUnitTests { +public class IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests extends IHealthDataPointMapperUnitTests { JsonNode bpNode; JsonNode spo2Node; - private IHealthHeartRateDataPointMapper mapper = new IHealthHeartRateDataPointMapper(); + + private IHealthBloodOxygenEndpointHeartRateDataPointMapper bloodOxygenMapper = + new IHealthBloodOxygenEndpointHeartRateDataPointMapper(); + + private IHealthBloodPressureEndpointHeartRateDataPointMapper bloodPressureMapper = + new IHealthBloodPressureEndpointHeartRateDataPointMapper(); @BeforeTest public void initializeResponseNodes() throws IOException { - ClassPathResource resource = new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-bp.json"); + ClassPathResource resource = + new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-bp.json"); bpNode = objectMapper.readTree(resource.getInputStream()); resource = new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-spo2.json"); @@ -53,21 +61,22 @@ public void initializeResponseNodes() throws IOException { } @Test - public void asDataPointsShouldReturnCorrectNumberOfDataPoints(){ + public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { List> dataPoints = mapper.asDataPoints(Lists.newArrayList(bpNode, spo2Node)); - assertThat(dataPoints.size(),equalTo(4)); + assertThat(dataPoints.size(), equalTo(4)); } @Test - public void asDataPointsShouldReturnCorrectSensedDataPointsFromBpResponse(){ + public void asDataPointsShouldReturnCorrectSensedDataPointsFromBpResponse() { - List> dataPoints = mapper.asDataPoints(Lists.newArrayList(bpNode, spo2Node)); + List> dataPoints = bloodPressureMapper.asDataPoints( + singletonList(bpNode)); HeartRate.Builder expectedHeartRateBuilder = new HeartRate.Builder(100) .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T04:04:23-08:00")); HeartRate expectedSensedHeartRate = expectedHeartRateBuilder.build(); - assertThat(dataPoints.get(0).getBody(),equalTo(expectedSensedHeartRate)); + assertThat(dataPoints.get(0).getBody(), equalTo(expectedSensedHeartRate)); } diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose-missing-timezone.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose-missing-timezone.json new file mode 100644 index 00000000..e0e7d177 --- /dev/null +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose-missing-timezone.json @@ -0,0 +1,24 @@ +{ + "BGDataList": [ + { + "BG": 60, + "DataID": "f706b6152f684c0e9185b1fa6b7c5148", + "DataSource": "FromDevice", + "DinnerSituation": "Before_breakfast", + "DrugSituation": "Before_taking_pills", + "LastChangeTime": 1442520221, + "Lat": -1, + "Lon": -1, + "MDate": 1442491407, + "Note": "Such glucose, much blood.", + "TimeZone": "" + } + ], + "BGUnit": 0, + "CurrentRecordCount": 3, + "NextPageUrl": "", + "PageLength": 50, + "PageNumber": 1, + "PrevPageUrl": "", + "RecordCount": 3 +} \ No newline at end of file diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-oxygen-missing-heart-rate.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-oxygen-missing-heart-rate.json new file mode 100644 index 00000000..a5cf36fa --- /dev/null +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-oxygen-missing-heart-rate.json @@ -0,0 +1,22 @@ +{ + "BODataList": [ + { + "BO": 99, + "DataID": "d7fb9db14b0fc3e8e1635720c28bda64", + "DataSource": "FromDevice", + "HR": 0, + "LastChangeTime": 1443044760, + "Lat": 0, + "Lon": 0, + "MDate": 1443023160, + "Note": "", + "TimeZone": "-0600" + } + ], + "CurrentRecordCount": 1, + "NextPageUrl": "", + "PageLength": 50, + "PageNumber": 1, + "PrevPageUrl": "", + "RecordCount": 1 +} \ No newline at end of file diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-spo2.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-oxygen.json similarity index 97% rename from shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-spo2.json rename to shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-oxygen.json index b185b5a2..e35aef3f 100644 --- a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-spo2.json +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-oxygen.json @@ -16,7 +16,7 @@ "BO": 90, "DataID": "217e56a104cf8462f4977fd8bf743ca5", "DataSource": "Manual", - "HR": 0, + "HR": 65, "LastChangeTime": 1443128580, "Lat": 0, "Lon": 0, diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure-missing-heart-rate.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure-missing-heart-rate.json new file mode 100644 index 00000000..c22dd10c --- /dev/null +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure-missing-heart-rate.json @@ -0,0 +1,26 @@ +{ + "BPDataList": [ + { + "BPL": 3, + "DataID": "d6c08fb0bde6dc50db5c5f305a76c455", + "DataSource": "Manual", + "HP": 130, + "HR": 0, + "IsArr": 0, + "LP": 95, + "LastChangeTime": 1442520465, + "Lat": 0, + "Lon": 0, + "MDate": 1442498865, + "Note": "BP on the up and up.", + "TimeZone": "-0600" + } + ], + "BPUnit": 0, + "CurrentRecordCount": 1, + "NextPageUrl": "", + "PageLength": 50, + "PageNumber": 1, + "PrevPageUrl": "", + "RecordCount": 1 +} \ No newline at end of file diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json index 42900818..20fa5f72 100644 --- a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json @@ -20,7 +20,7 @@ "DataID": "d6c08fb0bde6dc50db5c5f305a76c455", "DataSource": "Manual", "HP": 130, - "HR": 100, + "HR": 75, "IsArr": 0, "LP": 95, "LastChangeTime": 1442520465, diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-bp.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-bp.json deleted file mode 100644 index 77806516..00000000 --- a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-bp.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "BPDataList": [ - { - "BPL": 3, - "DataID": "c62b84d9d4b7480a8ff2aef1465aa454", - "DataSource": "FromDevice", - "HP": 120, - "HR": 100, - "IsArr": -1, - "LP": 90, - "LastChangeTime": 1442520270, - "Lat": -1, - "Lon": -1, - "MDate": 1442491463, - "Note": "", - "TimeZone": "-0800" - }, - { - "BPL": 3, - "DataID": "d6c08fb0bde6dc50db5c5f305a76c455", - "DataSource": "Manual", - "HP": 120, - "HR": 100, - "IsArr": 0, - "LP": 90, - "LastChangeTime": 1442520465, - "Lat": 0, - "Lon": 0, - "MDate": 1442498865, - "Note": "", - "TimeZone": "-0600" - } - ], - "BPUnit": 0, - "CurrentRecordCount": 3, - "NextPageUrl": "", - "PageLength": 50, - "PageNumber": 1, - "PrevPageUrl": "", - "RecordCount": 3 -} \ No newline at end of file From 6192ca30a6a2323ec9a250410702aa77140d3fce Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Mon, 5 Oct 2015 14:37:53 -0600 Subject: [PATCH 21/68] Fix zone offsets in iHealth mappers iHealth provides unix second timestamps that are offset to the timezone provided with the data, so the timestamps are not in utc, they are in local time. That means the offset times need to be adjusted based on the provided offset, so we now create offset times in utc, knowing that when the offset seconds are rendered in utc, they will have the correct local date and time, but the wrong offset, so we strip the offset and apply the appropriate offset. --- .../mapper/IHealthDataPointMapper.java | 27 ++++++-- ...HealthPhysicalActivityDataPointMapper.java | 12 +--- ...hBloodGlucoseDataPointMapperUnitTests.java | 4 +- ...BloodPressureDataPointMapperUnitTests.java | 4 +- ...ointHeartRateDataPointMapperUnitTests.java | 68 +++++++++++++------ ...BodyMassIndexDataPointMapperUnitTests.java | 4 +- ...lthBodyWeightDataPointMapperUnitTests.java | 4 +- .../IHealthDataPointMapperUnitTests.java | 7 ++ ...sicalActivityDataPointMapperUnitTests.java | 8 +-- 9 files changed, 90 insertions(+), 48 deletions(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java index 95258f58..ccff6fe3 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java @@ -27,6 +27,7 @@ import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneId; +import java.time.ZoneOffset; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -97,22 +98,34 @@ protected DataPointHeader createDataPointHeader(JsonNode listNode, Measure measu return dataPointHeader; } - protected void setEffectiveTimeFrameIfExists(JsonNode listNode, Measure.Builder builder) { + static protected void setEffectiveTimeFrameIfExists(JsonNode listNode, Measure.Builder builder) { Optional optionalOffsetDateTime = asOptionalLong(listNode, "MDate"); if (optionalOffsetDateTime.isPresent()) { - asOptionalString(listNode, "TimeZone").ifPresent(timezoneOffsetString -> builder - .setEffectiveTimeFrame( - OffsetDateTime.ofInstant( - Instant.ofEpochSecond(optionalOffsetDateTime.get()), - ZoneId.of(timezoneOffsetString)))); + Optional timeZoneString = asOptionalString(listNode, "TimeZone"); + + if( timeZoneString.isPresent()){ + + OffsetDateTime offsetDateTimeCorrectOffset = + getDateTimeWithCorrectOffset(optionalOffsetDateTime.get(), timeZoneString.get()); + builder.setEffectiveTimeFrame(offsetDateTimeCorrectOffset); + } } } - protected void setUserNoteIfExists(JsonNode listNode, Measure.Builder builder) { + protected static OffsetDateTime getDateTimeWithCorrectOffset(Long optionalOffsetDateTime, + String timeZoneString) { + + OffsetDateTime offsetDateTimeFromOffsetInstant = OffsetDateTime.ofInstant( + Instant.ofEpochSecond(optionalOffsetDateTime), + ZoneId.of("Z")); + return offsetDateTimeFromOffsetInstant.toLocalDateTime().atOffset(ZoneOffset.of(timeZoneString)); + } + + static protected void setUserNoteIfExists(JsonNode listNode, Measure.Builder builder) { Optional note = asOptionalString(listNode, "Note"); diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java index ac9b8059..469e4b91 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java @@ -21,10 +21,6 @@ import org.openmhealth.schema.domain.omh.PhysicalActivity; import org.openmhealth.schema.domain.omh.TimeInterval; -import java.time.Instant; -import java.time.OffsetDateTime; -import java.time.ZoneId; -import java.time.ZoneOffset; import java.util.Optional; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*; @@ -64,12 +60,8 @@ protected Optional> asDataPoint(JsonNode listNode, I if (startTimeUnixEpochSecs.isPresent() && endTimeUnixEpochSecs.isPresent() && timeZoneOffset.isPresent()) { physicalActivityBuilder.setEffectiveTimeFrame(TimeInterval.ofStartDateTimeAndEndDateTime( - OffsetDateTime.ofInstant( - Instant.ofEpochSecond(startTimeUnixEpochSecs.get()), - ZoneId.ofOffset("UTC", ZoneOffset.ofHours(timeZoneOffset.get()))), - OffsetDateTime.ofInstant( - Instant.ofEpochSecond(endTimeUnixEpochSecs.get()), - ZoneId.ofOffset("UTC", ZoneOffset.ofHours(timeZoneOffset.get()))))); + getDateTimeWithCorrectOffset(startTimeUnixEpochSecs.get(), timeZoneOffset.get().toString()), + getDateTimeWithCorrectOffset(endTimeUnixEpochSecs.get(), timeZoneOffset.get().toString()))); } PhysicalActivity physicalActivity = physicalActivityBuilder.build(); diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java index cb038e59..52d65731 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java @@ -65,7 +65,7 @@ public void asDataPointsShouldReturnCorrectSensedDataPoints() { BloodGlucose.Builder expectedBloodGlucoseBuilder = new BloodGlucose.Builder( new TypedUnitValue<>(BloodGlucoseUnit.MILLIGRAMS_PER_DECILITER, 60)) - .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T04:03:27-08:00")) + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T12:03:27-08:00")) .setTemporalRelationshipToMeal(TemporalRelationshipToMeal.BEFORE_BREAKFAST) .setUserNotes("Such glucose, much blood."); @@ -86,7 +86,7 @@ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { BloodGlucose.Builder expectedBloodGlucoseBuilder = new BloodGlucose.Builder(new TypedUnitValue<>(BloodGlucoseUnit.MILLIGRAMS_PER_DECILITER, 70)) .setTemporalRelationshipToMeal(TemporalRelationshipToMeal.AFTER_BREAKFAST) - .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-24T08:44:40-06:00")); + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-24T14:44:40-06:00")); assertThat(dataPoints.get(1).getBody(), equalTo(expectedBloodGlucoseBuilder.build())); diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java index b9483252..fb6c8f96 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java @@ -67,7 +67,7 @@ public void asDataPointsShouldReturnCorrectSensedDataPoints() { BloodPressure expectedBloodPressure = new BloodPressure.Builder( new SystolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, 120), new DiastolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, 90)) - .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T04:04:23-08:00")) + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T12:04:23-08:00")) .build(); assertThat(dataPoints.get(0).getBody(), equalTo(expectedBloodPressure)); @@ -87,7 +87,7 @@ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { BloodPressure expectedBloodPressure = new BloodPressure.Builder( new SystolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, 130), new DiastolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, 95)) - .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T08:07:45-06:00")) + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T14:07:45-06:00")) .setUserNotes("BP on the up and up.") .build(); diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java index c3a631ca..3aceab89 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java @@ -18,7 +18,6 @@ import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.collect.Lists; import org.openmhealth.schema.domain.omh.DataPoint; import org.openmhealth.schema.domain.omh.HeartRate; import org.springframework.core.io.ClassPathResource; @@ -27,12 +26,13 @@ import java.io.IOException; import java.time.OffsetDateTime; -import java.util.Collections; import java.util.List; -import static java.util.Collections.*; +import static java.util.Collections.singletonList; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; /** @@ -40,44 +40,74 @@ */ public class IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests extends IHealthDataPointMapperUnitTests { - JsonNode bpNode; - JsonNode spo2Node; + JsonNode responseNode; - private IHealthBloodOxygenEndpointHeartRateDataPointMapper bloodOxygenMapper = - new IHealthBloodOxygenEndpointHeartRateDataPointMapper(); - - private IHealthBloodPressureEndpointHeartRateDataPointMapper bloodPressureMapper = + private IHealthBloodPressureEndpointHeartRateDataPointMapper mapper = new IHealthBloodPressureEndpointHeartRateDataPointMapper(); @BeforeTest public void initializeResponseNodes() throws IOException { ClassPathResource resource = - new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-bp.json"); - bpNode = objectMapper.readTree(resource.getInputStream()); + new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json"); + responseNode = objectMapper.readTree(resource.getInputStream()); - resource = new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-heart-rate-from-spo2.json"); - spo2Node = objectMapper.readTree(resource.getInputStream()); } @Test public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { - List> dataPoints = mapper.asDataPoints(Lists.newArrayList(bpNode, spo2Node)); - assertThat(dataPoints.size(), equalTo(4)); + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + assertThat(dataPoints.size(), equalTo(2)); } @Test - public void asDataPointsShouldReturnCorrectSensedDataPointsFromBpResponse() { + public void asDataPointsShouldReturnCorrectSensedDataPoints() { - List> dataPoints = bloodPressureMapper.asDataPoints( - singletonList(bpNode)); + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); HeartRate.Builder expectedHeartRateBuilder = new HeartRate.Builder(100) - .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T04:04:23-08:00")); + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T12:04:23-08:00")); HeartRate expectedSensedHeartRate = expectedHeartRateBuilder.build(); assertThat(dataPoints.get(0).getBody(), equalTo(expectedSensedHeartRate)); + testDataPointHeader(dataPoints.get(0).getHeader(), HeartRate.SCHEMA_ID, SENSED, + "c62b84d9d4b7480a8ff2aef1465aa454", OffsetDateTime.parse("2015-09-17T20:04:30Z")); } + @Test + public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + + HeartRate expectedHeartRate = new HeartRate.Builder(75) + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T08:07:45-06:00")) + .setUserNotes("BP on the up and up.") + .build(); + +// assertThat(dataPoints.get(1).getBody(), equalTo(expectedHeartRate)); +// +// assertThat(dataPoints.get(1).getHeader().getAcquisitionProvenance().getModality(), equalTo(SELF_REPORTED)); + } + + @Test + public void asDataPointsShouldReturnCorrectUserNotesWithDataPoints() { + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + + assertThat(dataPoints.get(0).getBody().getUserNotes(), nullValue()); + assertThat(dataPoints.get(1).getBody().getUserNotes(), equalTo("BP on the up and up.")); + } + + @Test + public void asDataPointsShouldReturnNoDataPointWhenHeartRateDataIsNotPresent() throws IOException { + + ClassPathResource resource = new ClassPathResource( + "org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure-missing-heart-rate.json"); + JsonNode noHeartRateBloodPressureNode = objectMapper.readTree(resource.getInputStream()); + + List> dataPoints = mapper.asDataPoints(singletonList(noHeartRateBloodPressureNode)); + assertThat(dataPoints.size(),equalTo(0)); + + } } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java index 8c7d56b1..eddd1b9c 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java @@ -63,7 +63,7 @@ public void asDataPointsShouldReturnCorrectSensedDataPoints() { BodyMassIndex.Builder expectedBodyMassIndexBuilder = new BodyMassIndex.Builder(new TypedUnitValue<>( BodyMassIndexUnit.KILOGRAMS_PER_SQUARE_METER, 22.56052563257619)) - .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T04:04:09-08:00")); + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T12:04:09-08:00")); assertThat(dataPoints.get(0).getBody(), equalTo(expectedBodyMassIndexBuilder.build())); @@ -78,7 +78,7 @@ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { BodyMassIndex.Builder expectedBodyMassIndexBuilder = new BodyMassIndex.Builder( new TypedUnitValue<>(BodyMassIndexUnit.KILOGRAMS_PER_SQUARE_METER, 22.56052398681641)) - .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T08:07:57-06:00")) + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T14:07:57-06:00")) .setUserNotes("Weight so good, look at me now"); assertThat(dataPoints.get(1).getBody(), equalTo(expectedBodyMassIndexBuilder.build())); diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java index c1c7e215..fef78e38 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java @@ -65,7 +65,7 @@ public void asDataPointsShouldReturnCorrectSensedDataPoints() { BodyWeight.Builder expectedBodyWeightBuilder = new BodyWeight.Builder( new MassUnitValue(MassUnit.KILOGRAM, 77.5643875134944)) - .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T04:04:09-08:00")); + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T12:04:09-08:00")); assertThat(dataPoints.get(0).getBody(), equalTo(expectedBodyWeightBuilder.build())); @@ -83,7 +83,7 @@ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { BodyWeight.Builder expectedBodyWeightBuilder = new BodyWeight.Builder(new MassUnitValue(MassUnit.KILOGRAM, 77.56438446044922)) - .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T08:07:57-06:00")) + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T14:07:57-06:00")) .setUserNotes("Weight so good, look at me now"); assertThat(dataPoints.get(1).getBody(), equalTo(expectedBodyWeightBuilder.build())); diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java index b5f95c0e..008829e6 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java @@ -20,6 +20,7 @@ import org.openmhealth.schema.domain.omh.DataPointModality; import org.openmhealth.schema.domain.omh.SchemaId; import org.openmhealth.shim.common.mapper.DataPointMapperUnitTests; +import org.testng.annotations.Test; import java.time.OffsetDateTime; @@ -44,4 +45,10 @@ protected void testDataPointHeader(DataPointHeader testHeader, SchemaId schemaId equalTo(updatedDateTime)); } + @Test + public void setEffectiveTimeFrameShouldUseUtcWhenTimeZoneIsMissing(){ + + // Todo: waiting for response from iHealth on missing time zone representation + } + } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java index bd9362ac..ed5170b6 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java @@ -66,8 +66,8 @@ public void asDataPointsShouldReturnCorrectSensedDataPoints(){ PhysicalActivity.Builder expectedPhysicalActivityBuilder = new PhysicalActivity.Builder("Swimming, breaststroke") .setEffectiveTimeFrame(TimeInterval.ofStartDateTimeAndEndDateTime( - OffsetDateTime.parse("2015-09-17T12:02:28-08:00"), - OffsetDateTime.parse("2015-09-17T12:32:28-08:00"))); + OffsetDateTime.parse("2015-09-17T20:02:28-08:00"), + OffsetDateTime.parse("2015-09-17T20:32:28-08:00"))); assertThat(dataPoints.get(0).getBody(),equalTo(expectedPhysicalActivityBuilder.build())); testDataPointHeader(dataPoints.get(0).getHeader(), SCHEMA_ID, SENSED, @@ -83,8 +83,8 @@ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints(){ PhysicalActivity.Builder expectedPhysicalActivityBuilder = new PhysicalActivity.Builder("Running") .setEffectiveTimeFrame( TimeInterval.ofStartDateTimeAndEndDateTime( - OffsetDateTime.parse("2015-09-22T14:43:03-06:00"), - OffsetDateTime.parse("2015-09-22T15:13:03-06:00"))); + OffsetDateTime.parse("2015-09-22T20:43:03-06:00"), + OffsetDateTime.parse("2015-09-22T21:13:03-06:00"))); assertThat(dataPoints.get(1).getBody(),equalTo(expectedPhysicalActivityBuilder.build())); From ee5224fa36b22d9c837bc9024d0a4acdc292dc41 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Mon, 5 Oct 2015 14:44:57 -0600 Subject: [PATCH 22/68] Remove iHealth blood pressure unit enum The enum created unnecessary complexity and was captured with a couple constants instead of an entire enum structure. --- .../IHealthBloodPressureDataPointMapper.java | 42 ++++--------------- .../IHealthBodyWeightDataPointMapper.java | 4 +- .../mapper/IHealthDataPointMapper.java | 2 +- ...BloodPressureDataPointMapperUnitTests.java | 10 ++--- 4 files changed, 15 insertions(+), 43 deletions(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java index 089992f8..fef99f34 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java @@ -30,6 +30,8 @@ public class IHealthBloodPressureDataPointMapper extends IHealthDataPointMapper { static final double KPA_TO_MMHG_CONVERSION_RATE = 7.500617; + static final int MMHG_UNIT_MAGIC_NUMBER = 0; + static final int KPA_UNIT_MAGIC_NUMBER = 1; @Override protected String getListNodeName() { @@ -44,13 +46,11 @@ protected Optional getMeasureUnitNodeName() { @Override protected Optional> asDataPoint(JsonNode listNode, Integer bloodPressureUnit) { - IHealthBloodPressureUnit bloodPressureUnitType = IHealthBloodPressureUnit.fromIntegerValue(bloodPressureUnit); - - double systolicValue = getBloodPressureValueInMmHg(asRequiredDouble(listNode, "HP"), bloodPressureUnitType); + double systolicValue = getBloodPressureValueInMmHg(asRequiredDouble(listNode, "HP"), bloodPressureUnit); SystolicBloodPressure systolicBloodPressure = new SystolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, systolicValue); - double diastolicValue = getBloodPressureValueInMmHg(asRequiredDouble(listNode, "LP"), bloodPressureUnitType); + double diastolicValue = getBloodPressureValueInMmHg(asRequiredDouble(listNode, "LP"), bloodPressureUnit); DiastolicBloodPressure diastolicBloodPressure = new DiastolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, diastolicValue); @@ -64,44 +64,16 @@ protected Optional> asDataPoint(JsonNode listNode, Inte return Optional.of(new DataPoint<>(createDataPointHeader(listNode, bloodPressure), bloodPressure)); } - protected double getBloodPressureValueInMmHg(double rawBpValue, IHealthBloodPressureUnit bloodPressureUnit) { + protected double getBloodPressureValueInMmHg(double rawBpValue, Integer bloodPressureUnit) { switch ( bloodPressureUnit ) { - case mmHg: + case MMHG_UNIT_MAGIC_NUMBER: return rawBpValue; - case KPa: + case KPA_UNIT_MAGIC_NUMBER: return rawBpValue * KPA_TO_MMHG_CONVERSION_RATE; default: throw new UnsupportedOperationException(); } } - protected enum IHealthBloodPressureUnit { - - mmHg(0), - KPa(1); - - private int value; - - IHealthBloodPressureUnit(int value) { - this.value = value; - } - - protected int getValue() { - return value; - } - - public static IHealthBloodPressureUnit fromIntegerValue(int bpIntValue) { - - for (IHealthBloodPressureUnit type : values()) { - if (type.getValue() == bpIntValue) { - return type; - } - } - - throw new UnsupportedOperationException(); - } - } - - } diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java index ae90439b..71dac6de 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java @@ -61,8 +61,8 @@ protected Optional> asDataPoint(JsonNode listNode, Integer BodyWeight.Builder bodyWeightBuilder = new BodyWeight.Builder(new MassUnitValue(bodyWeightUnit, bodyWeightValue)); - setEffectiveTimeFrameIfExists(listNode,bodyWeightBuilder); - setUserNoteIfExists(listNode,bodyWeightBuilder); + setEffectiveTimeFrameIfExists(listNode, bodyWeightBuilder); + setUserNoteIfExists(listNode, bodyWeightBuilder); BodyWeight bodyWeight = bodyWeightBuilder.build(); diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java index ccff6fe3..aade1c6c 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java @@ -106,7 +106,7 @@ static protected void setEffectiveTimeFrameIfExists(JsonNode listNode, Measure.B Optional timeZoneString = asOptionalString(listNode, "TimeZone"); - if( timeZoneString.isPresent()){ + if (timeZoneString.isPresent()) { OffsetDateTime offsetDateTimeCorrectOffset = getDateTimeWithCorrectOffset(optionalOffsetDateTime.get(), timeZoneString.get()); diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java index fb6c8f96..7b987c92 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java @@ -30,8 +30,8 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; -import static org.openmhealth.schema.domain.omh.DataPointModality.*; -import static org.openmhealth.shim.ihealth.mapper.IHealthBloodPressureDataPointMapper.IHealthBloodPressureUnit; +import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; +import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; /** @@ -112,10 +112,10 @@ public void asDataPointsShouldReturnCorrectUserNotesWithDataPoints() { @Test public void getBloodPressureValueInMmHgReturnsCorrectValueForMmHgUnit() { - double bpValueFromMmHg = mapper.getBloodPressureValueInMmHg(120, IHealthBloodPressureUnit.mmHg); + double bpValueFromMmHg = mapper.getBloodPressureValueInMmHg(120, 0); assertThat(bpValueFromMmHg, equalTo(120.0)); - double bpValueFromKpa = mapper.getBloodPressureValueInMmHg(16, IHealthBloodPressureUnit.KPa); + double bpValueFromKpa = mapper.getBloodPressureValueInMmHg(16, 1); assertThat(bpValueFromKpa, equalTo(120.009872)); } @@ -123,7 +123,7 @@ public void getBloodPressureValueInMmHgReturnsCorrectValueForMmHgUnit() { @Test(expectedExceptions = UnsupportedOperationException.class) public void getBloodPressureValueShouldThrowExceptionForInvalidEnum() { - mapper.getBloodPressureValueInMmHg(12, IHealthBloodPressureUnit.fromIntegerValue(12)); + mapper.getBloodPressureValueInMmHg(12, 12); } From 3a2ab6c46d56031e048b6603f15675c4dd6259ea Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Mon, 5 Oct 2015 14:55:44 -0600 Subject: [PATCH 23/68] Remove iHealth blood glucose unit enum The enumeration was too much unnecessary complexity, so we replaced it with a simple method that switched on the iHealth unit magic number and then two constants to represent the potential magic numbers. --- .../IHealthBloodGlucoseDataPointMapper.java | 39 ++++++------------- .../mapper/IHealthDataPointMapper.java | 3 +- ...hBloodGlucoseDataPointMapperUnitTests.java | 13 ++----- 3 files changed, 16 insertions(+), 39 deletions(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java index 29ed08d0..e6870452 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java @@ -32,6 +32,8 @@ */ public class IHealthBloodGlucoseDataPointMapper extends IHealthDataPointMapper { + public static final int MG_PER_DL_MAGIC_NUMBER = 0; + public static final int MMOL_PER_L_MAGIC_NUMBER = 1; protected static ImmutableMap iHealthBloodGlucoseRelationshipToMeal; @Override @@ -60,8 +62,7 @@ protected Optional> asDataPoint(JsonNode listEntryNode, return Optional.empty(); } - BloodGlucoseUnit bloodGlucoseUnit = - IHealthBloodGlucoseUnit.fromIHealthMagicNumber(measureUnitMagicNumber).getBloodGlucoseUnit(); + BloodGlucoseUnit bloodGlucoseUnit = getBloodGlucoseUnitFromMagicNumber(measureUnitMagicNumber); BloodGlucose.Builder bloodGlucoseBuilder = new BloodGlucose.Builder(new TypedUnitValue<>(bloodGlucoseUnit, bloodGlucoseValue)); @@ -108,35 +109,17 @@ private void initializeTemporalRelationshipToFoodMap() { } - protected enum IHealthBloodGlucoseUnit { + protected BloodGlucoseUnit getBloodGlucoseUnitFromMagicNumber(Integer measureUnitMagicNumber) { - mgPerDl(0, BloodGlucoseUnit.MILLIGRAMS_PER_DECILITER), - mmolPerL(1, BloodGlucoseUnit.MILLIMOLES_PER_LITER); + switch ( measureUnitMagicNumber ) { - private int magicNumber; - private BloodGlucoseUnit bgUnit; - - IHealthBloodGlucoseUnit(int magicNumber, BloodGlucoseUnit bgUnit) { - - this.magicNumber = magicNumber; - this.bgUnit = bgUnit; - } - - protected BloodGlucoseUnit getBloodGlucoseUnit() { - return bgUnit; - } - - public static IHealthBloodGlucoseUnit fromIHealthMagicNumber(int magicNumberFromResponse) { - - for (IHealthBloodGlucoseUnit type : values()) { - if (type.magicNumber == magicNumberFromResponse) { - return type; - } - } - throw new UnsupportedOperationException(); + case MG_PER_DL_MAGIC_NUMBER: + return BloodGlucoseUnit.MILLIGRAMS_PER_DECILITER; + case MMOL_PER_L_MAGIC_NUMBER: + return BloodGlucoseUnit.MILLIMOLES_PER_LITER; + default: + throw new UnsupportedOperationException(); } - } - } diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java index aade1c6c..7df506ab 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java @@ -122,6 +122,7 @@ protected static OffsetDateTime getDateTimeWithCorrectOffset(Long optionalOffset OffsetDateTime offsetDateTimeFromOffsetInstant = OffsetDateTime.ofInstant( Instant.ofEpochSecond(optionalOffsetDateTime), ZoneId.of("Z")); + return offsetDateTimeFromOffsetInstant.toLocalDateTime().atOffset(ZoneOffset.of(timeZoneString)); } @@ -133,8 +134,6 @@ static protected void setUserNoteIfExists(JsonNode listNode, Measure.Builder bui builder.setUserNotes(note.get()); } - - } private void setAppropriateModality(String dataSourceValue, DataPointAcquisitionProvenance.Builder builder) { diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java index 52d65731..fc95a240 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java @@ -32,7 +32,6 @@ import static org.openmhealth.schema.domain.omh.BloodGlucose.SCHEMA_ID; import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; -import static org.openmhealth.shim.ihealth.mapper.IHealthBloodGlucoseDataPointMapper.IHealthBloodGlucoseUnit; /** @@ -108,20 +107,16 @@ public void asDataPointsShouldReturnNoDataPointsWhenBloodGlucoseListIsEmpty() th } @Test - public void iHealthBloodGlucoseUnitEnumShouldReturnCorrectOmhGlucoseUnit() { + public void getBloodGlucoseUnitFromMagicNumberShouldReturnCorrectBloodGlucoseUnit() { - IHealthBloodGlucoseUnit iHealthBloodGlucoseUnit = - IHealthBloodGlucoseUnit.fromIHealthMagicNumber(0); - assertThat(iHealthBloodGlucoseUnit.getBloodGlucoseUnit(), equalTo(BloodGlucoseUnit.MILLIGRAMS_PER_DECILITER)); - - iHealthBloodGlucoseUnit = IHealthBloodGlucoseUnit.fromIHealthMagicNumber(1); - assertThat(iHealthBloodGlucoseUnit.getBloodGlucoseUnit(), equalTo(BloodGlucoseUnit.MILLIMOLES_PER_LITER)); + assertThat(mapper.getBloodGlucoseUnitFromMagicNumber(0), equalTo(BloodGlucoseUnit.MILLIGRAMS_PER_DECILITER)); + assertThat(mapper.getBloodGlucoseUnitFromMagicNumber(1), equalTo(BloodGlucoseUnit.MILLIMOLES_PER_LITER)); } @Test(expectedExceptions = UnsupportedOperationException.class) public void iHealthBloodGlucoseUnitEnumShouldThrowExceptionWhenInvalidMagicNumber() { - IHealthBloodGlucoseUnit.fromIHealthMagicNumber(5); + mapper.getBloodGlucoseUnitFromMagicNumber(5); } } From dd95a45fcc32fdf9137b25fd938983e29f703df3 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Mon, 5 Oct 2015 15:07:35 -0600 Subject: [PATCH 24/68] Clean up the iHealth shim code [ci skip] --- .../openmhealth/shim/ihealth/IHealthShim.java | 39 +++++-------------- 1 file changed, 10 insertions(+), 29 deletions(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java index 5f52f622..f1ff5992 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java @@ -115,19 +115,6 @@ public ShimDataType[] getShimDataTypes() { }; } - // @Value("${openmhealth.shim.ihealth.sportSC}") - // public String sportSC; - // @Value("${openmhealth.shim.ihealth.sportSV}") - // public String sportSV; - // @Value("${openmhealth.shim.ihealth.bloodPressureSC}") - // public String bloodPressureSC; - // @Value("${openmhealth.shim.ihealth.bloodPressureSV}") - // public String bloodPressureSV; - // @Value("${openmhealth.shim.ihealth.spo2SC}") - // public String spo2SC; - // @Value("${openmhealth.shim.ihealth.spo2SV}") - // public String spo2SV; - Map serialValues; public Map getSerialValues() { @@ -146,8 +133,6 @@ public enum IHealthDataTypes implements ShimDataType { BLOOD_GLUCOSE(singletonList("glucose.json")), BLOOD_PRESSURE(singletonList("bp.json")), BODY_WEIGHT(singletonList("weight.json")), - //SLEEP("sleep"), - //STEP_COUNT("activity"), BODY_MASS_INDEX(singletonList("weight.json")), HEART_RATE(Lists.newArrayList("bp.json", "spo2.json")); @@ -193,14 +178,18 @@ protected ResponseEntity getData(OAuth2RestOperations restTemp int i = 0; for (String endPoint : dataType.getEndPoint()) { + UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(API_URL); + // Need to use a dummy userId if we haven't authenticated yet. This is the case where we are using + // getData to trigger Spring to conduct the OAuth exchange String userId = "uk"; + if (shimDataRequest.getAccessParameters() != null) { OAuth2AccessToken token = SerializationUtils.deserialize(shimDataRequest.getAccessParameters().getSerializedToken()); - ; + userId = Preconditions.checkNotNull((String) token.getAdditionalInformation().get("UserID")); uriBuilder.queryParam("access_token", token.getValue()); } @@ -216,8 +205,8 @@ protected ResponseEntity getData(OAuth2RestOperations restTemp .queryParam("sc", scValues.get(i)) .queryParam("sv", svValues.get(i)); - ResponseEntity responseEntity; + try { URI url = uriBuilder.build().encode().toUri(); responseEntity = restTemplate.getForEntity(url, JsonNode.class); @@ -229,9 +218,7 @@ protected ResponseEntity getData(OAuth2RestOperations restTemp } if (shimDataRequest.getNormalize()) { - // SimpleModule module = new SimpleModule(); - // module.addDeserializer(ShimDataResponse.class, dataType.getNormalizer()); - // objectMapper.registerModule(module); + IHealthDataPointMapper mapper; switch ( dataType ) { @@ -253,11 +240,11 @@ protected ResponseEntity getData(OAuth2RestOperations restTemp break; case HEART_RATE: // there are two different mappers for heart rate because the data can come from two endpoints - if(endPoint == "bp.json"){ + if (endPoint == "bp.json") { mapper = new IHealthBloodPressureEndpointHeartRateDataPointMapper(); break; } - else if (endPoint == "spo2.json"){ + else if (endPoint == "spo2.json") { mapper = new IHealthBloodOxygenEndpointHeartRateDataPointMapper(); break; } @@ -277,7 +264,7 @@ else if (endPoint == "spo2.json"){ } return ResponseEntity.ok().body( - ShimDataResponse.result(SHIM_KEY,responseEntities)); + ShimDataResponse.result(SHIM_KEY, responseEntities)); } @@ -321,12 +308,6 @@ private List getSvValues(IHealthDataTypes dataType) { } } - // @Override - // public void trigger(OAuth2RestOperations restTemplate, ShimDataRequest shimDataRequest) throws ShimException { - // - // - // } - @Override public OAuth2ProtectedResourceDetails getResource() { AuthorizationCodeResourceDetails resource = (AuthorizationCodeResourceDetails) super.getResource(); From d4b4bd17ebdab742a0fd3af8d3a5254f1ecd22b2 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Mon, 5 Oct 2015 16:40:18 -0600 Subject: [PATCH 25/68] Document iHealth mappers --- .../IHealthBloodPressureDataPointMapper.java | 4 +- .../IHealthBodyWeightDataPointMapper.java | 3 ++ .../mapper/IHealthDataPointMapper.java | 48 +++++++++++++++---- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java index fef99f34..0f2c51a7 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java @@ -29,7 +29,9 @@ */ public class IHealthBloodPressureDataPointMapper extends IHealthDataPointMapper { - static final double KPA_TO_MMHG_CONVERSION_RATE = 7.500617; + // documentation: http://www.ncbi.nlm.nih.gov/pmc/articles/PMC1603212/ + static final double KPA_TO_MMHG_CONVERSION_RATE = 7.50; + static final int MMHG_UNIT_MAGIC_NUMBER = 0; static final int KPA_UNIT_MAGIC_NUMBER = 1; diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java index 71dac6de..a1b54c6e 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java @@ -69,6 +69,7 @@ protected Optional> asDataPoint(JsonNode listNode, Integer return Optional.of(new DataPoint<>(createDataPointHeader(listNode, bodyWeight), bodyWeight)); } + protected double getBodyWeightValueForUnitType(JsonNode listNode, IHealthBodyWeightUnit bodyWeightUnitType) { @@ -78,6 +79,8 @@ protected double getBodyWeightValueForUnitType(JsonNode listNode, protected double getBodyWeightValueForUnitType(double bodyWeightValue, IHealthBodyWeightUnit bodyWeightUnitType) { + // iHealth has one unit type that is unsupported by OMH schemas, so we need to convert the value into a unit + // system that is supported by the schemas. return bodyWeightValue * bodyWeightUnitType.getConversionFactorToOmh(); } diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java index 7df506ab..f538ba75 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java @@ -39,6 +39,8 @@ /** + * The base class for mappers that translate iHealth API responses to {@link DataPoint} objects. + * * @author Chris Schaefbauer */ public abstract class IHealthDataPointMapper implements DataPointMapper { @@ -47,6 +49,11 @@ public abstract class IHealthDataPointMapper implements DataPointMapper> asDataPoints(List responseNodes) { @@ -72,6 +79,12 @@ public List> asDataPoints(List responseNodes) { return dataPoints; } + /** + * Creates a data point header with information describing the data point created around the measure. + *

+ *

Note: Additional properties within the header come from the iHealth API and are not defined by the data point + * header schema. Additional properties are subject to change.

+ */ protected DataPointHeader createDataPointHeader(JsonNode listNode, Measure measure) { DataPointAcquisitionProvenance.Builder acquisitionProvenanceBuilder = @@ -90,12 +103,10 @@ protected DataPointHeader createDataPointHeader(JsonNode listNode, Measure measu lastUpdatedInUnixSecs -> acquisitionProvenance.setAdditionalProperty("source_updated_date_time", OffsetDateTime.ofInstant(Instant.ofEpochSecond(lastUpdatedInUnixSecs), ZoneId.of("Z")))); - DataPointHeader dataPointHeader = - new DataPointHeader.Builder(UUID.randomUUID().toString(), measure.getSchemaId()) - .setAcquisitionProvenance(acquisitionProvenance) - .build(); + return new DataPointHeader.Builder(UUID.randomUUID().toString(), measure.getSchemaId()) + .setAcquisitionProvenance(acquisitionProvenance) + .build(); - return dataPointHeader; } static protected void setEffectiveTimeFrameIfExists(JsonNode listNode, Measure.Builder builder) { @@ -116,11 +127,18 @@ static protected void setEffectiveTimeFrameIfExists(JsonNode listNode, Measure.B } } - protected static OffsetDateTime getDateTimeWithCorrectOffset(Long optionalOffsetDateTime, + /** + * This method transforms the unix epoch second timestamps in iHealth responses, which are not in utc but instead + * offset to the local time zone of the data point, into an {@link OffsetDateTime} with the correct date/time and + * offset. + */ + protected static OffsetDateTime getDateTimeWithCorrectOffset(Long dateTimeInUnixSecondsWithLocalTimeOffset, String timeZoneString) { + // Since the timestamps are in local time, we can use the local date time provided by rendering the timestamp + // in UTC, then translating that local time to the appropriate offset. OffsetDateTime offsetDateTimeFromOffsetInstant = OffsetDateTime.ofInstant( - Instant.ofEpochSecond(optionalOffsetDateTime), + Instant.ofEpochSecond(dateTimeInUnixSecondsWithLocalTimeOffset), ZoneId.of("Z")); return offsetDateTimeFromOffsetInstant.toLocalDateTime().atOffset(ZoneOffset.of(timeZoneString)); @@ -146,10 +164,24 @@ else if (dataSourceValue.equals(DATA_SOURCE_MANUAL)) { } } - // TODO add Javadoc + /** + * @return The name of the JSON array that contains the individual data points. This is different per endpoint. + */ protected abstract String getListNodeName(); + /** + * @return The name of the JSON property whose value indicates the unit of measure used to render the values in + * the response. This is different per endpoint and some endpoints do not provide any units, in which case, the + * value should be an empty Optional. + */ protected abstract Optional getMeasureUnitNodeName(); + /** + * @param listEntryNode a single node from the + * @param measureUnitMagicNumber The number representing the units used to render the response, according to + * iHealth. This is retrieved from the main body of the response node. If the measure type does not use units, then + * this value is null. + * @return the data point mapped from the listEntryNode, unless skipped + */ protected abstract Optional> asDataPoint(JsonNode listEntryNode, Integer measureUnitMagicNumber); } From d08f76556e224378d79eb48ce53d60dd607ba1e3 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Mon, 5 Oct 2015 16:48:01 -0600 Subject: [PATCH 26/68] Fix incorrect value in blood pressure mapper test --- .../mapper/IHealthBloodPressureDataPointMapperUnitTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java index 7b987c92..45844e0d 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java @@ -116,7 +116,7 @@ public void getBloodPressureValueInMmHgReturnsCorrectValueForMmHgUnit() { assertThat(bpValueFromMmHg, equalTo(120.0)); double bpValueFromKpa = mapper.getBloodPressureValueInMmHg(16, 1); - assertThat(bpValueFromKpa, equalTo(120.009872)); + assertThat(bpValueFromKpa, equalTo(120.0)); } From d3e8080cd1cf6d308341a0a6088557301dd05cdc Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Mon, 5 Oct 2015 17:02:40 -0600 Subject: [PATCH 27/68] Use UriComponentsBuilder to build authorization url [ci skip] --- .../openmhealth/shim/ihealth/IHealthShim.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java index f1ff5992..ea671eea 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java @@ -318,11 +318,17 @@ public OAuth2ProtectedResourceDetails getResource() { @Override protected String getAuthorizationUrl(UserRedirectRequiredException exception) { final OAuth2ProtectedResourceDetails resource = getResource(); - return exception.getRedirectUri() - + "?client_id=" + resource.getClientId() - + "&response_type=code" - + "&APIName=" + Joiner.on(' ').join(resource.getScope()) - + "&redirect_uri=" + getCallbackUrl() + "?state=" + exception.getStateKey(); + + UriComponentsBuilder callBackUriBuilder = UriComponentsBuilder.fromUriString(getCallbackUrl()) + .queryParam("state",exception.getStateKey()); + + UriComponentsBuilder authorizationUriBuilder = UriComponentsBuilder.fromUriString(exception.getRedirectUri()) + .queryParam("client_id", resource.getClientId()) + .queryParam("response_type", "code") + .queryParam("APIName", Joiner.on(' ').join(resource.getScope())) + .queryParam("redirect_uri", callBackUriBuilder.build().toString()); + + return authorizationUriBuilder.build().encode().toString(); } public class IHealthAuthorizationCodeAccessTokenProvider extends AuthorizationCodeAccessTokenProvider { From 6e67a4314beeb1ae5135c5a391036b9858a9a4c6 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Mon, 5 Oct 2015 17:03:39 -0600 Subject: [PATCH 28/68] Document iHealth shim and mappers --- .../org/openmhealth/shim/ihealth/IHealthShim.java | 14 +++++++++++++- .../ihealth/mapper/IHealthDataPointMapper.java | 1 + .../mapper/IHealthHeartRateDataPointMapper.java | 1 + 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java index ea671eea..e31a045b 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java @@ -47,6 +47,12 @@ import static org.slf4j.LoggerFactory.getLogger; +/** + * Encapsulates parameters specific to the iHealth REST API and processes requests made of shimmer for iHealth data. + * + * @author Chris Schaefbauer + * @author Emerson Farrugia + */ @Component @EnableConfigurationProperties @ConfigurationProperties(prefix = "openmhealth.shim.ihealth") @@ -115,6 +121,9 @@ public ShimDataType[] getShimDataTypes() { }; } + /** + * Map of values auto-configured from the application properties yaml. + */ Map serialValues; public Map getSerialValues() { @@ -171,12 +180,16 @@ protected ResponseEntity getData(OAuth2RestOperations restTemp now.plusDays(1) : shimDataRequest.getEndDateTime(); + // SC and SV values are client-based keys that are unique to each endpoint within a project List scValues = getScValues(dataType); List svValues = getSvValues(dataType); List responseEntities = Lists.newArrayList(); int i = 0; + + // We iterate because one of the measures (Heart rate) comes from multiple endpoints, so we submit + // requests to each of these endpoints, map the responses separately and then combine them for (String endPoint : dataType.getEndPoint()) { UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(API_URL); @@ -265,7 +278,6 @@ else if (endPoint == "spo2.json") { return ResponseEntity.ok().body( ShimDataResponse.result(SHIM_KEY, responseEntities)); - } private List getScValues(IHealthDataTypes dataType) { diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java index f538ba75..a571d7d6 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java @@ -42,6 +42,7 @@ * The base class for mappers that translate iHealth API responses to {@link DataPoint} objects. * * @author Chris Schaefbauer + * @author Emerson Farrugia */ public abstract class IHealthDataPointMapper implements DataPointMapper { diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java index e2c0bae2..64dfc828 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java @@ -27,6 +27,7 @@ /** * @author Chris Schaefbauer + * @author Emerson Farrugia */ public abstract class IHealthHeartRateDataPointMapper extends IHealthDataPointMapper { From 5e90109237d4faf900b2aa91d0fda611e128a5d3 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Mon, 5 Oct 2015 17:38:57 -0600 Subject: [PATCH 29/68] Add markdown describing iHealth API --- .../openmhealth/shim/ihealth/IHealthShim.java | 2 +- .../org/openmhealth/shim/ihealth/iHealth.md | 190 ++++++++++++++++++ .../mapper/IHealthDataPointMapper.java | 5 +- ...ointHeartRateDataPointMapperUnitTests.java | 9 +- ...BodyMassIndexDataPointMapperUnitTests.java | 2 +- ...lthBodyWeightDataPointMapperUnitTests.java | 6 +- .../IHealthDataPointMapperUnitTests.java | 5 +- ...sicalActivityDataPointMapperUnitTests.java | 29 +-- 8 files changed, 222 insertions(+), 26 deletions(-) create mode 100644 shim-server/src/main/java/org/openmhealth/shim/ihealth/iHealth.md diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java index e31a045b..c1a8fb18 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java @@ -332,7 +332,7 @@ protected String getAuthorizationUrl(UserRedirectRequiredException exception) { final OAuth2ProtectedResourceDetails resource = getResource(); UriComponentsBuilder callBackUriBuilder = UriComponentsBuilder.fromUriString(getCallbackUrl()) - .queryParam("state",exception.getStateKey()); + .queryParam("state", exception.getStateKey()); UriComponentsBuilder authorizationUriBuilder = UriComponentsBuilder.fromUriString(exception.getRedirectUri()) .queryParam("client_id", resource.getClientId()) diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/iHealth.md b/shim-server/src/main/java/org/openmhealth/shim/ihealth/iHealth.md new file mode 100644 index 00000000..14fe02a1 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/iHealth.md @@ -0,0 +1,190 @@ +# api +api url: https://api.ihealthlabs.com:8443/openapiv2/user/{userid} + +version: v2 + +api reference: http://developer.ihealthlabs.com/dev_documentation_openapidoc.htm + +## authentication + +### OAuth 2.0 +- protocol: OAuth 2.0 +- reference: https://api.ihealthlabs.com:8443/OpenApiV2/OAuthv2/userauthorization/ +- flows: authorization code + - authorization URL: https://api.ihealthlabs.com:8443/OpenApiV2/OAuthv2/userauthorization/ + - access token: https://api.ihealthlabs.com:8443/OpenApiV2/OAuthv2/userauthorization/ + - May be a bit off of standard OAuth token uri here +- supports refresh tokens: yes + - refresh token: https://api.ihealthlabs.com:8443/OpenApiV2/OAuthv2/userauthorization/ + - response_type=refresh_token +- redirect_uri: uri must match the uri specified as a redirect_uri for the API developer account of the client key/secret being used +- scope: iHealth has its own name for the scope parameter in their authorization process: APIName. They do not refer to as scope in their API, but nonetheless it works the same as scope. During the authorization request you use the query parameter, “APIName” with values of one or more of the following, separated by spaces: + - OpenApiSleep + - OpenApiBP + - OpenApiWeight + - OpenApiActivity + - OpenApiBG + - OpenApiSpO2 + - OpenApiFood + - OpenApiSport +- access token: Authorization: Bearer token + - token lifetime: 3672000 seconds + +# pagination +- supported: yes, responses will have a “NextPageUrl” property if there is more data to be retrieved that is the URI for the endpoint uri for the next page. The next page url is endcoded such that it is in what appears to be an unusable in its current form, so it would need to be decoded in some way. However, there is a “page_index” parameter that can be incremented to step through pages of size 50 and reach all of the data. page_index = 1, 2, 3, 4, etc. + +- The limit is set at 50 and there is no ability to control page size or limit the number of responses. + +# rate limit +- 5000 requests per hour per user + +# query updated by date +- unsupported + +# query created by date +- unsupported + +# time zone and time representation +- Each datapoint contains a “timezone” property, which is a utc-offset in the form “-0800” +- Timestamps are represented as unix epoch seconds offset by the zone offset found in the “timezone” property, so the timestamps are, in essence, in local time +- Requests take unix epoch seconds and match on local time + +# endpoints + +## endpoint query parameters +- Required parameters: + - client_id: The ID for the client request + - client_secret: The key for the request + - access_token: The token for access + - sc: The serial number for the client application + - sv: A unique identifier for the client’s use of the specific endpoint (one for each endpoint per project) + +- Optional parameters + - start_time: the unix epoch second to constrain the search, when it is empty, the data would start from one year ago + - page_index: First page The page index of data, which starts from 1 + - locale: Default (example in Appendix) Set the locale / units returned + - end_time: the Unix epoch second to constrain the end of the search when the Activity data ends, it must be later than start_time + +## get weight +- Endpoint: /weight.json/ +- Reference: http://developer.ihealthlabs.com/dev_documentation_RequestfordataofWeight.htm + +### description +The endpoint returns body weight-related measurements that include weight, BMI, bone weight, and others. + +### response +The response contains meta information in the body, such as page length, record count, etc, as well as an array property “WeightDataList,” which contains the data. Each item in the list contains a set of unique properties describing the weight-related measurements taken/entered during that session and when that session occurred: + +- WeightValue +- MDate: measurement date +- TimeZone: time zone that the measurement occurred within +- BMI +- Note +- DataSource: ( Manual | FromDevice ) +- DataID + +It appears that the value is zero when it is missing, so zero values are not actually zero values, they are non values. + +All data in the response are rendered using the same unit, though it can change per response. The unit is contained in a property “WeightUnit” and is an integer between 0 - 2 corresponding to the following enum: {kg : 0}, {lbs : 1}, {stone : 2}. + +### measures mapped +omh:body-weight +omh:body-mass-index + +## get blood pressure +- endpoint: /bp.json/ +- reference: http://developer.ihealthlabs.com/dev_documentation_RequestfordataofBloodPressure.htm + +### description +Retrieves blood pressure measurements that are created and stored in iHealth. + +### response +The response contains meta information in the body, such as page length, record count, etc, as well as an array property “BPDataList,” which contains the data. Each item in the list contains a set of properties describing the blood-pressure measurements: + +- BPL: some sort of WHO blood pressure level/rating +- DataID +- DataSource:( Manual | FromDevice) +- HP: systolic blood pressure +- HR: heart rate +- IsArr: whether the user has an arrythemia +- LP: diastolic blood pressure +- MDate: measurement date +- Note +- TimeZone: utc offset for the zone where the measurement occurred + +All data in the response are rendered using the same unit, though it can change per response. The unit is contained in a property “BPUnit” and is an integer between 0 - 1 corresponding to the following enum: {mmHg : 0}, {KPa : 1} + +### measures mapped +omh:blood-pressure +omh:heart-rate + +## get blood glucose +- endpoint: /glucose.json/ +- reference: http://developer.ihealthlabs.com/dev_documentation_RequestfordataofBG.htm + +### description +Retrieves a list of blood glucose measurements from the iHealth API. + +### response +The response contains meta information in the body, such as page length, record count, etc, as well as an array property “BGDataList,” which contains the data. Each item in the list contains a set of properties describing the blood glucose measurement: + +- BG: glucose value +- DataID: the unique identity +- DinnerSituation - ( Before_Breakfast | After_breakfast | Before_lunch | After_lunch | +- Before_dinner | After_dinner | At_midnight ) +- DrugSituation - ( Before_taking_pills | After_taking_pills ) +- MDate - measurement date time +- Note: the note of this data +- DataSource: ( Manual | FromDevice ) +- TimeZone: Time zone of measurement location + +All data in the response are rendered using the same unit, though it can change per response. The unit is contained in a property “BGUnit” and is an integer between 0 - 1 corresponding to the following enum: {mg/dl : 0}, {mmol/l : 1} + +### measures mapped +omh:blood-glucose + +## get oxygen saturation +- endpoint: /spo2.json/ +- reference: http://developer.ihealthlabs.com/dev_documentation_RequestfordataofBloodOxygen.htm + +### description +Retrieves blood oxygen saturation and heart rate information stored in the iHealth API. + +### response +The response contains meta information in the body, such as page length, record count, etc, as well as an array property “BODataList,” which contains the data. Each item in the list contains a set of properties describing the blood glucose measurement: + +- BO: blood oxygen % saturation +- DataID: the unique identity +- HR: Heart rate +- MDate - measurement date time +- Note: the note of this data +- DataSource: ( Manual | FromDevice ) +- TimeZone: Time zone of measurement location + +### measures mapped +omh:heart-rate + +## get sports activities +- endpoint: /sport.json/ +- reference: http://developer.ihealthlabs.com/dev_documentation_RequestfordataofSport.htm + +### description +Retrieves the physical activities an individual has engaged in from the iHealth API + +### response +The response contains meta information in the body, such as page length, record count, etc, as well as an array property “SPORTDataList,” which contains the data. Each item in the list contains a set of properties describing a unique physical activity: + +- SportName: the name of the activity +- SportStartTime: start time of the activity in unix epoch seconds +- SportEndTime: end time of the activity in unix epoch seconds +- TimeZone: Time zone of measurement location +- DataID: the unique identity +- Calories: The total calories consumed +- LastChangeTime: Time of last change (UTC) +- DataSource: ( Manual | FromDevice ) + +### measures mapped +omh:physical-activity + +## future endpoint support +We hope to support step-count and sleep-duration measures from the activity and sleep endpoints in iHealth, however we are unable to ascertain the time frame on step data from the documentation and also need real device data to test uncertainties and ambiguities in these endpoints that are not clear from the documentation. diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java index a571d7d6..1f1045ed 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java @@ -110,12 +110,13 @@ protected DataPointHeader createDataPointHeader(JsonNode listNode, Measure measu } - static protected void setEffectiveTimeFrameIfExists(JsonNode listNode, Measure.Builder builder) { + protected static void setEffectiveTimeFrameIfExists(JsonNode listNode, Measure.Builder builder) { Optional optionalOffsetDateTime = asOptionalLong(listNode, "MDate"); if (optionalOffsetDateTime.isPresent()) { + // Todo: Revisit after clarification from iHealth on how time zones are set Optional timeZoneString = asOptionalString(listNode, "TimeZone"); if (timeZoneString.isPresent()) { @@ -145,7 +146,7 @@ protected static OffsetDateTime getDateTimeWithCorrectOffset(Long dateTimeInUnix return offsetDateTimeFromOffsetInstant.toLocalDateTime().atOffset(ZoneOffset.of(timeZoneString)); } - static protected void setUserNoteIfExists(JsonNode listNode, Measure.Builder builder) { + protected static void setUserNoteIfExists(JsonNode listNode, Measure.Builder builder) { Optional note = asOptionalString(listNode, "Note"); diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java index 3aceab89..56553232 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java @@ -85,9 +85,10 @@ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { .setUserNotes("BP on the up and up.") .build(); -// assertThat(dataPoints.get(1).getBody(), equalTo(expectedHeartRate)); -// -// assertThat(dataPoints.get(1).getHeader().getAcquisitionProvenance().getModality(), equalTo(SELF_REPORTED)); + // assertThat(dataPoints.get(1).getBody(), equalTo(expectedHeartRate)); + // + // assertThat(dataPoints.get(1).getHeader().getAcquisitionProvenance().getModality(), equalTo + // (SELF_REPORTED)); } @Test @@ -107,7 +108,7 @@ public void asDataPointsShouldReturnNoDataPointWhenHeartRateDataIsNotPresent() t JsonNode noHeartRateBloodPressureNode = objectMapper.readTree(resource.getInputStream()); List> dataPoints = mapper.asDataPoints(singletonList(noHeartRateBloodPressureNode)); - assertThat(dataPoints.size(),equalTo(0)); + assertThat(dataPoints.size(), equalTo(0)); } } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java index eddd1b9c..7238afef 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java @@ -106,7 +106,7 @@ public void asDataPointsShouldReturnNoDataPointsWhenWeightListIsEmpty() throws I JsonNode emptyListNode = objectMapper.readTree(resource.getInputStream()); List> dataPoints = mapper.asDataPoints(singletonList(emptyListNode)); - assertThat(dataPoints.size(),equalTo(0)); + assertThat(dataPoints.size(), equalTo(0)); } } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java index fef78e38..fdcc7b2c 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java @@ -98,7 +98,7 @@ public void asDataPointsShouldReturnCorrectUserNotes() { List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - assertThat(dataPoints.get(0).getBody().getUserNotes(),nullValue()); + assertThat(dataPoints.get(0).getBody().getUserNotes(), nullValue()); assertThat(dataPoints.get(1).getBody().getUserNotes(), equalTo("Weight so good, look at me now")); } @@ -110,7 +110,7 @@ public void asDataPointsShouldReturnNoDataPointsWhenWeightValueEqualsZero() thro JsonNode zeroValueNode = objectMapper.readTree(resource.getInputStream()); List> dataPoints = mapper.asDataPoints(singletonList(zeroValueNode)); - assertThat(dataPoints.size(),equalTo(0)); + assertThat(dataPoints.size(), equalTo(0)); } @Test @@ -121,7 +121,7 @@ public void asDataPointsShouldReturnNoDataPointsWhenWeightListIsEmpty() throws I JsonNode emptyListNode = objectMapper.readTree(resource.getInputStream()); List> dataPoints = mapper.asDataPoints(singletonList(emptyListNode)); - assertThat(dataPoints.size(),equalTo(0)); + assertThat(dataPoints.size(), equalTo(0)); } @Test diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java index 008829e6..e0b581ad 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java @@ -33,7 +33,8 @@ */ public class IHealthDataPointMapperUnitTests extends DataPointMapperUnitTests { - protected void testDataPointHeader(DataPointHeader testHeader, SchemaId schemaId, DataPointModality modality, String externalId, OffsetDateTime updatedDateTime){ + protected void testDataPointHeader(DataPointHeader testHeader, SchemaId schemaId, DataPointModality modality, + String externalId, OffsetDateTime updatedDateTime) { assertThat(testHeader.getBodySchemaId(), equalTo(schemaId)); assertThat(testHeader.getAcquisitionProvenance().getModality(), equalTo(modality)); @@ -46,7 +47,7 @@ protected void testDataPointHeader(DataPointHeader testHeader, SchemaId schemaId } @Test - public void setEffectiveTimeFrameShouldUseUtcWhenTimeZoneIsMissing(){ + public void setEffectiveTimeFrameShouldUseUtcWhenTimeZoneIsMissing() { // Todo: waiting for response from iHealth on missing time zone representation } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java index ed5170b6..7ab00d13 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java @@ -48,34 +48,36 @@ public class IHealthPhysicalActivityDataPointMapperUnitTests extends IHealthData @BeforeTest public void initializeResponseNode() throws IOException { - ClassPathResource resource = new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity.json"); + ClassPathResource resource = + new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity.json"); responseNode = objectMapper.readTree(resource.getInputStream()); } @Test - public void asDataPointsShouldReturnTheCorrectNumberOfDataPoints(){ + public void asDataPointsShouldReturnTheCorrectNumberOfDataPoints() { List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - assertThat(dataPoints.size(),equalTo(2)); + assertThat(dataPoints.size(), equalTo(2)); } @Test - public void asDataPointsShouldReturnCorrectSensedDataPoints(){ + public void asDataPointsShouldReturnCorrectSensedDataPoints() { List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - PhysicalActivity.Builder expectedPhysicalActivityBuilder = new PhysicalActivity.Builder("Swimming, breaststroke") - .setEffectiveTimeFrame(TimeInterval.ofStartDateTimeAndEndDateTime( - OffsetDateTime.parse("2015-09-17T20:02:28-08:00"), - OffsetDateTime.parse("2015-09-17T20:32:28-08:00"))); - assertThat(dataPoints.get(0).getBody(),equalTo(expectedPhysicalActivityBuilder.build())); + PhysicalActivity.Builder expectedPhysicalActivityBuilder = + new PhysicalActivity.Builder("Swimming, breaststroke") + .setEffectiveTimeFrame(TimeInterval.ofStartDateTimeAndEndDateTime( + OffsetDateTime.parse("2015-09-17T20:02:28-08:00"), + OffsetDateTime.parse("2015-09-17T20:32:28-08:00"))); + assertThat(dataPoints.get(0).getBody(), equalTo(expectedPhysicalActivityBuilder.build())); testDataPointHeader(dataPoints.get(0).getHeader(), SCHEMA_ID, SENSED, "3f8770f51cc84957a57d20f4fee1f34b", OffsetDateTime.parse("2015-09-17T20:02:57Z")); } @Test - public void asDataPointsShouldReturnCorrectSelfReportedDataPoints(){ + public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); @@ -86,7 +88,7 @@ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints(){ OffsetDateTime.parse("2015-09-22T20:43:03-06:00"), OffsetDateTime.parse("2015-09-22T21:13:03-06:00"))); - assertThat(dataPoints.get(1).getBody(),equalTo(expectedPhysicalActivityBuilder.build())); + assertThat(dataPoints.get(1).getBody(), equalTo(expectedPhysicalActivityBuilder.build())); assertThat(dataPoints.get(1).getHeader().getAcquisitionProvenance().getModality(), equalTo(SELF_REPORTED)); } @@ -94,11 +96,12 @@ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints(){ @Test public void asDataPointsReturnsNoDataPointsForAnEmptyList() throws IOException { - ClassPathResource resource = new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity-empty-list.json"); + ClassPathResource resource = + new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity-empty-list.json"); JsonNode emptyListResponseNode = objectMapper.readTree(resource.getInputStream()); List> dataPoints = mapper.asDataPoints(singletonList(emptyListResponseNode)); - assertThat(dataPoints.size(),equalTo(0)); + assertThat(dataPoints.size(), equalTo(0)); } } From ecb34afcaf80c3ec0ff24d814b934cd7cd92ad92 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Mon, 5 Oct 2015 17:42:19 -0600 Subject: [PATCH 30/68] Uncomment heart rate mapper test code --- ...ressureEndpointHeartRateDataPointMapperUnitTests.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java index 56553232..ae221bd3 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java @@ -32,6 +32,7 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; @@ -81,14 +82,12 @@ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); HeartRate expectedHeartRate = new HeartRate.Builder(75) - .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T08:07:45-06:00")) + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T14:07:45-06:00")) .setUserNotes("BP on the up and up.") .build(); - // assertThat(dataPoints.get(1).getBody(), equalTo(expectedHeartRate)); - // - // assertThat(dataPoints.get(1).getHeader().getAcquisitionProvenance().getModality(), equalTo - // (SELF_REPORTED)); + assertThat(dataPoints.get(1).getBody(), equalTo(expectedHeartRate)); + assertThat(dataPoints.get(1).getHeader().getAcquisitionProvenance().getModality(), equalTo(SELF_REPORTED)); } @Test From 8efa2a9a291f30a67c65188e1df54bd05dd1223e Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Tue, 6 Oct 2015 16:56:55 -0600 Subject: [PATCH 31/68] Test Jawbone body weight and bmi with real data --- ...BodyMassIndexDataPointMapperUnitTests.java | 22 ++--- ...oneBodyWeightDataPointMapperUnitTests.java | 34 +++++--- .../jawbone/mapper/jawbone-body-events.json | 83 +++++++++---------- 3 files changed, 75 insertions(+), 64 deletions(-) diff --git a/shim-server/src/test/java/org/openmhealth/shim/jawbone/mapper/JawboneBodyMassIndexDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/jawbone/mapper/JawboneBodyMassIndexDataPointMapperUnitTests.java index 7f21c228..0e235c65 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/jawbone/mapper/JawboneBodyMassIndexDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/jawbone/mapper/JawboneBodyMassIndexDataPointMapperUnitTests.java @@ -47,39 +47,39 @@ public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { // TODO why are first and last special? @Test - public void asDataPointsShouldReturnCorrectFirstDataPoint() { + public void asDataPointsShouldReturnCorrectDataPointWithTimeZone() { List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); BodyMassIndex expectedBodyMassIndex = new BodyMassIndex - .Builder(new TypedUnitValue<>(BodyMassIndexUnit.KILOGRAMS_PER_SQUARE_METER, 24)) - .setEffectiveTimeFrame(OffsetDateTime.parse("2015-08-11T22:37:18-06:00")) + .Builder(new TypedUnitValue<>(BodyMassIndexUnit.KILOGRAMS_PER_SQUARE_METER, 23)) + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-10-05T19:52:52-06:00")) .build(); assertThat(dataPoints.get(0).getBody(), equalTo(expectedBodyMassIndex)); Map testProperties = Maps.newHashMap(); testProperties.put(HEADER_SCHEMA_ID_KEY, BodyMassIndex.SCHEMA_ID); - testProperties.put(HEADER_EXTERNAL_ID_KEY, "QkfTizSpRdukQY3ns4PYbkucZTM5yPMg"); - testProperties.put(HEADER_SOURCE_UPDATE_KEY, "2015-08-13T08:23:58Z"); + testProperties.put(HEADER_EXTERNAL_ID_KEY, "JM2JlMHcHlUP2mAvWWVlwwNFFVo_4CfQ"); + testProperties.put(HEADER_SOURCE_UPDATE_KEY, "2015-10-06T01:52:52Z"); testProperties.put(HEADER_SHARED_KEY, true); testDataPointHeader(dataPoints.get(0).getHeader(), testProperties); } @Test - public void asDataPointsShouldReturnCorrectLastDataPoint() { + public void asDataPointsShouldReturnCorrectDataPointWithoutTimeZone() { List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); BodyMassIndex expectedBodyMassIndex = new BodyMassIndex - .Builder(new TypedUnitValue<>(BodyMassIndexUnit.KILOGRAMS_PER_SQUARE_METER, 25.2)) - .setEffectiveTimeFrame(OffsetDateTime.parse("2015-08-06T23:36:57-06:00")) + .Builder(new TypedUnitValue<>(BodyMassIndexUnit.KILOGRAMS_PER_SQUARE_METER, 22)) + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-10-06T19:39:01Z")) .build(); assertThat(dataPoints.get(1).getBody(), equalTo(expectedBodyMassIndex)); Map testProperties = Maps.newHashMap(); - testProperties.put(HEADER_EXTERNAL_ID_KEY, "QkfTizSpRdt6MGLRxULIlVTscmwD_cPJ"); + testProperties.put(HEADER_EXTERNAL_ID_KEY, "JM2JlMHcHlVYbz0vvV-tzteoDrIYcQ7k"); testProperties.put(HEADER_SCHEMA_ID_KEY, BodyMassIndex.SCHEMA_ID); - testProperties.put(HEADER_SOURCE_UPDATE_KEY, "2015-08-07T05:36:57Z"); - testProperties.put(HEADER_SHARED_KEY, false); + testProperties.put(HEADER_SOURCE_UPDATE_KEY, "2015-10-06T19:39:02Z"); + testProperties.put(HEADER_SHARED_KEY, null); testDataPointHeader(dataPoints.get(1).getHeader(), testProperties); } } diff --git a/shim-server/src/test/java/org/openmhealth/shim/jawbone/mapper/JawboneBodyWeightDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/jawbone/mapper/JawboneBodyWeightDataPointMapperUnitTests.java index b5fd7de4..be0ae0b0 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/jawbone/mapper/JawboneBodyWeightDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/jawbone/mapper/JawboneBodyWeightDataPointMapperUnitTests.java @@ -45,18 +45,18 @@ public void asDataPointsShouldReturnNoDataPointsWithEmptyResponse() { public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - assertThat(dataPoints.size(), equalTo(2)); + assertThat(dataPoints.size(), equalTo(3)); } @Test - public void asDataPointsShouldReturnCorrectDataPointWhenUserNotePropertyIsInResponse() { + public void asDataPointsShouldReturnCorrectDataPointWhenTimeZoneIsInOlsonFormat() { List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); BodyWeight testBodyWeight = dataPoints.get(0).getBody(); BodyWeight expectedBodyWeight = new BodyWeight. - Builder(new MassUnitValue(MassUnit.KILOGRAM, 86.0010436535)). - setEffectiveTimeFrame(OffsetDateTime.parse("2015-08-11T22:37:20-06:00")). + Builder(new MassUnitValue(MassUnit.KILOGRAM, 64)). + setEffectiveTimeFrame(OffsetDateTime.parse("2015-10-04T19:52:41-06:00")). setUserNotes("First weight"). build(); @@ -64,30 +64,42 @@ public void asDataPointsShouldReturnCorrectDataPointWhenUserNotePropertyIsInResp Map testProperties = Maps.newHashMap(); testProperties.put("schemaId", BodyWeight.SCHEMA_ID); - testProperties.put("externalId", "QkfTizSpRdubVXHaj3iZk6Vd3qqdl_RJ"); - testProperties.put("sourceUpdatedDateTime", "2015-08-12T04:37:20Z"); + testProperties.put("externalId", "JM2JlMHcHlVJUd597YY3Lnny5eEku5Ll"); + testProperties.put("sourceUpdatedDateTime", "2015-10-06T01:52:51Z"); testProperties.put("shared", false); testDataPointHeader(dataPoints.get(0).getHeader(), testProperties); } @Test - public void asDataPointsShouldReturnCorrectDataPointWhenUserNoteIsNull() { + public void asDataPointsShouldReturnCorrectDataPointWhenTimeZoneIsInGMTOffset() { List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); BodyWeight testBodyWeight = dataPoints.get(1).getBody(); - BodyWeight expectedBodyWeight = new BodyWeight.Builder(new MassUnitValue(MassUnit.KILOGRAM, 86.5010436535)) - .setEffectiveTimeFrame(OffsetDateTime.parse("2015-08-11T22:37:18-06:00")) + BodyWeight expectedBodyWeight = new BodyWeight.Builder(new MassUnitValue(MassUnit.KILOGRAM, 74.5)) + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-10-05T19:52:52-06:00")) .build(); assertThat(testBodyWeight, equalTo(expectedBodyWeight)); Map testProperties = Maps.newHashMap(); testProperties.put("schemaId", BodyWeight.SCHEMA_ID); - testProperties.put("externalId", "QkfTizSpRdukQY3ns4PYbkucZTM5yPMg"); - testProperties.put("sourceUpdatedDateTime", "2015-08-13T08:23:58Z"); + testProperties.put("externalId", "JM2JlMHcHlUP2mAvWWVlwwNFFVo_4CfQ"); + testProperties.put("sourceUpdatedDateTime", "2015-10-06T01:52:52Z"); testProperties.put("shared", true); testDataPointHeader(dataPoints.get(1).getHeader(), testProperties); } + @Test + public void asDataPointsShouldReturnDataPointWithoutEffectiveTimeFrameWhenTimeZoneIsNull(){ + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + + BodyWeight expectedBodyWeight = new BodyWeight.Builder(new MassUnitValue(MassUnit.KILOGRAM, 74.8)) + .setEffectiveTimeFrame(OffsetDateTime.parse("2015-10-06T19:39:01Z")) + .build(); + + assertThat(dataPoints.get(2).getBody(),equalTo(expectedBodyWeight)); + } + } diff --git a/shim-server/src/test/resources/org/openmhealth/shim/jawbone/mapper/jawbone-body-events.json b/shim-server/src/test/resources/org/openmhealth/shim/jawbone/mapper/jawbone-body-events.json index 85443756..b8a1e478 100644 --- a/shim-server/src/test/resources/org/openmhealth/shim/jawbone/mapper/jawbone-body-events.json +++ b/shim-server/src/test/resources/org/openmhealth/shim/jawbone/mapper/jawbone-body-events.json @@ -2,61 +2,60 @@ "data": { "items": [ { + "lean_mass": null, + "time_updated": 1444096371, + "xid": "JM2JlMHcHlVJUd597YY3Lnny5eEku5Ll", + "weight": 64, + "title": null, + "body_fat": 10.5, + "image": "", "bmi": null, - "body_fat": null, - "date": 20150811, + "time_created": 1444009961, + "note": "First weight", "details": { - "tz": "GMT-0600" + "tz": "America/Denver" }, - "image": "", - "lean_mass": null, - "note": "First weight", - "shared": false, - "time_created": 1439354240, - "time_updated": 1439354240, - "title": null, - "type": "body", "waistline": null, - "weight": 86.0010436535, - "xid": "QkfTizSpRdubVXHaj3iZk6Vd3qqdl_RJ" + "date": 20151004, + "shared": false, + "type": "body" }, { - "bmi": 24, + "lean_mass": null, + "time_updated": 1444096372, + "xid": "JM2JlMHcHlUP2mAvWWVlwwNFFVo_4CfQ", + "weight": 74.5, + "title": null, "body_fat": null, - "date": 20150811, + "image": "", + "bmi": 23, + "time_created": 1444096372, + "note": null, "details": { "tz": "GMT-0600" }, - "image": "", - "lean_mass": null, - "note": null, - "shared": true, - "time_created": 1439354238, - "time_updated": 1439454238, - "title": null, - "type": "body", "waistline": null, - "weight": 86.5010436535, - "xid": "QkfTizSpRdukQY3ns4PYbkucZTM5yPMg" + "date": 20151005, + "shared": true, + "type": "body" }, { - "bmi": 25.2, - "body_fat": null, - "date": 20150806, - "details": { - "tz": "GMT-0600" - }, + "lean_mass": 0, + "time_updated": 1444160342, + "xid": "JM2JlMHcHlVYbz0vvV-tzteoDrIYcQ7k", + "weight": 74.8, + "title": null, + "body_fat": 0, "image": "", - "lean_mass": null, + "bmi": 22, + "time_created": 1444160341, "note": null, - "shared": false, - "time_created": 1438925817, - "time_updated": 1438925817, - "title": null, - "type": "body", + "details": { + "tz": null + }, "waistline": null, - "weight": null, - "xid": "QkfTizSpRdt6MGLRxULIlVTscmwD_cPJ" + "date": 20151006, + "type": "body" } ], "size": 3 @@ -64,7 +63,7 @@ "meta": { "code": 200, "message": "OK", - "time": 1439865418, - "user_xid": "VB0mNZWqiOUDWkkl72vgRQ" + "time": 1444170383, + "user_xid": "VB0mNZWqiOWj26_3mxlZjg" } -} \ No newline at end of file +} From f3b31f22e89098b66a6362fd883970de051aa328 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Tue, 6 Oct 2015 17:16:30 -0600 Subject: [PATCH 32/68] Update README to reflect testing with real data Jawbone BMI and Body weight were not previously tested with real data, however we have now done that and need to remove the disclaimer for these two specifically. [ci skip] --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 34c33b25..0323a573 100644 --- a/README.md +++ b/README.md @@ -229,8 +229,8 @@ The currently supported shims are: | googlefit | step_count | [omh:step-count](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_step-count) | googlefit | calories_burned | [omh:calories-burned](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_calories-burned) | | jawbone | activity | [omh:physical-activity](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_physical-activity) | -| jawbone | weight2 | [omh:body-weight](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_body-weight) -| jawbone | body_mass_index2 | [omh:body-mass-index](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_body-mass-index) | +| jawbone | weight | [omh:body-weight](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_body-weight) +| jawbone | body_mass_index | [omh:body-mass-index](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_body-mass-index) | | jawbone | steps | [omh:step-count](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_step-count) | | jawbone | sleep | [omh:sleep-duration](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_sleep-duration) | | jawbone | heart_rate2 | [omh:heart-rate](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_heart-rate) | @@ -250,7 +250,7 @@ The currently supported shims are: 1 *The Fitbit API does not provide time zone information for the data points it returns. Furthermore, it is not possible to infer the time zone from any of the information provided. Because Open mHealth schemas require timestamps to have a time zone, we need to assign a time zone to timestamps. We set the time zone of all timestamps to UTC for consistency, even if the data may not have occurred in that time zone. This means that unless the event actually occurred in UTC, the timestamps will be incorrect. Please consider this when working with data normalized into OmH schemas that are retrieved from the Fitbit shim. We will fix this as soon as Fitbit makes changes to their API to provide time zone information.* -2 *Body weight, body mass index, and heart rate mappers have not been tested on real data from Jawbone devices. They have been tested on example data provided in Jawbone API documentation. Please help us out by testing Shimmer with real-world data of one of these types from a Jawbone device and letting us know whether or not it works correctly.* +2 *The Heart rate mapper has not been tested on real data from Jawbone devices. They have been tested on example data provided in Jawbone API documentation. Please help us out by testing Shimmer with real-world data of one of these types from a Jawbone device and letting us know whether or not it works correctly.* 3 *Uses the daily activity summary when partner access is disabled (default) and uses intraday activity when partner access is enabled. See the YAML configuration file for details. Intraday activity requests are limited to 24 hours worth of data per request.* From 255e19496418dfb78e0c70dfedfe2abb35c34de5 Mon Sep 17 00:00:00 2001 From: Emerson Farrugia Date: Wed, 7 Oct 2015 13:02:15 +0200 Subject: [PATCH 33/68] Update version in develop branch after release --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5ec4ec59..301083bd 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ subprojects { ext { javaVersion = 1.8 - shimmerVersion = '0.3.1' + shimmerVersion = '0.4.0.SNAPSHOT' omhSchemaSdkVersion = '1.0.3' } From 3e22e7cde93f69f553489e08a10f1ca5d2889935 Mon Sep 17 00:00:00 2001 From: Emerson Farrugia Date: Wed, 7 Oct 2015 15:51:58 +0200 Subject: [PATCH 34/68] Bump Spring Boot dependency --- shim-server/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shim-server/build.gradle b/shim-server/build.gradle index 07199db0..588b9c0c 100644 --- a/shim-server/build.gradle +++ b/shim-server/build.gradle @@ -5,7 +5,7 @@ buildscript { } ext { - springBootVersion = "1.2.5.RELEASE" + springBootVersion = "1.2.6.RELEASE" } dependencies { From 70a9310c54b5745608b6dc82161986640b340380 Mon Sep 17 00:00:00 2001 From: Emerson Farrugia Date: Wed, 14 Oct 2015 16:53:41 +0200 Subject: [PATCH 35/68] Add a Docker Hub deployment script --- .travis.yml | 9 +++++++- resources/docker-hub/deploy-to-docker-hub.sh | 23 ++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 resources/docker-hub/deploy-to-docker-hub.sh diff --git a/.travis.yml b/.travis.yml index aa5136ff..c1c86bc0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,4 +11,11 @@ sudo: false cache: directories: - $HOME/.gradle/wrapper/dists/gradle-2.6-bin - - $HOME/.gradle/caches/modules-2/files-2.1 \ No newline at end of file + - $HOME/.gradle/caches/modules-2/files-2.1 + +# TODO enable this once Travis builds the console +#deploy: +# provider: script +# script: resources/docker-hub/deploy-to-docker-hub.sh +# on: +# tags: true \ No newline at end of file diff --git a/resources/docker-hub/deploy-to-docker-hub.sh b/resources/docker-hub/deploy-to-docker-hub.sh new file mode 100644 index 00000000..451d834e --- /dev/null +++ b/resources/docker-hub/deploy-to-docker-hub.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +BASEDIR=$(pwd) +TAG=$(git describe --exact-match) + +if [[ ! ${TAG} =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "The tag ${TAG} isn't a valid version tag." + exit +fi + +VERSION=${TAG#v} + +cd ${BASEDIR}/shim-server/docker +docker build -t "openmhealth/shimmer-resource-server:latest" . +docker build -t "openmhealth/shimmer-resource-server:${VERSION}" . +docker push "openmhealth/shimmer-resource-server:latest" +docker push "openmhealth/shimmer-resource-server:${VERSION}" + +cd ${BASEDIR}/shim-server-ui/docker +docker build -t "openmhealth/shimmer-console:latest" . +docker build -t "openmhealth/shimmer-console:${VERSION}" . +docker push "openmhealth/shimmer-console:latest" +docker push "openmhealth/shimmer-console:${VERSION}" From 726c750aa4a6e1adae6b9c34749ceba5e1bbe726 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Mon, 19 Oct 2015 12:14:53 -0600 Subject: [PATCH 36/68] Add tests for optional BigDecimal mapping support --- .../JsonNodeMappingSupportUnitTests.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/java-shim-sdk/src/test/java/org/openmhealth/shim/common/mapper/JsonNodeMappingSupportUnitTests.java b/java-shim-sdk/src/test/java/org/openmhealth/shim/common/mapper/JsonNodeMappingSupportUnitTests.java index 189bff86..7b7b45eb 100644 --- a/java-shim-sdk/src/test/java/org/openmhealth/shim/common/mapper/JsonNodeMappingSupportUnitTests.java +++ b/java-shim-sdk/src/test/java/org/openmhealth/shim/common/mapper/JsonNodeMappingSupportUnitTests.java @@ -6,6 +6,7 @@ import org.testng.annotations.Test; import java.io.IOException; +import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.OffsetDateTime; @@ -608,4 +609,51 @@ public void asOptionalIntegerShouldReturnIntegerWhenPresent() { assertThat(value.isPresent(), equalTo(true)); assertThat(value.get(), equalTo(2)); } + + @Test + public void asOptionalBigDecimalShouldReturnBigDecimalForInteger() { + + Optional value = asOptionalBigDecimal(testNode, "integer"); + + assertThat(value, notNullValue()); + assertThat(value.isPresent(), equalTo(true)); + assertThat(value.get().intValue(), equalTo(2)); + } + + @Test + public void asOptionalBigDecimalShouldReturnBigDecimalForDecimalValue() { + + Optional value = asOptionalBigDecimal(testNode, "number"); + + assertThat(value, notNullValue()); + assertThat(value.isPresent(), equalTo(true)); + assertThat(value.get().doubleValue(), equalTo(2.3)); + } + + @Test + public void asOptionalBigDecimalShouldReturnEmptyOnMissingNode() { + + Optional value = asOptionalBigDecimal(testNode, "foo"); + + assertThat(value, notNullValue()); + assertThat(value.isPresent(), equalTo(false)); + } + + @Test + public void asOptionalBigDecimalShouldReturnEmptyOnNullNode() { + + Optional value = asOptionalBigDecimal(testNode, "empty"); + + assertThat(value, notNullValue()); + assertThat(value.isPresent(), equalTo(false)); + } + + @Test + public void asOptionalBigDecimalShouldReturnEmptyOnNonNumberNode() { + + Optional value = asOptionalBigDecimal(testNode, "string"); + + assertThat(value, notNullValue()); + assertThat(value.isPresent(), equalTo(false)); + } } \ No newline at end of file From 65cf7bc977947d09be2824e1db1af0ca0ef987ba Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Mon, 26 Oct 2015 17:55:52 -0600 Subject: [PATCH 37/68] Address inconsistency with auth endpoint response OAuth1 and OAuth2 shims handled requests to the shimmer authorize endpoint in the case where a user was already authorized for a shim. OAuth2 shims would not initialize another OAuth flow and would return that the shim was already authorized. OAuth1 shims would initiate a new OAuth flow and replace the tokens and auth credentials if completed. This inconsistent behavior was confusing. This change aligns the OAuth1 response with the OAuth2 response, such that when a request is made to the authorize endpoint for an OAuth1 shim that has already been authorized, it will respond with an indication that the shim is already authorized for the user and not initiate a new OAuth exchange. --- .../org/openmhealth/shim/Application.java | 2 +- .../org/openmhealth/shim/OAuth1ShimBase.java | 113 ++++++++++-------- .../openmhealth/shim/fitbit/FitbitShim.java | 5 +- .../shim/withings/WithingsShim.java | 7 +- 4 files changed, 74 insertions(+), 53 deletions(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/Application.java b/shim-server/src/main/java/org/openmhealth/shim/Application.java index e8f7ec6d..cca6c533 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/Application.java +++ b/shim-server/src/main/java/org/openmhealth/shim/Application.java @@ -294,7 +294,7 @@ public ShimDataResponse data( shimDataRequest.setDataTypeKey(dataTypeKey); - if(!normalize.equals("")){ + if (!normalize.equals("")) { shimDataRequest.setNormalize(Boolean.parseBoolean(normalize)); } diff --git a/shim-server/src/main/java/org/openmhealth/shim/OAuth1ShimBase.java b/shim-server/src/main/java/org/openmhealth/shim/OAuth1ShimBase.java index 52aab768..b8a6b507 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/OAuth1ShimBase.java +++ b/shim-server/src/main/java/org/openmhealth/shim/OAuth1ShimBase.java @@ -22,6 +22,7 @@ import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.impl.client.HttpClients; +import org.springframework.data.domain.Sort; import org.springframework.http.HttpMethod; import org.springframework.web.client.HttpClientErrorException; @@ -31,6 +32,7 @@ import java.util.HashMap; import java.util.Map; + /** * Common code for all OAuth1.0 based shims. * @@ -38,30 +40,42 @@ */ public abstract class OAuth1ShimBase extends ShimBase implements OAuth1Shim { + private AccessParametersRepo accessParametersRepo; + protected HttpClient httpClient = HttpClients.createDefault(); private AuthorizationRequestParametersRepo authorizationRequestParametersRepo; private ShimServerConfig shimServerConfig; - protected OAuth1ShimBase(ApplicationAccessParametersRepo applicationParametersRepo, - AuthorizationRequestParametersRepo authorizationRequestParametersRepo, - ShimServerConfig shimServerConfig) { + protected OAuth1ShimBase(ApplicationAccessParametersRepo applicationParametersRepo, + AuthorizationRequestParametersRepo authorizationRequestParametersRepo, + ShimServerConfig shimServerConfig, + AccessParametersRepo accessParametersRepo + ) { super(applicationParametersRepo); this.authorizationRequestParametersRepo = authorizationRequestParametersRepo; this.shimServerConfig = shimServerConfig; + this.accessParametersRepo = accessParametersRepo; } @Override @SuppressWarnings("unchecked") public AuthorizationRequestParameters getAuthorizationRequestParameters( - String username, - Map addlParameters + String username, + Map addlParameters ) throws ShimException { String stateKey = OAuth1Utils.generateStateKey(); + AccessParameters accessParams = accessParametersRepo + .findByUsernameAndShimKey(username, getShimKey(), new Sort(Sort.Direction.DESC, "dateCreated")); + + if (accessParams != null && accessParams.getAccessToken() != null) { + return AuthorizationRequestParameters.authorized(); + } HttpRequestBase tokenRequest = null; + try { String callbackUrl = shimServerConfig.getCallbackUrl(getShimKey(), stateKey); @@ -71,7 +85,7 @@ public AuthorizationRequestParameters getAuthorizationRequestParameters( String initiateAuthUrl = getBaseRequestTokenUrl(); tokenRequest = - getRequestTokenRequest(initiateAuthUrl, null, null, requestTokenParameters); + getRequestTokenRequest(initiateAuthUrl, null, null, requestTokenParameters); HttpResponse httpResponse = httpClient.execute(tokenRequest); @@ -104,14 +118,17 @@ public AuthorizationRequestParameters getAuthorizationRequestParameters( authorizationRequestParametersRepo.save(parameters); return parameters; - } catch (HttpClientErrorException e) { + } + catch (HttpClientErrorException e) { e.printStackTrace(); throw new ShimException("HTTP Error: " + e.getMessage()); - } catch (IOException e) { + } + catch (IOException e) { e.printStackTrace(); throw new ShimException("Unable to initiate OAuth1 authorization, " + - "could not parse token parameters"); - } finally { + "could not parse token parameters"); + } + finally { if (tokenRequest != null) { tokenRequest.releaseConnection(); } @@ -127,10 +144,10 @@ public AuthorizationResponse handleAuthorizationResponse(HttpServletRequest serv final String requestVerifier = servletRequest.getParameter(OAuth.OAUTH_VERIFIER); AuthorizationRequestParameters authParams = - authorizationRequestParametersRepo.findByStateKey(stateKey); + authorizationRequestParametersRepo.findByStateKey(stateKey); if (authParams == null) { throw new ShimException("Invalid state, could not find " + - "corresponding auth parameters"); + "corresponding auth parameters"); } // Get the token secret from the original access request. @@ -140,14 +157,16 @@ public AuthorizationResponse handleAuthorizationResponse(HttpServletRequest serv HttpRequestBase accessTokenRequest = null; try { accessTokenRequest = getAccessTokenRequest(getBaseTokenUrl(), - requestToken, requestTokenSecret, new HashMap() {{ - put(OAuth.OAUTH_VERIFIER, requestVerifier); - }}); + requestToken, requestTokenSecret, new HashMap() {{ + put(OAuth.OAUTH_VERIFIER, requestVerifier); + }}); response = httpClient.execute(accessTokenRequest); - } catch (IOException e) { + } + catch (IOException e) { e.printStackTrace(); throw new ShimException("Could not retrieve response from token URL"); - } finally { + } + finally { if (accessTokenRequest != null) { accessTokenRequest.releaseConnection(); } @@ -176,35 +195,35 @@ public AuthorizationResponse handleAuthorizationResponse(HttpServletRequest serv } protected void loadAdditionalAccessParameters( - HttpServletRequest request, - AccessParameters accessParameters + HttpServletRequest request, + AccessParameters accessParameters ) { //noop, override if additional parameters must be set here } protected HttpRequestBase getSignedRequest(String unsignedUrl, - String token, - String tokenSecret, - Map oauthParams) throws ShimException { + String token, + String tokenSecret, + Map oauthParams) throws ShimException { ApplicationAccessParameters parameters = findApplicationAccessParameters(); return OAuth1Utils.getSignedRequest( - unsignedUrl, - parameters.getClientId(), - parameters.getClientSecret(), - token, tokenSecret, oauthParams); + unsignedUrl, + parameters.getClientId(), + parameters.getClientSecret(), + token, tokenSecret, oauthParams); } protected URL signUrl(String unsignedUrl, - String token, - String tokenSecret, - Map oauthParams) - throws ShimException { + String token, + String tokenSecret, + Map oauthParams) + throws ShimException { ApplicationAccessParameters parameters = findApplicationAccessParameters(); return OAuth1Utils.buildSignedUrl( - unsignedUrl, - parameters.getClientId(), - parameters.getClientSecret(), - token, tokenSecret, oauthParams); + unsignedUrl, + parameters.getClientId(), + parameters.getClientSecret(), + token, tokenSecret, oauthParams); } /** @@ -212,43 +231,43 @@ protected URL signUrl(String unsignedUrl, * In which case the signing of the requests may differ. * * @param unsignedUrl - The unsigned URL for the request. - * @param token - The request token or access token. + * @param token - The request token or access token. * @param tokenSecret - The token secret, if any. * @param oauthParams - Any additional Oauth params. * @return - The appropriate request, signed. - * @throws ShimException */ protected HttpRequestBase getRequestTokenRequest(String unsignedUrl, - String token, - String tokenSecret, - Map oauthParams) throws ShimException { + String token, + String tokenSecret, + Map oauthParams) throws ShimException { if (HttpMethod.GET == getRequestTokenMethod()) { return new HttpGet(signUrl(unsignedUrl, token, tokenSecret, oauthParams).toString()); - } else { + } + else { return getSignedRequest(unsignedUrl, token, tokenSecret, oauthParams); } } /** * NOTE: Same as getRequestTokenRequest with difference being that this is for access tokens. - *

+ *

* Some external data providers require POST vs GET. * In which case the signing of the requests may differ. * * @param unsignedUrl - The unsigned URL for the request. - * @param token - The request token or access token. + * @param token - The request token or access token. * @param tokenSecret - The token secret, if any. * @param oauthParams - Any additional Oauth params. * @return - The appropriate request, signed. - * @throws ShimException */ protected HttpRequestBase getAccessTokenRequest(String unsignedUrl, - String token, - String tokenSecret, - Map oauthParams) throws ShimException { + String token, + String tokenSecret, + Map oauthParams) throws ShimException { if (HttpMethod.GET == getAccessTokenMethod()) { return new HttpGet(signUrl(unsignedUrl, token, tokenSecret, oauthParams).toString()); - } else { + } + else { return getSignedRequest(unsignedUrl, token, tokenSecret, oauthParams); } } diff --git a/shim-server/src/main/java/org/openmhealth/shim/fitbit/FitbitShim.java b/shim-server/src/main/java/org/openmhealth/shim/fitbit/FitbitShim.java index df095a22..9dc189dd 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/fitbit/FitbitShim.java +++ b/shim-server/src/main/java/org/openmhealth/shim/fitbit/FitbitShim.java @@ -64,9 +64,10 @@ public class FitbitShim extends OAuth1ShimBase { @Autowired public FitbitShim(ApplicationAccessParametersRepo applicationParametersRepo, AuthorizationRequestParametersRepo authorizationRequestParametersRepo, - ShimServerConfig shimServerConfig) { + ShimServerConfig shimServerConfig, + AccessParametersRepo accessParametersRepo) { - super(applicationParametersRepo, authorizationRequestParametersRepo, shimServerConfig); + super(applicationParametersRepo, authorizationRequestParametersRepo, shimServerConfig, accessParametersRepo); } @Override diff --git a/shim-server/src/main/java/org/openmhealth/shim/withings/WithingsShim.java b/shim-server/src/main/java/org/openmhealth/shim/withings/WithingsShim.java index 00d6df05..bc05f12f 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/withings/WithingsShim.java +++ b/shim-server/src/main/java/org/openmhealth/shim/withings/WithingsShim.java @@ -73,9 +73,10 @@ public class WithingsShim extends OAuth1ShimBase { @Autowired public WithingsShim(ApplicationAccessParametersRepo applicationParametersRepo, AuthorizationRequestParametersRepo authorizationRequestParametersRepo, - ShimServerConfig shimServerConfig) { + ShimServerConfig shimServerConfig, + AccessParametersRepo accessParametersRepo) { - super(applicationParametersRepo, authorizationRequestParametersRepo, shimServerConfig); + super(applicationParametersRepo, authorizationRequestParametersRepo, shimServerConfig, accessParametersRepo); } @@ -200,7 +201,7 @@ public ShimDataResponse getData(ShimDataRequest shimDataRequest) throws ShimExce if (shimDataRequest.getNormalize()) { WithingsDataPointMapper mapper; - switch (withingsDataType) { + switch ( withingsDataType ) { case BODY_WEIGHT: mapper = new WithingsBodyWeightDataPointMapper(); From af2daaff25a3bc398575340de3d9ac808ce67567 Mon Sep 17 00:00:00 2001 From: Emerson Farrugia Date: Tue, 27 Oct 2015 12:34:56 +0100 Subject: [PATCH 38/68] Clean up code formatting --- .../org/openmhealth/shim/OAuth1ShimBase.java | 81 ++++++++----------- 1 file changed, 34 insertions(+), 47 deletions(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/OAuth1ShimBase.java b/shim-server/src/main/java/org/openmhealth/shim/OAuth1ShimBase.java index b8a6b507..ac8a1c0d 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/OAuth1ShimBase.java +++ b/shim-server/src/main/java/org/openmhealth/shim/OAuth1ShimBase.java @@ -32,6 +32,8 @@ import java.util.HashMap; import java.util.Map; +import static org.springframework.data.domain.Sort.Direction.DESC; + /** * Common code for all OAuth1.0 based shims. @@ -51,8 +53,8 @@ public abstract class OAuth1ShimBase extends ShimBase implements OAuth1Shim { protected OAuth1ShimBase(ApplicationAccessParametersRepo applicationParametersRepo, AuthorizationRequestParametersRepo authorizationRequestParametersRepo, ShimServerConfig shimServerConfig, - AccessParametersRepo accessParametersRepo - ) { + AccessParametersRepo accessParametersRepo) { + super(applicationParametersRepo); this.authorizationRequestParametersRepo = authorizationRequestParametersRepo; this.shimServerConfig = shimServerConfig; @@ -61,14 +63,13 @@ protected OAuth1ShimBase(ApplicationAccessParametersRepo applicationParametersRe @Override @SuppressWarnings("unchecked") - public AuthorizationRequestParameters getAuthorizationRequestParameters( - String username, - Map addlParameters - ) throws ShimException { + public AuthorizationRequestParameters getAuthorizationRequestParameters(String username, + Map additionalParameters) + throws ShimException { String stateKey = OAuth1Utils.generateStateKey(); AccessParameters accessParams = accessParametersRepo - .findByUsernameAndShimKey(username, getShimKey(), new Sort(Sort.Direction.DESC, "dateCreated")); + .findByUsernameAndShimKey(username, getShimKey(), new Sort(DESC, "dateCreated")); if (accessParams != null && accessParams.getAccessToken() != null) { return AuthorizationRequestParameters.authorized(); @@ -125,8 +126,7 @@ public AuthorizationRequestParameters getAuthorizationRequestParameters( } catch (IOException e) { e.printStackTrace(); - throw new ShimException("Unable to initiate OAuth1 authorization, " + - "could not parse token parameters"); + throw new ShimException("Unable to initiate OAuth1 authorization, could not parse token parameters"); } finally { if (tokenRequest != null) { @@ -143,11 +143,9 @@ public AuthorizationResponse handleAuthorizationResponse(HttpServletRequest serv String requestToken = servletRequest.getParameter(OAuth.OAUTH_TOKEN); final String requestVerifier = servletRequest.getParameter(OAuth.OAUTH_VERIFIER); - AuthorizationRequestParameters authParams = - authorizationRequestParametersRepo.findByStateKey(stateKey); + AuthorizationRequestParameters authParams = authorizationRequestParametersRepo.findByStateKey(stateKey); if (authParams == null) { - throw new ShimException("Invalid state, could not find " + - "corresponding auth parameters"); + throw new ShimException("Invalid state, could not find corresponding auth parameters"); } // Get the token secret from the original access request. @@ -194,41 +192,31 @@ public AuthorizationResponse handleAuthorizationResponse(HttpServletRequest serv return AuthorizationResponse.authorized(accessParameters); } - protected void loadAdditionalAccessParameters( - HttpServletRequest request, - AccessParameters accessParameters - ) { + protected void loadAdditionalAccessParameters(HttpServletRequest request, AccessParameters accessParameters) { //noop, override if additional parameters must be set here } - protected HttpRequestBase getSignedRequest(String unsignedUrl, - String token, - String tokenSecret, - Map oauthParams) throws ShimException { + protected HttpRequestBase getSignedRequest(String unsignedUrl, String token, String tokenSecret, + Map oauthParams) + throws ShimException { + ApplicationAccessParameters parameters = findApplicationAccessParameters(); - return OAuth1Utils.getSignedRequest( - unsignedUrl, - parameters.getClientId(), - parameters.getClientSecret(), - token, tokenSecret, oauthParams); + + return OAuth1Utils.getSignedRequest(unsignedUrl, parameters.getClientId(), parameters.getClientSecret(), token, + tokenSecret, oauthParams); } - protected URL signUrl(String unsignedUrl, - String token, - String tokenSecret, - Map oauthParams) + protected URL signUrl(String unsignedUrl, String token, String tokenSecret, Map oauthParams) throws ShimException { + ApplicationAccessParameters parameters = findApplicationAccessParameters(); - return OAuth1Utils.buildSignedUrl( - unsignedUrl, - parameters.getClientId(), - parameters.getClientSecret(), - token, tokenSecret, oauthParams); + + return OAuth1Utils.buildSignedUrl(unsignedUrl, parameters.getClientId(), parameters.getClientSecret(), token, + tokenSecret, oauthParams); } /** - * Some external data providers require POST vs GET. - * In which case the signing of the requests may differ. + * Some external data providers require POST vs GET. In which case the signing of the requests may differ. * * @param unsignedUrl - The unsigned URL for the request. * @param token - The request token or access token. @@ -236,10 +224,10 @@ protected URL signUrl(String unsignedUrl, * @param oauthParams - Any additional Oauth params. * @return - The appropriate request, signed. */ - protected HttpRequestBase getRequestTokenRequest(String unsignedUrl, - String token, - String tokenSecret, - Map oauthParams) throws ShimException { + protected HttpRequestBase getRequestTokenRequest(String unsignedUrl, String token, String tokenSecret, + Map oauthParams) + throws ShimException { + if (HttpMethod.GET == getRequestTokenMethod()) { return new HttpGet(signUrl(unsignedUrl, token, tokenSecret, oauthParams).toString()); } @@ -251,8 +239,7 @@ protected HttpRequestBase getRequestTokenRequest(String unsignedUrl, /** * NOTE: Same as getRequestTokenRequest with difference being that this is for access tokens. *

- * Some external data providers require POST vs GET. - * In which case the signing of the requests may differ. + * Some external data providers require POST vs GET. In which case the signing of the requests may differ. * * @param unsignedUrl - The unsigned URL for the request. * @param token - The request token or access token. @@ -260,10 +247,10 @@ protected HttpRequestBase getRequestTokenRequest(String unsignedUrl, * @param oauthParams - Any additional Oauth params. * @return - The appropriate request, signed. */ - protected HttpRequestBase getAccessTokenRequest(String unsignedUrl, - String token, - String tokenSecret, - Map oauthParams) throws ShimException { + protected HttpRequestBase getAccessTokenRequest(String unsignedUrl, String token, String tokenSecret, + Map oauthParams) + throws ShimException { + if (HttpMethod.GET == getAccessTokenMethod()) { return new HttpGet(signUrl(unsignedUrl, token, tokenSecret, oauthParams).toString()); } From c09454cf93c119b06f834639f15b321ae992e938 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Wed, 28 Oct 2015 14:40:00 -0600 Subject: [PATCH 39/68] Fix type in step count mapper test class name --- ...tTests.java => FitbitStepCountDataPointMapperUnitTests.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/{FitbitStepCountDataPointUnitTests.java => FitbitStepCountDataPointMapperUnitTests.java} (97%) diff --git a/shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitStepCountDataPointUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitStepCountDataPointMapperUnitTests.java similarity index 97% rename from shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitStepCountDataPointUnitTests.java rename to shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitStepCountDataPointMapperUnitTests.java index 25853802..4c761c0d 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitStepCountDataPointUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitStepCountDataPointMapperUnitTests.java @@ -21,7 +21,7 @@ /** * @author Chris Schaefbauer */ -public class FitbitStepCountDataPointUnitTests extends DataPointMapperUnitTests { +public class FitbitStepCountDataPointMapperUnitTests extends DataPointMapperUnitTests { JsonNode responseNodeStepTimeSeries; protected final FitbitStepCountDataPointMapper mapper = new FitbitStepCountDataPointMapper(); From ca23fd5ec2d3b54d762a7d7f5f01ab5d195940ee Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Wed, 28 Oct 2015 16:40:16 -0600 Subject: [PATCH 40/68] Add step count intraday activity mapper --- .../common/mapper/JsonNodeMappingSupport.java | 12 ++ .../fitbit/mapper/FitbitDataPointMapper.java | 4 +- .../mapper/FitbitIntradayDataPointMapper.java | 73 ++++++++++++ ...itbitIntradayStepCountDataPointMapper.java | 72 ++++++++++++ ...adayStepCountDataPointMapperUnitTests.java | 109 ++++++++++++++++++ ...sicalActivityDataPointMapperUnitTests.java | 7 +- .../mapper/fitbit-get-intraday-steps.json | 26 +++++ 7 files changed, 297 insertions(+), 6 deletions(-) create mode 100644 shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayDataPointMapper.java create mode 100644 shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayStepCountDataPointMapper.java create mode 100644 shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayStepCountDataPointMapperUnitTests.java create mode 100644 shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-intraday-steps.json diff --git a/java-shim-sdk/src/main/java/org/openmhealth/shim/common/mapper/JsonNodeMappingSupport.java b/java-shim-sdk/src/main/java/org/openmhealth/shim/common/mapper/JsonNodeMappingSupport.java index 0d03d763..7b110cbf 100644 --- a/java-shim-sdk/src/main/java/org/openmhealth/shim/common/mapper/JsonNodeMappingSupport.java +++ b/java-shim-sdk/src/main/java/org/openmhealth/shim/common/mapper/JsonNodeMappingSupport.java @@ -135,6 +135,18 @@ public static Double asRequiredDouble(JsonNode parentNode, String path) { return asRequiredValue(parentNode, path, JsonNode::isNumber, JsonNode::doubleValue, Double.class); } + /** + * @param parentNode a parent node + * @param path the path to a child node + * @return the value of the child node as a BigDecimal + * @throws MissingJsonNodeMappingException if the child doesn't exist + * @throws IncompatibleJsonNodeMappingException if the value of the child node isn't numeric + */ + public static BigDecimal asRequiredBigDecimal(JsonNode parentNode, String path) { + + return asRequiredValue(parentNode, path, JsonNode::isNumber, JsonNode::decimalValue, BigDecimal.class); + } + /** * @param parentNode a parent node * @param path the path to a child node diff --git a/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitDataPointMapper.java index 0d88a943..e9d8a6d9 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitDataPointMapper.java @@ -105,8 +105,8 @@ protected DataPoint newDataPoint(T measure, Long external } /** - * Takes a Fitbit response JSON node, which contains a date and time property, and then maps them into an {@link - * OffsetDateTime} object + * Takes a Fitbit response JSON node, which contains a date and time property, and then maps those fields into an + * {@link OffsetDateTime} object * * @return the date and time based on the "date" and "time" properties of the JsonNode parameter, wrapped as an * {@link Optional} diff --git a/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayDataPointMapper.java new file mode 100644 index 00000000..42afc44b --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayDataPointMapper.java @@ -0,0 +1,73 @@ +/* + * 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.fitbit.mapper; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.Lists; +import org.openmhealth.schema.domain.omh.DataPoint; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asOptionalLocalDate; +import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredNode; + + +/** + * @author Chris Schaefbauer + */ +public abstract class FitbitIntradayDataPointMapper extends FitbitDataPointMapper { + + private JsonNode parentNode; + + @Override + public List> asDataPoints(List responseNodes) { + + checkNotNull(responseNodes); + checkArgument(responseNodes.size() == 1, "FitbitDataPointMapper requires one response node."); + + parentNode = responseNodes.get(0); + + JsonNode targetTypeNodeList = asRequiredNode(responseNodes.get(0), getListNodeName()); + + List> dataPoints = Lists.newArrayList(); + + for (JsonNode targetTypeNode : targetTypeNodeList) { + asDataPoint(targetTypeNode).ifPresent(dataPoints::add); + } + + return dataPoints; + + + } + + /** + * Allows specific intraday activity measure mappers to access the date that the datapoint occured, which is stored + * outside the individual list nodes + */ + public Optional getDateFromParentNode() { + + JsonNode summaryForDayNode = asRequiredNode(parentNode, getDateTimeNodeListName()).get(0); + return asOptionalLocalDate(summaryForDayNode, "dateTime"); + } + + public abstract String getDateTimeNodeListName(); + +} diff --git a/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayStepCountDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayStepCountDataPointMapper.java new file mode 100644 index 00000000..c8e82d4b --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayStepCountDataPointMapper.java @@ -0,0 +1,72 @@ +/* + * 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.fitbit.mapper; + +import com.fasterxml.jackson.databind.JsonNode; +import org.openmhealth.schema.domain.omh.*; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.util.Optional; + +import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*; + + +/** + * @author Chris Schaefbauer + */ +public class FitbitIntradayStepCountDataPointMapper extends FitbitIntradayDataPointMapper { + + @Override + protected Optional> asDataPoint(JsonNode listEntryNode) { + + BigDecimal stepCountValue = asRequiredBigDecimal(listEntryNode, "value"); + + if (stepCountValue.intValue() == 0) { + return Optional.empty(); + } + + StepCount.Builder stepCountBuilder = new StepCount.Builder(stepCountValue); + + // We use 1 minute because the Fitbit shim requests 1 minute granularity currently + Optional dateFromParent = getDateFromParentNode(); + + if (dateFromParent.isPresent()) { + + // Set the effective time frame only if we have access to the date and time + asOptionalString(listEntryNode, "time").ifPresent(time -> stepCountBuilder + .setEffectiveTimeFrame(TimeInterval.ofStartDateTimeAndDuration( + dateFromParent.get().atTime(LocalTime.parse(time)).atOffset(ZoneOffset.UTC), + new DurationUnitValue(DurationUnit.MINUTE, 1)))); + + } + + return Optional.of(newDataPoint(stepCountBuilder.build(), null)); + } + + @Override + protected String getListNodeName() { + return "activities-steps-intraday.dataset"; + } + + @Override + public String getDateTimeNodeListName() { + return "activities-steps"; + } +} diff --git a/shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayStepCountDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayStepCountDataPointMapperUnitTests.java new file mode 100644 index 00000000..95136d04 --- /dev/null +++ b/shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayStepCountDataPointMapperUnitTests.java @@ -0,0 +1,109 @@ +/* + * 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.fitbit.mapper; + +import com.fasterxml.jackson.databind.JsonNode; +import org.openmhealth.schema.domain.omh.DataPoint; +import org.openmhealth.schema.domain.omh.DurationUnit; +import org.openmhealth.schema.domain.omh.DurationUnitValue; +import org.openmhealth.schema.domain.omh.StepCount; +import org.openmhealth.shim.common.mapper.DataPointMapperUnitTests; +import org.springframework.core.io.ClassPathResource; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.time.OffsetDateTime; +import java.util.List; + +import static java.util.Collections.singletonList; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.openmhealth.schema.domain.omh.TimeInterval.ofStartDateTimeAndDuration; +import static org.openmhealth.shim.fitbit.mapper.FitbitDataPointMapper.RESOURCE_API_SOURCE_NAME; + + +/** + * @author Chris Schaefbauer + */ +public class FitbitIntradayStepCountDataPointMapperUnitTests extends DataPointMapperUnitTests { + + + private JsonNode responseNode; + private FitbitIntradayStepCountDataPointMapper mapper = new FitbitIntradayStepCountDataPointMapper(); + + + @BeforeTest + public void initializeResponseNode() throws IOException { + + ClassPathResource resource = + new ClassPathResource("/org/openmhealth/shim/fitbit/mapper/fitbit-get-intraday-steps.json"); + responseNode = objectMapper.readTree(resource.getInputStream()); + } + + @Test + public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + assertThat(dataPoints.size(), equalTo(2)); + } + + @Test + public void asDataPointsShouldReturnCorrectDataPoints() { + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + StepCount.Builder stepCountBuilder = new StepCount.Builder(7) + .setEffectiveTimeFrame(ofStartDateTimeAndDuration(OffsetDateTime.parse("2015-08-21T00:00:00Z"), + new DurationUnitValue(DurationUnit.MINUTE, 1))); + + assertThat(dataPoints.get(0).getBody(), equalTo(stepCountBuilder.build())); + assertThat(dataPoints.get(0).getHeader().getBodySchemaId(), equalTo(StepCount.SCHEMA_ID)); + assertThat(dataPoints.get(0).getHeader().getAcquisitionProvenance().getSourceName(), + equalTo(RESOURCE_API_SOURCE_NAME)); + + stepCountBuilder = new StepCount.Builder(52) + .setEffectiveTimeFrame(ofStartDateTimeAndDuration(OffsetDateTime.parse("2015-08-21T14:28:00Z"), + new DurationUnitValue(DurationUnit.MINUTE, 1))); + + assertThat(dataPoints.get(1).getBody(), equalTo(stepCountBuilder.build())); + assertThat(dataPoints.get(1).getHeader().getBodySchemaId(), equalTo(StepCount.SCHEMA_ID)); + assertThat(dataPoints.get(1).getHeader().getAcquisitionProvenance().getSourceName(), + equalTo(RESOURCE_API_SOURCE_NAME)); + } + + @Test + public void asDataPointsShouldReturnNoDataPointsWhenDataSetArrayIsEmpty() throws IOException { + + JsonNode emptyDataSetNode = objectMapper.readTree( + "{\n" + + "\"activities-steps\": [ \n" + + "{\n" + + "\"dateTime\": \"2015-05-24\"\n," + + "\"value\": 0\n" + + "}\n" + + "],\n" + + "\"activities-steps-intraday\": {\n" + + "\"dataset\": [],\n" + + "\"datasetInterval\": 1,\n" + + "\"datasetType\": \"minute\"\n" + + "}\n" + + "}"); + + List> dataPoints = mapper.asDataPoints(singletonList(emptyDataSetNode)); + assertThat(dataPoints.isEmpty(), equalTo(true)); + } +} diff --git a/shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitPhysicalActivityDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitPhysicalActivityDataPointMapperUnitTests.java index ed622934..d346b66e 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitPhysicalActivityDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitPhysicalActivityDataPointMapperUnitTests.java @@ -1,7 +1,6 @@ package org.openmhealth.shim.fitbit.mapper; import com.fasterxml.jackson.databind.JsonNode; -import org.hamcrest.CoreMatchers; import org.openmhealth.schema.domain.omh.*; import org.openmhealth.shim.common.mapper.DataPointMapperUnitTests; import org.springframework.core.io.ClassPathResource; @@ -87,10 +86,10 @@ else if(startTimeString !=null){ } PhysicalActivity expectedPhysicalActivity = dataPointBuilderForExpected.build(); - assertThat(dataPoint.getBody(), CoreMatchers.equalTo(expectedPhysicalActivity)); - assertThat(dataPoint.getHeader().getBodySchemaId(), CoreMatchers.equalTo(PhysicalActivity.SCHEMA_ID)); + assertThat(dataPoint.getBody(), equalTo(expectedPhysicalActivity)); + assertThat(dataPoint.getHeader().getBodySchemaId(), equalTo(PhysicalActivity.SCHEMA_ID)); assertThat(dataPoint.getHeader().getAcquisitionProvenance().getAdditionalProperties().get("external_id"), equalTo(logId)); - assertThat(dataPoint.getHeader().getAcquisitionProvenance().getSourceName(), CoreMatchers.equalTo(FitbitDataPointMapper.RESOURCE_API_SOURCE_NAME)); + assertThat(dataPoint.getHeader().getAcquisitionProvenance().getSourceName(), equalTo(FitbitDataPointMapper.RESOURCE_API_SOURCE_NAME)); } diff --git a/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-intraday-steps.json b/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-intraday-steps.json new file mode 100644 index 00000000..f7076fe5 --- /dev/null +++ b/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-intraday-steps.json @@ -0,0 +1,26 @@ +{ + "activities-steps": [ + { + "dateTime": "2015-08-21", + "value": "3212" + } + ], + "activities-steps-intraday": { + "dataset": [ + { + "time": "00:00:00", + "value": 7 + }, + { + "time": "14:27:00", + "value": 0 + }, + { + "time": "14:28:00", + "value": 52 + } + ], + "datasetInterval": 1, + "datasetType": "minute" + } +} \ No newline at end of file From c312c7444675c4eb960e9eff66ddc8a8807a6a0c Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Wed, 28 Oct 2015 17:23:57 -0600 Subject: [PATCH 41/68] Connect intraday step count mapper to fitbit shim --- .../openmhealth/shim/fitbit/FitbitShim.java | 20 +++++++++++++++++-- .../src/main/resources/application.yaml | 1 + 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/fitbit/FitbitShim.java b/shim-server/src/main/java/org/openmhealth/shim/fitbit/FitbitShim.java index 9dc189dd..7f602aae 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/fitbit/FitbitShim.java +++ b/shim-server/src/main/java/org/openmhealth/shim/fitbit/FitbitShim.java @@ -27,6 +27,7 @@ import org.openmhealth.shim.*; import org.openmhealth.shim.fitbit.mapper.*; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.http.HttpMethod; import org.springframework.stereotype.Component; @@ -61,6 +62,9 @@ public class FitbitShim extends OAuth1ShimBase { private static final String TOKEN_URL = "https://api.fitbit.com/oauth/access_token"; + @Value("${openmhealth.shim.fitbit.partner_access:false}") + protected boolean partnerAccess; + @Autowired public FitbitShim(ApplicationAccessParametersRepo applicationParametersRepo, AuthorizationRequestParametersRepo authorizationRequestParametersRepo, @@ -124,12 +128,14 @@ public enum FitbitDataType implements ShimDataType { private String endPoint; FitbitDataType(String endPoint) { + this.endPoint = endPoint; } public String getEndPoint() { return endPoint; } + } @Override @@ -274,7 +280,12 @@ private ShimDataResponse executeRequest(String endPointUrl, switch ( fitbitDataType ) { case STEPS: - dataPointMapper = new FitbitStepCountDataPointMapper(); + if(partnerAccess){ + dataPointMapper = new FitbitIntradayStepCountDataPointMapper(); + } + else{ + dataPointMapper = new FitbitStepCountDataPointMapper(); + } break; case ACTIVITY: dataPointMapper = new FitbitPhysicalActivityDataPointMapper(); @@ -364,7 +375,12 @@ private ShimDataResponse getDaysData(OffsetDateTime dateTime, switch ( fitbitDataType ) { case STEPS: - dataPointMapper = new FitbitStepCountDataPointMapper(); + if(partnerAccess){ + dataPointMapper = new FitbitIntradayStepCountDataPointMapper(); + } + else{ + dataPointMapper = new FitbitStepCountDataPointMapper(); + } break; case ACTIVITY: dataPointMapper = new FitbitPhysicalActivityDataPointMapper(); diff --git a/shim-server/src/main/resources/application.yaml b/shim-server/src/main/resources/application.yaml index a613f73d..3b6003f5 100644 --- a/shim-server/src/main/resources/application.yaml +++ b/shim-server/src/main/resources/application.yaml @@ -26,6 +26,7 @@ openmhealth: #NOTE: Un-comment and fill in with your credentials if you're not using the UI #fitbit: + # partner_access: true # clientId: [YOUR_CLIENT_ID] # clientSecret: [YOUR_CLIENT_SECRET] #fatsecret: From 9a606f5b1e77eae72a775cb32ce1e2662af794de Mon Sep 17 00:00:00 2001 From: Emerson Farrugia Date: Thu, 29 Oct 2015 18:40:26 +0100 Subject: [PATCH 42/68] Use static imports in Fitbit mappers --- .../fitbit/mapper/FitbitDataPointMapper.java | 2 +- .../mapper/FitbitIntradayDataPointMapper.java | 5 +---- ...FitbitIntradayStepCountDataPointMapper.java | 18 +++++++++++------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitDataPointMapper.java index e9d8a6d9..75380e74 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitDataPointMapper.java @@ -64,7 +64,7 @@ public abstract class FitbitDataPointMapper implements JsonNodeDataPointMappe public List> asDataPoints(List responseNodes) { checkNotNull(responseNodes); - checkArgument(responseNodes.size() == 1, "FitbitDataPointMapper requires one response node."); + checkArgument(responseNodes.size() == 1, "A single response node is allowed per call."); JsonNode targetTypeNodeList = asRequiredNode(responseNodes.get(0), getListNodeName()); diff --git a/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayDataPointMapper.java index 42afc44b..55377b5d 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayDataPointMapper.java @@ -41,7 +41,7 @@ public abstract class FitbitIntradayDataPointMapper extends FitbitDataPointMa public List> asDataPoints(List responseNodes) { checkNotNull(responseNodes); - checkArgument(responseNodes.size() == 1, "FitbitDataPointMapper requires one response node."); + checkArgument(responseNodes.size() == 1, "A single response node is allowed per call."); parentNode = responseNodes.get(0); @@ -54,8 +54,6 @@ public List> asDataPoints(List responseNodes) { } return dataPoints; - - } /** @@ -69,5 +67,4 @@ public Optional getDateFromParentNode() { } public abstract String getDateTimeNodeListName(); - } diff --git a/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayStepCountDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayStepCountDataPointMapper.java index c8e82d4b..4b591b9a 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayStepCountDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayStepCountDataPointMapper.java @@ -17,15 +17,20 @@ package org.openmhealth.shim.fitbit.mapper; import com.fasterxml.jackson.databind.JsonNode; -import org.openmhealth.schema.domain.omh.*; +import org.openmhealth.schema.domain.omh.DataPoint; +import org.openmhealth.schema.domain.omh.DurationUnitValue; +import org.openmhealth.schema.domain.omh.StepCount; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalTime; -import java.time.ZoneOffset; import java.util.Optional; -import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*; +import static java.time.ZoneOffset.UTC; +import static org.openmhealth.schema.domain.omh.DurationUnit.MINUTE; +import static org.openmhealth.schema.domain.omh.TimeInterval.ofStartDateTimeAndDuration; +import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asOptionalString; +import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredBigDecimal; /** @@ -51,10 +56,9 @@ protected Optional> asDataPoint(JsonNode listEntryNode) { // Set the effective time frame only if we have access to the date and time asOptionalString(listEntryNode, "time").ifPresent(time -> stepCountBuilder - .setEffectiveTimeFrame(TimeInterval.ofStartDateTimeAndDuration( - dateFromParent.get().atTime(LocalTime.parse(time)).atOffset(ZoneOffset.UTC), - new DurationUnitValue(DurationUnit.MINUTE, 1)))); - + .setEffectiveTimeFrame(ofStartDateTimeAndDuration( + dateFromParent.get().atTime(LocalTime.parse(time)).atOffset(UTC), + new DurationUnitValue(MINUTE, 1)))); } return Optional.of(newDataPoint(stepCountBuilder.build(), null)); From 4bc82a83ae6e0afb04f114b305b6085949a758d6 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Fri, 30 Oct 2015 14:00:59 -0600 Subject: [PATCH 43/68] Improve naming for fitbit intraday step mapper --- .../mapper/FitbitIntradayDataPointMapper.java | 19 ++++++++++--------- ...itbitIntradayStepCountDataPointMapper.java | 8 ++++---- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayDataPointMapper.java index 55377b5d..3c0c7a8a 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayDataPointMapper.java @@ -35,7 +35,7 @@ */ public abstract class FitbitIntradayDataPointMapper extends FitbitDataPointMapper { - private JsonNode parentNode; + private JsonNode responseNode; @Override public List> asDataPoints(List responseNodes) { @@ -43,14 +43,12 @@ public List> asDataPoints(List responseNodes) { checkNotNull(responseNodes); checkArgument(responseNodes.size() == 1, "A single response node is allowed per call."); - parentNode = responseNodes.get(0); - - JsonNode targetTypeNodeList = asRequiredNode(responseNodes.get(0), getListNodeName()); + responseNode = responseNodes.get(0); List> dataPoints = Lists.newArrayList(); - for (JsonNode targetTypeNode : targetTypeNodeList) { - asDataPoint(targetTypeNode).ifPresent(dataPoints::add); + for (JsonNode listEntryNode : asRequiredNode(responseNode, getListNodeName())) { + asDataPoint(listEntryNode).ifPresent(dataPoints::add); } return dataPoints; @@ -60,11 +58,14 @@ public List> asDataPoints(List responseNodes) { * Allows specific intraday activity measure mappers to access the date that the datapoint occured, which is stored * outside the individual list nodes */ - public Optional getDateFromParentNode() { + public Optional getDateFromSummaryForDay() { - JsonNode summaryForDayNode = asRequiredNode(parentNode, getDateTimeNodeListName()).get(0); + JsonNode summaryForDayNode = asRequiredNode(responseNode, getSummaryForDayNodeName()).get(0); return asOptionalLocalDate(summaryForDayNode, "dateTime"); } - public abstract String getDateTimeNodeListName(); + /** + * @return the name of the summary list node which contains a data point with the dateTime field + */ + public abstract String getSummaryForDayNodeName(); } diff --git a/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayStepCountDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayStepCountDataPointMapper.java index 4b591b9a..df470b00 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayStepCountDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitIntradayStepCountDataPointMapper.java @@ -49,8 +49,7 @@ protected Optional> asDataPoint(JsonNode listEntryNode) { StepCount.Builder stepCountBuilder = new StepCount.Builder(stepCountValue); - // We use 1 minute because the Fitbit shim requests 1 minute granularity currently - Optional dateFromParent = getDateFromParentNode(); + Optional dateFromParent = getDateFromSummaryForDay(); if (dateFromParent.isPresent()) { @@ -58,7 +57,8 @@ protected Optional> asDataPoint(JsonNode listEntryNode) { asOptionalString(listEntryNode, "time").ifPresent(time -> stepCountBuilder .setEffectiveTimeFrame(ofStartDateTimeAndDuration( dateFromParent.get().atTime(LocalTime.parse(time)).atOffset(UTC), - new DurationUnitValue(MINUTE, 1)))); + new DurationUnitValue(MINUTE, + 1)))); // We use 1 minute since the shim requests data at 1 minute granularity } return Optional.of(newDataPoint(stepCountBuilder.build(), null)); @@ -70,7 +70,7 @@ protected String getListNodeName() { } @Override - public String getDateTimeNodeListName() { + public String getSummaryForDayNodeName() { return "activities-steps"; } } From 826c17ddb5c7382862ad82706edf3ca18643c1b4 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Sun, 1 Nov 2015 23:27:49 -0700 Subject: [PATCH 44/68] Update README with intraday update to fitbit steps --- README.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 0323a573..bd39dd21 100644 --- a/README.md +++ b/README.md @@ -218,7 +218,7 @@ The currently supported shims are: | shim | endPoint | OmH data produced by endpoint | | ------------ | ----------------- | -------------------------- | | fitbit1 | activity | [omh:physical-activity](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_physical-activity) | -| fitbit1 | steps | [omh:step-count](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_step-count) | +| fitbit1 | steps2 | [omh:step-count](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_step-count) | | fitbit1 | weight | [omh:body-weight](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_body-weight) | | fitbit1 | body_mass_index | [omh:body-mass-index](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_body-mass-index)| | fitbit1 | sleep | [omh:sleep-duration](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_sleep-duration) | @@ -233,7 +233,7 @@ The currently supported shims are: | jawbone | body_mass_index | [omh:body-mass-index](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_body-mass-index) | | jawbone | steps | [omh:step-count](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_step-count) | | jawbone | sleep | [omh:sleep-duration](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_sleep-duration) | -| jawbone | heart_rate2 | [omh:heart-rate](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_heart-rate) | +| jawbone | heart_rate3 | [omh:heart-rate](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_heart-rate) | | misfit | activities | [omh:physical-activity](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_physical-activity) | | misfit | steps | [omh:step-count](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_step-count) | misfit | sleep | [omh:sleep-duration](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_sleep-duration) | @@ -243,18 +243,20 @@ The currently supported shims are: | withings | body_height | [omh:body-height](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_body-height)| | withings | body_weight | [omh:body-weight](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_body-weight) | | withings | heart_rate | [omh:heart-rate](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_heart-rate) | -| withings | steps3 | [omh:step-count](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_step-count) | -| withings | calories3 | [omh:calories-burned](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_calories-burned) | -| withings | sleep4 | [omh:sleep-duration](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_sleep-duration) | +| withings | steps4 | [omh:step-count](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_step-count) | +| withings | calories4 | [omh:calories-burned](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_calories-burned) | +| withings | sleep5 | [omh:sleep-duration](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_sleep-duration) | 1 *The Fitbit API does not provide time zone information for the data points it returns. Furthermore, it is not possible to infer the time zone from any of the information provided. Because Open mHealth schemas require timestamps to have a time zone, we need to assign a time zone to timestamps. We set the time zone of all timestamps to UTC for consistency, even if the data may not have occurred in that time zone. This means that unless the event actually occurred in UTC, the timestamps will be incorrect. Please consider this when working with data normalized into OmH schemas that are retrieved from the Fitbit shim. We will fix this as soon as Fitbit makes changes to their API to provide time zone information.* -2 *The Heart rate mapper has not been tested on real data from Jawbone devices. They have been tested on example data provided in Jawbone API documentation. Please help us out by testing Shimmer with real-world data of one of these types from a Jawbone device and letting us know whether or not it works correctly.* +2 *Uses the daily step summary when partner access is disabled (default) and uses intraday step count (at 1 minute granularity) when partner access is enabled. Intraday activity requests are limited to 24 hours worth of data per request. See the YAML configuration file (application.yaml) to enable partner access if your API credentials have been granted partner access. Attempting to generate normalized data with the partner access property set to true, but when your API credentials have not been granted partner access will result in an error.* -3 *Uses the daily activity summary when partner access is disabled (default) and uses intraday activity when partner access is enabled. See the YAML configuration file for details. Intraday activity requests are limited to 24 hours worth of data per request.* +3 *The Heart rate mapper has not been tested on real data from Jawbone devices. They have been tested on example data provided in Jawbone API documentation. Please help us out by testing Shimmer with real-world data of one of these types from a Jawbone device and letting us know whether or not it works correctly.* -4 *Sleep data has not been tested using real data directly from a device. It has been tested with example data provided in the Withings API documentation. Please help us out by testing real-world Withings sleep data with Shimmer and letting us know whether or not it works correctly.* +4 *Uses the daily activity summary when partner access is disabled (default) and uses intraday activity when partner access is enabled. See the YAML configuration file for details. Intraday activity requests are limited to 24 hours worth of data per request.* + +5 *Sleep data has not been tested using real data directly from a device. It has been tested with example data provided in the Withings API documentation. Please help us out by testing real-world Withings sleep data with Shimmer and letting us know whether or not it works correctly.* ### Contributing From f9abec338e755016a618c7b0d4d468f9e9b12387 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Sun, 1 Nov 2015 23:51:44 -0700 Subject: [PATCH 45/68] Update partner access property name The property name for partner access in Fitbit and Withings previously did not follow conventions. We have changed the name of this property to be consistent with other properties in the application.yaml file. --- .../main/java/org/openmhealth/shim/fitbit/FitbitShim.java | 2 +- .../java/org/openmhealth/shim/withings/WithingsShim.java | 2 +- shim-server/src/main/resources/application.yaml | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/fitbit/FitbitShim.java b/shim-server/src/main/java/org/openmhealth/shim/fitbit/FitbitShim.java index 7f602aae..adc29f4f 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/fitbit/FitbitShim.java +++ b/shim-server/src/main/java/org/openmhealth/shim/fitbit/FitbitShim.java @@ -62,7 +62,7 @@ public class FitbitShim extends OAuth1ShimBase { private static final String TOKEN_URL = "https://api.fitbit.com/oauth/access_token"; - @Value("${openmhealth.shim.fitbit.partner_access:false}") + @Value("${openmhealth.shim.fitbit.partnerAccess:false}") protected boolean partnerAccess; @Autowired diff --git a/shim-server/src/main/java/org/openmhealth/shim/withings/WithingsShim.java b/shim-server/src/main/java/org/openmhealth/shim/withings/WithingsShim.java index bc05f12f..0d4eaa85 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/withings/WithingsShim.java +++ b/shim-server/src/main/java/org/openmhealth/shim/withings/WithingsShim.java @@ -67,7 +67,7 @@ public class WithingsShim extends OAuth1ShimBase { private static final String PARTNER_ACCESS_ACTIVITY_ENDPOINT = "getintradayactivity"; - @Value("${openmhealth.shim.withings.partner_access:false}") + @Value("${openmhealth.shim.withings.partnerAccess:false}") protected boolean partnerAccess; @Autowired diff --git a/shim-server/src/main/resources/application.yaml b/shim-server/src/main/resources/application.yaml index 3b6003f5..82b01332 100644 --- a/shim-server/src/main/resources/application.yaml +++ b/shim-server/src/main/resources/application.yaml @@ -25,8 +25,9 @@ openmhealth: callbackUrlBase: http://localhost:8083 #NOTE: Un-comment and fill in with your credentials if you're not using the UI + #NOTE: Un-comment and set partnerAccess to true if your credentials for a given API have partner access #fitbit: - # partner_access: true + # partnerAccess: true # clientId: [YOUR_CLIENT_ID] # clientSecret: [YOUR_CLIENT_SECRET] #fatsecret: @@ -45,7 +46,7 @@ openmhealth: # clientId: [YOUR_CLIENT_ID] # clientSecret: [YOUR_CLIENT_SECRET] #withings: - # partner_access: true + # partnerAccess: true # clientId: [YOUR_CLIENT_ID] # clientSecret: [YOUR_CLIENT_SECRET] #healthvault: From ec21c89415ee3199ab7f741caa4aa166bedfa676 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Tue, 17 Nov 2015 23:35:40 -0700 Subject: [PATCH 46/68] Handle positive integer time zones For the sports endpoint, the API provides time zones in a different way than other endpoints. Instead of providing a string in the form "+0700" they provide a single integer - 0, 1, -4, etc. The ZoneOffset class could parse the negative values, however it could not parse positive integers or timezones with the value 0. The class needs to have a '+' in front of the positive/zero values. This change supports those time zones by adding appending a '+' at the beginning to the timezone representation. --- .../IHealthPhysicalActivityDataPointMapper.java | 11 +++++++++-- ...ealthPhysicalActivityDataPointMapperUnitTests.java | 4 ++-- .../shim/ihealth/mapper/ihealth-sports-activity.json | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java index 469e4b91..ae71d9a7 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java @@ -59,9 +59,16 @@ protected Optional> asDataPoint(JsonNode listNode, I if (startTimeUnixEpochSecs.isPresent() && endTimeUnixEpochSecs.isPresent() && timeZoneOffset.isPresent()) { + Integer timeZoneOffsetValue = timeZoneOffset.get(); + String timeZoneString = timeZoneOffsetValue.toString(); + + if(timeZoneOffsetValue>=0){ + timeZoneString = "+"+timeZoneOffsetValue.toString(); + } + physicalActivityBuilder.setEffectiveTimeFrame(TimeInterval.ofStartDateTimeAndEndDateTime( - getDateTimeWithCorrectOffset(startTimeUnixEpochSecs.get(), timeZoneOffset.get().toString()), - getDateTimeWithCorrectOffset(endTimeUnixEpochSecs.get(), timeZoneOffset.get().toString()))); + getDateTimeWithCorrectOffset(startTimeUnixEpochSecs.get(), timeZoneString), + getDateTimeWithCorrectOffset(endTimeUnixEpochSecs.get(), timeZoneString))); } PhysicalActivity physicalActivity = physicalActivityBuilder.build(); diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java index 7ab00d13..9378b297 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java @@ -85,8 +85,8 @@ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { PhysicalActivity.Builder expectedPhysicalActivityBuilder = new PhysicalActivity.Builder("Running") .setEffectiveTimeFrame( TimeInterval.ofStartDateTimeAndEndDateTime( - OffsetDateTime.parse("2015-09-22T20:43:03-06:00"), - OffsetDateTime.parse("2015-09-22T21:13:03-06:00"))); + OffsetDateTime.parse("2015-09-22T20:43:03+01:00"), + OffsetDateTime.parse("2015-09-22T21:13:03+01:00"))); assertThat(dataPoints.get(1).getBody(), equalTo(expectedPhysicalActivityBuilder.build())); diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity.json index 8eed28b3..2744bc8a 100644 --- a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity.json +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity.json @@ -28,7 +28,7 @@ "SportEndTime": 1442956383, "SportName": "Running", "SportStartTime": 1442954583, - "TimeZone": -6 + "TimeZone": 1 } ] } \ No newline at end of file From b1474af36b476c9de1833e0daa03538ed4df2cf9 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Sat, 21 Nov 2015 20:20:36 -0700 Subject: [PATCH 47/68] Fix iHealth time zone handling for corner cases --- .../mapper/IHealthDataPointMapper.java | 23 ++++- .../IHealthDataPointMapperUnitTests.java | 6 -- ...ealthDatapointMapperDateTimeUnitTests.java | 86 +++++++++++++++++++ 3 files changed, 106 insertions(+), 9 deletions(-) create mode 100644 shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java index 1f1045ed..f7335a0e 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java @@ -117,12 +117,29 @@ protected static void setEffectiveTimeFrameIfExists(JsonNode listNode, Measure.B if (optionalOffsetDateTime.isPresent()) { // Todo: Revisit after clarification from iHealth on how time zones are set - Optional timeZoneString = asOptionalString(listNode, "TimeZone"); + Optional timeZone = asOptionalString(listNode, "TimeZone"); - if (timeZoneString.isPresent()) { + if (timeZone.isPresent() && !timeZone.get().isEmpty()) { OffsetDateTime offsetDateTimeCorrectOffset = - getDateTimeWithCorrectOffset(optionalOffsetDateTime.get(), timeZoneString.get()); + getDateTimeWithCorrectOffset(optionalOffsetDateTime.get(), timeZone.get()); + builder.setEffectiveTimeFrame(offsetDateTimeCorrectOffset); + } + + else if (asOptionalLong(listNode, "TimeZone").isPresent()) { + + Long timeZoneOffsetValue = asOptionalLong(listNode, "TimeZone").get(); + String timeZoneString = timeZoneOffsetValue.toString(); + + + if(timeZoneOffsetValue>=0){ + timeZoneString = "+"+timeZoneOffsetValue.toString(); + } + + OffsetDateTime offsetDateTimeCorrectOffset = + getDateTimeWithCorrectOffset(optionalOffsetDateTime.get(), + timeZoneString); + builder.setEffectiveTimeFrame(offsetDateTimeCorrectOffset); } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java index e0b581ad..013f1c8a 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java @@ -20,7 +20,6 @@ import org.openmhealth.schema.domain.omh.DataPointModality; import org.openmhealth.schema.domain.omh.SchemaId; import org.openmhealth.shim.common.mapper.DataPointMapperUnitTests; -import org.testng.annotations.Test; import java.time.OffsetDateTime; @@ -46,10 +45,5 @@ protected void testDataPointHeader(DataPointHeader testHeader, SchemaId schemaId equalTo(updatedDateTime)); } - @Test - public void setEffectiveTimeFrameShouldUseUtcWhenTimeZoneIsMissing() { - - // Todo: waiting for response from iHealth on missing time zone representation - } } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java new file mode 100644 index 00000000..6bc2d659 --- /dev/null +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java @@ -0,0 +1,86 @@ +/* + * 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.ihealth.mapper; + +import com.fasterxml.jackson.databind.JsonNode; +import org.openmhealth.schema.domain.omh.HeartRate; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.time.OffsetDateTime; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsNull.notNullValue; + + +/** + * @author Chris Schaefbauer + */ +public class IHealthDatapointMapperDateTimeUnitTests extends IHealthDataPointMapperUnitTests { + + @Test + public void setEffectiveTimeFrameShouldNotAddTimeFrameWhenTimeZoneIsMissing() throws IOException { + + HeartRate.Builder builder = new HeartRate.Builder(45); + + JsonNode timeInfoNode = createResponseNodeWithTimeZone(null); + IHealthDataPointMapper.setEffectiveTimeFrameIfExists(timeInfoNode, builder); + + assertThat(builder.build().getEffectiveTimeFrame(), nullValue()); + } + + @Test + public void setEffectiveTimeFrameReturnsTimeFrameInUtcWhenTimeZoneEqualsZero() throws IOException { + + HeartRate.Builder builder = new HeartRate.Builder(45); + + JsonNode timeInfoNode = createResponseNodeWithTimeZone("0"); + + IHealthDataPointMapper.setEffectiveTimeFrameIfExists(timeInfoNode, builder); + + assertThat(builder.build().getEffectiveTimeFrame(), notNullValue()); + assertThat(builder.build().getEffectiveTimeFrame().getDateTime(), equalTo( + OffsetDateTime.parse("2015-11-17T18:24:23Z"))); + } + + @Test + public void setEffectiveTimeFrameShouldNotAddTimeFrameWhenTimeZoneIsEmpty() throws IOException{ + + HeartRate.Builder builder = new HeartRate.Builder(45); + + JsonNode timeInfoNode = createResponseNodeWithTimeZone("\"\""); + + IHealthDataPointMapper.setEffectiveTimeFrameIfExists(timeInfoNode, builder); + + assertThat(builder.build().getEffectiveTimeFrame(), nullValue()); + } + + public JsonNode createResponseNodeWithTimeZone(String timezoneString) throws IOException { + + if(timezoneString == null){ + return objectMapper.readTree("{\"MDate\": 1447784663,\n" + + " \"Steps\": 100}\n"); + } + else{ + return objectMapper.readTree("{\"MDate\": 1447784663,\n" + + " \"Steps\": 100,\n" + + "\"TimeZone\": "+timezoneString+"}\n"); + } + } +} From bfcf1703be139ceea0feff3f53d49cdca977fc4e Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Tue, 24 Nov 2015 13:42:54 -0700 Subject: [PATCH 48/68] Add tests for iHealth mapper time frame setting --- ...ealthDatapointMapperDateTimeUnitTests.java | 63 +++++++++++++++---- 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java index 6bc2d659..c369db7c 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.databind.JsonNode; import org.openmhealth.schema.domain.omh.HeartRate; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.io.IOException; @@ -34,41 +35,79 @@ */ public class IHealthDatapointMapperDateTimeUnitTests extends IHealthDataPointMapperUnitTests { + HeartRate.Builder builder; + + @BeforeMethod + public void initializeBuilder(){ + + builder = new HeartRate.Builder(45); + } + @Test public void setEffectiveTimeFrameShouldNotAddTimeFrameWhenTimeZoneIsMissing() throws IOException { - HeartRate.Builder builder = new HeartRate.Builder(45); - JsonNode timeInfoNode = createResponseNodeWithTimeZone(null); IHealthDataPointMapper.setEffectiveTimeFrameIfExists(timeInfoNode, builder); assertThat(builder.build().getEffectiveTimeFrame(), nullValue()); } + @Test + public void setEffectiveTimeFrameShouldNotAddTimeFrameWhenTimeZoneIsEmpty() throws IOException{ + + JsonNode timeInfoNode = createResponseNodeWithTimeZone("\"\""); + + IHealthDataPointMapper.setEffectiveTimeFrameIfExists(timeInfoNode, builder); + + assertThat(builder.build().getEffectiveTimeFrame(), nullValue()); + } + @Test public void setEffectiveTimeFrameReturnsTimeFrameInUtcWhenTimeZoneEqualsZero() throws IOException { - HeartRate.Builder builder = new HeartRate.Builder(45); + testTimeFrameWhenItShouldBeSetCorrectly("0", "2015-11-17T18:24:23Z"); + } - JsonNode timeInfoNode = createResponseNodeWithTimeZone("0"); + @Test + public void setEffectiveTimeFrameShouldAddCorrectTimeFrameWhenTimeZoneIsPositiveInteger() throws IOException { - IHealthDataPointMapper.setEffectiveTimeFrameIfExists(timeInfoNode, builder); + testTimeFrameWhenItShouldBeSetCorrectly("1", "2015-11-17T18:24:23+01:00"); + } - assertThat(builder.build().getEffectiveTimeFrame(), notNullValue()); - assertThat(builder.build().getEffectiveTimeFrame().getDateTime(), equalTo( - OffsetDateTime.parse("2015-11-17T18:24:23Z"))); + @Test + public void setEffectiveTimeFrameShouldAddCorrectTimeFrameWhenTimeZoneIsNegativeInteger() throws IOException { + + testTimeFrameWhenItShouldBeSetCorrectly("-8", "2015-11-17T18:24:23-08:00"); } @Test - public void setEffectiveTimeFrameShouldNotAddTimeFrameWhenTimeZoneIsEmpty() throws IOException{ + public void setEffectiveTimeFrameShouldAddCorrectTimeFrameWhenTimeZoneIsPositiveOffsetString() throws IOException { - HeartRate.Builder builder = new HeartRate.Builder(45); + testTimeFrameWhenItShouldBeSetCorrectly("\"+0100\"", "2015-11-17T18:24:23+01:00"); + } - JsonNode timeInfoNode = createResponseNodeWithTimeZone("\"\""); + @Test + public void setEffectiveTimeFrameShouldAddCorrectTimeFrameWhenTimeZoneIsNegativeOffsetString() throws IOException { + + testTimeFrameWhenItShouldBeSetCorrectly("\"-0700\"", "2015-11-17T18:24:23-07:00"); + } + + @Test + public void setEffectiveTimeFrameShouldAddTimeInUtcWhenTimeZoneIsZeroOffsetString() throws IOException { + + testTimeFrameWhenItShouldBeSetCorrectly("\"+0000\"", "2015-11-17T18:24:23Z"); + } + + public void testTimeFrameWhenItShouldBeSetCorrectly(String timezoneString, String expectedDateTime) + throws IOException { + + JsonNode timeInfoNode = createResponseNodeWithTimeZone(timezoneString); IHealthDataPointMapper.setEffectiveTimeFrameIfExists(timeInfoNode, builder); - assertThat(builder.build().getEffectiveTimeFrame(), nullValue()); + assertThat(builder.build().getEffectiveTimeFrame(), notNullValue()); + assertThat(builder.build().getEffectiveTimeFrame().getDateTime(), equalTo( + OffsetDateTime.parse(expectedDateTime))); } public JsonNode createResponseNodeWithTimeZone(String timezoneString) throws IOException { From d19e66be4ce6a5e05fba40d616955937b24ab716 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Wed, 25 Nov 2015 09:31:41 -0700 Subject: [PATCH 49/68] Add iHealth step count mapper and tests --- .../IHealthBloodPressureDataPointMapper.java | 5 +- .../IHealthBodyWeightDataPointMapper.java | 5 +- .../mapper/IHealthDataPointMapper.java | 9 ++- ...HealthPhysicalActivityDataPointMapper.java | 4 +- .../IHealthStepCountDataPointMapper.java | 79 +++++++++++++++++++ .../mapper/DataPointMapperUnitTests.java | 27 +++++++ ...althStepCountDataPointMapperUnitTests.java | 77 ++++++++++++++++++ .../mapper/ihealth-activity-no-steps.json | 24 ++++++ .../shim/ihealth/mapper/ihealth-activity.json | 37 +++++++++ 9 files changed, 259 insertions(+), 8 deletions(-) create mode 100644 shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java create mode 100644 shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java create mode 100644 shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-activity-no-steps.json create mode 100644 shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-activity.json diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java index 0f2c51a7..491e0699 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java @@ -21,6 +21,7 @@ import java.util.Optional; +import static com.google.common.base.Preconditions.checkNotNull; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredDouble; @@ -29,7 +30,7 @@ */ public class IHealthBloodPressureDataPointMapper extends IHealthDataPointMapper { - // documentation: http://www.ncbi.nlm.nih.gov/pmc/articles/PMC1603212/ + // Reference for conversion: http://www.ncbi.nlm.nih.gov/pmc/articles/PMC1603212/ static final double KPA_TO_MMHG_CONVERSION_RATE = 7.50; static final int MMHG_UNIT_MAGIC_NUMBER = 0; @@ -48,6 +49,8 @@ protected Optional getMeasureUnitNodeName() { @Override protected Optional> asDataPoint(JsonNode listNode, Integer bloodPressureUnit) { + checkNotNull(bloodPressureUnit); + double systolicValue = getBloodPressureValueInMmHg(asRequiredDouble(listNode, "HP"), bloodPressureUnit); SystolicBloodPressure systolicBloodPressure = new SystolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, systolicValue); diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java index a1b54c6e..46b82893 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java @@ -24,6 +24,7 @@ import java.util.Optional; +import static com.google.common.base.Preconditions.checkNotNull; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredDouble; @@ -32,7 +33,7 @@ */ public class IHealthBodyWeightDataPointMapper extends IHealthDataPointMapper { - // Reference: https://en.wikipedia.org/wiki/Stone_(unit) + // Reference for conversion: https://en.wikipedia.org/wiki/Stone_(unit) private static final double STONE_TO_KG_FACTOR = 6.3503; @Override @@ -48,6 +49,8 @@ protected Optional getMeasureUnitNodeName() { @Override protected Optional> asDataPoint(JsonNode listNode, Integer measureUnitMagicNumber) { + checkNotNull(measureUnitMagicNumber); + IHealthBodyWeightUnit bodyWeightUnitType = IHealthBodyWeightUnit.fromIntegerValue(measureUnitMagicNumber); MassUnit bodyWeightUnit = bodyWeightUnitType.getOmhUnit(); diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java index f7335a0e..7cf1b5e0 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java @@ -35,6 +35,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; +import static org.openmhealth.schema.domain.omh.Measure.*; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*; @@ -110,7 +111,7 @@ protected DataPointHeader createDataPointHeader(JsonNode listNode, Measure measu } - protected static void setEffectiveTimeFrameIfExists(JsonNode listNode, Measure.Builder builder) { + protected static void setEffectiveTimeFrameIfExists(JsonNode listNode, Builder builder) { Optional optionalOffsetDateTime = asOptionalLong(listNode, "MDate"); @@ -132,8 +133,8 @@ else if (asOptionalLong(listNode, "TimeZone").isPresent()) { String timeZoneString = timeZoneOffsetValue.toString(); - if(timeZoneOffsetValue>=0){ - timeZoneString = "+"+timeZoneOffsetValue.toString(); + if (timeZoneOffsetValue >= 0) { + timeZoneString = "+" + timeZoneOffsetValue.toString(); } OffsetDateTime offsetDateTimeCorrectOffset = @@ -163,7 +164,7 @@ protected static OffsetDateTime getDateTimeWithCorrectOffset(Long dateTimeInUnix return offsetDateTimeFromOffsetInstant.toLocalDateTime().atOffset(ZoneOffset.of(timeZoneString)); } - protected static void setUserNoteIfExists(JsonNode listNode, Measure.Builder builder) { + protected static void setUserNoteIfExists(JsonNode listNode, Builder builder) { Optional note = asOptionalString(listNode, "Note"); diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java index ae71d9a7..e8c94ac4 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java @@ -62,8 +62,8 @@ protected Optional> asDataPoint(JsonNode listNode, I Integer timeZoneOffsetValue = timeZoneOffset.get(); String timeZoneString = timeZoneOffsetValue.toString(); - if(timeZoneOffsetValue>=0){ - timeZoneString = "+"+timeZoneOffsetValue.toString(); + if (timeZoneOffsetValue >= 0) { + timeZoneString = "+" + timeZoneOffsetValue.toString(); } physicalActivityBuilder.setEffectiveTimeFrame(TimeInterval.ofStartDateTimeAndEndDateTime( diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java new file mode 100644 index 00000000..4ca17346 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java @@ -0,0 +1,79 @@ +/* + * 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.ihealth.mapper; + +import com.fasterxml.jackson.databind.JsonNode; +import org.openmhealth.schema.domain.omh.*; + +import java.time.*; +import java.util.Optional; + +import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*; + + +/** + * @author Chris Schaefbauer + */ +public class IHealthStepCountDataPointMapper extends IHealthDataPointMapper { + + @Override + protected String getListNodeName() { + return "ARDataList"; + } + + @Override + protected Optional getMeasureUnitNodeName() { + return Optional.empty(); + } + + @Override + protected Optional> asDataPoint(JsonNode listEntryNode, Integer measureUnitMagicNumber) { + + Double steps = asRequiredDouble(listEntryNode, "Steps"); + + if (steps == 0) { + return Optional.empty(); + } + + StepCount.Builder stepCountBuilder = new StepCount.Builder(steps); + + Optional dateTimeString = asOptionalLong(listEntryNode, "MDate"); + + if (dateTimeString.isPresent()) { + + Optional timeZone = asOptionalString(listEntryNode, "TimeZone"); + + if (timeZone.isPresent()) { + + OffsetDateTime startDateTime = + OffsetDateTime.ofInstant(Instant.ofEpochSecond(dateTimeString.get()), ZoneId.of("Z")) + .toLocalDate().atStartOfDay().atOffset(ZoneOffset.of(timeZone.get())); + + stepCountBuilder.setEffectiveTimeFrame(TimeInterval.ofStartDateTimeAndDuration(startDateTime, + new DurationUnitValue(DurationUnit.DAY, 1))); + } + } + + setUserNoteIfExists(listEntryNode, stepCountBuilder); + + StepCount stepCount = stepCountBuilder.build(); + + return Optional.of(new DataPoint<>(createDataPointHeader(listEntryNode, stepCount), stepCount)); + } + + +} diff --git a/shim-server/src/test/java/org/openmhealth/shim/common/mapper/DataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/common/mapper/DataPointMapperUnitTests.java index 83c78005..171cf8c8 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/common/mapper/DataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/common/mapper/DataPointMapperUnitTests.java @@ -1,7 +1,13 @@ package org.openmhealth.shim.common.mapper; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.core.io.ClassPathResource; +import java.io.IOException; +import java.io.InputStream; + +import static java.lang.String.format; import static org.openmhealth.schema.configuration.JacksonConfiguration.newObjectMapper; @@ -11,4 +17,25 @@ public abstract class DataPointMapperUnitTests { protected static final ObjectMapper objectMapper = newObjectMapper(); + + + /** + * @param classPathResourceName the name of the class path resource to load + * @return the contents of the resource as a {@link JsonNode} + * @throws RuntimeException if the resource can't be loaded + */ + protected JsonNode asJsonNode(String classPathResourceName) { + + ClassPathResource resource = new ClassPathResource(classPathResourceName); + + try { + InputStream resourceInputStream = resource.getInputStream(); + return objectMapper.readTree(resourceInputStream); + } + catch (IOException e) { + throw new RuntimeException( + format("The class path resource '%s' can't be loaded as a JSON node.", classPathResourceName), e); + } + } + } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java new file mode 100644 index 00000000..1653a378 --- /dev/null +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java @@ -0,0 +1,77 @@ +/* + * 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.ihealth.mapper; + +import com.fasterxml.jackson.databind.JsonNode; +import org.openmhealth.schema.domain.omh.*; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.time.OffsetDateTime; +import java.util.List; + +import static java.util.Collections.singletonList; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.core.Is.is; + + +/** + * @author Chris Schaefbauer + */ +public class IHealthStepCountDataPointMapperUnitTests extends IHealthDataPointMapperUnitTests { + + private JsonNode responseNode; + private IHealthStepCountDataPointMapper mapper = new IHealthStepCountDataPointMapper(); + + @BeforeClass + public void initializeResponseNode() { + + responseNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-activity.json"); + } + + @Test + public void asDataPointsShouldNotMapDataPointsWithZeroSteps() { + + JsonNode nodeWithNoSteps = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-activity-no-steps.json"); + List> dataPoints = mapper.asDataPoints(singletonList(nodeWithNoSteps)); + assertThat(dataPoints, is(empty())); + } + + @Test + public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { + + assertThat(mapper.asDataPoints(singletonList(responseNode)).size(), equalTo(2)); + } + + @Test + public void asDataPointsShouldReturnCorrectDeviceGeneratedDataPoints() { + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + + + StepCount.Builder expectedStepCountBuilder = new StepCount.Builder(21); + + expectedStepCountBuilder.setEffectiveTimeFrame( + TimeInterval.ofStartDateTimeAndDuration(OffsetDateTime.parse("2015-11-16T00:00:00+05:00"), + new DurationUnitValue(DurationUnit.DAY, 1))); + + StepCount stepCount = expectedStepCountBuilder.build(); + assertThat(dataPoints.get(0).getBody(), equalTo(stepCount)); + } +} diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-activity-no-steps.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-activity-no-steps.json new file mode 100644 index 00000000..820e6a1b --- /dev/null +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-activity-no-steps.json @@ -0,0 +1,24 @@ +{ + "ARDataList": [ + { + "Calories": 1963, + "DataID": "de97222d4069d922b2ee8965d491c2e4", + "DataSource": "Manual", + "DistanceTraveled": 0, + "LastChangeTime": 1447826586, + "Lat": 0, + "Lon": 0, + "MDate": 1447800900, + "Note": "", + "Steps": 0, + "TimeZone": "-0700" + } + ], + "CurrentRecordCount": 1, + "DistanceUnit": 0, + "NextPageUrl": "", + "PageLength": 50, + "PageNumber": 1, + "PrevPageUrl": "", + "RecordCount": 1 +} \ No newline at end of file diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-activity.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-activity.json new file mode 100644 index 00000000..dc056e80 --- /dev/null +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-activity.json @@ -0,0 +1,37 @@ +{ + "ARDataList": [ + { + "Calories": 1571, + "DataID": "ac67c4ccf64af669d92569af85d19f59", + "DataSource": "FromDevice", + "DistanceTraveled": 0.01743, + "LastChangeTime": 1447788201, + "Lat": 0, + "Lon": 0, + "MDate": 1447698000, + "Note": "", + "Steps": 21, + "TimeZone": "+0500" + }, + { + "Calories": 713, + "DataID": "a1c7ec7c4d75cf1fdb15e0d8acc4f83c", + "DataSource": "Manual", + "DistanceTraveled": 0, + "LastChangeTime": 1447827935, + "Lat": 0, + "Lon": 0, + "MDate": 1447831200, + "Note": "Great steps", + "Steps": 4398, + "TimeZone": "+0000" + } + ], + "CurrentRecordCount": 2, + "DistanceUnit": 0, + "NextPageUrl": "", + "PageLength": 50, + "PageNumber": 1, + "PrevPageUrl": "", + "RecordCount": 2 +} \ No newline at end of file From 349f8770556802623bf83aad4ed928c40f109d5e Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Wed, 25 Nov 2015 09:40:44 -0700 Subject: [PATCH 50/68] Fix iHealth mapper test --- .../mapper/IHealthStepCountDataPointMapperUnitTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java index 1653a378..1b820753 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java @@ -65,7 +65,7 @@ public void asDataPointsShouldReturnCorrectDeviceGeneratedDataPoints() { List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - StepCount.Builder expectedStepCountBuilder = new StepCount.Builder(21); + StepCount.Builder expectedStepCountBuilder = new StepCount.Builder(21.0); expectedStepCountBuilder.setEffectiveTimeFrame( TimeInterval.ofStartDateTimeAndDuration(OffsetDateTime.parse("2015-11-16T00:00:00+05:00"), From e51cac6a5b5a2c49baf36fc9b566ca20d8f3c46a Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Wed, 25 Nov 2015 09:45:40 -0700 Subject: [PATCH 51/68] Use BigDecimal for iHealth step count mapping --- .../shim/ihealth/mapper/IHealthStepCountDataPointMapper.java | 5 +++-- .../mapper/IHealthStepCountDataPointMapperUnitTests.java | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java index 4ca17346..72734ede 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode; import org.openmhealth.schema.domain.omh.*; +import java.math.BigDecimal; import java.time.*; import java.util.Optional; @@ -43,9 +44,9 @@ protected Optional getMeasureUnitNodeName() { @Override protected Optional> asDataPoint(JsonNode listEntryNode, Integer measureUnitMagicNumber) { - Double steps = asRequiredDouble(listEntryNode, "Steps"); + BigDecimal steps = asRequiredBigDecimal(listEntryNode, "Steps"); - if (steps == 0) { + if (steps.intValue() == 0) { return Optional.empty(); } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java index 1b820753..1653a378 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java @@ -65,7 +65,7 @@ public void asDataPointsShouldReturnCorrectDeviceGeneratedDataPoints() { List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - StepCount.Builder expectedStepCountBuilder = new StepCount.Builder(21.0); + StepCount.Builder expectedStepCountBuilder = new StepCount.Builder(21); expectedStepCountBuilder.setEffectiveTimeFrame( TimeInterval.ofStartDateTimeAndDuration(OffsetDateTime.parse("2015-11-16T00:00:00+05:00"), From 6bc7839cfc35e232c7ee4a00d522b4b7dd12d0db Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Wed, 25 Nov 2015 10:31:02 -0700 Subject: [PATCH 52/68] Extract iHealth time calculation into parent --- .../mapper/IHealthDataPointMapper.java | 13 +++++++- .../IHealthStepCountDataPointMapper.java | 8 ++--- ...ealthDatapointMapperDateTimeUnitTests.java | 32 ++++++++++++++++--- ...althStepCountDataPointMapperUnitTests.java | 29 +++++++++++++++-- 4 files changed, 67 insertions(+), 15 deletions(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java index 7cf1b5e0..d4762c5c 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java @@ -117,7 +117,6 @@ protected static void setEffectiveTimeFrameIfExists(JsonNode listNode, Builder b if (optionalOffsetDateTime.isPresent()) { - // Todo: Revisit after clarification from iHealth on how time zones are set Optional timeZone = asOptionalString(listNode, "TimeZone"); if (timeZone.isPresent() && !timeZone.get().isEmpty()) { @@ -164,6 +163,18 @@ protected static OffsetDateTime getDateTimeWithCorrectOffset(Long dateTimeInUnix return offsetDateTimeFromOffsetInstant.toLocalDateTime().atOffset(ZoneOffset.of(timeZoneString)); } + protected static OffsetDateTime getDateTimeAtStartOfDayWithCorrectOffset( + Long dateTimeInUnixSecondsWithLocalTimeOffset, String timeZoneString) { + + // Since the timestamps are in local time, we can use the local date time provided by rendering the timestamp + // in UTC, then translating that local time to the appropriate offset. + OffsetDateTime dateTimeFromOffsetInstant = + OffsetDateTime.ofInstant(Instant.ofEpochSecond(dateTimeInUnixSecondsWithLocalTimeOffset), + ZoneId.of("Z")); + + return dateTimeFromOffsetInstant.toLocalDate().atStartOfDay().atOffset(ZoneOffset.of(timeZoneString)); + } + protected static void setUserNoteIfExists(JsonNode listNode, Builder builder) { Optional note = asOptionalString(listNode, "Note"); diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java index 72734ede..7aa12f19 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java @@ -20,7 +20,6 @@ import org.openmhealth.schema.domain.omh.*; import java.math.BigDecimal; -import java.time.*; import java.util.Optional; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*; @@ -60,11 +59,8 @@ protected Optional> asDataPoint(JsonNode listEntryNode, Int if (timeZone.isPresent()) { - OffsetDateTime startDateTime = - OffsetDateTime.ofInstant(Instant.ofEpochSecond(dateTimeString.get()), ZoneId.of("Z")) - .toLocalDate().atStartOfDay().atOffset(ZoneOffset.of(timeZone.get())); - - stepCountBuilder.setEffectiveTimeFrame(TimeInterval.ofStartDateTimeAndDuration(startDateTime, + stepCountBuilder.setEffectiveTimeFrame(TimeInterval.ofStartDateTimeAndDuration( + getDateTimeAtStartOfDayWithCorrectOffset(dateTimeString.get(), timeZone.get()), new DurationUnitValue(DurationUnit.DAY, 1))); } } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java index c369db7c..12d2bbd2 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java @@ -38,7 +38,7 @@ public class IHealthDatapointMapperDateTimeUnitTests extends IHealthDataPointMap HeartRate.Builder builder; @BeforeMethod - public void initializeBuilder(){ + public void initializeBuilder() { builder = new HeartRate.Builder(45); } @@ -53,7 +53,7 @@ public void setEffectiveTimeFrameShouldNotAddTimeFrameWhenTimeZoneIsMissing() th } @Test - public void setEffectiveTimeFrameShouldNotAddTimeFrameWhenTimeZoneIsEmpty() throws IOException{ + public void setEffectiveTimeFrameShouldNotAddTimeFrameWhenTimeZoneIsEmpty() throws IOException { JsonNode timeInfoNode = createResponseNodeWithTimeZone("\"\""); @@ -99,6 +99,28 @@ public void setEffectiveTimeFrameShouldAddTimeInUtcWhenTimeZoneIsZeroOffsetStrin testTimeFrameWhenItShouldBeSetCorrectly("\"+0000\"", "2015-11-17T18:24:23Z"); } + @Test + public void getDateTimeAtStartOfDayWithCorrectOffsetShouldReturnCorrectDateTimeWhenTimeIsAtStartOfDay() { + + long startOfDayEpochSecond = OffsetDateTime.parse("2015-11-12T00:00:00Z").toEpochSecond(); + + OffsetDateTime dateTimeAtStartOfDay = + IHealthDataPointMapper.getDateTimeAtStartOfDayWithCorrectOffset(startOfDayEpochSecond, "-0100"); + + assertThat(dateTimeAtStartOfDay, equalTo(OffsetDateTime.parse("2015-11-12T00:00:00-01:00"))); + } + + @Test + public void getDateTimeAtStartOfDayWithCorrectOffsetShouldReturnCorrectDateTimeWhenTimeIsAtEndOfDay() { + + long startOfDayEpochSecond = OffsetDateTime.parse("2015-11-12T23:59:59Z").toEpochSecond(); + + OffsetDateTime dateTimeAtStartOfDay = + IHealthDataPointMapper.getDateTimeAtStartOfDayWithCorrectOffset(startOfDayEpochSecond, "+0100"); + + assertThat(dateTimeAtStartOfDay, equalTo(OffsetDateTime.parse("2015-11-12T00:00:00+01:00"))); + } + public void testTimeFrameWhenItShouldBeSetCorrectly(String timezoneString, String expectedDateTime) throws IOException { @@ -112,14 +134,14 @@ public void testTimeFrameWhenItShouldBeSetCorrectly(String timezoneString, Strin public JsonNode createResponseNodeWithTimeZone(String timezoneString) throws IOException { - if(timezoneString == null){ + if (timezoneString == null) { return objectMapper.readTree("{\"MDate\": 1447784663,\n" + " \"Steps\": 100}\n"); } - else{ + else { return objectMapper.readTree("{\"MDate\": 1447784663,\n" + " \"Steps\": 100,\n" + - "\"TimeZone\": "+timezoneString+"}\n"); + "\"TimeZone\": " + timezoneString + "}\n"); } } } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java index 1653a378..52c2c445 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java @@ -17,6 +17,7 @@ package org.openmhealth.shim.ihealth.mapper; import com.fasterxml.jackson.databind.JsonNode; +import org.hamcrest.Matchers; import org.openmhealth.schema.domain.omh.*; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -64,14 +65,36 @@ public void asDataPointsShouldReturnCorrectDeviceGeneratedDataPoints() { List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - StepCount.Builder expectedStepCountBuilder = new StepCount.Builder(21); expectedStepCountBuilder.setEffectiveTimeFrame( TimeInterval.ofStartDateTimeAndDuration(OffsetDateTime.parse("2015-11-16T00:00:00+05:00"), new DurationUnitValue(DurationUnit.DAY, 1))); - StepCount stepCount = expectedStepCountBuilder.build(); - assertThat(dataPoints.get(0).getBody(), equalTo(stepCount)); + assertThat(dataPoints.get(0).getBody(), equalTo(expectedStepCountBuilder.build())); + + testDataPointHeader(dataPoints.get(0).getHeader(), StepCount.SCHEMA_ID, DataPointModality.SENSED, + "ac67c4ccf64af669d92569af85d19f59", OffsetDateTime.parse("2015-11-17T19:23:21Z")); + } + + @Test + public void asDataPointsShouldReturnDataPointWithUserNoteWhenNoteIsPresent() { + + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + + StepCount.Builder expectedStepCountBuilder = new StepCount.Builder(4398); + + expectedStepCountBuilder.setEffectiveTimeFrame( + TimeInterval.ofStartDateTimeAndDuration(OffsetDateTime.parse("2015-11-18T00:00:00Z"), + new DurationUnitValue(DurationUnit.DAY, 1))).setUserNotes("Great steps"); + + assertThat(dataPoints.get(1).getBody(), Matchers.equalTo(expectedStepCountBuilder.build())); + } + + @Test + public void asDataPointsShouldReturnSensedDataPointWhenManuallyEntered() { + + assertThat(mapper.asDataPoints(singletonList(responseNode)).get(1).getHeader().getAcquisitionProvenance() + .getModality(), equalTo(DataPointModality.SELF_REPORTED)); } } From 1860d533817d4a11f59b6f92e9cc4ec790d1c945 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Wed, 25 Nov 2015 14:02:15 -0700 Subject: [PATCH 53/68] Add iHealth sleep duration mapper and tests --- .../IHealthSleepDurationDataPointMapper.java | 72 +++++++++++ .../IHealthStepCountDataPointMapper.java | 8 +- ...SleepDurationDataPointMapperUnitTests.java | 113 ++++++++++++++++++ ...althStepCountDataPointMapperUnitTests.java | 7 +- .../shim/ihealth/mapper/ihealth-sleep.json | 55 +++++++++ 5 files changed, 250 insertions(+), 5 deletions(-) create mode 100644 shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapper.java create mode 100644 shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapperUnitTests.java create mode 100644 shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sleep.json diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapper.java new file mode 100644 index 00000000..775932b4 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapper.java @@ -0,0 +1,72 @@ +/* + * 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.ihealth.mapper; + +import com.fasterxml.jackson.databind.JsonNode; +import org.openmhealth.schema.domain.omh.*; + +import java.util.Optional; + +import static org.openmhealth.schema.domain.omh.TimeInterval.*; +import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*; + + +/** + * @author Chris Schaefbauer + */ +public class IHealthSleepDurationDataPointMapper extends IHealthDataPointMapper { + + @Override + protected String getListNodeName() { + return "SRDataList"; + } + + @Override + protected Optional getMeasureUnitNodeName() { + return Optional.empty(); + } + + @Override + protected Optional> asDataPoint(JsonNode listEntryNode, Integer measureUnitMagicNumber) { + + SleepDuration.Builder sleepDurationBuilder = new SleepDuration.Builder( + new DurationUnitValue(DurationUnit.MINUTE, asRequiredBigDecimal(listEntryNode, "HoursSlept"))); + + Optional startTime = asOptionalLong(listEntryNode, "StartTime"); + Optional endTime = asOptionalLong(listEntryNode, "EndTime"); + + if (startTime.isPresent() && endTime.isPresent()) { + + Optional timeZone = asOptionalString(listEntryNode, "TimeZone"); + + if (timeZone.isPresent()) { + + sleepDurationBuilder.setEffectiveTimeFrame(ofStartDateTimeAndEndDateTime( + getDateTimeWithCorrectOffset(startTime.get(), timeZone.get()), + getDateTimeWithCorrectOffset(endTime.get(), timeZone.get()))); + } + } + + setUserNoteIfExists(listEntryNode, sleepDurationBuilder); + + SleepDuration sleepDuration = sleepDurationBuilder.build(); + asOptionalBigDecimal(listEntryNode, "Awaken") + .ifPresent(awaken -> sleepDuration.setAdditionalProperty("wakeup_count", awaken)); + + return Optional.of(new DataPoint<>(createDataPointHeader(listEntryNode, sleepDuration), sleepDuration)); + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java index 7aa12f19..f0db11b8 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java @@ -17,11 +17,15 @@ package org.openmhealth.shim.ihealth.mapper; import com.fasterxml.jackson.databind.JsonNode; -import org.openmhealth.schema.domain.omh.*; +import org.openmhealth.schema.domain.omh.DataPoint; +import org.openmhealth.schema.domain.omh.DurationUnit; +import org.openmhealth.schema.domain.omh.DurationUnitValue; +import org.openmhealth.schema.domain.omh.StepCount; import java.math.BigDecimal; import java.util.Optional; +import static org.openmhealth.schema.domain.omh.TimeInterval.ofStartDateTimeAndDuration; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*; @@ -59,7 +63,7 @@ protected Optional> asDataPoint(JsonNode listEntryNode, Int if (timeZone.isPresent()) { - stepCountBuilder.setEffectiveTimeFrame(TimeInterval.ofStartDateTimeAndDuration( + stepCountBuilder.setEffectiveTimeFrame(ofStartDateTimeAndDuration( getDateTimeAtStartOfDayWithCorrectOffset(dateTimeString.get(), timeZone.get()), new DurationUnitValue(DurationUnit.DAY, 1))); } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapperUnitTests.java new file mode 100644 index 00000000..d79e7504 --- /dev/null +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapperUnitTests.java @@ -0,0 +1,113 @@ +/* + * 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.ihealth.mapper; + +import com.fasterxml.jackson.databind.JsonNode; +import org.openmhealth.schema.domain.omh.DataPoint; +import org.openmhealth.schema.domain.omh.DurationUnitValue; +import org.openmhealth.schema.domain.omh.SleepDuration; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.math.BigDecimal; +import java.time.OffsetDateTime; +import java.util.List; + +import static java.util.Collections.singletonList; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; +import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; +import static org.openmhealth.schema.domain.omh.DurationUnit.MINUTE; +import static org.openmhealth.schema.domain.omh.TimeInterval.ofStartDateTimeAndEndDateTime; + + +/** + * @author Chris Schaefbauer + */ +public class IHealthSleepDurationDataPointMapperUnitTests extends IHealthDataPointMapperUnitTests { + + private JsonNode responseNode; + private IHealthSleepDurationDataPointMapper mapper = new IHealthSleepDurationDataPointMapper(); + private List> dataPoints; + + @BeforeClass + public void initializeResponse() { + + responseNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-sleep.json"); + } + + @BeforeMethod + public void initializeDataPoints() { + + dataPoints = mapper.asDataPoints(singletonList(responseNode)); + } + + @Test + public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { + + assertThat(dataPoints.size(), equalTo(3)); + } + + @Test + public void asDataPointsShouldReturnCorrectDataPointsWhenSensed() { + + SleepDuration.Builder expectedSleepDurationBuilder = new SleepDuration.Builder(new DurationUnitValue( + MINUTE, 345)); + + expectedSleepDurationBuilder.setEffectiveTimeFrame(ofStartDateTimeAndEndDateTime( + OffsetDateTime.parse("2015-11-15T01:51:00-07:00"), + OffsetDateTime.parse("2015-11-15T09:16:00-07:00"))); + + assertThat(dataPoints.get(0).getBody(), equalTo(expectedSleepDurationBuilder.build())); + + testDataPointHeader(dataPoints.get(0).getHeader(), SleepDuration.SCHEMA_ID, SENSED, + "7eb7292b90d710ae7b7f61b75f9425cf", OffsetDateTime.parse("2015-11-15T16:19:10Z")); + } + + @Test + public void asDataPointsShouldMapAwakenAsAdditionalProperty() { + + assertThat(((BigDecimal) dataPoints.get(0).getBody().getAdditionalProperties().get("wakeup_count")).intValue(), + equalTo(13)); + + assertThat(((BigDecimal) dataPoints.get(2).getBody().getAdditionalProperties().get("wakeup_count")).intValue(), + equalTo(0)); + } + + @Test + public void asDataPointsShouldReturnDataPointWithUserNoteWhenNoteIsPresent() { + + SleepDuration.Builder expectedSleepDurationBuilder = + new SleepDuration.Builder(new DurationUnitValue(MINUTE, 195)); + + expectedSleepDurationBuilder.setEffectiveTimeFrame(ofStartDateTimeAndEndDateTime( + OffsetDateTime.parse("2015-11-15T13:51:00+01:00"), + OffsetDateTime.parse("2015-11-15T17:16:00+01:00"))); + + expectedSleepDurationBuilder.setUserNotes("Best sleep ever"); + + assertThat(dataPoints.get(1).getBody(), equalTo(expectedSleepDurationBuilder.build())); + } + + @Test + public void asDataPointsShouldReturnCorrectDataPointsWhenManuallyEntered() { + + assertThat(dataPoints.get(2).getHeader().getAcquisitionProvenance().getModality(), equalTo(SELF_REPORTED)); + } +} diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java index 52c2c445..2468eec5 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java @@ -30,6 +30,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; import static org.hamcrest.core.Is.is; +import static org.openmhealth.schema.domain.omh.DataPointModality.*; /** @@ -61,7 +62,7 @@ public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { } @Test - public void asDataPointsShouldReturnCorrectDeviceGeneratedDataPoints() { + public void asDataPointsShouldReturnCorrectDataPointsWhenSensed() { List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); @@ -73,7 +74,7 @@ public void asDataPointsShouldReturnCorrectDeviceGeneratedDataPoints() { assertThat(dataPoints.get(0).getBody(), equalTo(expectedStepCountBuilder.build())); - testDataPointHeader(dataPoints.get(0).getHeader(), StepCount.SCHEMA_ID, DataPointModality.SENSED, + testDataPointHeader(dataPoints.get(0).getHeader(), StepCount.SCHEMA_ID, SENSED, "ac67c4ccf64af669d92569af85d19f59", OffsetDateTime.parse("2015-11-17T19:23:21Z")); } @@ -95,6 +96,6 @@ public void asDataPointsShouldReturnDataPointWithUserNoteWhenNoteIsPresent() { public void asDataPointsShouldReturnSensedDataPointWhenManuallyEntered() { assertThat(mapper.asDataPoints(singletonList(responseNode)).get(1).getHeader().getAcquisitionProvenance() - .getModality(), equalTo(DataPointModality.SELF_REPORTED)); + .getModality(), equalTo(SELF_REPORTED)); } } diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sleep.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sleep.json new file mode 100644 index 00000000..4a553fb7 --- /dev/null +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sleep.json @@ -0,0 +1,55 @@ +{ + "CurrentRecordCount": 3, + "NextPageUrl": "", + "PageLength": 50, + "PageNumber": 1, + "PrevPageUrl": "", + "RecordCount": 3, + "SRDataList": [ + { + "Awaken": 13, + "DataID": "7eb7292b90d710ae7b7f61b75f9425cf", + "DataSource": "FromDevice", + "EndTime": 1447578960, + "FallSleep": 0, + "HoursSlept": 345, + "LastChangeTime": 1447604350, + "Lat": 0, + "Lon": 0, + "Note": "", + "SleepEfficiency": 77, + "StartTime": 1447552260, + "TimeZone": "-0700" + }, + { + "Awaken": 2, + "DataID": "34aa1c5351f965dbb6a09f3c5b730e83", + "DataSource": "FromDevice", + "EndTime": 1447607760, + "FallSleep": 0, + "HoursSlept": 195, + "LastChangeTime": 1447689288, + "Lat": 0, + "Lon": 0, + "Note": "Best sleep ever", + "SleepEfficiency": 95, + "StartTime": 1447595460, + "TimeZone": "+0100" + }, + { + "Awaken": 0, + "DataID": "ed650c323ebd78e444430291675bd2fd", + "DataSource": "Manual", + "EndTime": 1447657920, + "FallSleep": 0, + "HoursSlept": 355, + "LastChangeTime": 1447689288, + "Lat": 0, + "Lon": 0, + "Note": "", + "SleepEfficiency": 87, + "StartTime": 1447633620, + "TimeZone": "-0700" + } + ] +} \ No newline at end of file From 22931704b3431f27ef4daa57ac6c09be660e0ad2 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Wed, 25 Nov 2015 15:07:58 -0700 Subject: [PATCH 54/68] Test empty list response for iHealth mappers --- ...hBloodGlucoseDataPointMapperUnitTests.java | 7 +++--- ...ointHeartRateDataPointMapperUnitTests.java | 11 +++++++++ ...BloodPressureDataPointMapperUnitTests.java | 11 +++++++-- ...BodyMassIndexDataPointMapperUnitTests.java | 2 +- ...lthBodyWeightDataPointMapperUnitTests.java | 3 +-- ...sicalActivityDataPointMapperUnitTests.java | 2 +- ...SleepDurationDataPointMapperUnitTests.java | 10 ++++++++ ...althStepCountDataPointMapperUnitTests.java | 8 +++++++ ...health-blood-glucose-missing-timezone.json | 24 ------------------- .../ihealth-empty-activity-response.json | 10 ++++++++ .../mapper/ihealth-empty-blood-oxygen.json | 9 +++++++ .../mapper/ihealth-empty-blood-pressure.json | 10 ++++++++ ...st.json => ihealth-empty-body-weight.json} | 0 ...y-list.json => ihealth-empty-glucose.json} | 0 .../ihealth/mapper/ihealth-empty-sleep.json | 9 +++++++ ...son => ihealth-empty-sports-activity.json} | 0 16 files changed, 83 insertions(+), 33 deletions(-) delete mode 100644 shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose-missing-timezone.json create mode 100644 shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-activity-response.json create mode 100644 shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-blood-oxygen.json create mode 100644 shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-blood-pressure.json rename shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/{ihealth-body-weight-empty-list.json => ihealth-empty-body-weight.json} (100%) rename shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/{ihealth-blood-glucose-empty-list.json => ihealth-empty-glucose.json} (100%) create mode 100644 shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-sleep.json rename shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/{ihealth-sports-activity-empty-list.json => ihealth-empty-sports-activity.json} (100%) diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java index fc95a240..72cd83df 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java @@ -28,7 +28,9 @@ import static java.util.Collections.singletonList; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; import static org.openmhealth.schema.domain.omh.BloodGlucose.SCHEMA_ID; import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; @@ -99,11 +101,10 @@ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { public void asDataPointsShouldReturnNoDataPointsWhenBloodGlucoseListIsEmpty() throws IOException { ClassPathResource resource = - new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose-empty-list.json"); + new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-empty-glucose.json"); JsonNode emptyListResponseNode = objectMapper.readTree(resource.getInputStream()); - List> dataPoints = mapper.asDataPoints(singletonList(emptyListResponseNode)); - assertThat(dataPoints.size(), equalTo(0)); + assertThat(mapper.asDataPoints(singletonList(emptyListResponseNode)), is(empty())); } @Test diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapperUnitTests.java index 690f1cdc..a01e03b4 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapperUnitTests.java @@ -29,8 +29,10 @@ import static java.util.Collections.singletonList; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; @@ -107,4 +109,13 @@ public void asDataPointsShouldReturnNoDataPointWhenHeartRateDataIsNotPresent() t assertThat(dataPoints.size(), equalTo(0)); } + + @Test + public void asDataPointsShouldReturnEmptyListWhenEmptyIHealthResponse() { + + JsonNode emptyNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-empty-blood-oxygen.json"); + + assertThat(mapper.asDataPoints(singletonList(emptyNode)), is(empty())); + + } } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java index 45844e0d..81e4f8c0 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java @@ -28,8 +28,10 @@ import static java.util.Collections.singletonList; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; @@ -50,8 +52,6 @@ public void initializeResponseNode() throws IOException { responseNode = objectMapper.readTree(resource.getInputStream()); } - // TODO: Test/handle datapoints that have zero values for BP (awaiting response from iHealth) - @Test public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { @@ -108,6 +108,13 @@ public void asDataPointsShouldReturnCorrectUserNotesWithDataPoints() { assertThat(dataPoints.get(1).getBody().getUserNotes(), equalTo("BP on the up and up.")); } + @Test + public void asDataPointsShouldReturnEmptyListWhenEmptyIHealthResponse() { + + JsonNode emptyNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-empty-blood-pressure.json"); + + assertThat(mapper.asDataPoints(singletonList(emptyNode)), is(empty())); + } @Test public void getBloodPressureValueInMmHgReturnsCorrectValueForMmHgUnit() { diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java index 7238afef..2e097b63 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java @@ -102,7 +102,7 @@ public void asDataPointsShouldReturnNoDataPointsWhenBodyMassIndexValueIsZero() t public void asDataPointsShouldReturnNoDataPointsWhenWeightListIsEmpty() throws IOException { ClassPathResource resource = - new ClassPathResource("org/openmhealth/shim/ihealth/mapper/ihealth-body-weight-empty-list.json"); + new ClassPathResource("org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight.json"); JsonNode emptyListNode = objectMapper.readTree(resource.getInputStream()); List> dataPoints = mapper.asDataPoints(singletonList(emptyListNode)); diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java index fdcc7b2c..c76b06f3 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java @@ -73,7 +73,6 @@ public void asDataPointsShouldReturnCorrectSensedDataPoints() { DataPointHeader dataPointHeader = dataPoints.get(0).getHeader(); testDataPointHeader(dataPointHeader, BodyWeight.SCHEMA_ID, SENSED, "5fe5893c418b48cd8da7954f8b6c2f36", OffsetDateTime.parse("2015-09-17T20:04:17Z")); - } @Test @@ -117,7 +116,7 @@ public void asDataPointsShouldReturnNoDataPointsWhenWeightValueEqualsZero() thro public void asDataPointsShouldReturnNoDataPointsWhenWeightListIsEmpty() throws IOException { ClassPathResource resource = - new ClassPathResource("org/openmhealth/shim/ihealth/mapper/ihealth-body-weight-empty-list.json"); + new ClassPathResource("org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight.json"); JsonNode emptyListNode = objectMapper.readTree(resource.getInputStream()); List> dataPoints = mapper.asDataPoints(singletonList(emptyListNode)); diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java index 9378b297..928d7774 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java @@ -97,7 +97,7 @@ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { public void asDataPointsReturnsNoDataPointsForAnEmptyList() throws IOException { ClassPathResource resource = - new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity-empty-list.json"); + new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-empty-sports-activity.json"); JsonNode emptyListResponseNode = objectMapper.readTree(resource.getInputStream()); List> dataPoints = mapper.asDataPoints(singletonList(emptyListResponseNode)); diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapperUnitTests.java index d79e7504..5e5f628b 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapperUnitTests.java @@ -30,7 +30,9 @@ import static java.util.Collections.singletonList; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; import static org.openmhealth.schema.domain.omh.DurationUnit.MINUTE; @@ -110,4 +112,12 @@ public void asDataPointsShouldReturnCorrectDataPointsWhenManuallyEntered() { assertThat(dataPoints.get(2).getHeader().getAcquisitionProvenance().getModality(), equalTo(SELF_REPORTED)); } + + @Test + public void asDataPointsShouldReturnEmptyListWhenEmptyIHealthResponse() { + + JsonNode emptyNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-empty-sleep.json"); + + assertThat(mapper.asDataPoints(singletonList(emptyNode)), is(empty())); + } } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java index 2468eec5..5f1a6aee 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java @@ -98,4 +98,12 @@ public void asDataPointsShouldReturnSensedDataPointWhenManuallyEntered() { assertThat(mapper.asDataPoints(singletonList(responseNode)).get(1).getHeader().getAcquisitionProvenance() .getModality(), equalTo(SELF_REPORTED)); } + + @Test + public void asDataPointsShouldReturnEmptyListWhenEmptyIHealthResponse() { + + JsonNode emptyNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-empty-activity-response.json"); + + assertThat(mapper.asDataPoints(singletonList(emptyNode)), is(empty())); + } } diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose-missing-timezone.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose-missing-timezone.json deleted file mode 100644 index e0e7d177..00000000 --- a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose-missing-timezone.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "BGDataList": [ - { - "BG": 60, - "DataID": "f706b6152f684c0e9185b1fa6b7c5148", - "DataSource": "FromDevice", - "DinnerSituation": "Before_breakfast", - "DrugSituation": "Before_taking_pills", - "LastChangeTime": 1442520221, - "Lat": -1, - "Lon": -1, - "MDate": 1442491407, - "Note": "Such glucose, much blood.", - "TimeZone": "" - } - ], - "BGUnit": 0, - "CurrentRecordCount": 3, - "NextPageUrl": "", - "PageLength": 50, - "PageNumber": 1, - "PrevPageUrl": "", - "RecordCount": 3 -} \ No newline at end of file diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-activity-response.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-activity-response.json new file mode 100644 index 00000000..eabb67c7 --- /dev/null +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-activity-response.json @@ -0,0 +1,10 @@ +{ + "ARDataList": [], + "CurrentRecordCount": 0, + "DistanceUnit": 0, + "NextPageUrl": "", + "PageLength": 50, + "PageNumber": 1, + "PrevPageUrl": "", + "RecordCount": 0 +} \ No newline at end of file diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-blood-oxygen.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-blood-oxygen.json new file mode 100644 index 00000000..895db9cb --- /dev/null +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-blood-oxygen.json @@ -0,0 +1,9 @@ +{ + "BODataList": [], + "CurrentRecordCount": 0, + "NextPageUrl": "", + "PageLength": 50, + "PageNumber": 1, + "PrevPageUrl": "", + "RecordCount": 0 +} \ No newline at end of file diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-blood-pressure.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-blood-pressure.json new file mode 100644 index 00000000..138bb791 --- /dev/null +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-blood-pressure.json @@ -0,0 +1,10 @@ +{ + "BPDataList": [], + "BPUnit": 0, + "CurrentRecordCount": 0, + "NextPageUrl": "", + "PageLength": 50, + "PageNumber": 1, + "PrevPageUrl": "", + "RecordCount": 0 +} \ No newline at end of file diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-body-weight-empty-list.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight.json similarity index 100% rename from shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-body-weight-empty-list.json rename to shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight.json diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose-empty-list.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-glucose.json similarity index 100% rename from shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose-empty-list.json rename to shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-glucose.json diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-sleep.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-sleep.json new file mode 100644 index 00000000..9b372102 --- /dev/null +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-sleep.json @@ -0,0 +1,9 @@ +{ + "CurrentRecordCount": 0, + "NextPageUrl": "", + "PageLength": 50, + "PageNumber": 1, + "PrevPageUrl": "", + "RecordCount": 0, + "SRDataList": [] +} \ No newline at end of file diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity-empty-list.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-sports-activity.json similarity index 100% rename from shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity-empty-list.json rename to shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-sports-activity.json From c9be60f99d99dd008f31fcccc672fd1dac4b1eb8 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Wed, 25 Nov 2015 18:08:00 -0700 Subject: [PATCH 55/68] Clean-up iHealth mapper unit tests --- ...hBloodGlucoseDataPointMapperUnitTests.java | 17 +++++----- ...ointHeartRateDataPointMapperUnitTests.java | 33 ++++++++----------- ...BloodPressureDataPointMapperUnitTests.java | 24 ++++++-------- ...ointHeartRateDataPointMapperUnitTests.java | 2 ++ ...BodyMassIndexDataPointMapperUnitTests.java | 17 +++++----- ...lthBodyWeightDataPointMapperUnitTests.java | 29 +++++++--------- .../IHealthDataPointMapperUnitTests.java | 2 +- ...sicalActivityDataPointMapperUnitTests.java | 25 +++++++------- ...althStepCountDataPointMapperUnitTests.java | 17 ++++++---- 9 files changed, 80 insertions(+), 86 deletions(-) diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java index 72cd83df..a6b66f12 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode; import org.openmhealth.schema.domain.omh.*; import org.springframework.core.io.ClassPathResource; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; @@ -43,27 +44,29 @@ public class IHealthBloodGlucoseDataPointMapperUnitTests extends IHealthDataPoin private JsonNode responseNode; private IHealthBloodGlucoseDataPointMapper mapper = new IHealthBloodGlucoseDataPointMapper(); + List> dataPoints; @BeforeTest public void initializeResponseNode() throws IOException { - ClassPathResource resource = - new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose.json"); - responseNode = objectMapper.readTree(resource.getInputStream()); + responseNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose.json"); + } + + @BeforeMethod + public void initializeDataPoints() { + + dataPoints = mapper.asDataPoints(singletonList(responseNode)); } @Test public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); assertThat(dataPoints.size(), equalTo(2)); } @Test public void asDataPointsShouldReturnCorrectSensedDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - BloodGlucose.Builder expectedBloodGlucoseBuilder = new BloodGlucose.Builder( new TypedUnitValue<>(BloodGlucoseUnit.MILLIGRAMS_PER_DECILITER, 60)) .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T12:03:27-08:00")) @@ -82,8 +85,6 @@ public void asDataPointsShouldReturnCorrectSensedDataPoints() { @Test public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - BloodGlucose.Builder expectedBloodGlucoseBuilder = new BloodGlucose.Builder(new TypedUnitValue<>(BloodGlucoseUnit.MILLIGRAMS_PER_DECILITER, 70)) .setTemporalRelationshipToMeal(TemporalRelationshipToMeal.AFTER_BREAKFAST) diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapperUnitTests.java index a01e03b4..b8a4588f 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapperUnitTests.java @@ -19,7 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode; import org.openmhealth.schema.domain.omh.DataPoint; import org.openmhealth.schema.domain.omh.HeartRate; -import org.springframework.core.io.ClassPathResource; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; @@ -28,9 +28,7 @@ import java.util.List; import static java.util.Collections.singletonList; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; @@ -43,29 +41,33 @@ public class IHealthBloodOxygenEndpointHeartRateDataPointMapperUnitTests extends IHealthDataPointMapperUnitTests { JsonNode responseNode; + private IHealthBloodOxygenEndpointHeartRateDataPointMapper mapper = new IHealthBloodOxygenEndpointHeartRateDataPointMapper(); + List> dataPoints; + @BeforeTest public void initializeResponseNode() throws IOException { - ClassPathResource resource = - new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-blood-oxygen.json"); - responseNode = objectMapper.readTree(resource.getInputStream()); + responseNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-blood-oxygen.json"); + } + + @BeforeMethod + public void initializeDataPoints() { + + dataPoints = mapper.asDataPoints(singletonList(responseNode)); } @Test public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); assertThat(dataPoints.size(), equalTo(2)); } @Test public void asDataPointsShouldReturnCorrectSensedDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - HeartRate.Builder expectedHeartRateBuilder = new HeartRate.Builder(80) .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-23T15:46:00-06:00")); @@ -78,8 +80,6 @@ public void asDataPointsShouldReturnCorrectSensedDataPoints() { @Test public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - HeartRate.Builder expectedHeartRateBuilder = new HeartRate.Builder(65) .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-24T15:03:00-06:00")) .setUserNotes("Satch on satch "); @@ -92,8 +92,6 @@ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { @Test public void asDataPointsShouldReturnCorrectUserNotesWithDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - assertThat(dataPoints.get(0).getBody().getUserNotes(), nullValue()); assertThat(dataPoints.get(1).getBody().getUserNotes(), equalTo("Satch on satch ")); } @@ -101,13 +99,10 @@ public void asDataPointsShouldReturnCorrectUserNotesWithDataPoints() { @Test public void asDataPointsShouldReturnNoDataPointWhenHeartRateDataIsNotPresent() throws IOException { - ClassPathResource resource = new ClassPathResource( + JsonNode noHeartRateBloodOxygenNode = asJsonNode( "org/openmhealth/shim/ihealth/mapper/ihealth-blood-oxygen-missing-heart-rate.json"); - JsonNode noHeartRateBloodOxygenNode = objectMapper.readTree(resource.getInputStream()); - - List> dataPoints = mapper.asDataPoints(singletonList(noHeartRateBloodOxygenNode)); - assertThat(dataPoints.size(), equalTo(0)); + assertThat(mapper.asDataPoints(singletonList(noHeartRateBloodOxygenNode)), is(empty())); } @Test diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java index 81e4f8c0..d53cbf3e 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java @@ -18,7 +18,7 @@ import com.fasterxml.jackson.databind.JsonNode; import org.openmhealth.schema.domain.omh.*; -import org.springframework.core.io.ClassPathResource; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; @@ -27,9 +27,7 @@ import java.util.List; import static java.util.Collections.singletonList; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; @@ -43,27 +41,29 @@ public class IHealthBloodPressureDataPointMapperUnitTests extends IHealthDataPoi private JsonNode responseNode; private IHealthBloodPressureDataPointMapper mapper = new IHealthBloodPressureDataPointMapper(); + List> dataPoints; @BeforeTest public void initializeResponseNode() throws IOException { - ClassPathResource resource = - new ClassPathResource("org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json"); - responseNode = objectMapper.readTree(resource.getInputStream()); + responseNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json"); + } + + @BeforeMethod + public void initializeDataPoints() { + + dataPoints = mapper.asDataPoints(singletonList(responseNode)); } @Test public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); assertThat(dataPoints.size(), equalTo(2)); } @Test public void asDataPointsShouldReturnCorrectSensedDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - BloodPressure expectedBloodPressure = new BloodPressure.Builder( new SystolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, 120), new DiastolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, 90)) @@ -82,8 +82,6 @@ public void asDataPointsShouldReturnCorrectSensedDataPoints() { @Test public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - BloodPressure expectedBloodPressure = new BloodPressure.Builder( new SystolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, 130), new DiastolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, 95)) @@ -102,8 +100,6 @@ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { @Test public void asDataPointsShouldReturnCorrectUserNotesWithDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - assertThat(dataPoints.get(0).getBody().getUserNotes(), nullValue()); assertThat(dataPoints.get(1).getBody().getUserNotes(), equalTo("BP on the up and up.")); } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java index ae221bd3..0a26d9e9 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java @@ -46,6 +46,8 @@ public class IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests exten private IHealthBloodPressureEndpointHeartRateDataPointMapper mapper = new IHealthBloodPressureEndpointHeartRateDataPointMapper(); + List> dataPoints; + @BeforeTest public void initializeResponseNodes() throws IOException { diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java index 2e097b63..eb234af5 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode; import org.openmhealth.schema.domain.omh.*; import org.springframework.core.io.ClassPathResource; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; @@ -40,27 +41,29 @@ public class IHealthBodyMassIndexDataPointMapperUnitTests extends IHealthDataPoi private JsonNode responseNode; private IHealthBodyMassIndexDataPointMapper mapper = new IHealthBodyMassIndexDataPointMapper(); + List> dataPoints; @BeforeTest public void initializeResponse() throws IOException { - ClassPathResource resource = - new ClassPathResource("org/openmhealth/shim/ihealth/mapper/ihealth-body-weight.json"); - responseNode = objectMapper.readTree(resource.getInputStream()); + responseNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-body-weight.json"); + } + + @BeforeMethod + public void initializeDataPoints() { + + dataPoints = mapper.asDataPoints(singletonList(responseNode)); } @Test public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); assertThat(dataPoints.size(), equalTo(2)); } @Test public void asDataPointsShouldReturnCorrectSensedDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - BodyMassIndex.Builder expectedBodyMassIndexBuilder = new BodyMassIndex.Builder(new TypedUnitValue<>( BodyMassIndexUnit.KILOGRAMS_PER_SQUARE_METER, 22.56052563257619)) .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T12:04:09-08:00")); @@ -74,8 +77,6 @@ public void asDataPointsShouldReturnCorrectSensedDataPoints() { @Test public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - BodyMassIndex.Builder expectedBodyMassIndexBuilder = new BodyMassIndex.Builder( new TypedUnitValue<>(BodyMassIndexUnit.KILOGRAMS_PER_SQUARE_METER, 22.56052398681641)) .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T14:07:57-06:00")) diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java index c76b06f3..c1525646 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java @@ -18,7 +18,7 @@ import com.fasterxml.jackson.databind.JsonNode; import org.openmhealth.schema.domain.omh.*; -import org.springframework.core.io.ClassPathResource; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; @@ -42,27 +42,29 @@ public class IHealthBodyWeightDataPointMapperUnitTests extends IHealthDataPointM protected JsonNode responseNode; IHealthBodyWeightDataPointMapper mapper = new IHealthBodyWeightDataPointMapper(); + List> dataPoints; @BeforeTest public void initializeResponseNode() throws IOException { - ClassPathResource resource = - new ClassPathResource("org/openmhealth/shim/ihealth/mapper/ihealth-body-weight.json"); - responseNode = objectMapper.readTree(resource.getInputStream()); + responseNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-body-weight.json"); + } + + @BeforeMethod + public void initializeDataPoints() { + + dataPoints = mapper.asDataPoints(singletonList(responseNode)); } @Test public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); assertThat(dataPoints.size(), equalTo(2)); } @Test public void asDataPointsShouldReturnCorrectSensedDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - BodyWeight.Builder expectedBodyWeightBuilder = new BodyWeight.Builder( new MassUnitValue(MassUnit.KILOGRAM, 77.5643875134944)) .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T12:04:09-08:00")); @@ -78,8 +80,6 @@ public void asDataPointsShouldReturnCorrectSensedDataPoints() { @Test public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - BodyWeight.Builder expectedBodyWeightBuilder = new BodyWeight.Builder(new MassUnitValue(MassUnit.KILOGRAM, 77.56438446044922)) .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T14:07:57-06:00")) @@ -95,8 +95,6 @@ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { @Test public void asDataPointsShouldReturnCorrectUserNotes() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - assertThat(dataPoints.get(0).getBody().getUserNotes(), nullValue()); assertThat(dataPoints.get(1).getBody().getUserNotes(), equalTo("Weight so good, look at me now")); } @@ -104,9 +102,8 @@ public void asDataPointsShouldReturnCorrectUserNotes() { @Test public void asDataPointsShouldReturnNoDataPointsWhenWeightValueEqualsZero() throws IOException { - ClassPathResource resource = - new ClassPathResource("org/openmhealth/shim/ihealth/mapper/ihealth-missing-body-weight-value.json"); - JsonNode zeroValueNode = objectMapper.readTree(resource.getInputStream()); + JsonNode zeroValueNode = + asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-missing-body-weight-value.json"); List> dataPoints = mapper.asDataPoints(singletonList(zeroValueNode)); assertThat(dataPoints.size(), equalTo(0)); @@ -115,9 +112,7 @@ public void asDataPointsShouldReturnNoDataPointsWhenWeightValueEqualsZero() thro @Test public void asDataPointsShouldReturnNoDataPointsWhenWeightListIsEmpty() throws IOException { - ClassPathResource resource = - new ClassPathResource("org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight.json"); - JsonNode emptyListNode = objectMapper.readTree(resource.getInputStream()); + JsonNode emptyListNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight.json"); List> dataPoints = mapper.asDataPoints(singletonList(emptyListNode)); assertThat(dataPoints.size(), equalTo(0)); diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java index 013f1c8a..16c0ca74 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java @@ -32,6 +32,7 @@ */ public class IHealthDataPointMapperUnitTests extends DataPointMapperUnitTests { + protected void testDataPointHeader(DataPointHeader testHeader, SchemaId schemaId, DataPointModality modality, String externalId, OffsetDateTime updatedDateTime) { @@ -45,5 +46,4 @@ protected void testDataPointHeader(DataPointHeader testHeader, SchemaId schemaId equalTo(updatedDateTime)); } - } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java index 928d7774..4b64c96c 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java @@ -20,7 +20,7 @@ import org.openmhealth.schema.domain.omh.DataPoint; import org.openmhealth.schema.domain.omh.PhysicalActivity; import org.openmhealth.schema.domain.omh.TimeInterval; -import org.springframework.core.io.ClassPathResource; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; @@ -44,32 +44,35 @@ public class IHealthPhysicalActivityDataPointMapperUnitTests extends IHealthData private JsonNode responseNode; private IHealthPhysicalActivityDataPointMapper mapper = new IHealthPhysicalActivityDataPointMapper(); + List> dataPoints; @BeforeTest public void initializeResponseNode() throws IOException { - ClassPathResource resource = - new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity.json"); - responseNode = objectMapper.readTree(resource.getInputStream()); + responseNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity.json"); + } + + @BeforeMethod + public void initializeDataPoints() { + + dataPoints = mapper.asDataPoints(singletonList(responseNode)); } @Test public void asDataPointsShouldReturnTheCorrectNumberOfDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); assertThat(dataPoints.size(), equalTo(2)); } @Test public void asDataPointsShouldReturnCorrectSensedDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - PhysicalActivity.Builder expectedPhysicalActivityBuilder = new PhysicalActivity.Builder("Swimming, breaststroke") .setEffectiveTimeFrame(TimeInterval.ofStartDateTimeAndEndDateTime( OffsetDateTime.parse("2015-09-17T20:02:28-08:00"), OffsetDateTime.parse("2015-09-17T20:32:28-08:00"))); + assertThat(dataPoints.get(0).getBody(), equalTo(expectedPhysicalActivityBuilder.build())); testDataPointHeader(dataPoints.get(0).getHeader(), SCHEMA_ID, SENSED, @@ -79,9 +82,6 @@ public void asDataPointsShouldReturnCorrectSensedDataPoints() { @Test public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - - PhysicalActivity.Builder expectedPhysicalActivityBuilder = new PhysicalActivity.Builder("Running") .setEffectiveTimeFrame( TimeInterval.ofStartDateTimeAndEndDateTime( @@ -96,9 +96,8 @@ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { @Test public void asDataPointsReturnsNoDataPointsForAnEmptyList() throws IOException { - ClassPathResource resource = - new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-empty-sports-activity.json"); - JsonNode emptyListResponseNode = objectMapper.readTree(resource.getInputStream()); + JsonNode emptyListResponseNode = + asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-empty-sports-activity.json"); List> dataPoints = mapper.asDataPoints(singletonList(emptyListResponseNode)); assertThat(dataPoints.size(), equalTo(0)); diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java index 5f1a6aee..52a14630 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java @@ -20,6 +20,7 @@ import org.hamcrest.Matchers; import org.openmhealth.schema.domain.omh.*; import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.time.OffsetDateTime; @@ -40,6 +41,8 @@ public class IHealthStepCountDataPointMapperUnitTests extends IHealthDataPointMa private JsonNode responseNode; private IHealthStepCountDataPointMapper mapper = new IHealthStepCountDataPointMapper(); + List> dataPoints; + @BeforeClass public void initializeResponseNode() { @@ -47,12 +50,18 @@ public void initializeResponseNode() { responseNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-activity.json"); } + @BeforeMethod + public void initializeDataPoints() { + + dataPoints = mapper.asDataPoints(singletonList(responseNode)); + } + @Test public void asDataPointsShouldNotMapDataPointsWithZeroSteps() { JsonNode nodeWithNoSteps = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-activity-no-steps.json"); - List> dataPoints = mapper.asDataPoints(singletonList(nodeWithNoSteps)); - assertThat(dataPoints, is(empty())); + + assertThat(mapper.asDataPoints(singletonList(nodeWithNoSteps)), is(empty())); } @Test @@ -64,8 +73,6 @@ public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { @Test public void asDataPointsShouldReturnCorrectDataPointsWhenSensed() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - StepCount.Builder expectedStepCountBuilder = new StepCount.Builder(21); expectedStepCountBuilder.setEffectiveTimeFrame( @@ -81,8 +88,6 @@ public void asDataPointsShouldReturnCorrectDataPointsWhenSensed() { @Test public void asDataPointsShouldReturnDataPointWithUserNoteWhenNoteIsPresent() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - StepCount.Builder expectedStepCountBuilder = new StepCount.Builder(4398); expectedStepCountBuilder.setEffectiveTimeFrame( From 245aa5f85a5b78e2bfe7278de916a1b7d42c61b1 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Wed, 25 Nov 2015 19:44:49 -0700 Subject: [PATCH 56/68] Clean-up iHealth mapper unit tests --- ...ointHeartRateDataPointMapperUnitTests.java | 9 +++--- ...BodyMassIndexDataPointMapperUnitTests.java | 28 +++++++++---------- ...lthBodyWeightDataPointMapperUnitTests.java | 8 +++--- ...sicalActivityDataPointMapperUnitTests.java | 5 ++-- ...SleepDurationDataPointMapperUnitTests.java | 4 +++ ...althStepCountDataPointMapperUnitTests.java | 4 +++ 6 files changed, 33 insertions(+), 25 deletions(-) diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java index 0a26d9e9..60730fa3 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java @@ -30,8 +30,10 @@ import static java.util.Collections.singletonList; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsEmptyCollection.empty; import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; @@ -104,12 +106,9 @@ public void asDataPointsShouldReturnCorrectUserNotesWithDataPoints() { @Test public void asDataPointsShouldReturnNoDataPointWhenHeartRateDataIsNotPresent() throws IOException { - ClassPathResource resource = new ClassPathResource( + JsonNode noHeartRateBloodPressureNode = asJsonNode( "org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure-missing-heart-rate.json"); - JsonNode noHeartRateBloodPressureNode = objectMapper.readTree(resource.getInputStream()); - - List> dataPoints = mapper.asDataPoints(singletonList(noHeartRateBloodPressureNode)); - assertThat(dataPoints.size(), equalTo(0)); + assertThat(mapper.asDataPoints(singletonList(noHeartRateBloodPressureNode)), is(empty())); } } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java index eb234af5..7e7c129c 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java @@ -17,8 +17,10 @@ package org.openmhealth.shim.ihealth.mapper; import com.fasterxml.jackson.databind.JsonNode; -import org.openmhealth.schema.domain.omh.*; -import org.springframework.core.io.ClassPathResource; +import org.openmhealth.schema.domain.omh.BodyMassIndex; +import org.openmhealth.schema.domain.omh.BodyMassIndexUnit; +import org.openmhealth.schema.domain.omh.DataPoint; +import org.openmhealth.schema.domain.omh.TypedUnitValue; import org.testng.annotations.BeforeMethod; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; @@ -29,9 +31,12 @@ import static java.util.Collections.singletonList; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.openmhealth.schema.domain.omh.BodyMassIndex.*; -import static org.openmhealth.schema.domain.omh.DataPointModality.*; +import static org.hamcrest.Matchers.empty; +import static org.openmhealth.schema.domain.omh.BodyMassIndex.SCHEMA_ID; +import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; +import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; /** @@ -91,23 +96,18 @@ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { @Test public void asDataPointsShouldReturnNoDataPointsWhenBodyMassIndexValueIsZero() throws IOException { - ClassPathResource resource = - new ClassPathResource("org/openmhealth/shim/ihealth/mapper/ihealth-missing-body-weight-value.json"); - JsonNode zeroValueNode = objectMapper.readTree(resource.getInputStream()); + JsonNode zeroValueNode = + asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-missing-body-weight-value.json"); - List> dataPoints = mapper.asDataPoints(singletonList(zeroValueNode)); - assertThat(dataPoints.size(), equalTo(0)); + assertThat(mapper.asDataPoints(singletonList(zeroValueNode)), is(empty())); } @Test public void asDataPointsShouldReturnNoDataPointsWhenWeightListIsEmpty() throws IOException { - ClassPathResource resource = - new ClassPathResource("org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight.json"); - JsonNode emptyListNode = objectMapper.readTree(resource.getInputStream()); + JsonNode emptyListNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight.json"); - List> dataPoints = mapper.asDataPoints(singletonList(emptyListNode)); - assertThat(dataPoints.size(), equalTo(0)); + assertThat(mapper.asDataPoints(singletonList(emptyListNode)), is(empty())); } } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java index c1525646..2b7404c0 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java @@ -28,8 +28,10 @@ import static java.util.Collections.singletonList; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsEmptyCollection.empty; import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; import static org.openmhealth.shim.ihealth.mapper.IHealthBodyWeightDataPointMapper.IHealthBodyWeightUnit; @@ -105,8 +107,7 @@ public void asDataPointsShouldReturnNoDataPointsWhenWeightValueEqualsZero() thro JsonNode zeroValueNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-missing-body-weight-value.json"); - List> dataPoints = mapper.asDataPoints(singletonList(zeroValueNode)); - assertThat(dataPoints.size(), equalTo(0)); + assertThat(mapper.asDataPoints(singletonList(zeroValueNode)), is(empty())); } @Test @@ -114,8 +115,7 @@ public void asDataPointsShouldReturnNoDataPointsWhenWeightListIsEmpty() throws I JsonNode emptyListNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight.json"); - List> dataPoints = mapper.asDataPoints(singletonList(emptyListNode)); - assertThat(dataPoints.size(), equalTo(0)); + assertThat(mapper.asDataPoints(singletonList(emptyListNode)), is(empty())); } @Test diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java index 4b64c96c..f221b101 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapperUnitTests.java @@ -30,7 +30,9 @@ import static java.util.Collections.singletonList; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; import static org.openmhealth.schema.domain.omh.PhysicalActivity.SCHEMA_ID; @@ -99,8 +101,7 @@ public void asDataPointsReturnsNoDataPointsForAnEmptyList() throws IOException { JsonNode emptyListResponseNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-empty-sports-activity.json"); - List> dataPoints = mapper.asDataPoints(singletonList(emptyListResponseNode)); - assertThat(dataPoints.size(), equalTo(0)); + assertThat(mapper.asDataPoints(singletonList(emptyListResponseNode)), is(empty())); } } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapperUnitTests.java index 5e5f628b..75947e22 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapperUnitTests.java @@ -31,6 +31,7 @@ import static java.util.Collections.singletonList; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; @@ -105,6 +106,9 @@ public void asDataPointsShouldReturnDataPointWithUserNoteWhenNoteIsPresent() { expectedSleepDurationBuilder.setUserNotes("Best sleep ever"); assertThat(dataPoints.get(1).getBody(), equalTo(expectedSleepDurationBuilder.build())); + + assertThat(dataPoints.get(0).getBody().getUserNotes(), nullValue()); + assertThat(dataPoints.get(1).getBody().getUserNotes(), equalTo("Best sleep ever")); } @Test diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java index 52a14630..60b2a9cb 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java @@ -28,6 +28,7 @@ import static java.util.Collections.singletonList; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; import static org.hamcrest.core.Is.is; @@ -95,6 +96,9 @@ public void asDataPointsShouldReturnDataPointWithUserNoteWhenNoteIsPresent() { new DurationUnitValue(DurationUnit.DAY, 1))).setUserNotes("Great steps"); assertThat(dataPoints.get(1).getBody(), Matchers.equalTo(expectedStepCountBuilder.build())); + + assertThat(dataPoints.get(0).getBody().getUserNotes(), nullValue()); + assertThat(dataPoints.get(1).getBody().getUserNotes(), equalTo("Great steps")); } @Test From 90e275ab6ddb70983a578c92f52c1e4b8dbbab7f Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Wed, 25 Nov 2015 22:51:50 -0700 Subject: [PATCH 57/68] Connect step and sleep mappers to iHealth shim --- .../openmhealth/shim/ihealth/IHealthShim.java | 41 +++++++++---------- .../src/main/resources/application.yaml | 28 ++++++------- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java index c1a8fb18..f7e2f31d 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java @@ -117,7 +117,9 @@ public ShimDataType[] getShimDataTypes() { IHealthDataTypes.BLOOD_PRESSURE, IHealthDataTypes.BODY_WEIGHT, IHealthDataTypes.BODY_MASS_INDEX, - IHealthDataTypes.HEART_RATE + IHealthDataTypes.HEART_RATE, + IHealthDataTypes.STEP_COUNT, + IHealthDataTypes.SLEEP_DURATION }; } @@ -143,7 +145,9 @@ public enum IHealthDataTypes implements ShimDataType { BLOOD_PRESSURE(singletonList("bp.json")), BODY_WEIGHT(singletonList("weight.json")), BODY_MASS_INDEX(singletonList("weight.json")), - HEART_RATE(Lists.newArrayList("bp.json", "spo2.json")); + HEART_RATE(Lists.newArrayList("bp.json", "spo2.json")), + STEP_COUNT(singletonList("activity.json")), + SLEEP_DURATION(singletonList("sleep.json")); private List endPoint; @@ -181,7 +185,7 @@ protected ResponseEntity getData(OAuth2RestOperations restTemp // SC and SV values are client-based keys that are unique to each endpoint within a project - List scValues = getScValues(dataType); + String scValue = getScValue(); List svValues = getSvValues(dataType); List responseEntities = Lists.newArrayList(); @@ -215,7 +219,7 @@ protected ResponseEntity getData(OAuth2RestOperations restTemp .queryParam("start_time", startDate.toEpochSecond()) .queryParam("end_time", endDate.toEpochSecond()) .queryParam("locale", "default") - .queryParam("sc", scValues.get(i)) + .queryParam("sc", scValue) .queryParam("sv", svValues.get(i)); ResponseEntity responseEntity; @@ -251,6 +255,12 @@ protected ResponseEntity getData(OAuth2RestOperations restTemp case BODY_MASS_INDEX: mapper = new IHealthBodyMassIndexDataPointMapper(); break; + case STEP_COUNT: + mapper = new IHealthStepCountDataPointMapper(); + break; + case SLEEP_DURATION: + mapper = new IHealthSleepDurationDataPointMapper(); + break; case HEART_RATE: // there are two different mappers for heart rate because the data can come from two endpoints if (endPoint == "bp.json") { @@ -280,24 +290,9 @@ else if (endPoint == "spo2.json") { ShimDataResponse.result(SHIM_KEY, responseEntities)); } - private List getScValues(IHealthDataTypes dataType) { + private String getScValue() { - switch ( dataType ) { - case PHYSICAL_ACTIVITY: - return singletonList(serialValues.get("sportSC")); - case BODY_WEIGHT: - return singletonList(serialValues.get("weightSC")); - case BODY_MASS_INDEX: - return singletonList(serialValues.get("weightSC")); // body mass index comes from the weight endpoint - case BLOOD_PRESSURE: - return singletonList(serialValues.get("bloodPressureSC")); - case BLOOD_GLUCOSE: - return singletonList(serialValues.get("bloodGlucoseSC")); - case HEART_RATE: - return Lists.newArrayList(serialValues.get("bloodPressureSC"), serialValues.get("spo2SC")); - default: - throw new UnsupportedOperationException(); - } + return serialValues.get("SC"); } private List getSvValues(IHealthDataTypes dataType) { @@ -313,6 +308,10 @@ private List getSvValues(IHealthDataTypes dataType) { return singletonList(serialValues.get("bloodPressureSV")); case BLOOD_GLUCOSE: return singletonList(serialValues.get("bloodGlucoseSV")); + case STEP_COUNT: + return singletonList(serialValues.get("activitySV")); + case SLEEP_DURATION: + return singletonList(serialValues.get("sleepSV")); case HEART_RATE: return Lists.newArrayList(serialValues.get("bloodPressureSV"), serialValues.get("spo2SV")); default: diff --git a/shim-server/src/main/resources/application.yaml b/shim-server/src/main/resources/application.yaml index 6976b6ac..70d56ee8 100644 --- a/shim-server/src/main/resources/application.yaml +++ b/shim-server/src/main/resources/application.yaml @@ -24,21 +24,21 @@ openmhealth: server: callbackUrlBase: http://localhost:8083 - #NOTE: Un-comment and fill in with your credentials if you're not using the UI + #NOTE: Un-comment and fill in the clientId/clientSecret with your credentials if you're not using the UI #NOTE: Un-comment and set partnerAccess to true if your credentials for a given API have partner access - ihealth: - serialValues: - sportSC: "4627a40e37104fb98af95c91ae4201e0" - sportSV: "c343378aba1545bbb5daa0ada0c3f6bd" - bloodPressureSC: "4627a40e37104fb98af95c91ae4201e0" - bloodPressureSV: "3ffcde7a7c6444e79bd4307c117041f8" - spo2SC: "4627a40e37104fb98af95c91ae4201e0" - spo2SV: "3ab71cace61245bc8b2af4f90f329be6" - weightSC: "4627a40e37104fb98af95c91ae4201e0" - weightSV: "24cbdf8952c44ae1a92939132beedb47" - bloodGlucoseSC: "4627a40e37104fb98af95c91ae4201e0" - bloodGlucoseSV: "cd44c9fd42444b729757b69e88be2eb7" - + #NOTE: Un-comment and fill in your serialValues for iHealth, otherwise the iHealth shim will not work correctly + #ihealth: + # serialValues: + # SC: [YOUR_SC_VALUE] + # sportSV: [YOUR_SV_VALUE_FOR_THE_SPORT_ENDPOINT] + # bloodPressureSV: [YOUR_SV_VALUE_FOR_THE_BLOOD_PRESSURE_ENDPOINT] + # spo2SV: [YOUR_SV_VALUE_FOR_THE_SPO2_ENDPOINT] + # weightSV: [YOUR_SV_VALUE_FOR_THE_WEIGHT_ENDPOINT] + # bloodGlucoseSV: [YOUR_SV_VALUE_FOR_THE_BLOOD_GLUCOSE_ENDPOINT] + # activitySV: [YOUR_SV_VALUE_FOR_THE_ACTIVITY_ENDPOINT] + # sleepSV: [YOUR_SV_VALUE_FOR_THE_SLEEP_ENDPOINT] + # clientId: [YOUR_CLIENT_ID] + # clientSecret: [YOUR_CLIENT_SECRET] #fitbit: # partnerAccess: true # clientId: [YOUR_CLIENT_ID] From 1c4263ce04817c0efeddff72f4c0662f30de543d Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Mon, 30 Nov 2015 11:07:32 -0700 Subject: [PATCH 58/68] Clean up and document iHealth mappers --- .../IHealthBloodGlucoseDataPointMapper.java | 14 ++++- ...xygenEndpointHeartRateDataPointMapper.java | 8 +++ .../IHealthBloodPressureDataPointMapper.java | 26 +++++--- ...ssureEndpointHeartRateDataPointMapper.java | 8 +++ .../IHealthBodyMassIndexDataPointMapper.java | 16 +++-- .../IHealthBodyWeightDataPointMapper.java | 37 ++++++++---- .../mapper/IHealthDataPointMapper.java | 59 ++++++++++++++----- .../IHealthHeartRateDataPointMapper.java | 5 +- ...HealthPhysicalActivityDataPointMapper.java | 21 +++++-- .../IHealthSleepDurationDataPointMapper.java | 5 +- .../IHealthStepCountDataPointMapper.java | 6 ++ ...ealthDatapointMapperDateTimeUnitTests.java | 6 +- 12 files changed, 156 insertions(+), 55 deletions(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java index e6870452..27aa57c0 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java @@ -28,7 +28,11 @@ /** + * A mapper that translates responses from the iHealth /glucose.json/ endpoint into {@link BloodGlucose} measures. + * * @author Chris Schaefbauer + * @see + * iHealth Blood Glucose Endpoint Documentation */ public class IHealthBloodGlucoseDataPointMapper extends IHealthDataPointMapper { @@ -79,7 +83,7 @@ protected Optional> asDataPoint(JsonNode listEntryNode, } } - setEffectiveTimeFrameIfExists(listEntryNode, bloodGlucoseBuilder); + setEffectiveTimeFrameWithDateTimeIfExists(listEntryNode, bloodGlucoseBuilder); setUserNoteIfExists(listEntryNode, bloodGlucoseBuilder); BloodGlucose bloodGlucose = bloodGlucoseBuilder.build(); @@ -93,6 +97,10 @@ protected Optional> asDataPoint(JsonNode listEntryNode, return Optional.of(new DataPoint<>(createDataPointHeader(listEntryNode, bloodGlucose), bloodGlucose)); } + /** + * Maps strings used by iHealth to represent the relationship between a blood glucose measure and meals to the + * values used in OMH schema. + */ private void initializeTemporalRelationshipToFoodMap() { ImmutableMap.Builder relationshipToMealMapBuilder = ImmutableMap.builder(); @@ -109,6 +117,10 @@ private void initializeTemporalRelationshipToFoodMap() { } + /** + * @param measureUnitMagicNumber The number from the iHealth response representing the unit of measure. + * @return The corresponding OMH schema unit of measure for blood glucose. + */ protected BloodGlucoseUnit getBloodGlucoseUnitFromMagicNumber(Integer measureUnitMagicNumber) { switch ( measureUnitMagicNumber ) { diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapper.java index 334a954c..4c4e52a9 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapper.java @@ -16,8 +16,16 @@ package org.openmhealth.shim.ihealth.mapper; +import org.openmhealth.schema.domain.omh.HeartRate; + + /** + * A mapper that translates responses from the iHealth /spo2.json/ endpoint into {@link HeartRate} measures. + * * @author Emerson Farrugia + * @author Chris Schaefbauer + * @see + * iHealth Blood Oxygen Endpoint Documentation */ public class IHealthBloodOxygenEndpointHeartRateDataPointMapper extends IHealthHeartRateDataPointMapper { diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java index 491e0699..2634565e 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java @@ -26,7 +26,11 @@ /** + * A mapper that translates responses from the iHealth /bp.json/ endpoint into {@link BloodPressure} measures. + * * @author Chris Schaefbauer + * @see + * iHealth Blood Pressure Endpoint Documentation */ public class IHealthBloodPressureDataPointMapper extends IHealthDataPointMapper { @@ -47,31 +51,35 @@ protected Optional getMeasureUnitNodeName() { } @Override - protected Optional> asDataPoint(JsonNode listNode, Integer bloodPressureUnit) { + protected Optional> asDataPoint(JsonNode listEntryNode, Integer measureUnitMagicNumber) { - checkNotNull(bloodPressureUnit); + checkNotNull(measureUnitMagicNumber); - double systolicValue = getBloodPressureValueInMmHg(asRequiredDouble(listNode, "HP"), bloodPressureUnit); + double systolicValue = getBloodPressureValueInMmHg(asRequiredDouble(listEntryNode, "HP"), measureUnitMagicNumber); SystolicBloodPressure systolicBloodPressure = new SystolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, systolicValue); - double diastolicValue = getBloodPressureValueInMmHg(asRequiredDouble(listNode, "LP"), bloodPressureUnit); + double diastolicValue = getBloodPressureValueInMmHg(asRequiredDouble(listEntryNode, "LP"), measureUnitMagicNumber); DiastolicBloodPressure diastolicBloodPressure = new DiastolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, diastolicValue); BloodPressure.Builder bloodPressureBuilder = new BloodPressure.Builder(systolicBloodPressure, diastolicBloodPressure); - setEffectiveTimeFrameIfExists(listNode, bloodPressureBuilder); - setUserNoteIfExists(listNode, bloodPressureBuilder); + setEffectiveTimeFrameWithDateTimeIfExists(listEntryNode, bloodPressureBuilder); + setUserNoteIfExists(listEntryNode, bloodPressureBuilder); BloodPressure bloodPressure = bloodPressureBuilder.build(); - return Optional.of(new DataPoint<>(createDataPointHeader(listNode, bloodPressure), bloodPressure)); + return Optional.of(new DataPoint<>(createDataPointHeader(listEntryNode, bloodPressure), bloodPressure)); } - protected double getBloodPressureValueInMmHg(double rawBpValue, Integer bloodPressureUnit) { + /** + * @param measureUnitMagicNumber The number from the iHealth response representing the unit of measure. + * @return The corresponding OMH schema unit of measure for blood pressure. + */ + protected double getBloodPressureValueInMmHg(double rawBpValue, Integer measureUnitMagicNumber) { - switch ( bloodPressureUnit ) { + switch ( measureUnitMagicNumber ) { case MMHG_UNIT_MAGIC_NUMBER: return rawBpValue; case KPA_UNIT_MAGIC_NUMBER: diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapper.java index 508e5697..b5364e73 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapper.java @@ -16,8 +16,16 @@ package org.openmhealth.shim.ihealth.mapper; +import org.openmhealth.schema.domain.omh.HeartRate; + + /** + * A mapper that translates responses from the iHealth /bp.json/ endpoint into {@link HeartRate} measures. + * * @author Emerson Farrugia + * @author Chris Schaefbauer + * @see + * iHealth Blood Pressure Endpoint Documentation */ public class IHealthBloodPressureEndpointHeartRateDataPointMapper extends IHealthHeartRateDataPointMapper { diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java index fbf61ddb..f1a9fb0f 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java @@ -26,9 +26,13 @@ import static org.openmhealth.schema.domain.omh.BodyMassIndexUnit.KILOGRAMS_PER_SQUARE_METER; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredDouble; - /** + * A mapper that translates responses from the iHealth /weight.json/ endpoint into {@link BodyMassIndex} measures. + * + * @author Emerson Farrugia * @author Chris Schaefbauer + * @see + * iHealth Body Weight Endpoint Documentation */ public class IHealthBodyMassIndexDataPointMapper extends IHealthDataPointMapper { @@ -43,9 +47,9 @@ protected Optional getMeasureUnitNodeName() { } @Override - protected Optional> asDataPoint(JsonNode listNode, Integer measureUnitMagicNumber) { + protected Optional> asDataPoint(JsonNode listEntryNode, Integer measureUnitMagicNumber) { - Double bmiValue = asRequiredDouble(listNode, "BMI"); + Double bmiValue = asRequiredDouble(listEntryNode, "BMI"); if (bmiValue == 0) { return Optional.empty(); @@ -54,11 +58,11 @@ protected Optional> asDataPoint(JsonNode listNode, Inte BodyMassIndex.Builder bodyMassIndexBuilder = new BodyMassIndex.Builder(new TypedUnitValue<>(KILOGRAMS_PER_SQUARE_METER, bmiValue)); - setEffectiveTimeFrameIfExists(listNode, bodyMassIndexBuilder); - setUserNoteIfExists(listNode, bodyMassIndexBuilder); + setEffectiveTimeFrameWithDateTimeIfExists(listEntryNode, bodyMassIndexBuilder); + setUserNoteIfExists(listEntryNode, bodyMassIndexBuilder); BodyMassIndex bodyMassIndex = bodyMassIndexBuilder.build(); - return Optional.of(new DataPoint<>(createDataPointHeader(listNode, bodyMassIndex), bodyMassIndex)); + return Optional.of(new DataPoint<>(createDataPointHeader(listEntryNode, bodyMassIndex), bodyMassIndex)); } } diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java index 46b82893..2100083c 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java @@ -17,10 +17,7 @@ package org.openmhealth.shim.ihealth.mapper; import com.fasterxml.jackson.databind.JsonNode; -import org.openmhealth.schema.domain.omh.BodyWeight; -import org.openmhealth.schema.domain.omh.DataPoint; -import org.openmhealth.schema.domain.omh.MassUnit; -import org.openmhealth.schema.domain.omh.MassUnitValue; +import org.openmhealth.schema.domain.omh.*; import java.util.Optional; @@ -29,7 +26,12 @@ /** + * A mapper that translates responses from the iHealth /weight.json/ endpoint into {@link BodyWeight} measures. + * + * @author Emerson Farrugia * @author Chris Schaefbauer + * @see + * iHealth Body Weight Endpoint Documentation */ public class IHealthBodyWeightDataPointMapper extends IHealthDataPointMapper { @@ -47,14 +49,14 @@ protected Optional getMeasureUnitNodeName() { } @Override - protected Optional> asDataPoint(JsonNode listNode, Integer measureUnitMagicNumber) { + protected Optional> asDataPoint(JsonNode listEntryNode, Integer measureUnitMagicNumber) { checkNotNull(measureUnitMagicNumber); IHealthBodyWeightUnit bodyWeightUnitType = IHealthBodyWeightUnit.fromIntegerValue(measureUnitMagicNumber); MassUnit bodyWeightUnit = bodyWeightUnitType.getOmhUnit(); - double bodyWeightValue = getBodyWeightValueForUnitType(listNode, bodyWeightUnitType); + double bodyWeightValue = getBodyWeightValueForUnitType(listEntryNode, bodyWeightUnitType); if (bodyWeightValue == 0) { @@ -64,22 +66,33 @@ protected Optional> asDataPoint(JsonNode listNode, Integer BodyWeight.Builder bodyWeightBuilder = new BodyWeight.Builder(new MassUnitValue(bodyWeightUnit, bodyWeightValue)); - setEffectiveTimeFrameIfExists(listNode, bodyWeightBuilder); - setUserNoteIfExists(listNode, bodyWeightBuilder); + setEffectiveTimeFrameWithDateTimeIfExists(listEntryNode, bodyWeightBuilder); + setUserNoteIfExists(listEntryNode, bodyWeightBuilder); BodyWeight bodyWeight = bodyWeightBuilder.build(); - return Optional.of(new DataPoint<>(createDataPointHeader(listNode, bodyWeight), bodyWeight)); + return Optional.of(new DataPoint<>(createDataPointHeader(listEntryNode, bodyWeight), bodyWeight)); } - - protected double getBodyWeightValueForUnitType(JsonNode listNode, + /** + * + * @param listEntryNode A single entry from the response result array. + * @param bodyWeightUnitType The unit type for the measure. + * @return The body weight value for the list entry that is rendered in the correct unit. + */ + protected double getBodyWeightValueForUnitType(JsonNode listEntryNode, IHealthBodyWeightUnit bodyWeightUnitType) { - Double weightValueFromApi = asRequiredDouble(listNode, "WeightValue"); + Double weightValueFromApi = asRequiredDouble(listEntryNode, "WeightValue"); return getBodyWeightValueForUnitType(weightValueFromApi, bodyWeightUnitType); } + /** + * + * @param bodyWeightValue The body weight value that has been extracted from the list entry node. + * @param bodyWeightUnitType The unit type for the measure. + * @return A body weight value that is rendered in the correct unit. + */ protected double getBodyWeightValueForUnitType(double bodyWeightValue, IHealthBodyWeightUnit bodyWeightUnitType) { // iHealth has one unit type that is unsupported by OMH schemas, so we need to convert the value into a unit diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java index d4762c5c..57ed9d3e 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java @@ -87,21 +87,21 @@ public List> asDataPoints(List responseNodes) { *

Note: Additional properties within the header come from the iHealth API and are not defined by the data point * header schema. Additional properties are subject to change.

*/ - protected DataPointHeader createDataPointHeader(JsonNode listNode, Measure measure) { + protected DataPointHeader createDataPointHeader(JsonNode listEntryNode, Measure measure) { DataPointAcquisitionProvenance.Builder acquisitionProvenanceBuilder = new DataPointAcquisitionProvenance.Builder(RESOURCE_API_SOURCE_NAME); - asOptionalString(listNode, "DataSource").ifPresent( + asOptionalString(listEntryNode, "DataSource").ifPresent( dataSource -> setAppropriateModality(dataSource, acquisitionProvenanceBuilder)); DataPointAcquisitionProvenance acquisitionProvenance = acquisitionProvenanceBuilder.build(); - asOptionalString(listNode, "DataID") + asOptionalString(listEntryNode, "DataID") .ifPresent(externalId -> acquisitionProvenance.setAdditionalProperty("external_id", externalId)); - asOptionalLong(listNode, "LastChangeTime").ifPresent( + asOptionalLong(listEntryNode, "LastChangeTime").ifPresent( lastUpdatedInUnixSecs -> acquisitionProvenance.setAdditionalProperty("source_updated_date_time", OffsetDateTime.ofInstant(Instant.ofEpochSecond(lastUpdatedInUnixSecs), ZoneId.of("Z")))); @@ -111,13 +111,22 @@ protected DataPointHeader createDataPointHeader(JsonNode listNode, Measure measu } - protected static void setEffectiveTimeFrameIfExists(JsonNode listNode, Builder builder) { + /** + * Sets the effective time frame of a measure builder as a single point in time using a date_time. This method does + * not set time intervals. + * + * @param listEntryNode A single node from the response result array that contains the MDate field that needs to + * get + * mapped as a date_time in the timeframe. + * @param builder The measure builder to set the effective time frame. + */ + protected static void setEffectiveTimeFrameWithDateTimeIfExists(JsonNode listEntryNode, Builder builder) { - Optional optionalOffsetDateTime = asOptionalLong(listNode, "MDate"); + Optional optionalOffsetDateTime = asOptionalLong(listEntryNode, "MDate"); if (optionalOffsetDateTime.isPresent()) { - Optional timeZone = asOptionalString(listNode, "TimeZone"); + Optional timeZone = asOptionalString(listEntryNode, "TimeZone"); if (timeZone.isPresent() && !timeZone.get().isEmpty()) { @@ -126,12 +135,13 @@ protected static void setEffectiveTimeFrameIfExists(JsonNode listNode, Builder b builder.setEffectiveTimeFrame(offsetDateTimeCorrectOffset); } - else if (asOptionalLong(listNode, "TimeZone").isPresent()) { + else if (asOptionalLong(listEntryNode, "TimeZone").isPresent()) { - Long timeZoneOffsetValue = asOptionalLong(listNode, "TimeZone").get(); + Long timeZoneOffsetValue = asOptionalLong(listEntryNode, "TimeZone").get(); String timeZoneString = timeZoneOffsetValue.toString(); + // Zone offset cannot parse a positive string offset that's missing a '+' sign (i.e., "0200" vs "+0200") if (timeZoneOffsetValue >= 0) { timeZoneString = "+" + timeZoneOffsetValue.toString(); } @@ -147,9 +157,9 @@ else if (asOptionalLong(listNode, "TimeZone").isPresent()) { } /** - * This method transforms the unix epoch second timestamps in iHealth responses, which are not in utc but instead - * offset to the local time zone of the data point, into an {@link OffsetDateTime} with the correct date/time and - * offset. + * This method transforms the unix epoch second timestamps in iHealth responses into an {@link OffsetDateTime} with + * the correct date/time and offset. The timestamps provided in iHealth responses are not in UTC but instead offset + * to the local time zone of the data point. */ protected static OffsetDateTime getDateTimeWithCorrectOffset(Long dateTimeInUnixSecondsWithLocalTimeOffset, String timeZoneString) { @@ -163,6 +173,11 @@ protected static OffsetDateTime getDateTimeWithCorrectOffset(Long dateTimeInUnix return offsetDateTimeFromOffsetInstant.toLocalDateTime().atOffset(ZoneOffset.of(timeZoneString)); } + /** + * @param dateTimeInUnixSecondsWithLocalTimeOffset A unix epoch timestamp in local time. + * @param timeZoneString The time zone offset as a String (e.g., "+0200","-2"). + * @return The date time with the correct offset. + */ protected static OffsetDateTime getDateTimeAtStartOfDayWithCorrectOffset( Long dateTimeInUnixSecondsWithLocalTimeOffset, String timeZoneString) { @@ -175,9 +190,16 @@ protected static OffsetDateTime getDateTimeAtStartOfDayWithCorrectOffset( return dateTimeFromOffsetInstant.toLocalDate().atStartOfDay().atOffset(ZoneOffset.of(timeZoneString)); } - protected static void setUserNoteIfExists(JsonNode listNode, Builder builder) { + /** + * Sets the user note in a measure builder with the value of the note property in the list entry node if that + * property exists. + * + * @param listEntryNode A single entry from the response result array. + * @param builder The measure builder to set the user note. + */ + protected static void setUserNoteIfExists(JsonNode listEntryNode, Builder builder) { - Optional note = asOptionalString(listNode, "Note"); + Optional note = asOptionalString(listEntryNode, "Note"); if (note.isPresent() && !note.get().isEmpty()) { @@ -185,6 +207,11 @@ protected static void setUserNoteIfExists(JsonNode listNode, Builder builder) { } } + /** + * Sets the correct DataPointModality based on the iHealth value indicating the source of the DataPoint. + * @param dataSourceValue The iHealth value in the list entry node indicating the source of the DataPoint. + * @param builder The DataPointAcquisitionProvenance builder to set the modality. + */ private void setAppropriateModality(String dataSourceValue, DataPointAcquisitionProvenance.Builder builder) { if (dataSourceValue.equals(DATA_SOURCE_FROM_DEVICE)) { @@ -208,11 +235,11 @@ else if (dataSourceValue.equals(DATA_SOURCE_MANUAL)) { protected abstract Optional getMeasureUnitNodeName(); /** - * @param listEntryNode a single node from the + * @param listEntryNode A single entry from the response result array. * @param measureUnitMagicNumber The number representing the units used to render the response, according to * iHealth. This is retrieved from the main body of the response node. If the measure type does not use units, then * this value is null. - * @return the data point mapped from the listEntryNode, unless skipped + * @return The data point mapped from the listEntryNode, unless it is skipped. */ protected abstract Optional> asDataPoint(JsonNode listEntryNode, Integer measureUnitMagicNumber); } diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java index 64dfc828..2c69344f 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java @@ -26,6 +26,9 @@ /** + * Base class that handles mapping iHealth responses from endpoints containing heart rate information into {@link + * HeartRate} measures + * * @author Chris Schaefbauer * @author Emerson Farrugia */ @@ -48,7 +51,7 @@ protected Optional> asDataPoint(JsonNode listEntryNode, Int HeartRate.Builder heartRateBuilder = new HeartRate.Builder(heartRateValue); - setEffectiveTimeFrameIfExists(listEntryNode, heartRateBuilder); + setEffectiveTimeFrameWithDateTimeIfExists(listEntryNode, heartRateBuilder); setUserNoteIfExists(listEntryNode, heartRateBuilder); HeartRate heartRate = heartRateBuilder.build(); diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java index e8c94ac4..fb1823bf 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java @@ -27,7 +27,13 @@ /** + * A mapper that translates responses from the iHealth /sport.json/ endpoint into {@link PhysicalActivity} measures. + * + * @author Emerson Farrugia * @author Chris Schaefbauer + * + * @see + * iHealth Sport Endpoint Documentation */ public class IHealthPhysicalActivityDataPointMapper extends IHealthDataPointMapper { @@ -43,25 +49,28 @@ protected Optional getMeasureUnitNodeName() { } @Override - protected Optional> asDataPoint(JsonNode listNode, Integer measureUnitMagicNumber) { + protected Optional> asDataPoint(JsonNode listEntryNode, + Integer measureUnitMagicNumber) { - String activityName = asRequiredString(listNode, "SportName"); + String activityName = asRequiredString(listEntryNode, "SportName"); if (activityName.isEmpty()) { + return Optional.empty(); } PhysicalActivity.Builder physicalActivityBuilder = new PhysicalActivity.Builder(activityName); - Optional startTimeUnixEpochSecs = asOptionalLong(listNode, "SportStartTime"); - Optional endTimeUnixEpochSecs = asOptionalLong(listNode, "SportEndTime"); - Optional timeZoneOffset = asOptionalInteger(listNode, "TimeZone"); + Optional startTimeUnixEpochSecs = asOptionalLong(listEntryNode, "SportStartTime"); + Optional endTimeUnixEpochSecs = asOptionalLong(listEntryNode, "SportEndTime"); + Optional timeZoneOffset = asOptionalInteger(listEntryNode, "TimeZone"); if (startTimeUnixEpochSecs.isPresent() && endTimeUnixEpochSecs.isPresent() && timeZoneOffset.isPresent()) { Integer timeZoneOffsetValue = timeZoneOffset.get(); String timeZoneString = timeZoneOffsetValue.toString(); + // Zone offset cannot parse a positive string offset that's missing a '+' sign (i.e., "0200" vs "+0200") if (timeZoneOffsetValue >= 0) { timeZoneString = "+" + timeZoneOffsetValue.toString(); } @@ -72,6 +81,6 @@ protected Optional> asDataPoint(JsonNode listNode, I } PhysicalActivity physicalActivity = physicalActivityBuilder.build(); - return Optional.of(new DataPoint<>(createDataPointHeader(listNode, physicalActivity), physicalActivity)); + return Optional.of(new DataPoint<>(createDataPointHeader(listEntryNode, physicalActivity), physicalActivity)); } } diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapper.java index 775932b4..2338d7a5 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapper.java @@ -24,9 +24,12 @@ import static org.openmhealth.schema.domain.omh.TimeInterval.*; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*; - /** + * A mapper that translates responses from the iHealth /sleep.json/ endpoint into {@link SleepDuration} measures. + * * @author Chris Schaefbauer + * @see + * iHealth Sleep Endpoint Documentation */ public class IHealthSleepDurationDataPointMapper extends IHealthDataPointMapper { diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java index f0db11b8..a85713fc 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java @@ -30,7 +30,11 @@ /** + * A mapper that translates responses from the iHealth /activity.json/ endpoint into {@link StepCount} measures. + * * @author Chris Schaefbauer + * @see + * iHealth Activity Endpoint Documentation */ public class IHealthStepCountDataPointMapper extends IHealthDataPointMapper { @@ -63,6 +67,8 @@ protected Optional> asDataPoint(JsonNode listEntryNode, Int if (timeZone.isPresent()) { + /* iHealth provides daily summaries for step counts and timestamp the datapoint at either the end of + the day (23:50) or at the latest time that datapoint was synced */ stepCountBuilder.setEffectiveTimeFrame(ofStartDateTimeAndDuration( getDateTimeAtStartOfDayWithCorrectOffset(dateTimeString.get(), timeZone.get()), new DurationUnitValue(DurationUnit.DAY, 1))); diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java index 12d2bbd2..2751856b 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java @@ -47,7 +47,7 @@ public void initializeBuilder() { public void setEffectiveTimeFrameShouldNotAddTimeFrameWhenTimeZoneIsMissing() throws IOException { JsonNode timeInfoNode = createResponseNodeWithTimeZone(null); - IHealthDataPointMapper.setEffectiveTimeFrameIfExists(timeInfoNode, builder); + IHealthDataPointMapper.setEffectiveTimeFrameWithDateTimeIfExists(timeInfoNode, builder); assertThat(builder.build().getEffectiveTimeFrame(), nullValue()); } @@ -57,7 +57,7 @@ public void setEffectiveTimeFrameShouldNotAddTimeFrameWhenTimeZoneIsEmpty() thro JsonNode timeInfoNode = createResponseNodeWithTimeZone("\"\""); - IHealthDataPointMapper.setEffectiveTimeFrameIfExists(timeInfoNode, builder); + IHealthDataPointMapper.setEffectiveTimeFrameWithDateTimeIfExists(timeInfoNode, builder); assertThat(builder.build().getEffectiveTimeFrame(), nullValue()); } @@ -125,7 +125,7 @@ public void testTimeFrameWhenItShouldBeSetCorrectly(String timezoneString, Strin throws IOException { JsonNode timeInfoNode = createResponseNodeWithTimeZone(timezoneString); - IHealthDataPointMapper.setEffectiveTimeFrameIfExists(timeInfoNode, builder); + IHealthDataPointMapper.setEffectiveTimeFrameWithDateTimeIfExists(timeInfoNode, builder); assertThat(builder.build().getEffectiveTimeFrame(), notNullValue()); assertThat(builder.build().getEffectiveTimeFrame().getDateTime(), equalTo( From 86e7516ce5510bedc817961765f672ceff298286 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Mon, 30 Nov 2015 13:52:53 -0700 Subject: [PATCH 59/68] Fix dates for physical activity request from iHealth The sports.json iHealth endpoint uses the start_time and end_time parameters differently than other endpoints, so we have to add an extra day onto the end of the range so that in encompasses the entire final day until the beginning of the next day. The other endpoints don't require this and instead seem to request the entire day of each parameter. --- .../openmhealth/shim/ihealth/IHealthShim.java | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java index f7e2f31d..42f14475 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java @@ -44,6 +44,7 @@ import java.util.Map; import static java.util.Collections.singletonList; +import static org.openmhealth.shim.ihealth.IHealthShim.IHealthDataTypes.*; import static org.slf4j.LoggerFactory.getLogger; @@ -112,19 +113,19 @@ public AuthorizationCodeAccessTokenProvider getAuthorizationCodeAccessTokenProvi @Override public ShimDataType[] getShimDataTypes() { return new ShimDataType[] { - IHealthDataTypes.PHYSICAL_ACTIVITY, - IHealthDataTypes.BLOOD_GLUCOSE, - IHealthDataTypes.BLOOD_PRESSURE, - IHealthDataTypes.BODY_WEIGHT, - IHealthDataTypes.BODY_MASS_INDEX, - IHealthDataTypes.HEART_RATE, - IHealthDataTypes.STEP_COUNT, - IHealthDataTypes.SLEEP_DURATION + PHYSICAL_ACTIVITY, + BLOOD_GLUCOSE, + BLOOD_PRESSURE, + BODY_WEIGHT, + BODY_MASS_INDEX, + HEART_RATE, + STEP_COUNT, + SLEEP_DURATION }; } /** - * Map of values auto-configured from the application properties yaml. + * Map of values auto-configured from the application.yaml. */ Map serialValues; @@ -168,7 +169,7 @@ protected ResponseEntity getData(OAuth2RestOperations restTemp final IHealthDataTypes dataType; try { - dataType = IHealthDataTypes.valueOf( + dataType = valueOf( shimDataRequest.getDataTypeKey().trim().toUpperCase()); } catch (NullPointerException | IllegalArgumentException e) { @@ -183,6 +184,14 @@ protected ResponseEntity getData(OAuth2RestOperations restTemp OffsetDateTime endDate = shimDataRequest.getEndDateTime() == null ? now.plusDays(1) : shimDataRequest.getEndDateTime(); + /* + The physical activity point handles start and end datetimes differently than the other endpoints. It + requires use to include the range until the beginning of the next day. + */ + if (dataType == PHYSICAL_ACTIVITY) { + + endDate = endDate.plusDays(1); + } // SC and SV values are client-based keys that are unique to each endpoint within a project String scValue = getScValue(); From 4cf2e824fde00e327f89f8e4bd5216d21e9766f4 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Mon, 30 Nov 2015 14:38:10 -0700 Subject: [PATCH 60/68] Update markdown describing iHealth API --- .../org/openmhealth/shim/ihealth/iHealth.md | 78 +++++++++++++++---- 1 file changed, 63 insertions(+), 15 deletions(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/iHealth.md b/shim-server/src/main/java/org/openmhealth/shim/ihealth/iHealth.md index 14fe02a1..d279d85e 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/iHealth.md +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/iHealth.md @@ -1,3 +1,5 @@ +### This is currently a work in progress + # api api url: https://api.ihealthlabs.com:8443/openapiv2/user/{userid} @@ -13,7 +15,6 @@ api reference: http://developer.ihealthlabs.com/dev_documentation_openapidoc.htm - flows: authorization code - authorization URL: https://api.ihealthlabs.com:8443/OpenApiV2/OAuthv2/userauthorization/ - access token: https://api.ihealthlabs.com:8443/OpenApiV2/OAuthv2/userauthorization/ - - May be a bit off of standard OAuth token uri here - supports refresh tokens: yes - refresh token: https://api.ihealthlabs.com:8443/OpenApiV2/OAuthv2/userauthorization/ - response_type=refresh_token @@ -33,7 +34,7 @@ api reference: http://developer.ihealthlabs.com/dev_documentation_openapidoc.htm # pagination - supported: yes, responses will have a “NextPageUrl” property if there is more data to be retrieved that is the URI for the endpoint uri for the next page. The next page url is endcoded such that it is in what appears to be an unusable in its current form, so it would need to be decoded in some way. However, there is a “page_index” parameter that can be incremented to step through pages of size 50 and reach all of the data. page_index = 1, 2, 3, 4, etc. -- The limit is set at 50 and there is no ability to control page size or limit the number of responses. +- The limit is set at 50 and there is no ability to control page size or change the limit of the number of responses. # rate limit - 5000 requests per hour per user @@ -45,7 +46,7 @@ api reference: http://developer.ihealthlabs.com/dev_documentation_openapidoc.htm - unsupported # time zone and time representation -- Each datapoint contains a “timezone” property, which is a utc-offset in the form “-0800” +- Each datapoint contains a “timezone” property, which is a utc-offset in the form of a string (“-0800” or "0800") or integer (-8 or 8) - Timestamps are represented as unix epoch seconds offset by the zone offset found in the “timezone” property, so the timestamps are, in essence, in local time - Requests take unix epoch seconds and match on local time @@ -60,8 +61,7 @@ api reference: http://developer.ihealthlabs.com/dev_documentation_openapidoc.htm - sv: A unique identifier for the client’s use of the specific endpoint (one for each endpoint per project) - Optional parameters - - start_time: the unix epoch second to constrain the search, when it is empty, the data would start from one year ago - - page_index: First page The page index of data, which starts from 1 + - start_time: the unix epoch second to start the search for datapoints to return, when it is empty, the data would start from one year ago. - locale: Default (example in Appendix) Set the locale / units returned - end_time: the Unix epoch second to constrain the end of the search when the Activity data ends, it must be later than start_time @@ -87,9 +87,9 @@ It appears that the value is zero when it is missing, so zero values are not act All data in the response are rendered using the same unit, though it can change per response. The unit is contained in a property “WeightUnit” and is an integer between 0 - 2 corresponding to the following enum: {kg : 0}, {lbs : 1}, {stone : 2}. -### measures mapped -omh:body-weight -omh:body-mass-index +### measures +body-weight: mapped +body-mass-index: mapped ## get blood pressure - endpoint: /bp.json/ @@ -140,8 +140,8 @@ The response contains meta information in the body, such as page length, record All data in the response are rendered using the same unit, though it can change per response. The unit is contained in a property “BGUnit” and is an integer between 0 - 1 corresponding to the following enum: {mg/dl : 0}, {mmol/l : 1} -### measures mapped -omh:blood-glucose +### measures +- blood-glucose: mapped ## get oxygen saturation - endpoint: /spo2.json/ @@ -161,8 +161,9 @@ The response contains meta information in the body, such as page length, record - DataSource: ( Manual | FromDevice ) - TimeZone: Time zone of measurement location -### measures mapped -omh:heart-rate +### measures +heart-rate: mapped +oxygen-saturation: not mapped, schema and schema-sdk support is in development ## get sports activities - endpoint: /sport.json/ @@ -171,6 +172,9 @@ omh:heart-rate ### description Retrieves the physical activities an individual has engaged in from the iHealth API +### request +A note on the sport activity request: the start and end + ### response The response contains meta information in the body, such as page length, record count, etc, as well as an array property “SPORTDataList,” which contains the data. Each item in the list contains a set of properties describing a unique physical activity: @@ -183,8 +187,52 @@ The response contains meta information in the body, such as page length, record - LastChangeTime: Time of last change (UTC) - DataSource: ( Manual | FromDevice ) -### measures mapped -omh:physical-activity +### measures +- physical-activity: mapped + +## get activity +- endpoint: /activity.json/ +- reference: http://developer.ihealthlabs.com/dev_documentation_RequestfordataofActivityReport.htm + +### description +Retrieves daily summaries of activity information (steps, calories, etc) that comes from an iHealth activity tracker device. + +### response +The response contains meta information in the body, such as page length, record count, etc, as well as an array property “ARDataList,” which contains the data. Each item in the list contains a set of properties describing a daily summary of activities for a given day: + +Calories: The total number of calories burned including BMR and activity calories +Steps: The total number of steps counted by the device +MDate: the datetime that this entry was last updated with new information or the end of the day on the date that this entry represents (if the day has already completed) +TimeZone: Time zone of measurement location +DataID: the unique identity +LastChangeTime: Time of last change (UTC) +DataSource: ( Manual | FromDevice ) + +### measures +- step-count: mapped +- calories-burned: not mapped because activity calories are combined with BMR + +## get sleep +- endpoint: /sleep.json/ +- reference: http://developer.ihealthlabs.com/dev_documentation_RequestfordataofSleepReport.htm + +### description +Returns a list of sleep activities for the user, including sleep summary information. + +### response +The response contains meta information in the body, such as page length, record count, etc, as well as an array property “SRDataList,” which contains the data. Each item in the list contains a set of properties describing the sleep event: + +“Awaken” - Number of times awoken +“Fallsleep” - Time until asleep +“HoursSlept” - The length of the sleep in minutes +“SleepEfficiency” - Sleep efficiency - unspecified +“StartTime” - Start time of sleep (in unix epoch probably) +“EndTime” - End time of sleep (in unix epoch probably) +DataSource: ( Manual | FromDevice ) +“TimeZone” - Time zone of measurement location + +### measures +sleep-duration: mapped ## future endpoint support -We hope to support step-count and sleep-duration measures from the activity and sleep endpoints in iHealth, however we are unable to ascertain the time frame on step data from the documentation and also need real device data to test uncertainties and ambiguities in these endpoints that are not clear from the documentation. +We hope to support oxygen-saturation from the spo2 endpoint in iHealth, however we are finalizing a schema and schema-sdk support to represent that data. From c17ea61f87f3e1ca678e85f8122d894432ab1392 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Mon, 30 Nov 2015 15:09:59 -0700 Subject: [PATCH 61/68] Update readme with iHealth information --- README.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bd39dd21..9813c09f 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,12 @@ We currently support the following APIs * [Misfit](http://misfit.com/) * [RunKeeper](https://runkeeper.com/index) * [Withings](http://www.withings.com/) +* [iHealth](http://www.ihealthlabs.com/) And the following APIs are in the works * [FatSecret](https://www.fatsecret.com/) * [Ginsberg](https://www.ginsberg.io/) -* [iHealth](http://www.ihealthlabs.com/) * [Strava](https://www.strava.com/) This README should have everything you need to get started. If you have any questions, feel free to [open an issue](https://github.com/openmhealth/shimmer/issues), [email us](mailto://admin@openmhealth.org), [post on our form](https://groups.google.com/forum/#!forum/omh-developers), or [visit our website](http://www.openmhealth.org/documentation/#/data-providers/get-started). @@ -122,6 +122,11 @@ You need to obtain client credentials for any shim you'd like to use. These cred * [Misfit](https://build.misfit.com/) * [RunKeeper](http://developer.runkeeper.com/healthgraph) ([application management portal](http://runkeeper.com/partner)) * [Withings](http://oauth.withings.com/api) +* [iHealth](http://developer.ihealthlabs.com/index.htm) (see below for setting up special iHealth application specific credentials) + +> If you are using the iHealth shim, you must uncomment and replace the SC and SV values for each endpoint in the `iHealth:serialValues` map in the `application.yaml` file. +These values are uniquely associated with each project you have and can be found in your project details on the [application management page](http://developer.ihealthlabs.com/developermanagepage.htm) +of the iHealth developers site. If any of the links are incorrect or out of date, please [submit an issue](https://github.com/openmhealth/shimmer/issues) to let us know. @@ -246,7 +251,14 @@ The currently supported shims are: | withings | steps4 | [omh:step-count](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_step-count) | | withings | calories4 | [omh:calories-burned](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_calories-burned) | | withings | sleep5 | [omh:sleep-duration](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_sleep-duration) | - +| ihealth | physical_activity | [omh:physical-activity](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_physical-activity) | +| ihealth | blood_glucose | [omh:blood-glucose](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_blood-glucose) | +| ihealth | blood_pressure | [omh:blood-pressure](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_blood-pressure) | +| ihealth | body_weight | [omh:body-weight](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_body-weight) | +| ihealth | body_mass_index | [omh:body-mass-index](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_body-mass-index) | +| ihealth | heart_rate | [omh:heart-rate](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_heart-rate) | +| ihealth | step_count | [omh:step-count](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_step-count) | +| ihealth | sleep_duration | [omh:sleep-duration](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_sleep-duration) | 1 *The Fitbit API does not provide time zone information for the data points it returns. Furthermore, it is not possible to infer the time zone from any of the information provided. Because Open mHealth schemas require timestamps to have a time zone, we need to assign a time zone to timestamps. We set the time zone of all timestamps to UTC for consistency, even if the data may not have occurred in that time zone. This means that unless the event actually occurred in UTC, the timestamps will be incorrect. Please consider this when working with data normalized into OmH schemas that are retrieved from the Fitbit shim. We will fix this as soon as Fitbit makes changes to their API to provide time zone information.* From 82cbb17f01eb3671a2d09a5f6c995081f8847799 Mon Sep 17 00:00:00 2001 From: Emerson Farrugia Date: Tue, 1 Dec 2015 16:42:33 +0100 Subject: [PATCH 62/68] Clean up JavaDoc --- .../IHealthBloodGlucoseDataPointMapper.java | 42 +++++++------- ...xygenEndpointHeartRateDataPointMapper.java | 7 ++- .../IHealthBloodPressureDataPointMapper.java | 36 ++++++------ ...ssureEndpointHeartRateDataPointMapper.java | 6 +- .../IHealthBodyMassIndexDataPointMapper.java | 8 ++- .../IHealthBodyWeightDataPointMapper.java | 11 ++-- .../mapper/IHealthDataPointMapper.java | 57 +++++++++---------- .../IHealthHeartRateDataPointMapper.java | 4 +- ...HealthPhysicalActivityDataPointMapper.java | 10 ++-- .../IHealthSleepDurationDataPointMapper.java | 8 ++- .../IHealthStepCountDataPointMapper.java | 7 ++- .../IHealthDataPointMapperUnitTests.java | 2 - 12 files changed, 101 insertions(+), 97 deletions(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java index 27aa57c0..45557102 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java @@ -20,25 +20,28 @@ import com.google.common.collect.ImmutableMap; import org.openmhealth.schema.domain.omh.*; +import java.util.Map; import java.util.Optional; import static com.google.common.base.Preconditions.checkNotNull; +import static org.openmhealth.schema.domain.omh.BloodGlucoseUnit.*; +import static org.openmhealth.schema.domain.omh.TemporalRelationshipToMeal.*; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asOptionalString; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredDouble; /** - * A mapper that translates responses from the iHealth /glucose.json/ endpoint into {@link BloodGlucose} measures. + * A mapper that translates responses from the iHealth /glucose.json endpoint into {@link BloodGlucose} + * measures. * * @author Chris Schaefbauer - * @see - * iHealth Blood Glucose Endpoint Documentation + * @see endpoint documentation */ public class IHealthBloodGlucoseDataPointMapper extends IHealthDataPointMapper { public static final int MG_PER_DL_MAGIC_NUMBER = 0; public static final int MMOL_PER_L_MAGIC_NUMBER = 1; - protected static ImmutableMap iHealthBloodGlucoseRelationshipToMeal; + protected static Map iHealthBloodGlucoseRelationshipToMeal; @Override protected String getListNodeName() { @@ -105,13 +108,13 @@ private void initializeTemporalRelationshipToFoodMap() { ImmutableMap.Builder relationshipToMealMapBuilder = ImmutableMap.builder(); - relationshipToMealMapBuilder.put("Before_breakfast", TemporalRelationshipToMeal.BEFORE_BREAKFAST) - .put("After_breakfast", TemporalRelationshipToMeal.AFTER_BREAKFAST) - .put("Before_lunch", TemporalRelationshipToMeal.BEFORE_LUNCH) - .put("After_lunch", TemporalRelationshipToMeal.AFTER_LUNCH) - .put("Before_dinner", TemporalRelationshipToMeal.BEFORE_DINNER) - .put("After_dinner", TemporalRelationshipToMeal.AFTER_DINNER) - .put("At_midnight", TemporalRelationshipToMeal.AFTER_DINNER); + relationshipToMealMapBuilder.put("Before_breakfast", BEFORE_BREAKFAST) + .put("After_breakfast", AFTER_BREAKFAST) + .put("Before_lunch", BEFORE_LUNCH) + .put("After_lunch", AFTER_LUNCH) + .put("Before_dinner", BEFORE_DINNER) + .put("After_dinner", AFTER_DINNER) + .put("At_midnight", AFTER_DINNER); iHealthBloodGlucoseRelationshipToMeal = relationshipToMealMapBuilder.build(); @@ -123,15 +126,14 @@ private void initializeTemporalRelationshipToFoodMap() { */ protected BloodGlucoseUnit getBloodGlucoseUnitFromMagicNumber(Integer measureUnitMagicNumber) { - switch ( measureUnitMagicNumber ) { - - case MG_PER_DL_MAGIC_NUMBER: - return BloodGlucoseUnit.MILLIGRAMS_PER_DECILITER; - case MMOL_PER_L_MAGIC_NUMBER: - return BloodGlucoseUnit.MILLIMOLES_PER_LITER; - default: - throw new UnsupportedOperationException(); + if (measureUnitMagicNumber.equals(MG_PER_DL_MAGIC_NUMBER)) { + return MILLIGRAMS_PER_DECILITER; + } + else if (measureUnitMagicNumber.equals(MMOL_PER_L_MAGIC_NUMBER)) { + return MILLIMOLES_PER_LITER; + } + else { + throw new UnsupportedOperationException(); } } - } diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapper.java index 4c4e52a9..83d38ad1 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapper.java @@ -20,12 +20,13 @@ /** - * A mapper that translates responses from the iHealth /spo2.json/ endpoint into {@link HeartRate} measures. + * A mapper that translates responses from the iHealth /spo2.json endpoint into {@link HeartRate} + * measures. * * @author Emerson Farrugia * @author Chris Schaefbauer - * @see - * iHealth Blood Oxygen Endpoint Documentation + * @see endpoint + * documentation */ public class IHealthBloodOxygenEndpointHeartRateDataPointMapper extends IHealthHeartRateDataPointMapper { diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java index 2634565e..8114a850 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java @@ -22,15 +22,17 @@ import java.util.Optional; import static com.google.common.base.Preconditions.checkNotNull; +import static org.openmhealth.schema.domain.omh.BloodPressureUnit.MM_OF_MERCURY; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredDouble; /** - * A mapper that translates responses from the iHealth /bp.json/ endpoint into {@link BloodPressure} measures. + * A mapper that translates responses from the iHealth /bp.json endpoint into {@link BloodPressure} + * measures. * * @author Chris Schaefbauer - * @see - * iHealth Blood Pressure Endpoint Documentation + * @see endpoint + * documentation */ public class IHealthBloodPressureDataPointMapper extends IHealthDataPointMapper { @@ -55,13 +57,13 @@ protected Optional> asDataPoint(JsonNode listEntryNode, checkNotNull(measureUnitMagicNumber); - double systolicValue = getBloodPressureValueInMmHg(asRequiredDouble(listEntryNode, "HP"), measureUnitMagicNumber); - SystolicBloodPressure systolicBloodPressure = - new SystolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, systolicValue); + double systolicValue = + getBloodPressureValueInMmHg(asRequiredDouble(listEntryNode, "HP"), measureUnitMagicNumber); + SystolicBloodPressure systolicBloodPressure = new SystolicBloodPressure(MM_OF_MERCURY, systolicValue); - double diastolicValue = getBloodPressureValueInMmHg(asRequiredDouble(listEntryNode, "LP"), measureUnitMagicNumber); - DiastolicBloodPressure diastolicBloodPressure = - new DiastolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, diastolicValue); + double diastolicValue = + getBloodPressureValueInMmHg(asRequiredDouble(listEntryNode, "LP"), measureUnitMagicNumber); + DiastolicBloodPressure diastolicBloodPressure = new DiastolicBloodPressure(MM_OF_MERCURY, diastolicValue); BloodPressure.Builder bloodPressureBuilder = new BloodPressure.Builder(systolicBloodPressure, diastolicBloodPressure); @@ -79,14 +81,14 @@ protected Optional> asDataPoint(JsonNode listEntryNode, */ protected double getBloodPressureValueInMmHg(double rawBpValue, Integer measureUnitMagicNumber) { - switch ( measureUnitMagicNumber ) { - case MMHG_UNIT_MAGIC_NUMBER: - return rawBpValue; - case KPA_UNIT_MAGIC_NUMBER: - return rawBpValue * KPA_TO_MMHG_CONVERSION_RATE; - default: - throw new UnsupportedOperationException(); + if (measureUnitMagicNumber.equals(MMHG_UNIT_MAGIC_NUMBER)) { + return rawBpValue; + } + else if (measureUnitMagicNumber.equals(KPA_UNIT_MAGIC_NUMBER)) { + return rawBpValue * KPA_TO_MMHG_CONVERSION_RATE; + } + else { + throw new UnsupportedOperationException(); } } - } diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapper.java index b5364e73..fe00dd7a 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapper.java @@ -20,12 +20,12 @@ /** - * A mapper that translates responses from the iHealth /bp.json/ endpoint into {@link HeartRate} measures. + * A mapper that translates responses from the iHealth /bp.json endpoint into {@link HeartRate} measures. * * @author Emerson Farrugia * @author Chris Schaefbauer - * @see - * iHealth Blood Pressure Endpoint Documentation + * @see endpoint + * documentation */ public class IHealthBloodPressureEndpointHeartRateDataPointMapper extends IHealthHeartRateDataPointMapper { diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java index f1a9fb0f..f5107965 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java @@ -26,13 +26,15 @@ import static org.openmhealth.schema.domain.omh.BodyMassIndexUnit.KILOGRAMS_PER_SQUARE_METER; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredDouble; + /** - * A mapper that translates responses from the iHealth /weight.json/ endpoint into {@link BodyMassIndex} measures. + * A mapper that translates responses from the iHealth /weight.json endpoint into {@link BodyMassIndex} + * measures. * * @author Emerson Farrugia * @author Chris Schaefbauer - * @see - * iHealth Body Weight Endpoint Documentation + * @see endpoint + * documentation */ public class IHealthBodyMassIndexDataPointMapper extends IHealthDataPointMapper { diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java index 2100083c..2d8c69b7 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java @@ -26,12 +26,13 @@ /** - * A mapper that translates responses from the iHealth /weight.json/ endpoint into {@link BodyWeight} measures. + * A mapper that translates responses from the iHealth /weight.json endpoint into {@link BodyWeight} + * measures. * - * @author Emerson Farrugia * @author Chris Schaefbauer - * @see - * iHealth Body Weight Endpoint Documentation + * @author Emerson Farrugia + * @see endpoint + * documentation */ public class IHealthBodyWeightDataPointMapper extends IHealthDataPointMapper { @@ -75,7 +76,6 @@ protected Optional> asDataPoint(JsonNode listEntryNode, In } /** - * * @param listEntryNode A single entry from the response result array. * @param bodyWeightUnitType The unit type for the measure. * @return The body weight value for the list entry that is rendered in the correct unit. @@ -88,7 +88,6 @@ protected double getBodyWeightValueForUnitType(JsonNode listEntryNode, } /** - * * @param bodyWeightValue The body weight value that has been extracted from the list entry node. * @param bodyWeightUnitType The unit type for the measure. * @return A body weight value that is rendered in the correct unit. diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java index 57ed9d3e..53129af7 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java @@ -53,8 +53,7 @@ public abstract class IHealthDataPointMapper implements DataPointMapper> asDataPoints(List responseNodes) { @@ -84,8 +83,8 @@ public List> asDataPoints(List responseNodes) { /** * Creates a data point header with information describing the data point created around the measure. *

- *

Note: Additional properties within the header come from the iHealth API and are not defined by the data point - * header schema. Additional properties are subject to change.

+ * Note: Additional properties within the header come from the iHealth API and are not defined by the data point + * header schema. Additional properties are subject to change. */ protected DataPointHeader createDataPointHeader(JsonNode listEntryNode, Measure measure) { @@ -115,8 +114,7 @@ protected DataPointHeader createDataPointHeader(JsonNode listEntryNode, Measure * Sets the effective time frame of a measure builder as a single point in time using a date_time. This method does * not set time intervals. * - * @param listEntryNode A single node from the response result array that contains the MDate field that needs to - * get + * @param listEntryNode A single node from the response result array that contains the MDate field that needs to get * mapped as a date_time in the timeframe. * @param builder The measure builder to set the effective time frame. */ @@ -124,35 +122,35 @@ protected static void setEffectiveTimeFrameWithDateTimeIfExists(JsonNode listEnt Optional optionalOffsetDateTime = asOptionalLong(listEntryNode, "MDate"); - if (optionalOffsetDateTime.isPresent()) { - - Optional timeZone = asOptionalString(listEntryNode, "TimeZone"); - - if (timeZone.isPresent() && !timeZone.get().isEmpty()) { + if (!optionalOffsetDateTime.isPresent()) { + return; + } - OffsetDateTime offsetDateTimeCorrectOffset = - getDateTimeWithCorrectOffset(optionalOffsetDateTime.get(), timeZone.get()); - builder.setEffectiveTimeFrame(offsetDateTimeCorrectOffset); - } + Optional timeZone = asOptionalString(listEntryNode, "TimeZone"); - else if (asOptionalLong(listEntryNode, "TimeZone").isPresent()) { + if (timeZone.isPresent() && !timeZone.get().isEmpty()) { - Long timeZoneOffsetValue = asOptionalLong(listEntryNode, "TimeZone").get(); - String timeZoneString = timeZoneOffsetValue.toString(); + OffsetDateTime offsetDateTimeCorrectOffset = + getDateTimeWithCorrectOffset(optionalOffsetDateTime.get(), timeZone.get()); + builder.setEffectiveTimeFrame(offsetDateTimeCorrectOffset); + } + else if (asOptionalLong(listEntryNode, "TimeZone").isPresent()) { - // Zone offset cannot parse a positive string offset that's missing a '+' sign (i.e., "0200" vs "+0200") - if (timeZoneOffsetValue >= 0) { - timeZoneString = "+" + timeZoneOffsetValue.toString(); - } + Long timeZoneOffsetValue = asOptionalLong(listEntryNode, "TimeZone").get(); + String timeZoneString = timeZoneOffsetValue.toString(); - OffsetDateTime offsetDateTimeCorrectOffset = - getDateTimeWithCorrectOffset(optionalOffsetDateTime.get(), - timeZoneString); - builder.setEffectiveTimeFrame(offsetDateTimeCorrectOffset); + // Zone offset cannot parse a positive string offset that's missing a '+' sign (i.e., "0200" vs "+0200") + if (timeZoneOffsetValue >= 0) { + timeZoneString = "+" + timeZoneOffsetValue.toString(); } + OffsetDateTime offsetDateTimeCorrectOffset = + getDateTimeWithCorrectOffset(optionalOffsetDateTime.get(), + timeZoneString); + + builder.setEffectiveTimeFrame(offsetDateTimeCorrectOffset); } } @@ -209,6 +207,7 @@ protected static void setUserNoteIfExists(JsonNode listEntryNode, Builder builde /** * Sets the correct DataPointModality based on the iHealth value indicating the source of the DataPoint. + * * @param dataSourceValue The iHealth value in the list entry node indicating the source of the DataPoint. * @param builder The DataPointAcquisitionProvenance builder to set the modality. */ @@ -228,9 +227,9 @@ else if (dataSourceValue.equals(DATA_SOURCE_MANUAL)) { protected abstract String getListNodeName(); /** - * @return The name of the JSON property whose value indicates the unit of measure used to render the values in - * the response. This is different per endpoint and some endpoints do not provide any units, in which case, the - * value should be an empty Optional. + * @return The name of the JSON property whose value indicates the unit of measure used to render the values in the + * response. This is different per endpoint and some endpoints do not provide any units, in which case, the value + * should be an empty Optional. */ protected abstract Optional getMeasureUnitNodeName(); diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java index 2c69344f..a7d91f25 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java @@ -26,8 +26,7 @@ /** - * Base class that handles mapping iHealth responses from endpoints containing heart rate information into {@link - * HeartRate} measures + * An abstract mapper that maps iHealth responses into {@link HeartRate} measures. * * @author Chris Schaefbauer * @author Emerson Farrugia @@ -45,7 +44,6 @@ protected Optional> asDataPoint(JsonNode listEntryNode, Int double heartRateValue = asRequiredDouble(listEntryNode, "HR"); if (heartRateValue == 0) { - return Optional.empty(); } diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java index fb1823bf..b6c101bc 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java @@ -27,13 +27,13 @@ /** - * A mapper that translates responses from the iHealth /sport.json/ endpoint into {@link PhysicalActivity} measures. + * A mapper that translates responses from the iHealth /sport.json endpoint into {@link PhysicalActivity} + * measures. * - * @author Emerson Farrugia * @author Chris Schaefbauer - * - * @see - * iHealth Sport Endpoint Documentation + * @author Emerson Farrugia + * @see endpoint + * documentation */ public class IHealthPhysicalActivityDataPointMapper extends IHealthDataPointMapper { diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapper.java index 2338d7a5..4afae11a 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapper.java @@ -24,12 +24,14 @@ import static org.openmhealth.schema.domain.omh.TimeInterval.*; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*; + /** - * A mapper that translates responses from the iHealth /sleep.json/ endpoint into {@link SleepDuration} measures. + * A mapper that translates responses from the iHealth /sleep.json endpoint into {@link SleepDuration} + * measures. * * @author Chris Schaefbauer - * @see - * iHealth Sleep Endpoint Documentation + * @see endpoint + * documentation */ public class IHealthSleepDurationDataPointMapper extends IHealthDataPointMapper { diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java index a85713fc..0f6f5f47 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java @@ -30,11 +30,12 @@ /** - * A mapper that translates responses from the iHealth /activity.json/ endpoint into {@link StepCount} measures. + * A mapper that translates responses from the iHealth /activity.json endpoint into {@link StepCount} + * measures. * * @author Chris Schaefbauer - * @see - * iHealth Activity Endpoint Documentation + * @see endpoint + * documentation */ public class IHealthStepCountDataPointMapper extends IHealthDataPointMapper { diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java index 16c0ca74..71622dee 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java @@ -32,7 +32,6 @@ */ public class IHealthDataPointMapperUnitTests extends DataPointMapperUnitTests { - protected void testDataPointHeader(DataPointHeader testHeader, SchemaId schemaId, DataPointModality modality, String externalId, OffsetDateTime updatedDateTime) { @@ -45,5 +44,4 @@ protected void testDataPointHeader(DataPointHeader testHeader, SchemaId schemaId assertThat(testHeader.getAcquisitionProvenance().getAdditionalProperties().get("source_updated_date_time"), equalTo(updatedDateTime)); } - } From c50991b0df5c327e3563435c0963f682d09dd39e Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Tue, 1 Dec 2015 13:32:15 -0700 Subject: [PATCH 63/68] Clean-up iHealth mappers, add static imports --- .../openmhealth/shim/ihealth/IHealthShim.java | 8 +++--- .../IHealthBloodGlucoseDataPointMapper.java | 3 ++- .../IHealthBodyWeightDataPointMapper.java | 8 +++--- .../mapper/IHealthDataPointMapper.java | 13 +++++----- ...HealthPhysicalActivityDataPointMapper.java | 4 +-- ...hBloodGlucoseDataPointMapperUnitTests.java | 14 +++++----- ...ointHeartRateDataPointMapperUnitTests.java | 3 ++- ...BloodPressureDataPointMapperUnitTests.java | 12 +++++---- ...ointHeartRateDataPointMapperUnitTests.java | 26 ++++++++----------- ...BodyMassIndexDataPointMapperUnitTests.java | 6 ++--- ...lthBodyWeightDataPointMapperUnitTests.java | 26 ++++++++++--------- .../IHealthDataPointMapperUnitTests.java | 3 ++- ...ealthDatapointMapperDateTimeUnitTests.java | 12 ++++----- ...SleepDurationDataPointMapperUnitTests.java | 3 ++- ...althStepCountDataPointMapperUnitTests.java | 8 +++--- 15 files changed, 79 insertions(+), 70 deletions(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java index 42f14475..56e00a18 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java @@ -5,7 +5,6 @@ import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Lists; import org.openmhealth.shim.*; import org.openmhealth.shim.ihealth.mapper.*; import org.slf4j.Logger; @@ -43,6 +42,7 @@ import java.util.List; import java.util.Map; +import static com.google.common.collect.Lists.newArrayList; import static java.util.Collections.singletonList; import static org.openmhealth.shim.ihealth.IHealthShim.IHealthDataTypes.*; import static org.slf4j.LoggerFactory.getLogger; @@ -146,7 +146,7 @@ public enum IHealthDataTypes implements ShimDataType { BLOOD_PRESSURE(singletonList("bp.json")), BODY_WEIGHT(singletonList("weight.json")), BODY_MASS_INDEX(singletonList("weight.json")), - HEART_RATE(Lists.newArrayList("bp.json", "spo2.json")), + HEART_RATE(newArrayList("bp.json", "spo2.json")), STEP_COUNT(singletonList("activity.json")), SLEEP_DURATION(singletonList("sleep.json")); @@ -197,7 +197,7 @@ protected ResponseEntity getData(OAuth2RestOperations restTemp String scValue = getScValue(); List svValues = getSvValues(dataType); - List responseEntities = Lists.newArrayList(); + List responseEntities = newArrayList(); int i = 0; @@ -322,7 +322,7 @@ private List getSvValues(IHealthDataTypes dataType) { case SLEEP_DURATION: return singletonList(serialValues.get("sleepSV")); case HEART_RATE: - return Lists.newArrayList(serialValues.get("bloodPressureSV"), serialValues.get("spo2SV")); + return newArrayList(serialValues.get("bloodPressureSV"), serialValues.get("spo2SV")); default: throw new UnsupportedOperationException(); } diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java index 45557102..df9e1e59 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java @@ -24,7 +24,8 @@ import java.util.Optional; import static com.google.common.base.Preconditions.checkNotNull; -import static org.openmhealth.schema.domain.omh.BloodGlucoseUnit.*; +import static org.openmhealth.schema.domain.omh.BloodGlucoseUnit.MILLIGRAMS_PER_DECILITER; +import static org.openmhealth.schema.domain.omh.BloodGlucoseUnit.MILLIMOLES_PER_LITER; import static org.openmhealth.schema.domain.omh.TemporalRelationshipToMeal.*; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asOptionalString; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredDouble; diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java index 2d8c69b7..c8b1717e 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java @@ -107,12 +107,12 @@ The conversion factor handles conversions from unsupported mass units (currently ValueIntoSchema = ValueFromApi * Conversion - We map stone into kg because it is the SI unit for mass and the most widely accepted for measuring human + We map STONE into KG because it is the SI unit for mass and the most widely accepted for measuring human body weight in a clinical/scientific context. */ - kg(0, MassUnit.KILOGRAM, 1), - lb(1, MassUnit.POUND, 1), - stone(2, MassUnit.KILOGRAM, STONE_TO_KG_FACTOR); + KG(0, MassUnit.KILOGRAM, 1), + LB(1, MassUnit.POUND, 1), + STONE(2, MassUnit.KILOGRAM, STONE_TO_KG_FACTOR); private final MassUnit omhUnit; private final double conversionFactorToOmh; diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java index 53129af7..9246f3f7 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java @@ -24,7 +24,6 @@ import org.openmhealth.schema.domain.omh.Measure; import org.openmhealth.shim.common.mapper.DataPointMapper; -import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.ZoneOffset; @@ -33,9 +32,11 @@ import java.util.UUID; import static com.google.common.base.Preconditions.checkNotNull; +import static java.time.Instant.ofEpochSecond; +import static java.time.OffsetDateTime.ofInstant; import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; -import static org.openmhealth.schema.domain.omh.Measure.*; +import static org.openmhealth.schema.domain.omh.Measure.Builder; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*; @@ -102,7 +103,7 @@ protected DataPointHeader createDataPointHeader(JsonNode listEntryNode, Measure asOptionalLong(listEntryNode, "LastChangeTime").ifPresent( lastUpdatedInUnixSecs -> acquisitionProvenance.setAdditionalProperty("source_updated_date_time", - OffsetDateTime.ofInstant(Instant.ofEpochSecond(lastUpdatedInUnixSecs), ZoneId.of("Z")))); + ofInstant(ofEpochSecond(lastUpdatedInUnixSecs), ZoneId.of("Z")))); return new DataPointHeader.Builder(UUID.randomUUID().toString(), measure.getSchemaId()) .setAcquisitionProvenance(acquisitionProvenance) @@ -164,8 +165,8 @@ protected static OffsetDateTime getDateTimeWithCorrectOffset(Long dateTimeInUnix // Since the timestamps are in local time, we can use the local date time provided by rendering the timestamp // in UTC, then translating that local time to the appropriate offset. - OffsetDateTime offsetDateTimeFromOffsetInstant = OffsetDateTime.ofInstant( - Instant.ofEpochSecond(dateTimeInUnixSecondsWithLocalTimeOffset), + OffsetDateTime offsetDateTimeFromOffsetInstant = ofInstant( + ofEpochSecond(dateTimeInUnixSecondsWithLocalTimeOffset), ZoneId.of("Z")); return offsetDateTimeFromOffsetInstant.toLocalDateTime().atOffset(ZoneOffset.of(timeZoneString)); @@ -182,7 +183,7 @@ protected static OffsetDateTime getDateTimeAtStartOfDayWithCorrectOffset( // Since the timestamps are in local time, we can use the local date time provided by rendering the timestamp // in UTC, then translating that local time to the appropriate offset. OffsetDateTime dateTimeFromOffsetInstant = - OffsetDateTime.ofInstant(Instant.ofEpochSecond(dateTimeInUnixSecondsWithLocalTimeOffset), + ofInstant(ofEpochSecond(dateTimeInUnixSecondsWithLocalTimeOffset), ZoneId.of("Z")); return dateTimeFromOffsetInstant.toLocalDate().atStartOfDay().atOffset(ZoneOffset.of(timeZoneString)); diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java index b6c101bc..d96d06d5 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java @@ -19,10 +19,10 @@ import com.fasterxml.jackson.databind.JsonNode; import org.openmhealth.schema.domain.omh.DataPoint; import org.openmhealth.schema.domain.omh.PhysicalActivity; -import org.openmhealth.schema.domain.omh.TimeInterval; import java.util.Optional; +import static org.openmhealth.schema.domain.omh.TimeInterval.ofStartDateTimeAndEndDateTime; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*; @@ -75,7 +75,7 @@ protected Optional> asDataPoint(JsonNode listEntryNo timeZoneString = "+" + timeZoneOffsetValue.toString(); } - physicalActivityBuilder.setEffectiveTimeFrame(TimeInterval.ofStartDateTimeAndEndDateTime( + physicalActivityBuilder.setEffectiveTimeFrame(ofStartDateTimeAndEndDateTime( getDateTimeWithCorrectOffset(startTimeUnixEpochSecs.get(), timeZoneString), getDateTimeWithCorrectOffset(endTimeUnixEpochSecs.get(), timeZoneString))); } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java index a6b66f12..ecaf346a 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapperUnitTests.java @@ -33,8 +33,10 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; import static org.openmhealth.schema.domain.omh.BloodGlucose.SCHEMA_ID; +import static org.openmhealth.schema.domain.omh.BloodGlucoseUnit.*; import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; +import static org.openmhealth.schema.domain.omh.TemporalRelationshipToMeal.*; /** @@ -68,9 +70,9 @@ public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { public void asDataPointsShouldReturnCorrectSensedDataPoints() { BloodGlucose.Builder expectedBloodGlucoseBuilder = new BloodGlucose.Builder( - new TypedUnitValue<>(BloodGlucoseUnit.MILLIGRAMS_PER_DECILITER, 60)) + new TypedUnitValue<>(MILLIGRAMS_PER_DECILITER, 60)) .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T12:03:27-08:00")) - .setTemporalRelationshipToMeal(TemporalRelationshipToMeal.BEFORE_BREAKFAST) + .setTemporalRelationshipToMeal(BEFORE_BREAKFAST) .setUserNotes("Such glucose, much blood."); assertThat(dataPoints.get(0).getBody(), equalTo(expectedBloodGlucoseBuilder.build())); @@ -86,8 +88,8 @@ public void asDataPointsShouldReturnCorrectSensedDataPoints() { public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { BloodGlucose.Builder expectedBloodGlucoseBuilder = - new BloodGlucose.Builder(new TypedUnitValue<>(BloodGlucoseUnit.MILLIGRAMS_PER_DECILITER, 70)) - .setTemporalRelationshipToMeal(TemporalRelationshipToMeal.AFTER_BREAKFAST) + new BloodGlucose.Builder(new TypedUnitValue<>(MILLIGRAMS_PER_DECILITER, 70)) + .setTemporalRelationshipToMeal(AFTER_BREAKFAST) .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-24T14:44:40-06:00")); assertThat(dataPoints.get(1).getBody(), equalTo(expectedBloodGlucoseBuilder.build())); @@ -111,8 +113,8 @@ public void asDataPointsShouldReturnNoDataPointsWhenBloodGlucoseListIsEmpty() th @Test public void getBloodGlucoseUnitFromMagicNumberShouldReturnCorrectBloodGlucoseUnit() { - assertThat(mapper.getBloodGlucoseUnitFromMagicNumber(0), equalTo(BloodGlucoseUnit.MILLIGRAMS_PER_DECILITER)); - assertThat(mapper.getBloodGlucoseUnitFromMagicNumber(1), equalTo(BloodGlucoseUnit.MILLIMOLES_PER_LITER)); + assertThat(mapper.getBloodGlucoseUnitFromMagicNumber(0), equalTo(MILLIGRAMS_PER_DECILITER)); + assertThat(mapper.getBloodGlucoseUnitFromMagicNumber(1), equalTo(MILLIMOLES_PER_LITER)); } @Test(expectedExceptions = UnsupportedOperationException.class) diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapperUnitTests.java index b8a4588f..b1a168ee 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodOxygenEndpointHeartRateDataPointMapperUnitTests.java @@ -33,6 +33,7 @@ import static org.hamcrest.Matchers.empty; import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; +import static org.openmhealth.schema.domain.omh.HeartRate.*; /** @@ -73,7 +74,7 @@ public void asDataPointsShouldReturnCorrectSensedDataPoints() { assertThat(dataPoints.get(0).getBody(), equalTo(expectedHeartRateBuilder.build())); - testDataPointHeader(dataPoints.get(0).getHeader(), HeartRate.SCHEMA_ID, SENSED, + testDataPointHeader(dataPoints.get(0).getHeader(), SCHEMA_ID, SENSED, "d7fb9db14b0fc3e8e1635720c28bda64", OffsetDateTime.parse("2015-09-23T21:46:00Z")); } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java index d53cbf3e..9a06c4b2 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapperUnitTests.java @@ -30,6 +30,8 @@ import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; +import static org.openmhealth.schema.domain.omh.BloodPressure.*; +import static org.openmhealth.schema.domain.omh.BloodPressureUnit.*; import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; @@ -65,8 +67,8 @@ public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { public void asDataPointsShouldReturnCorrectSensedDataPoints() { BloodPressure expectedBloodPressure = new BloodPressure.Builder( - new SystolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, 120), - new DiastolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, 90)) + new SystolicBloodPressure(MM_OF_MERCURY, 120), + new DiastolicBloodPressure(MM_OF_MERCURY, 90)) .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T12:04:23-08:00")) .build(); @@ -74,7 +76,7 @@ public void asDataPointsShouldReturnCorrectSensedDataPoints() { DataPointHeader testHeader = dataPoints.get(0).getHeader(); - testDataPointHeader(testHeader, BloodPressure.SCHEMA_ID, SENSED, "c62b84d9d4b7480a8ff2aef1465aa454", + testDataPointHeader(testHeader, SCHEMA_ID, SENSED, "c62b84d9d4b7480a8ff2aef1465aa454", OffsetDateTime.parse("2015-09-17T20:04:30Z")); } @@ -83,8 +85,8 @@ public void asDataPointsShouldReturnCorrectSensedDataPoints() { public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { BloodPressure expectedBloodPressure = new BloodPressure.Builder( - new SystolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, 130), - new DiastolicBloodPressure(BloodPressureUnit.MM_OF_MERCURY, 95)) + new SystolicBloodPressure(MM_OF_MERCURY, 130), + new DiastolicBloodPressure(MM_OF_MERCURY, 95)) .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T14:07:45-06:00")) .setUserNotes("BP on the up and up.") .build(); diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java index 60730fa3..1ca5931d 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests.java @@ -20,7 +20,7 @@ import com.fasterxml.jackson.databind.JsonNode; import org.openmhealth.schema.domain.omh.DataPoint; import org.openmhealth.schema.domain.omh.HeartRate; -import org.springframework.core.io.ClassPathResource; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; @@ -29,13 +29,12 @@ import java.util.List; import static java.util.Collections.singletonList; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.collection.IsEmptyCollection.empty; import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; +import static org.openmhealth.schema.domain.omh.HeartRate.SCHEMA_ID; /** @@ -53,38 +52,37 @@ public class IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests exten @BeforeTest public void initializeResponseNodes() throws IOException { - ClassPathResource resource = - new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json"); - responseNode = objectMapper.readTree(resource.getInputStream()); + responseNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json"); + } + + @BeforeMethod + public void initializeDataPoints() { + dataPoints = mapper.asDataPoints(singletonList(responseNode)); } + @Test public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); assertThat(dataPoints.size(), equalTo(2)); } @Test public void asDataPointsShouldReturnCorrectSensedDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - HeartRate.Builder expectedHeartRateBuilder = new HeartRate.Builder(100) .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T12:04:23-08:00")); HeartRate expectedSensedHeartRate = expectedHeartRateBuilder.build(); assertThat(dataPoints.get(0).getBody(), equalTo(expectedSensedHeartRate)); - testDataPointHeader(dataPoints.get(0).getHeader(), HeartRate.SCHEMA_ID, SENSED, + testDataPointHeader(dataPoints.get(0).getHeader(), SCHEMA_ID, SENSED, "c62b84d9d4b7480a8ff2aef1465aa454", OffsetDateTime.parse("2015-09-17T20:04:30Z")); } @Test public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - HeartRate expectedHeartRate = new HeartRate.Builder(75) .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T14:07:45-06:00")) .setUserNotes("BP on the up and up.") @@ -97,8 +95,6 @@ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { @Test public void asDataPointsShouldReturnCorrectUserNotesWithDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); - assertThat(dataPoints.get(0).getBody().getUserNotes(), nullValue()); assertThat(dataPoints.get(1).getBody().getUserNotes(), equalTo("BP on the up and up.")); } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java index 7e7c129c..43d38d6c 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapperUnitTests.java @@ -18,7 +18,6 @@ import com.fasterxml.jackson.databind.JsonNode; import org.openmhealth.schema.domain.omh.BodyMassIndex; -import org.openmhealth.schema.domain.omh.BodyMassIndexUnit; import org.openmhealth.schema.domain.omh.DataPoint; import org.openmhealth.schema.domain.omh.TypedUnitValue; import org.testng.annotations.BeforeMethod; @@ -35,6 +34,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; import static org.openmhealth.schema.domain.omh.BodyMassIndex.SCHEMA_ID; +import static org.openmhealth.schema.domain.omh.BodyMassIndexUnit.KILOGRAMS_PER_SQUARE_METER; import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; @@ -70,7 +70,7 @@ public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { public void asDataPointsShouldReturnCorrectSensedDataPoints() { BodyMassIndex.Builder expectedBodyMassIndexBuilder = new BodyMassIndex.Builder(new TypedUnitValue<>( - BodyMassIndexUnit.KILOGRAMS_PER_SQUARE_METER, 22.56052563257619)) + KILOGRAMS_PER_SQUARE_METER, 22.56052563257619)) .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T12:04:09-08:00")); assertThat(dataPoints.get(0).getBody(), equalTo(expectedBodyMassIndexBuilder.build())); @@ -83,7 +83,7 @@ public void asDataPointsShouldReturnCorrectSensedDataPoints() { public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { BodyMassIndex.Builder expectedBodyMassIndexBuilder = new BodyMassIndex.Builder( - new TypedUnitValue<>(BodyMassIndexUnit.KILOGRAMS_PER_SQUARE_METER, 22.56052398681641)) + new TypedUnitValue<>(KILOGRAMS_PER_SQUARE_METER, 22.56052398681641)) .setEffectiveTimeFrame(OffsetDateTime.parse("2015-09-17T14:07:57-06:00")) .setUserNotes("Weight so good, look at me now"); diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java index 2b7404c0..94b29f79 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapperUnitTests.java @@ -27,14 +27,13 @@ import java.util.List; import static java.util.Collections.singletonList; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.collection.IsEmptyCollection.empty; +import static org.openmhealth.schema.domain.omh.BodyWeight.*; import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; -import static org.openmhealth.shim.ihealth.mapper.IHealthBodyWeightDataPointMapper.IHealthBodyWeightUnit; +import static org.openmhealth.shim.ihealth.mapper.IHealthBodyWeightDataPointMapper.*; /** @@ -75,7 +74,7 @@ public void asDataPointsShouldReturnCorrectSensedDataPoints() { DataPointHeader dataPointHeader = dataPoints.get(0).getHeader(); - testDataPointHeader(dataPointHeader, BodyWeight.SCHEMA_ID, SENSED, "5fe5893c418b48cd8da7954f8b6c2f36", + testDataPointHeader(dataPointHeader, SCHEMA_ID, SENSED, "5fe5893c418b48cd8da7954f8b6c2f36", OffsetDateTime.parse("2015-09-17T20:04:17Z")); } @@ -89,7 +88,7 @@ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { assertThat(dataPoints.get(1).getBody(), equalTo(expectedBodyWeightBuilder.build())); - testDataPointHeader(dataPoints.get(1).getHeader(), BodyWeight.SCHEMA_ID, SELF_REPORTED, + testDataPointHeader(dataPoints.get(1).getHeader(), SCHEMA_ID, SELF_REPORTED, "b702a3a5e998f2fca268df6daaa69871", OffsetDateTime.parse("2015-09-17T20:08:00Z")); } @@ -121,17 +120,20 @@ public void asDataPointsShouldReturnNoDataPointsWhenWeightListIsEmpty() throws I @Test public void getBodyWeightValueForUnitTypeShouldReturnCorrectValueForOmhCompatibleTypes() { - double bodyWeightValueForUnitType = mapper.getBodyWeightValueForUnitType(66.3, IHealthBodyWeightUnit.kg); + double bodyWeightValueForUnitType = mapper.getBodyWeightValueForUnitType(66.3, + IHealthBodyWeightUnit.KG); assertThat(bodyWeightValueForUnitType, equalTo(66.3)); - bodyWeightValueForUnitType = mapper.getBodyWeightValueForUnitType(100.5, IHealthBodyWeightUnit.lb); + bodyWeightValueForUnitType = mapper.getBodyWeightValueForUnitType(100.5, + IHealthBodyWeightUnit.LB); assertThat(bodyWeightValueForUnitType, equalTo(100.5)); } @Test public void getBodyWeightValueForUnitTypeShouldReturnCorrectValueForOmhIncompatibleTypes() { - double bodyWeightValueForUnitType = mapper.getBodyWeightValueForUnitType(12.4, IHealthBodyWeightUnit.stone); + double bodyWeightValueForUnitType = mapper.getBodyWeightValueForUnitType(12.4, + IHealthBodyWeightUnit.STONE); assertThat(bodyWeightValueForUnitType, equalTo(78.74372)); } @@ -139,9 +141,9 @@ public void getBodyWeightValueForUnitTypeShouldReturnCorrectValueForOmhIncompati @Test public void getOmhUnitInBodyWeightUnitTypeShouldReturnCorrectMassUnits() { - assertThat(IHealthBodyWeightUnit.kg.getOmhUnit(), equalTo(MassUnit.KILOGRAM)); - assertThat(IHealthBodyWeightUnit.lb.getOmhUnit(), equalTo(MassUnit.POUND)); - assertThat(IHealthBodyWeightUnit.stone.getOmhUnit(), equalTo(MassUnit.KILOGRAM)); + assertThat(IHealthBodyWeightUnit.KG.getOmhUnit(), equalTo(MassUnit.KILOGRAM)); + assertThat(IHealthBodyWeightUnit.LB.getOmhUnit(), equalTo(MassUnit.POUND)); + assertThat(IHealthBodyWeightUnit.STONE.getOmhUnit(), equalTo(MassUnit.KILOGRAM)); } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java index 71622dee..d7efdf07 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapperUnitTests.java @@ -25,6 +25,7 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; +import static org.openmhealth.shim.ihealth.mapper.IHealthDataPointMapper.*; /** @@ -38,7 +39,7 @@ protected void testDataPointHeader(DataPointHeader testHeader, SchemaId schemaId assertThat(testHeader.getBodySchemaId(), equalTo(schemaId)); assertThat(testHeader.getAcquisitionProvenance().getModality(), equalTo(modality)); assertThat(testHeader.getAcquisitionProvenance().getSourceName(), - equalTo(IHealthDataPointMapper.RESOURCE_API_SOURCE_NAME)); + equalTo(RESOURCE_API_SOURCE_NAME)); assertThat(testHeader.getAcquisitionProvenance().getAdditionalProperties().get("external_id"), equalTo( externalId)); assertThat(testHeader.getAcquisitionProvenance().getAdditionalProperties().get("source_updated_date_time"), diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java index 2751856b..e90a5e4b 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java @@ -28,6 +28,7 @@ import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsNull.notNullValue; +import static org.openmhealth.shim.ihealth.mapper.IHealthDataPointMapper.*; /** @@ -47,7 +48,7 @@ public void initializeBuilder() { public void setEffectiveTimeFrameShouldNotAddTimeFrameWhenTimeZoneIsMissing() throws IOException { JsonNode timeInfoNode = createResponseNodeWithTimeZone(null); - IHealthDataPointMapper.setEffectiveTimeFrameWithDateTimeIfExists(timeInfoNode, builder); + setEffectiveTimeFrameWithDateTimeIfExists(timeInfoNode, builder); assertThat(builder.build().getEffectiveTimeFrame(), nullValue()); } @@ -57,7 +58,7 @@ public void setEffectiveTimeFrameShouldNotAddTimeFrameWhenTimeZoneIsEmpty() thro JsonNode timeInfoNode = createResponseNodeWithTimeZone("\"\""); - IHealthDataPointMapper.setEffectiveTimeFrameWithDateTimeIfExists(timeInfoNode, builder); + setEffectiveTimeFrameWithDateTimeIfExists(timeInfoNode, builder); assertThat(builder.build().getEffectiveTimeFrame(), nullValue()); } @@ -104,8 +105,7 @@ public void getDateTimeAtStartOfDayWithCorrectOffsetShouldReturnCorrectDateTimeW long startOfDayEpochSecond = OffsetDateTime.parse("2015-11-12T00:00:00Z").toEpochSecond(); - OffsetDateTime dateTimeAtStartOfDay = - IHealthDataPointMapper.getDateTimeAtStartOfDayWithCorrectOffset(startOfDayEpochSecond, "-0100"); + OffsetDateTime dateTimeAtStartOfDay = getDateTimeAtStartOfDayWithCorrectOffset(startOfDayEpochSecond, "-0100"); assertThat(dateTimeAtStartOfDay, equalTo(OffsetDateTime.parse("2015-11-12T00:00:00-01:00"))); } @@ -116,7 +116,7 @@ public void getDateTimeAtStartOfDayWithCorrectOffsetShouldReturnCorrectDateTimeW long startOfDayEpochSecond = OffsetDateTime.parse("2015-11-12T23:59:59Z").toEpochSecond(); OffsetDateTime dateTimeAtStartOfDay = - IHealthDataPointMapper.getDateTimeAtStartOfDayWithCorrectOffset(startOfDayEpochSecond, "+0100"); + getDateTimeAtStartOfDayWithCorrectOffset(startOfDayEpochSecond, "+0100"); assertThat(dateTimeAtStartOfDay, equalTo(OffsetDateTime.parse("2015-11-12T00:00:00+01:00"))); } @@ -125,7 +125,7 @@ public void testTimeFrameWhenItShouldBeSetCorrectly(String timezoneString, Strin throws IOException { JsonNode timeInfoNode = createResponseNodeWithTimeZone(timezoneString); - IHealthDataPointMapper.setEffectiveTimeFrameWithDateTimeIfExists(timeInfoNode, builder); + setEffectiveTimeFrameWithDateTimeIfExists(timeInfoNode, builder); assertThat(builder.build().getEffectiveTimeFrame(), notNullValue()); assertThat(builder.build().getEffectiveTimeFrame().getDateTime(), equalTo( diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapperUnitTests.java index 75947e22..0cdd4cf8 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapperUnitTests.java @@ -37,6 +37,7 @@ import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; import static org.openmhealth.schema.domain.omh.DurationUnit.MINUTE; +import static org.openmhealth.schema.domain.omh.SleepDuration.*; import static org.openmhealth.schema.domain.omh.TimeInterval.ofStartDateTimeAndEndDateTime; @@ -79,7 +80,7 @@ public void asDataPointsShouldReturnCorrectDataPointsWhenSensed() { assertThat(dataPoints.get(0).getBody(), equalTo(expectedSleepDurationBuilder.build())); - testDataPointHeader(dataPoints.get(0).getHeader(), SleepDuration.SCHEMA_ID, SENSED, + testDataPointHeader(dataPoints.get(0).getHeader(), SCHEMA_ID, SENSED, "7eb7292b90d710ae7b7f61b75f9425cf", OffsetDateTime.parse("2015-11-15T16:19:10Z")); } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java index 60b2a9cb..69a35e97 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapperUnitTests.java @@ -33,6 +33,8 @@ import static org.hamcrest.Matchers.empty; import static org.hamcrest.core.Is.is; import static org.openmhealth.schema.domain.omh.DataPointModality.*; +import static org.openmhealth.schema.domain.omh.DurationUnit.*; +import static org.openmhealth.schema.domain.omh.StepCount.*; /** @@ -78,11 +80,11 @@ public void asDataPointsShouldReturnCorrectDataPointsWhenSensed() { expectedStepCountBuilder.setEffectiveTimeFrame( TimeInterval.ofStartDateTimeAndDuration(OffsetDateTime.parse("2015-11-16T00:00:00+05:00"), - new DurationUnitValue(DurationUnit.DAY, 1))); + new DurationUnitValue(DAY, 1))); assertThat(dataPoints.get(0).getBody(), equalTo(expectedStepCountBuilder.build())); - testDataPointHeader(dataPoints.get(0).getHeader(), StepCount.SCHEMA_ID, SENSED, + testDataPointHeader(dataPoints.get(0).getHeader(), SCHEMA_ID, SENSED, "ac67c4ccf64af669d92569af85d19f59", OffsetDateTime.parse("2015-11-17T19:23:21Z")); } @@ -93,7 +95,7 @@ public void asDataPointsShouldReturnDataPointWithUserNoteWhenNoteIsPresent() { expectedStepCountBuilder.setEffectiveTimeFrame( TimeInterval.ofStartDateTimeAndDuration(OffsetDateTime.parse("2015-11-18T00:00:00Z"), - new DurationUnitValue(DurationUnit.DAY, 1))).setUserNotes("Great steps"); + new DurationUnitValue(DAY, 1))).setUserNotes("Great steps"); assertThat(dataPoints.get(1).getBody(), Matchers.equalTo(expectedStepCountBuilder.build())); From 82cfa81a336b071612c389d263ecff50cec039ee Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Tue, 1 Dec 2015 19:22:21 -0700 Subject: [PATCH 64/68] Refactor time frame setting in iHealth mappers --- .../IHealthTemporalRelationshipToMeal.java | 42 ++++++++++++ .../IHealthBloodGlucoseDataPointMapper.java | 40 ++--------- .../IHealthBloodPressureDataPointMapper.java | 4 +- .../IHealthBodyMassIndexDataPointMapper.java | 3 +- .../IHealthBodyWeightDataPointMapper.java | 2 +- .../mapper/IHealthDataPointMapper.java | 66 ++++++++++--------- .../IHealthHeartRateDataPointMapper.java | 4 +- ...HealthPhysicalActivityDataPointMapper.java | 5 +- .../IHealthSleepDurationDataPointMapper.java | 5 +- ...ealthDatapointMapperDateTimeUnitTests.java | 23 ++++--- 10 files changed, 109 insertions(+), 85 deletions(-) create mode 100644 shim-server/src/main/java/org/openmhealth/shim/ihealth/domain/IHealthTemporalRelationshipToMeal.java diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/domain/IHealthTemporalRelationshipToMeal.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/domain/IHealthTemporalRelationshipToMeal.java new file mode 100644 index 00000000..0e4bf169 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/domain/IHealthTemporalRelationshipToMeal.java @@ -0,0 +1,42 @@ +package org.openmhealth.shim.ihealth.domain; + +import org.openmhealth.schema.domain.omh.TemporalRelationshipToMeal; + +import java.util.Optional; + + +/** + * An enumeration of iHealth response values representing the temporal relationship between a blood glucose measure and + * a meal. + * + * @author Emerson Farrugia + */ +public enum IHealthTemporalRelationshipToMeal { + + BEFORE_BREAKFAST(TemporalRelationshipToMeal.BEFORE_BREAKFAST), + AFTER_BREAKFAST(TemporalRelationshipToMeal.AFTER_BREAKFAST), + BEFORE_LUNCH(TemporalRelationshipToMeal.BEFORE_LUNCH), + AFTER_LUNCH(TemporalRelationshipToMeal.AFTER_LUNCH), + BEFORE_DINNER(TemporalRelationshipToMeal.BEFORE_DINNER), + AFTER_DINNER(TemporalRelationshipToMeal.AFTER_DINNER), + AT_MIDNIGHT(TemporalRelationshipToMeal.AFTER_DINNER); + + private TemporalRelationshipToMeal standardConstant; + + IHealthTemporalRelationshipToMeal(TemporalRelationshipToMeal standardConstant) { + this.standardConstant = standardConstant; + } + + /** + * @return the standard constant used to refer to this temporal relationship + */ + public TemporalRelationshipToMeal getStandardConstant() { + return standardConstant; + } + + + public static Optional findByResponseValue(String responseValue) { + + return Optional.of(IHealthTemporalRelationshipToMeal.valueOf(responseValue.toUpperCase())); + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java index df9e1e59..8f1ba445 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java @@ -17,16 +17,14 @@ package org.openmhealth.shim.ihealth.mapper; import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.collect.ImmutableMap; import org.openmhealth.schema.domain.omh.*; +import org.openmhealth.shim.ihealth.domain.IHealthTemporalRelationshipToMeal; -import java.util.Map; import java.util.Optional; import static com.google.common.base.Preconditions.checkNotNull; import static org.openmhealth.schema.domain.omh.BloodGlucoseUnit.MILLIGRAMS_PER_DECILITER; import static org.openmhealth.schema.domain.omh.BloodGlucoseUnit.MILLIMOLES_PER_LITER; -import static org.openmhealth.schema.domain.omh.TemporalRelationshipToMeal.*; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asOptionalString; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredDouble; @@ -42,7 +40,6 @@ public class IHealthBloodGlucoseDataPointMapper extends IHealthDataPointMapper iHealthBloodGlucoseRelationshipToMeal; @Override protected String getListNodeName() { @@ -54,10 +51,6 @@ protected Optional getMeasureUnitNodeName() { return Optional.of("BGUnit"); } - public IHealthBloodGlucoseDataPointMapper() { - - initializeTemporalRelationshipToFoodMap(); - } @Override protected Optional> asDataPoint(JsonNode listEntryNode, Integer measureUnitMagicNumber) { @@ -79,15 +72,14 @@ protected Optional> asDataPoint(JsonNode listEntryNode, if (dinnerSituation.isPresent()) { - TemporalRelationshipToMeal temporalRelationshipToMeal = - iHealthBloodGlucoseRelationshipToMeal.get(dinnerSituation.get()); + IHealthTemporalRelationshipToMeal temporalRelationshipToMeal = + IHealthTemporalRelationshipToMeal.findByResponseValue(dinnerSituation.get()).get(); + //iHealthBloodGlucoseRelationshipToMeal.get(dinnerSituation.get()); - if (temporalRelationshipToMeal != null) { - bloodGlucoseBuilder.setTemporalRelationshipToMeal(temporalRelationshipToMeal); - } + bloodGlucoseBuilder.setTemporalRelationshipToMeal(temporalRelationshipToMeal.getStandardConstant()); } - setEffectiveTimeFrameWithDateTimeIfExists(listEntryNode, bloodGlucoseBuilder); + getEffectiveTimeFrameAsDateTime(listEntryNode).ifPresent(etf -> bloodGlucoseBuilder.setEffectiveTimeFrame(etf)); setUserNoteIfExists(listEntryNode, bloodGlucoseBuilder); BloodGlucose bloodGlucose = bloodGlucoseBuilder.build(); @@ -101,26 +93,6 @@ protected Optional> asDataPoint(JsonNode listEntryNode, return Optional.of(new DataPoint<>(createDataPointHeader(listEntryNode, bloodGlucose), bloodGlucose)); } - /** - * Maps strings used by iHealth to represent the relationship between a blood glucose measure and meals to the - * values used in OMH schema. - */ - private void initializeTemporalRelationshipToFoodMap() { - - ImmutableMap.Builder relationshipToMealMapBuilder = ImmutableMap.builder(); - - relationshipToMealMapBuilder.put("Before_breakfast", BEFORE_BREAKFAST) - .put("After_breakfast", AFTER_BREAKFAST) - .put("Before_lunch", BEFORE_LUNCH) - .put("After_lunch", AFTER_LUNCH) - .put("Before_dinner", BEFORE_DINNER) - .put("After_dinner", AFTER_DINNER) - .put("At_midnight", AFTER_DINNER); - - iHealthBloodGlucoseRelationshipToMeal = relationshipToMealMapBuilder.build(); - - } - /** * @param measureUnitMagicNumber The number from the iHealth response representing the unit of measure. * @return The corresponding OMH schema unit of measure for blood glucose. diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java index 8114a850..fcc1b65b 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java @@ -68,7 +68,9 @@ protected Optional> asDataPoint(JsonNode listEntryNode, BloodPressure.Builder bloodPressureBuilder = new BloodPressure.Builder(systolicBloodPressure, diastolicBloodPressure); - setEffectiveTimeFrameWithDateTimeIfExists(listEntryNode, bloodPressureBuilder); + getEffectiveTimeFrameAsDateTime(listEntryNode) + .ifPresent(etf -> bloodPressureBuilder.setEffectiveTimeFrame(etf)); + // setEffectiveTimeFrameWithDateTimeIfExists(listEntryNode, bloodPressureBuilder); setUserNoteIfExists(listEntryNode, bloodPressureBuilder); BloodPressure bloodPressure = bloodPressureBuilder.build(); diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java index f5107965..8859af64 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java @@ -60,7 +60,8 @@ protected Optional> asDataPoint(JsonNode listEntryNode, BodyMassIndex.Builder bodyMassIndexBuilder = new BodyMassIndex.Builder(new TypedUnitValue<>(KILOGRAMS_PER_SQUARE_METER, bmiValue)); - setEffectiveTimeFrameWithDateTimeIfExists(listEntryNode, bodyMassIndexBuilder); + getEffectiveTimeFrameAsDateTime(listEntryNode) + .ifPresent(etf -> bodyMassIndexBuilder.setEffectiveTimeFrame(etf)); setUserNoteIfExists(listEntryNode, bodyMassIndexBuilder); BodyMassIndex bodyMassIndex = bodyMassIndexBuilder.build(); diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java index c8b1717e..e22f7bd2 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java @@ -67,7 +67,7 @@ protected Optional> asDataPoint(JsonNode listEntryNode, In BodyWeight.Builder bodyWeightBuilder = new BodyWeight.Builder(new MassUnitValue(bodyWeightUnit, bodyWeightValue)); - setEffectiveTimeFrameWithDateTimeIfExists(listEntryNode, bodyWeightBuilder); + getEffectiveTimeFrameAsDateTime(listEntryNode).ifPresent(etf -> bodyWeightBuilder.setEffectiveTimeFrame(etf)); setUserNoteIfExists(listEntryNode, bodyWeightBuilder); BodyWeight bodyWeight = bodyWeightBuilder.build(); diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java index 9246f3f7..b2d844eb 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java @@ -18,12 +18,10 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.Lists; -import org.openmhealth.schema.domain.omh.DataPoint; -import org.openmhealth.schema.domain.omh.DataPointAcquisitionProvenance; -import org.openmhealth.schema.domain.omh.DataPointHeader; -import org.openmhealth.schema.domain.omh.Measure; +import org.openmhealth.schema.domain.omh.*; import org.openmhealth.shim.common.mapper.DataPointMapper; +import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.ZoneOffset; @@ -115,61 +113,64 @@ protected DataPointHeader createDataPointHeader(JsonNode listEntryNode, Measure * Sets the effective time frame of a measure builder as a single point in time using a date_time. This method does * not set time intervals. * - * @param listEntryNode A single node from the response result array that contains the MDate field that needs to get + * @param listEntryNode A single node from the response result array that contains the MDate field that needs to + * get * mapped as a date_time in the timeframe. - * @param builder The measure builder to set the effective time frame. */ - protected static void setEffectiveTimeFrameWithDateTimeIfExists(JsonNode listEntryNode, Builder builder) { + protected static Optional getEffectiveTimeFrameAsDateTime(JsonNode listEntryNode) { - Optional optionalOffsetDateTime = asOptionalLong(listEntryNode, "MDate"); + Optional weirdSeconds = asOptionalLong(listEntryNode, "MDate"); - if (!optionalOffsetDateTime.isPresent()) { - return; + if (!weirdSeconds.isPresent()) { + return Optional.empty(); } - Optional timeZone = asOptionalString(listEntryNode, "TimeZone"); + ZoneOffset zoneOffset = null; - if (timeZone.isPresent() && !timeZone.get().isEmpty()) { + // if the time zone is a JSON string + if (asOptionalString(listEntryNode, "TimeZone").isPresent() && + !asOptionalString(listEntryNode, "TimeZone").get().isEmpty()) { - OffsetDateTime offsetDateTimeCorrectOffset = - getDateTimeWithCorrectOffset(optionalOffsetDateTime.get(), timeZone.get()); - builder.setEffectiveTimeFrame(offsetDateTimeCorrectOffset); + zoneOffset = ZoneOffset.of(asOptionalString(listEntryNode, "TimeZone").get()); } - + // if the time zone is an JSON integer else if (asOptionalLong(listEntryNode, "TimeZone").isPresent()) { + Long timeZoneOffsetValue = asOptionalLong(listEntryNode, "TimeZone").get(); - String timeZoneString = timeZoneOffsetValue.toString(); + String timeZoneString = timeZoneOffsetValue.toString(); // Zone offset cannot parse a positive string offset that's missing a '+' sign (i.e., "0200" vs "+0200") if (timeZoneOffsetValue >= 0) { - timeZoneString = "+" + timeZoneOffsetValue.toString(); + + timeZoneString = "+" + timeZoneString; } - OffsetDateTime offsetDateTimeCorrectOffset = - getDateTimeWithCorrectOffset(optionalOffsetDateTime.get(), - timeZoneString); + zoneOffset = ZoneOffset.of(timeZoneString); + } - builder.setEffectiveTimeFrame(offsetDateTimeCorrectOffset); + if (zoneOffset == null) { + + return Optional.empty(); } + + return Optional.of(new TimeFrame(getDateTimeWithCorrectOffset(weirdSeconds.get(), zoneOffset))); } /** - * This method transforms the unix epoch second timestamps in iHealth responses into an {@link OffsetDateTime} with - * the correct date/time and offset. The timestamps provided in iHealth responses are not in UTC but instead offset + * This method transforms the unix epoch second timestamps in iHealth responses into an {@link + * OffsetDateTime} with + * the correct date/time and offset. The timestamps provided in iHealth responses are not in UTC but instead + * offset * to the local time zone of the data point. */ - protected static OffsetDateTime getDateTimeWithCorrectOffset(Long dateTimeInUnixSecondsWithLocalTimeOffset, - String timeZoneString) { + protected static OffsetDateTime getDateTimeWithCorrectOffset(Long localTimeAsEpochSeconds, ZoneOffset zoneOffset) { // Since the timestamps are in local time, we can use the local date time provided by rendering the timestamp // in UTC, then translating that local time to the appropriate offset. - OffsetDateTime offsetDateTimeFromOffsetInstant = ofInstant( - ofEpochSecond(dateTimeInUnixSecondsWithLocalTimeOffset), - ZoneId.of("Z")); - - return offsetDateTimeFromOffsetInstant.toLocalDateTime().atOffset(ZoneOffset.of(timeZoneString)); + return OffsetDateTime.ofInstant(Instant.ofEpochSecond(localTimeAsEpochSeconds), ZoneOffset.UTC) + .withOffsetSameLocal(zoneOffset); } /** @@ -204,6 +205,9 @@ protected static void setUserNoteIfExists(JsonNode listEntryNode, Builder builde builder.setUserNotes(note.get()); } + + //asOptionalString(listEntryNode, "Note").ifPresent(builder::setUserNotes); + } /** diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java index a7d91f25..42faa2ea 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java @@ -49,7 +49,9 @@ protected Optional> asDataPoint(JsonNode listEntryNode, Int HeartRate.Builder heartRateBuilder = new HeartRate.Builder(heartRateValue); - setEffectiveTimeFrameWithDateTimeIfExists(listEntryNode, heartRateBuilder); + getEffectiveTimeFrameAsDateTime(listEntryNode).ifPresent(etf -> heartRateBuilder.setEffectiveTimeFrame(etf)); + + //setEffectiveTimeFrameWithDateTimeIfExists(listEntryNode, heartRateBuilder); setUserNoteIfExists(listEntryNode, heartRateBuilder); HeartRate heartRate = heartRateBuilder.build(); diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java index d96d06d5..833ae7bd 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java @@ -20,6 +20,7 @@ import org.openmhealth.schema.domain.omh.DataPoint; import org.openmhealth.schema.domain.omh.PhysicalActivity; +import java.time.ZoneOffset; import java.util.Optional; import static org.openmhealth.schema.domain.omh.TimeInterval.ofStartDateTimeAndEndDateTime; @@ -76,8 +77,8 @@ protected Optional> asDataPoint(JsonNode listEntryNo } physicalActivityBuilder.setEffectiveTimeFrame(ofStartDateTimeAndEndDateTime( - getDateTimeWithCorrectOffset(startTimeUnixEpochSecs.get(), timeZoneString), - getDateTimeWithCorrectOffset(endTimeUnixEpochSecs.get(), timeZoneString))); + getDateTimeWithCorrectOffset(startTimeUnixEpochSecs.get(), ZoneOffset.of(timeZoneString)), + getDateTimeWithCorrectOffset(endTimeUnixEpochSecs.get(), ZoneOffset.of(timeZoneString)))); } PhysicalActivity physicalActivity = physicalActivityBuilder.build(); diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapper.java index 4afae11a..9fe9c71d 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapper.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode; import org.openmhealth.schema.domain.omh.*; +import java.time.ZoneOffset; import java.util.Optional; import static org.openmhealth.schema.domain.omh.TimeInterval.*; @@ -61,8 +62,8 @@ protected Optional> asDataPoint(JsonNode listEntryNode, if (timeZone.isPresent()) { sleepDurationBuilder.setEffectiveTimeFrame(ofStartDateTimeAndEndDateTime( - getDateTimeWithCorrectOffset(startTime.get(), timeZone.get()), - getDateTimeWithCorrectOffset(endTime.get(), timeZone.get()))); + getDateTimeWithCorrectOffset(startTime.get(), ZoneOffset.of(timeZone.get())), + getDateTimeWithCorrectOffset(endTime.get(), ZoneOffset.of(timeZone.get())))); } } diff --git a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java index e90a5e4b..a384bb8b 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/ihealth/mapper/IHealthDatapointMapperDateTimeUnitTests.java @@ -18,17 +18,19 @@ import com.fasterxml.jackson.databind.JsonNode; import org.openmhealth.schema.domain.omh.HeartRate; +import org.openmhealth.schema.domain.omh.TimeFrame; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.io.IOException; import java.time.OffsetDateTime; +import java.util.Optional; import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.IsNull.notNullValue; -import static org.openmhealth.shim.ihealth.mapper.IHealthDataPointMapper.*; +import static org.hamcrest.core.Is.is; +import static org.openmhealth.shim.ihealth.mapper.IHealthDataPointMapper.getDateTimeAtStartOfDayWithCorrectOffset; +import static org.openmhealth.shim.ihealth.mapper.IHealthDataPointMapper.getEffectiveTimeFrameAsDateTime; /** @@ -48,9 +50,9 @@ public void initializeBuilder() { public void setEffectiveTimeFrameShouldNotAddTimeFrameWhenTimeZoneIsMissing() throws IOException { JsonNode timeInfoNode = createResponseNodeWithTimeZone(null); - setEffectiveTimeFrameWithDateTimeIfExists(timeInfoNode, builder); + getEffectiveTimeFrameAsDateTime(timeInfoNode); - assertThat(builder.build().getEffectiveTimeFrame(), nullValue()); + assertThat(getEffectiveTimeFrameAsDateTime(timeInfoNode).isPresent(), is(false)); } @Test @@ -58,9 +60,7 @@ public void setEffectiveTimeFrameShouldNotAddTimeFrameWhenTimeZoneIsEmpty() thro JsonNode timeInfoNode = createResponseNodeWithTimeZone("\"\""); - setEffectiveTimeFrameWithDateTimeIfExists(timeInfoNode, builder); - - assertThat(builder.build().getEffectiveTimeFrame(), nullValue()); + assertThat(getEffectiveTimeFrameAsDateTime(timeInfoNode).isPresent(), is(false)); } @Test @@ -125,11 +125,10 @@ public void testTimeFrameWhenItShouldBeSetCorrectly(String timezoneString, Strin throws IOException { JsonNode timeInfoNode = createResponseNodeWithTimeZone(timezoneString); - setEffectiveTimeFrameWithDateTimeIfExists(timeInfoNode, builder); + Optional effectiveTimeFrameAsDateTime = getEffectiveTimeFrameAsDateTime(timeInfoNode); - assertThat(builder.build().getEffectiveTimeFrame(), notNullValue()); - assertThat(builder.build().getEffectiveTimeFrame().getDateTime(), equalTo( - OffsetDateTime.parse(expectedDateTime))); + assertThat(effectiveTimeFrameAsDateTime.isPresent(), is(true)); + assertThat(effectiveTimeFrameAsDateTime.get().getDateTime(), equalTo(OffsetDateTime.parse(expectedDateTime))); } public JsonNode createResponseNodeWithTimeZone(String timezoneString) throws IOException { From 95d94aee1ae47eb92ff70686b2a24cfe8a56f8fa Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Tue, 1 Dec 2015 19:52:57 -0700 Subject: [PATCH 65/68] Refactor user note setting --- .../mapper/IHealthBloodGlucoseDataPointMapper.java | 11 +++++------ .../mapper/IHealthBloodPressureDataPointMapper.java | 7 +++---- .../mapper/IHealthBodyMassIndexDataPointMapper.java | 6 +++--- .../mapper/IHealthBodyWeightDataPointMapper.java | 5 +++-- .../shim/ihealth/mapper/IHealthDataPointMapper.java | 9 +++------ .../mapper/IHealthHeartRateDataPointMapper.java | 5 ++--- .../IHealthPhysicalActivityDataPointMapper.java | 1 + .../mapper/IHealthSleepDurationDataPointMapper.java | 3 ++- .../mapper/IHealthStepCountDataPointMapper.java | 6 +++--- 9 files changed, 25 insertions(+), 28 deletions(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java index 8f1ba445..834d0b34 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodGlucoseDataPointMapper.java @@ -68,19 +68,18 @@ protected Optional> asDataPoint(JsonNode listEntryNode, BloodGlucose.Builder bloodGlucoseBuilder = new BloodGlucose.Builder(new TypedUnitValue<>(bloodGlucoseUnit, bloodGlucoseValue)); - Optional dinnerSituation = asOptionalString(listEntryNode, "DinnerSituation"); + Optional relationshipToMeal = asOptionalString(listEntryNode, "DinnerSituation"); - if (dinnerSituation.isPresent()) { + if (relationshipToMeal.isPresent()) { IHealthTemporalRelationshipToMeal temporalRelationshipToMeal = - IHealthTemporalRelationshipToMeal.findByResponseValue(dinnerSituation.get()).get(); - //iHealthBloodGlucoseRelationshipToMeal.get(dinnerSituation.get()); + IHealthTemporalRelationshipToMeal.findByResponseValue(relationshipToMeal.get()).get(); bloodGlucoseBuilder.setTemporalRelationshipToMeal(temporalRelationshipToMeal.getStandardConstant()); } - getEffectiveTimeFrameAsDateTime(listEntryNode).ifPresent(etf -> bloodGlucoseBuilder.setEffectiveTimeFrame(etf)); - setUserNoteIfExists(listEntryNode, bloodGlucoseBuilder); + getEffectiveTimeFrameAsDateTime(listEntryNode).ifPresent(bloodGlucoseBuilder::setEffectiveTimeFrame); + getUserNoteIfExists(listEntryNode).ifPresent(bloodGlucoseBuilder::setUserNotes); BloodGlucose bloodGlucose = bloodGlucoseBuilder.build(); diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java index fcc1b65b..adea11b7 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBloodPressureDataPointMapper.java @@ -68,10 +68,9 @@ protected Optional> asDataPoint(JsonNode listEntryNode, BloodPressure.Builder bloodPressureBuilder = new BloodPressure.Builder(systolicBloodPressure, diastolicBloodPressure); - getEffectiveTimeFrameAsDateTime(listEntryNode) - .ifPresent(etf -> bloodPressureBuilder.setEffectiveTimeFrame(etf)); - // setEffectiveTimeFrameWithDateTimeIfExists(listEntryNode, bloodPressureBuilder); - setUserNoteIfExists(listEntryNode, bloodPressureBuilder); + getEffectiveTimeFrameAsDateTime(listEntryNode).ifPresent(bloodPressureBuilder::setEffectiveTimeFrame); + + getUserNoteIfExists(listEntryNode).ifPresent(bloodPressureBuilder::setUserNotes); BloodPressure bloodPressure = bloodPressureBuilder.build(); return Optional.of(new DataPoint<>(createDataPointHeader(listEntryNode, bloodPressure), bloodPressure)); diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java index 8859af64..28063664 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyMassIndexDataPointMapper.java @@ -60,9 +60,9 @@ protected Optional> asDataPoint(JsonNode listEntryNode, BodyMassIndex.Builder bodyMassIndexBuilder = new BodyMassIndex.Builder(new TypedUnitValue<>(KILOGRAMS_PER_SQUARE_METER, bmiValue)); - getEffectiveTimeFrameAsDateTime(listEntryNode) - .ifPresent(etf -> bodyMassIndexBuilder.setEffectiveTimeFrame(etf)); - setUserNoteIfExists(listEntryNode, bodyMassIndexBuilder); + getEffectiveTimeFrameAsDateTime(listEntryNode).ifPresent(bodyMassIndexBuilder::setEffectiveTimeFrame); + + getUserNoteIfExists(listEntryNode).ifPresent(bodyMassIndexBuilder::setUserNotes); BodyMassIndex bodyMassIndex = bodyMassIndexBuilder.build(); return Optional.of(new DataPoint<>(createDataPointHeader(listEntryNode, bodyMassIndex), bodyMassIndex)); diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java index e22f7bd2..97c1e0ba 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthBodyWeightDataPointMapper.java @@ -67,8 +67,9 @@ protected Optional> asDataPoint(JsonNode listEntryNode, In BodyWeight.Builder bodyWeightBuilder = new BodyWeight.Builder(new MassUnitValue(bodyWeightUnit, bodyWeightValue)); - getEffectiveTimeFrameAsDateTime(listEntryNode).ifPresent(etf -> bodyWeightBuilder.setEffectiveTimeFrame(etf)); - setUserNoteIfExists(listEntryNode, bodyWeightBuilder); + getEffectiveTimeFrameAsDateTime(listEntryNode).ifPresent(bodyWeightBuilder::setEffectiveTimeFrame); + + getUserNoteIfExists(listEntryNode).ifPresent(bodyWeightBuilder::setUserNotes); BodyWeight bodyWeight = bodyWeightBuilder.build(); diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java index b2d844eb..ff5895b0 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java @@ -34,7 +34,6 @@ import static java.time.OffsetDateTime.ofInstant; import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; -import static org.openmhealth.schema.domain.omh.Measure.Builder; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*; @@ -195,19 +194,17 @@ protected static OffsetDateTime getDateTimeAtStartOfDayWithCorrectOffset( * property exists. * * @param listEntryNode A single entry from the response result array. - * @param builder The measure builder to set the user note. */ - protected static void setUserNoteIfExists(JsonNode listEntryNode, Builder builder) { + protected static Optional getUserNoteIfExists(JsonNode listEntryNode) { Optional note = asOptionalString(listEntryNode, "Note"); if (note.isPresent() && !note.get().isEmpty()) { - builder.setUserNotes(note.get()); + return note; } - //asOptionalString(listEntryNode, "Note").ifPresent(builder::setUserNotes); - + return Optional.empty(); } /** diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java index 42faa2ea..0b31f73c 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthHeartRateDataPointMapper.java @@ -49,10 +49,9 @@ protected Optional> asDataPoint(JsonNode listEntryNode, Int HeartRate.Builder heartRateBuilder = new HeartRate.Builder(heartRateValue); - getEffectiveTimeFrameAsDateTime(listEntryNode).ifPresent(etf -> heartRateBuilder.setEffectiveTimeFrame(etf)); + getEffectiveTimeFrameAsDateTime(listEntryNode).ifPresent(heartRateBuilder::setEffectiveTimeFrame); - //setEffectiveTimeFrameWithDateTimeIfExists(listEntryNode, heartRateBuilder); - setUserNoteIfExists(listEntryNode, heartRateBuilder); + getUserNoteIfExists(listEntryNode).ifPresent(heartRateBuilder::setUserNotes); HeartRate heartRate = heartRateBuilder.build(); return Optional.of(new DataPoint<>(createDataPointHeader(listEntryNode, heartRate), heartRate)); diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java index 833ae7bd..f735a677 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthPhysicalActivityDataPointMapper.java @@ -82,6 +82,7 @@ protected Optional> asDataPoint(JsonNode listEntryNo } PhysicalActivity physicalActivity = physicalActivityBuilder.build(); + return Optional.of(new DataPoint<>(createDataPointHeader(listEntryNode, physicalActivity), physicalActivity)); } } diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapper.java index 9fe9c71d..8259b37e 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthSleepDurationDataPointMapper.java @@ -67,9 +67,10 @@ protected Optional> asDataPoint(JsonNode listEntryNode, } } - setUserNoteIfExists(listEntryNode, sleepDurationBuilder); + getUserNoteIfExists(listEntryNode).ifPresent(sleepDurationBuilder::setUserNotes); SleepDuration sleepDuration = sleepDurationBuilder.build(); + asOptionalBigDecimal(listEntryNode, "Awaken") .ifPresent(awaken -> sleepDuration.setAdditionalProperty("wakeup_count", awaken)); diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java index 0f6f5f47..04a04f44 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthStepCountDataPointMapper.java @@ -18,13 +18,13 @@ import com.fasterxml.jackson.databind.JsonNode; import org.openmhealth.schema.domain.omh.DataPoint; -import org.openmhealth.schema.domain.omh.DurationUnit; import org.openmhealth.schema.domain.omh.DurationUnitValue; import org.openmhealth.schema.domain.omh.StepCount; import java.math.BigDecimal; import java.util.Optional; +import static org.openmhealth.schema.domain.omh.DurationUnit.DAY; import static org.openmhealth.schema.domain.omh.TimeInterval.ofStartDateTimeAndDuration; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*; @@ -72,11 +72,11 @@ protected Optional> asDataPoint(JsonNode listEntryNode, Int the day (23:50) or at the latest time that datapoint was synced */ stepCountBuilder.setEffectiveTimeFrame(ofStartDateTimeAndDuration( getDateTimeAtStartOfDayWithCorrectOffset(dateTimeString.get(), timeZone.get()), - new DurationUnitValue(DurationUnit.DAY, 1))); + new DurationUnitValue(DAY, 1))); } } - setUserNoteIfExists(listEntryNode, stepCountBuilder); + getUserNoteIfExists(listEntryNode).ifPresent(stepCountBuilder::setUserNotes); StepCount stepCount = stepCountBuilder.build(); From 64b507b7210216e3d9bf0d88cb5f7e225ec817af Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Tue, 1 Dec 2015 22:03:24 -0700 Subject: [PATCH 66/68] Update documentation about iHealth date/times --- .../mapper/IHealthDataPointMapper.java | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java index ff5895b0..559f38b8 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/mapper/IHealthDataPointMapper.java @@ -109,12 +109,11 @@ protected DataPointHeader createDataPointHeader(JsonNode listEntryNode, Measure } /** - * Sets the effective time frame of a measure builder as a single point in time using a date_time. This method does - * not set time intervals. + * Get an effective time frame based on the measurement date/time information in the list entry node. The effective + * time frame is set as a single point in time using an OffsetDateTime. This method does not get effective time + * frame as a time interval. * - * @param listEntryNode A single node from the response result array that contains the MDate field that needs to - * get - * mapped as a date_time in the timeframe. + * @param listEntryNode A single node from the response result array. */ protected static Optional getEffectiveTimeFrameAsDateTime(JsonNode listEntryNode) { @@ -135,7 +134,6 @@ protected static Optional getEffectiveTimeFrameAsDateTime(JsonNode li // if the time zone is an JSON integer else if (asOptionalLong(listEntryNode, "TimeZone").isPresent()) { - Long timeZoneOffsetValue = asOptionalLong(listEntryNode, "TimeZone").get(); String timeZoneString = timeZoneOffsetValue.toString(); @@ -158,16 +156,18 @@ else if (asOptionalLong(listEntryNode, "TimeZone").isPresent()) { } /** - * This method transforms the unix epoch second timestamps in iHealth responses into an {@link - * OffsetDateTime} with - * the correct date/time and offset. The timestamps provided in iHealth responses are not in UTC but instead - * offset - * to the local time zone of the data point. + * This method transforms a timestamp from an iHealth response (which is in the form of local time as epoch + * seconds) into an {@link OffsetDateTime} with the correct date/time and offset. The timestamps provided in + * iHealth responses are not unix epoch seconds in UTC but instead a unix epoch seconds value that is offset by the + * time zone of the data point. */ protected static OffsetDateTime getDateTimeWithCorrectOffset(Long localTimeAsEpochSeconds, ZoneOffset zoneOffset) { - // Since the timestamps are in local time, we can use the local date time provided by rendering the timestamp - // in UTC, then translating that local time to the appropriate offset. + /* + iHealth provides the local time of a measurement as if it had occurred in UTC, along with the timezone + offset where the measurement occurred. To retrieve the correct OffsetDateTime, we must retain the local + date/time value, but replace the timezone offset. + */ return OffsetDateTime.ofInstant(Instant.ofEpochSecond(localTimeAsEpochSeconds), ZoneOffset.UTC) .withOffsetSameLocal(zoneOffset); } @@ -190,8 +190,7 @@ protected static OffsetDateTime getDateTimeAtStartOfDayWithCorrectOffset( } /** - * Sets the user note in a measure builder with the value of the note property in the list entry node if that - * property exists. + * Gets the user note from a list entry node if that property exists. * * @param listEntryNode A single entry from the response result array. */ From 5a2a20d125a3202b26d8ee1a5392e612ca83ea64 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Wed, 2 Dec 2015 11:32:52 -0700 Subject: [PATCH 67/68] Add copyright header to shim --- .../openmhealth/shim/ihealth/IHealthShim.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java index 56e00a18..31250005 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java +++ b/shim-server/src/main/java/org/openmhealth/shim/ihealth/IHealthShim.java @@ -1,3 +1,19 @@ +/* + * 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.ihealth; import com.fasterxml.jackson.databind.JsonNode; From eee35c7f3568c9a43e0f5deb3594ba161e24db67 Mon Sep 17 00:00:00 2001 From: Chris Schaefbauer Date: Wed, 2 Dec 2015 11:55:00 -0700 Subject: [PATCH 68/68] Bump version number --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 301083bd..fda69f5f 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ subprojects { ext { javaVersion = 1.8 - shimmerVersion = '0.4.0.SNAPSHOT' + shimmerVersion = '0.4.0' omhSchemaSdkVersion = '1.0.3' }