Skip to content

Commit 0c4c3e4

Browse files
committed
Merge branch 'release/0.6.0'
2 parents df1c29c + 390e61e commit 0c4c3e4

File tree

127 files changed

+4120
-23113
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

127 files changed

+4120
-23113
lines changed

README.md

Lines changed: 17 additions & 12 deletions
Large diffs are not rendered by default.

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ subprojects {
1111
ext {
1212
javaVersion = 1.8
1313
shimmerVersion = "0.6.0"
14-
omhSchemaSdkVersion = "1.1.0"
14+
omhSchemaSdkVersion = "1.2.1"
1515
}
1616

1717
sourceCompatibility = javaVersion

shim-server/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ ext {
3333
dependencies {
3434
compile project(":java-shim-sdk")
3535
compile "commons-io:commons-io:2.4"
36+
compile "com.google.guava:guava:23.0"
3637
compile "org.hibernate:hibernate-validator"
3738
compile "org.apache.httpcomponents:httpclient"
3839
compile "org.apache.httpcomponents:httpcore"
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2017 Open mHealth
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.openmhealth.shim;
18+
19+
import org.springframework.http.client.ClientHttpRequest;
20+
import org.springframework.security.oauth2.client.DefaultOAuth2RequestAuthenticator;
21+
import org.springframework.security.oauth2.client.OAuth2ClientContext;
22+
import org.springframework.security.oauth2.client.OAuth2RequestAuthenticator;
23+
import org.springframework.security.oauth2.client.http.AccessTokenRequiredException;
24+
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
25+
import org.springframework.security.oauth2.common.OAuth2AccessToken;
26+
import org.springframework.util.StringUtils;
27+
28+
29+
/**
30+
* A customization of {@link DefaultOAuth2RequestAuthenticator} that standardizes the case of the Authorization header
31+
* token type to "Bearer". This is necessary because the default implementation doesn't work for Moves, which serves up
32+
* a "bearer" token but only accepts "Bearer" authorization headers.
33+
*
34+
* @author Dave Syer
35+
* @author Emerson Farrugia
36+
*/
37+
public class CaseStandardizingOAuth2RequestAuthenticator implements OAuth2RequestAuthenticator {
38+
39+
@Override
40+
public void authenticate(OAuth2ProtectedResourceDetails resource, OAuth2ClientContext clientContext,
41+
ClientHttpRequest request) {
42+
43+
OAuth2AccessToken accessToken = clientContext.getAccessToken();
44+
if (accessToken == null) {
45+
throw new AccessTokenRequiredException(resource);
46+
}
47+
48+
String tokenType = accessToken.getTokenType();
49+
50+
if (!StringUtils.hasText(tokenType) || tokenType.equalsIgnoreCase(OAuth2AccessToken.BEARER_TYPE)) {
51+
tokenType = OAuth2AccessToken.BEARER_TYPE; // we'll assume basic bearer token type if none is specified.
52+
}
53+
54+
request.getHeaders().set("Authorization", String.format("%s %s", tokenType, accessToken.getValue()));
55+
}
56+
}

shim-server/src/main/java/org/openmhealth/shim/OAuth2Shim.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,8 @@ protected OAuth2RestOperations restTemplate(String stateKey, String code) {
274274
new AccessParameterClientTokenServices(accessParametersRepo));
275275
restTemplate.setAccessTokenProvider(tokenProviderChain);
276276

277+
restTemplate.setAuthenticator(new CaseStandardizingOAuth2RequestAuthenticator());
278+
277279
return restTemplate;
278280
}
279281

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2017 Open mHealth
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.openmhealth.shim;
18+
19+
import java.util.Optional;
20+
import java.util.stream.Stream;
21+
22+
23+
/**
24+
* A set of utility methods to help with {@link Optional} {@link Stream} objects.
25+
*
26+
* @author Emerson Farrugia
27+
*/
28+
public class OptionalStreamSupport {
29+
30+
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
31+
public static <O> Stream<O> asStream(Optional<O> optional) {
32+
33+
return optional.map(Stream::of).orElseGet(Stream::empty);
34+
}
35+
}

shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitSleepMeasureDataPointMapper.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
/*
2+
* Copyright 2017 Open mHealth
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
117
package org.openmhealth.shim.fitbit.mapper;
218

319
import com.fasterxml.jackson.databind.JsonNode;

shim-server/src/main/java/org/openmhealth/shim/googlefit/GoogleFitClientSettings.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ public class GoogleFitClientSettings extends OAuth2ClientSettings {
3434

3535
private List<String> scopes = Arrays.asList(
3636
"https://www.googleapis.com/auth/fitness.activity.read",
37-
"https://www.googleapis.com/auth/fitness.body.read"
37+
"https://www.googleapis.com/auth/fitness.body.read",
38+
"https://www.googleapis.com/auth/fitness.location.read"
3839
);
3940

4041
@Override

shim-server/src/main/java/org/openmhealth/shim/googlefit/GoogleFitShim.java

Lines changed: 51 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
import java.time.ZoneOffset;
4949
import java.util.Map;
5050

51-
import static java.util.Collections.singletonList;
5251
import static org.slf4j.LoggerFactory.getLogger;
5352
import static org.springframework.http.ResponseEntity.ok;
5453

@@ -114,8 +113,10 @@ public ShimDataType[] getShimDataTypes() {
114113
GoogleFitDataTypes.BODY_HEIGHT,
115114
GoogleFitDataTypes.BODY_WEIGHT,
116115
GoogleFitDataTypes.CALORIES_BURNED,
116+
GoogleFitDataTypes.GEOPOSITION,
117117
GoogleFitDataTypes.HEART_RATE,
118118
GoogleFitDataTypes.PHYSICAL_ACTIVITY,
119+
GoogleFitDataTypes.SPEED,
119120
GoogleFitDataTypes.STEP_COUNT
120121
};
121122
}
@@ -125,22 +126,21 @@ public enum GoogleFitDataTypes implements ShimDataType {
125126
BODY_HEIGHT("derived:com.google.height:com.google.android.gms:merge_height"),
126127
BODY_WEIGHT("derived:com.google.weight:com.google.android.gms:merge_weight"),
127128
CALORIES_BURNED("derived:com.google.calories.expended:com.google.android.gms:merge_calories_expended"),
129+
GEOPOSITION("derived:com.google.location.sample:com.google.android.gms:merge_location_samples"),
128130
HEART_RATE("derived:com.google.heart_rate.bpm:com.google.android.gms:merge_heart_rate_bpm"),
129131
PHYSICAL_ACTIVITY("derived:com.google.activity.segment:com.google.android.gms:merge_activity_segments"),
132+
SPEED("derived:com.google.speed:com.google.android.gms:merge_speed"),
130133
STEP_COUNT("derived:com.google.step_count.delta:com.google.android.gms:merge_step_deltas");
131134

132135
private final String streamId;
133136

134137
GoogleFitDataTypes(String streamId) {
135-
136138
this.streamId = streamId;
137139
}
138140

139141
public String getStreamId() {
140-
141142
return streamId;
142143
}
143-
144144
}
145145

146146
protected ResponseEntity<ShimDataResponse> getData(OAuth2RestOperations restTemplate,
@@ -157,31 +157,34 @@ protected ResponseEntity<ShimDataResponse> getData(OAuth2RestOperations restTemp
157157
+ " in shimDataRequest, cannot retrieve data.");
158158
}
159159

160-
161160
OffsetDateTime todayInUTC =
162161
LocalDate.now().atStartOfDay().atOffset(ZoneOffset.UTC);
163162

164163
OffsetDateTime startDateInUTC = shimDataRequest.getStartDateTime() == null ?
164+
165165
todayInUTC.minusDays(1) : shimDataRequest.getStartDateTime();
166-
long startTimeNanos = (startDateInUTC.toEpochSecond() * 1000000000) + startDateInUTC.toInstant().getNano();
166+
long startTimeNanos = (startDateInUTC.toEpochSecond() * 1_000_000_000) + startDateInUTC.toInstant().getNano();
167167

168168
OffsetDateTime endDateInUTC = shimDataRequest.getEndDateTime() == null ?
169169
todayInUTC.plusDays(1) :
170170
shimDataRequest.getEndDateTime().plusDays(1); // We are inclusive of the last day, so add 1 day to get
171+
171172
// the end of day on the last day, which captures the
172173
// entire last day
173-
long endTimeNanos = (endDateInUTC.toEpochSecond() * 1000000000) + endDateInUTC.toInstant().getNano();
174+
long endTimeNanos = (endDateInUTC.toEpochSecond() * 1_000_000_000) + endDateInUTC.toInstant().getNano();
174175

175176

176-
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(DATA_URL)
177-
.pathSegment(googleFitDataType.getStreamId(), "datasets", "{startDate}-{endDate}");
178177
// TODO: Add limits back into the request once Google has fixed the 'limit' query parameter and paging
179-
180-
URI uriRequest = uriBuilder.buildAndExpand(startTimeNanos, endTimeNanos).encode().toUri();
178+
URI uri = UriComponentsBuilder
179+
.fromUriString(DATA_URL)
180+
.pathSegment(googleFitDataType.getStreamId(), "datasets", "{startDate}-{endDate}")
181+
.buildAndExpand(startTimeNanos, endTimeNanos)
182+
.encode()
183+
.toUri();
181184

182185
ResponseEntity<JsonNode> responseEntity;
183186
try {
184-
responseEntity = restTemplate.getForEntity(uriRequest, JsonNode.class);
187+
responseEntity = restTemplate.getForEntity(uri, JsonNode.class);
185188
}
186189
catch (HttpClientErrorException | HttpServerErrorException e) {
187190
// TODO figure out how to handle this
@@ -190,36 +193,46 @@ protected ResponseEntity<ShimDataResponse> getData(OAuth2RestOperations restTemp
190193
}
191194

192195
if (shimDataRequest.getNormalize()) {
193-
GoogleFitDataPointMapper<?> dataPointMapper;
194-
switch (googleFitDataType) {
195-
case BODY_WEIGHT:
196-
dataPointMapper = new GoogleFitBodyWeightDataPointMapper();
197-
break;
198-
case BODY_HEIGHT:
199-
dataPointMapper = new GoogleFitBodyHeightDataPointMapper();
200-
break;
201-
case PHYSICAL_ACTIVITY:
202-
dataPointMapper = new GoogleFitPhysicalActivityDataPointMapper();
203-
break;
204-
case STEP_COUNT:
205-
dataPointMapper = new GoogleFitStepCountDataPointMapper();
206-
break;
207-
case HEART_RATE:
208-
dataPointMapper = new GoogleFitHeartRateDataPointMapper();
209-
break;
210-
case CALORIES_BURNED:
211-
dataPointMapper = new GoogleFitCaloriesBurnedDataPointMapper();
212-
break;
213-
default:
214-
throw new UnsupportedOperationException();
215-
}
196+
GoogleFitDataPointMapper<?> dataPointMapper = getDataPointMapper(googleFitDataType);
216197

217-
return ok().body(ShimDataResponse.result(GoogleFitShim.SHIM_KEY, dataPointMapper.asDataPoints(
218-
singletonList(responseEntity.getBody()))));
198+
return ok().body(ShimDataResponse
199+
.result(GoogleFitShim.SHIM_KEY, dataPointMapper.asDataPoints(responseEntity.getBody())));
219200
}
220201
else {
202+
return ok().body(ShimDataResponse
203+
.result(GoogleFitShim.SHIM_KEY, responseEntity.getBody()));
204+
}
205+
}
206+
207+
private GoogleFitDataPointMapper<?> getDataPointMapper(GoogleFitDataTypes googleFitDataType) {
208+
209+
switch (googleFitDataType) {
210+
case BODY_HEIGHT:
211+
return new GoogleFitBodyHeightDataPointMapper();
212+
213+
case BODY_WEIGHT:
214+
return new GoogleFitBodyWeightDataPointMapper();
215+
216+
case CALORIES_BURNED:
217+
return new GoogleFitCaloriesBurnedDataPointMapper();
218+
219+
case GEOPOSITION:
220+
return new GoogleFitGeopositionDataPointMapper();
221+
222+
case HEART_RATE:
223+
return new GoogleFitHeartRateDataPointMapper();
224+
225+
case PHYSICAL_ACTIVITY:
226+
return new GoogleFitPhysicalActivityDataPointMapper();
227+
228+
case SPEED:
229+
return new GoogleFitSpeedDataPointMapper();
230+
231+
case STEP_COUNT:
232+
return new GoogleFitStepCountDataPointMapper();
221233

222-
return ok().body(ShimDataResponse.result(GoogleFitShim.SHIM_KEY, responseEntity.getBody()));
234+
default:
235+
throw new UnsupportedOperationException();
223236
}
224237
}
225238

shim-server/src/main/java/org/openmhealth/shim/googlefit/mapper/GoogleFitBodyHeightDataPointMapper.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@ public Optional<DataPoint<BodyHeight>> asDataPoint(JsonNode listNode) {
4646
return Optional.empty();
4747
}
4848

49-
BodyHeight.Builder bodyHeightBuilder = new BodyHeight.Builder(new LengthUnitValue(METER, bodyHeightValue));
49+
BodyHeight.Builder measureBuilder = new BodyHeight.Builder(new LengthUnitValue(METER, bodyHeightValue));
5050

51-
setEffectiveTimeFrameIfPresent(bodyHeightBuilder, listNode);
51+
getOptionalTimeFrame(listNode).ifPresent(measureBuilder::setEffectiveTimeFrame);
5252

53-
BodyHeight bodyHeight = bodyHeightBuilder.build();
53+
BodyHeight bodyHeight = measureBuilder.build();
5454
Optional<String> originDataSourceId = asOptionalString(listNode, "originDataSourceId");
5555

5656
return Optional.of(newDataPoint(bodyHeight, originDataSourceId.orElse(null)));

0 commit comments

Comments
 (0)