diff --git a/README.md b/README.md index 7c10cc13..2df7b752 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Shimmer [![Build Status](https://travis-ci.org/openmhealth/shimmer.svg?branch=master)](https://travis-ci.org/openmhealth/shimmer) +# Shimmer [![Build Status](https://travis-ci.org/openmhealth/shimmer.svg?branch=develop)](https://travis-ci.org/openmhealth/shimmer) Shimmer is an application that makes it easy to pull health data from popular third-party APIs like Runkeeper and Fitbit. It converts that data into an [Open mHealth](http://www.openmhealth.org) compliant format, letting your application work with clean and clinically meaningful data. @@ -98,6 +98,7 @@ If you want to build and run the code natively, in a terminal 1. Run the `./run-natively.sh` script and follow the instructions. 1. When the script blocks with the message `Started Application`, the components are running. * Press Ctrl-C to stop them. + * The script creates a WAR file which you can alternatively drop into an application server. [This issue](https://github.com/openmhealth/shimmer/issues/31) has details. 1. Visit `http://localhost:8083` in a browser. If you want to build and run the code in Docker, in a terminal diff --git a/build.gradle b/build.gradle index a0910bbf..0527d34d 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ subprojects { ext { javaVersion = 1.8 - shimmerVersion = '0.4.1' + shimmerVersion = '0.4.2' omhSchemaSdkVersion = '1.0.3' } diff --git a/java-shim-sdk/build.gradle b/java-shim-sdk/build.gradle index 433b2a57..709822b8 100644 --- a/java-shim-sdk/build.gradle +++ b/java-shim-sdk/build.gradle @@ -31,14 +31,13 @@ archivesBaseName = 'omh-shim-sdk' version = '1.0.0' ext { - jacksonVersion = '2.5.3' + jacksonVersion = '2.6.1' } dependencies { compile "com.google.guava:guava:18.0" compile "com.fasterxml.jackson.core:jackson-annotations:${jacksonVersion}" compile "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}" - compile "joda-time:joda-time:2.5" // TODO remove this when refactored to java.time compile "org.openmhealth.schema:omh-schema-sdk:${omhSchemaSdkVersion}" compile 'org.slf4j:slf4j-api:1.7.12' diff --git a/java-shim-sdk/src/main/java/org/openmhealth/shim/ShimDataRequest.java b/java-shim-sdk/src/main/java/org/openmhealth/shim/ShimDataRequest.java index 45c16873..ce276a4f 100644 --- a/java-shim-sdk/src/main/java/org/openmhealth/shim/ShimDataRequest.java +++ b/java-shim-sdk/src/main/java/org/openmhealth/shim/ShimDataRequest.java @@ -16,14 +16,11 @@ package org.openmhealth.shim; -import org.joda.time.DateTime; - import java.time.OffsetDateTime; -import java.util.List; + /** - * A wrapper for encapsulating data requests sent - * to shims. Prevents from having long method signatures. + * A wrapper for encapsulating data requests sent to shims. Prevents from having long method signatures. * * @author Danilo Bonilla */ @@ -34,15 +31,14 @@ public class ShimDataRequest { */ private String dataTypeKey; /** - * parameters required for acessing data, this - * will likely be oauth token + any extras or some - * kind of trusted access. + * parameters required for acessing data, this will likely be oauth token + any extras or some kind of trusted + * access. */ private AccessParameters accessParameters; /** - * // TODO replace this with filters on effective time, using the Data Point API - * The start date for the data being retrieved + * // TODO replace this with filters on effective time, using the Data Point API The start date for the data being + * retrieved */ private OffsetDateTime startDateTime; @@ -52,47 +48,10 @@ public class ShimDataRequest { private OffsetDateTime endDateTime; /** - * List of columns required - */ - private List columnList; - - /** - * The starting row for the data (for pagination purposes) - */ - private Long numToSkip; - - /** - * Number of rows to return - */ - private Long numToReturn; - - /** - * If true, returns normalized results - * from the external data provider, otherwise - * returns raw data. + * If true, returns normalized results from the external data provider, otherwise returns raw data. */ private boolean normalize = true; - public ShimDataRequest() { - } - - public ShimDataRequest(String dataTypeKey, - AccessParameters accessParameters, - OffsetDateTime startDateTime, - OffsetDateTime endDateTime, - List columnList, - Long numToSkip, - Long numToReturn, - boolean normalize) { - this.dataTypeKey = dataTypeKey; - this.accessParameters = accessParameters; - this.startDateTime = startDateTime; - this.endDateTime = endDateTime; - this.columnList = columnList; - this.numToSkip = numToSkip; - this.numToReturn = numToReturn; - this.normalize = false; - } public void setDataTypeKey(String dataTypeKey) { this.dataTypeKey = dataTypeKey; @@ -106,10 +65,6 @@ public OffsetDateTime getStartDateTime() { return startDateTime; } - public DateTime getStartDate() { - return getStartDateTime() == null ? null : new DateTime(getStartDateTime().toInstant()); - } - public void setStartDateTime(OffsetDateTime startDateTime) { this.startDateTime = startDateTime; } @@ -118,25 +73,10 @@ public OffsetDateTime getEndDateTime() { return endDateTime; } - public DateTime getEndDate() { - return getStartDateTime() == null ? null : new DateTime(getStartDateTime().toInstant()); - } - public void setEndDateTime(OffsetDateTime endDateTime) { this.endDateTime = endDateTime; } - public void setColumnList(List columnList) { - this.columnList = columnList; - } - - public void setNumToSkip(Long numToSkip) { - this.numToSkip = numToSkip; - } - - public void setNumToReturn(Long numToReturn) { - this.numToReturn = numToReturn; - } public String getDataTypeKey() { return dataTypeKey; @@ -146,18 +86,6 @@ public AccessParameters getAccessParameters() { return accessParameters; } - public List getColumnList() { - return columnList; - } - - public Long getNumToSkip() { - return numToSkip; - } - - public Long getNumToReturn() { - return numToReturn; - } - public boolean getNormalize() { return normalize; } diff --git a/java-shim-sdk/src/main/java/org/openmhealth/shim/common/mapper/DataPointMapper.java b/java-shim-sdk/src/main/java/org/openmhealth/shim/common/mapper/DataPointMapper.java index cdc61f3b..c58bf462 100644 --- a/java-shim-sdk/src/main/java/org/openmhealth/shim/common/mapper/DataPointMapper.java +++ b/java-shim-sdk/src/main/java/org/openmhealth/shim/common/mapper/DataPointMapper.java @@ -2,9 +2,16 @@ import org.openmhealth.schema.domain.omh.DataPoint; +import org.openmhealth.schema.domain.omh.SchemaId; +import org.openmhealth.schema.domain.omh.SchemaSupport; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.ParameterizedType; import java.util.List; +import static java.util.Collections.singletonList; + /** * A mapper that creates data points from one or more inputs. @@ -13,15 +20,46 @@ * @param the input type * @author Emerson Farrugia */ -public interface DataPointMapper { +public interface DataPointMapper { /** - * Maps one or more inputs into one or more data points. This cardinality allows a mapper to use different inputs - * to assemble a data point, e.g. combining a user profile API response and a blood pressure API response to build - * an identified blood pressure data point. + * Maps one or more inputs into one or more data points. The parameter cardinality allows a mapper to use different + * inputs to assemble a data point, e.g. combining a user profile API response and a blood pressure API response to + * build an identified blood pressure data point. * * @param inputs the list of inputs - * @return the list of data points + * @return the list of data points mapped from those inputs */ List> asDataPoints(List inputs); + + /** + * @see #asDataPoints(List) + */ + default List> asDataPoints(I input) { + + return asDataPoints(singletonList(input)); + } + + /** + * Gets the schema identifier of the data point body that this mapper creates. This default implementation assumes + * that body classes have a default constructor used for serialization, and must be overridden if they don't. + * + * @return the schema identifier of the body type + */ + @SuppressWarnings("unchecked") + default SchemaId getBodySchemaId() { + + try { + Class bodyClass = (Class) + ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; + + Constructor bodyClassConstructor = bodyClass.getDeclaredConstructor(); + bodyClassConstructor.setAccessible(true); + + return bodyClassConstructor.newInstance().getSchemaId(); + } + catch (NoSuchMethodException | InstantiationException | InvocationTargetException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } } diff --git a/java-shim-sdk/src/main/java/org/openmhealth/shim/common/mapper/JsonNodeDataPointMapper.java b/java-shim-sdk/src/main/java/org/openmhealth/shim/common/mapper/JsonNodeDataPointMapper.java index 15d24e36..ea3e6bea 100644 --- a/java-shim-sdk/src/main/java/org/openmhealth/shim/common/mapper/JsonNodeDataPointMapper.java +++ b/java-shim-sdk/src/main/java/org/openmhealth/shim/common/mapper/JsonNodeDataPointMapper.java @@ -1,6 +1,7 @@ package org.openmhealth.shim.common.mapper; import com.fasterxml.jackson.databind.JsonNode; +import org.openmhealth.schema.domain.omh.SchemaSupport; /** @@ -9,6 +10,6 @@ * @param the body type of the data points to create * @author Emerson Farrugia */ -public interface JsonNodeDataPointMapper extends DataPointMapper { +public interface JsonNodeDataPointMapper extends DataPointMapper { } diff --git a/shim-server-ui/app/views/authorizationComplete.html b/shim-server-ui/app/views/authorizationComplete.html index 405f60c8..54ca5869 100644 --- a/shim-server-ui/app/views/authorizationComplete.html +++ b/shim-server-ui/app/views/authorizationComplete.html @@ -1,9 +1,9 @@
-
- We're sorry, - Great, - authorization was not successful.
-
- This window should now close automatically.
-
-
\ No newline at end of file +
+ We're sorry, + Great, + authorization was not successful.
+
+ This window should now close automatically.
+
+ diff --git a/shim-server-ui/package.json b/shim-server-ui/package.json index d33f9a59..b8c21951 100644 --- a/shim-server-ui/package.json +++ b/shim-server-ui/package.json @@ -1,6 +1,6 @@ { "name": "shim-server-ui", - "version": "0.4.1", + "version": "0.4.2", "dependencies": {}, "devDependencies": { "grunt": "~0.4.5", diff --git a/shim-server/build.gradle b/shim-server/build.gradle index 588b9c0c..b5d8d9ee 100644 --- a/shim-server/build.gradle +++ b/shim-server/build.gradle @@ -5,86 +5,94 @@ buildscript { } ext { - springBootVersion = "1.2.6.RELEASE" + springBootVersion = "1.3.1.RELEASE" } dependencies { - classpath 'io.spring.gradle:dependency-management-plugin:0.5.3.RELEASE' classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}" } } +apply plugin: 'war' apply plugin: 'spring-boot' -apply plugin: 'io.spring.dependency-management' version = shimmerVersion -jar { +war { baseName = 'shimmer' } bootRepackage { - mainClass = "org.openmhealth.shim.Application" + mainClass = "org.openmhealth.shimmer.Application" } ext { signpostVersion = '1.2.1.2' - springSecurityOAuthVersion = '2.0.7.RELEASE' -} - -ext['jackson.version'] = '2.5.3' -ext["spring-data-releasetrain.version"] = "Fowler-SR2" - -configurations { - compile.exclude module: "spring-boot-starter-tomcat" -} - -dependencyManagement { - imports { - mavenBom "org.springframework.boot:spring-boot-dependencies:${springBootVersion}" - } } dependencies { compile project(':java-shim-sdk') compile "commons-io:commons-io:2.4" - compile "com.google.code.findbugs:jsr305:3.0.0" + compile "org.hibernate:hibernate-validator" compile "org.apache.httpcomponents:httpclient" - compile "org.apache.httpcomponents:httpcore:4.3.3" + compile "org.apache.httpcomponents:httpcore" compile "com.fasterxml.jackson.core:jackson-annotations" compile "com.fasterxml.jackson.dataformat:jackson-dataformat-xml" compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" compile "com.fasterxml.jackson.datatype:jackson-datatype-jdk8" - compile "joda-time:joda-time" // TODO delete this when shims have been migrated + compile "com.google.code.findbugs:jsr305:3.0.0" compile "org.mongodb:mongo-java-driver" compile "org.openmhealth.schema:omh-schema-sdk:${omhSchemaSdkVersion}" compile "oauth.signpost:signpost-commonshttp4:${signpostVersion}" compile "oauth.signpost:signpost-core:${signpostVersion}" compile "org.springframework.boot:spring-boot-autoconfigure" - compile "org.springframework.boot:spring-boot-starter-jetty" compile "org.springframework.data:spring-data-mongodb" - compile "org.springframework.security.oauth:spring-security-oauth:${springSecurityOAuthVersion}" - compile "org.springframework.security.oauth:spring-security-oauth2:${springSecurityOAuthVersion}" + compile "org.springframework.security.oauth:spring-security-oauth" + compile "org.springframework.security.oauth:spring-security-oauth2" compile "org.springframework:spring-web" compile "org.springframework:spring-webmvc" - compile "org.codehaus.woodstox:woodstox-core-asl:4.4.1" // required to print XML + compile "javax.validation:validation-api:1.1.0.Final" - testCompile 'org.testng:testng:6.8.21' // slowly migrate to TestNG + testCompile "org.hamcrest:hamcrest-core" + testCompile "org.hamcrest:hamcrest-library" testCompile "junit:junit" testCompile "org.mockito:mockito-core" - testCompile "org.springframework.boot:spring-boot-starter-test" + testCompile "org.springframework:spring-test" + testCompile "org.testng:testng:6.8.21" // slowly migrate to TestNG + runtime "org.glassfish:javax.el" runtime 'org.slf4j:jcl-over-slf4j' runtime 'org.slf4j:log4j-over-slf4j' runtime 'ch.qos.logback:logback-classic' + runtime "org.yaml:snakeyaml" + + /* Servlet API and Jetty dependencies shouldn't be included in the `lib` directory of the assembled WAR file, + as they'll conflict with existing classes in the application server's classpath. To prevent them from being + copied into the WAR file, their scope is set to `providedCompile` or `providedRuntime` as defined + by the Gradle WAR plugin. + + In order to make the WAR executable from the command line, the Spring Boot Gradle plugin copies the + dependencies to a different directory in the WAR file called `lib-provided`, which is ignored by the application + server but contributes classes to the classpath when running with `java -jar`. + + Unfortunately, IntelliJ's Spring Boot run configuration doesn't include dependencies with provided scope in the + application classpath, and the application fails to start because of missing classes. This is tracked + at https://youtrack.jetbrains.com/issue/IDEA-107048. To get around this, you can either use a Gradle run + configuration and set it to execute the `bootRun` task. Or you can use the Spring Boot run configuration + and toggle the comments on the following four lines. + */ + providedCompile "javax.servlet:javax.servlet-api" + providedRuntime "org.springframework.boot:spring-boot-starter-jetty" + // compile "javax.servlet:javax.servlet-api" + // runtime "org.springframework.boot:spring-boot-starter-jetty" } -task copyArchiveJarToDockerContext(dependsOn: assemble, type: Copy) { +task copyWarToDockerContext(dependsOn: assemble, type: Copy) { from 'build/libs' into 'docker' - include "${jar.archiveName}" + include "${war.archiveName}" rename { String fileName -> - fileName.replace("${jar.archiveName}", "${jar.baseName}.jar") + fileName.replace("${war.archiveName}", "${war.baseName}.war") } } -build.dependsOn copyArchiveJarToDockerContext +build.dependsOn copyWarToDockerContext diff --git a/shim-server/docker/Dockerfile b/shim-server/docker/Dockerfile index 50d14fd0..d9f2edd9 100644 --- a/shim-server/docker/Dockerfile +++ b/shim-server/docker/Dockerfile @@ -4,7 +4,7 @@ MAINTAINER Emerson Farrugia ENV SERVER_PREFIX /opt/omh/shimmer RUN mkdir -p $SERVER_PREFIX -ADD shimmer.jar $SERVER_PREFIX/ +ADD shimmer.war $SERVER_PREFIX/ EXPOSE 8083 -CMD /usr/bin/java -jar $SERVER_PREFIX/shimmer.jar --spring.config.location=file:$SERVER_PREFIX/ +CMD /usr/bin/java -jar $SERVER_PREFIX/shimmer.war --spring.config.location=file:$SERVER_PREFIX/ diff --git a/shim-server/src/main/java/org/openmhealth/shim/AuthorizationRequestParameters.java b/shim-server/src/main/java/org/openmhealth/shim/AuthorizationRequestParameters.java index 4d937108..d6259ea4 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/AuthorizationRequestParameters.java +++ b/shim-server/src/main/java/org/openmhealth/shim/AuthorizationRequestParameters.java @@ -17,7 +17,6 @@ package org.openmhealth.shim; import org.springframework.data.annotation.Id; -import org.springframework.http.HttpMethod; import java.util.Map; @@ -33,9 +32,6 @@ public class AuthorizationRequestParameters { private String username; - // TODO: unused, drop - private HttpMethod httpMethod = HttpMethod.POST; - private String redirectUri; private Map requestParams; 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 7e9dc346..6ecdc475 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/OAuth2ShimBase.java +++ b/shim-server/src/main/java/org/openmhealth/shim/OAuth2ShimBase.java @@ -127,7 +127,6 @@ public OAuth2ProtectedResourceDetails getResource() { protected ShimDataRequest getTriggerDataRequest() { ShimDataRequest shimDataRequest = new ShimDataRequest(); shimDataRequest.setDataTypeKey(getShimDataTypes()[0].toString()); - shimDataRequest.setNumToReturn(1l); return shimDataRequest; } 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 adc29f4f..2c4494d7 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 @@ -43,10 +43,13 @@ import java.util.List; import static java.util.Collections.singletonList; +import static org.openmhealth.shim.fitbit.FitbitShim.FitbitDataType.*; /** * @author Danilo Bonilla + * @author Chris Schaefbauer + * @author Emerson Farrugia */ @Component @ConfigurationProperties(prefix = "openmhealth.shim.fitbit") @@ -114,7 +117,7 @@ protected HttpMethod getAccessTokenMethod() { @Override public ShimDataType[] getShimDataTypes() { - return FitbitDataType.values(); + return values(); } public enum FitbitDataType implements ShimDataType { @@ -146,10 +149,12 @@ public ShimDataResponse getData(ShimDataRequest shimDataRequest) throws ShimExce String tokenSecret = accessParameters.getTokenSecret(); FitbitDataType fitbitDataType; + try { - fitbitDataType = FitbitDataType.valueOf(shimDataRequest.getDataTypeKey().trim().toUpperCase()); + fitbitDataType = 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."); @@ -168,38 +173,49 @@ public ShimDataResponse getData(ShimDataRequest shimDataRequest) throws ShimExce OffsetDateTime currentDate = startDate; - if (fitbitDataType.equals(FitbitDataType.WEIGHT)) { - return getRangeData( + if (usesDateRangeQuery(fitbitDataType)) { + + return getDataForDateRange( startDate, endDate, fitbitDataType, shimDataRequest.getNormalize(), accessToken, tokenSecret); } else { /** - * Fitbit's API limits you to making a request for each given day - * of data. Thus we make a request for each day in the submitted time - * range and then aggregate the response based on the normalization parameter. + * Fitbit's API limits you to making a request for each given day of data for some endpoints. Thus we + * make a request for each day in the submitted time range and then aggregate the response based on the + * normalization parameter. */ List dayResponses = new ArrayList<>(); while (currentDate.toLocalDate().isBefore(endDate.toLocalDate()) || currentDate.toLocalDate().isEqual(endDate.toLocalDate())) { - dayResponses.add(getDaysData(currentDate, fitbitDataType, + dayResponses.add(getDataForSingleDate(currentDate, fitbitDataType, shimDataRequest.getNormalize(), accessToken, tokenSecret)); currentDate = currentDate.plusDays(1); } - ShimDataResponse shimDataResponse = shimDataRequest.getNormalize() ? + return shimDataRequest.getNormalize() ? aggregateNormalized(dayResponses) : aggregateIntoList(dayResponses); - - return shimDataResponse; } } /** - * Each 'dayResponse', when normalized, will have a type->list[objects] for the day. - * So we collect each daily map to create an aggregate map of the full - * time range. + * Determines whether a range query should be used for submitting requests based on the data type. Based on the + * Fitbit API, we are able to use range queries for weight, BMI, and daily step summaries, without losing + * information needed for schema mapping. + */ + private boolean usesDateRangeQuery(FitbitDataType fitbitDataType) { + + // partnerAccess means that intraday steps will be used, which are not accessible from a ranged query + return fitbitDataType.equals(WEIGHT) + || fitbitDataType.equals(BODY_MASS_INDEX) + || (fitbitDataType.equals(STEPS) && !partnerAccess); + } + + /** + * Each 'dayResponse', when normalized, will have a type->list[objects] for the day. So we collect each daily map + * to create an aggregate map of the full time range. */ @SuppressWarnings("unchecked") private ShimDataResponse aggregateNormalized(List dayResponses) { @@ -250,18 +266,20 @@ private ShimDataResponse executeRequest(String endPointUrl, String accessToken, String tokenSecret, boolean normalize, - FitbitDataType fitbitDataType + FitbitDataType fitbitDataType, + String dateString ) throws ShimException { ApplicationAccessParameters parameters = findApplicationAccessParameters(); + HttpRequestBase dataRequest = OAuth1Utils.getSignedRequest(HttpMethod.GET, endPointUrl, parameters.getClientId(), parameters.getClientSecret(), accessToken, tokenSecret, null); - HttpResponse response; try { - response = httpClient.execute(dataRequest); + + HttpResponse response = httpClient.execute(dataRequest); HttpEntity responseEntity = response.getEntity(); StringWriter writer = new StringWriter(); @@ -272,18 +290,16 @@ private ShimDataResponse executeRequest(String endPointUrl, ObjectMapper objectMapper = new ObjectMapper(); if (normalize) { - IOUtils.copy(responseEntity.getContent(), writer); - - JsonNode jsonNode = objectMapper.readValue(writer.toString(), JsonNode.class); + JsonNode jsonNode = objectMapper.readValue(jsonContent, JsonNode.class); FitbitDataPointMapper dataPointMapper; switch ( fitbitDataType ) { case STEPS: - if(partnerAccess){ + if (partnerAccess) { dataPointMapper = new FitbitIntradayStepCountDataPointMapper(); } - else{ + else { dataPointMapper = new FitbitStepCountDataPointMapper(); } break; @@ -309,6 +325,17 @@ private ShimDataResponse executeRequest(String endPointUrl, } else { + /** + * For types that only allow us to retrieve a single day at a time, Fitbit does not always provide + * date information since it is assumed we know what date we requested. However, this is problematic + * when we are aggregating multiple single date responses, so we wrap each single day Fitbit data + * point with date information. + */ + if (dateString != null) { + jsonContent = "{\"result\": {\"date\": \"" + dateString + "\" " + + ",\"content\": " + jsonContent + "}}"; + } + return ShimDataResponse.result(FitbitShim.SHIM_KEY, objectMapper.readTree(jsonContent)); } @@ -321,105 +348,40 @@ private ShimDataResponse executeRequest(String endPointUrl, } } - private ShimDataResponse getRangeData(OffsetDateTime fromTime, + private ShimDataResponse getDataForDateRange(OffsetDateTime fromTime, OffsetDateTime toTime, FitbitDataType fitbitDataType, boolean normalize, - String accessToken, String tokenSecret) throws ShimException { + String accessToken, + String tokenSecret) throws ShimException { String fromDateString = fromTime.toLocalDate().toString(); String toDateString = toTime.toLocalDate().toString(); UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(DATA_URL). - path("/1/user/-/{fitbitDataTypeEndpoint}/date/{fromDateString}/{toDateString}{stepTimeSeries}.json"); + path("/1/user/-/{fitbitDataTypeEndpoint}/date/{fromDateString}/{toDateString}.json"); + String endpointUrl = - uriComponentsBuilder.buildAndExpand(fitbitDataType.getEndPoint(), fromDateString, toDateString, - (fitbitDataType == FitbitDataType.STEPS ? "/1d/1min" : "")).encode().toUriString(); + uriComponentsBuilder.buildAndExpand(fitbitDataType.getEndPoint(), fromDateString, toDateString).encode() + .toUriString(); - return executeRequest(endpointUrl, accessToken, tokenSecret, normalize, fitbitDataType); + return executeRequest(endpointUrl, accessToken, tokenSecret, normalize, fitbitDataType, null); } - private ShimDataResponse getDaysData(OffsetDateTime dateTime, + private ShimDataResponse getDataForSingleDate(OffsetDateTime dateTime, FitbitDataType fitbitDataType, boolean normalize, - String accessToken, String tokenSecret) throws ShimException { + String accessToken, + String tokenSecret) throws ShimException { String dateString = dateTime.toLocalDate().toString(); UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(DATA_URL). path("/1/user/-/{fitbitDataTypeEndpoint}/date/{dateString}{stepTimeSeries}.json"); - String endpointUrl = uriComponentsBuilder.buildAndExpand(fitbitDataType.getEndPoint(), dateString, - (fitbitDataType == FitbitDataType.STEPS ? "/1d/1min" : "")).encode().toString(); - - ApplicationAccessParameters parameters = findApplicationAccessParameters(); - HttpRequestBase dataRequest = - OAuth1Utils.getSignedRequest(HttpMethod.GET, - endpointUrl, parameters.getClientId(), parameters.getClientSecret(), accessToken, tokenSecret, - null); - - HttpResponse response; - try { - response = httpClient.execute(dataRequest); - HttpEntity responseEntity = response.getEntity(); - - StringWriter writer = new StringWriter(); - IOUtils.copy(responseEntity.getContent(), writer); - - ObjectMapper objectMapper = new ObjectMapper(); - - if (normalize) { - - JsonNode jsonNode = objectMapper.readValue(writer.toString(), JsonNode.class); - - FitbitDataPointMapper dataPointMapper; - - switch ( fitbitDataType ) { - case STEPS: - if(partnerAccess){ - dataPointMapper = new FitbitIntradayStepCountDataPointMapper(); - } - else{ - dataPointMapper = new FitbitStepCountDataPointMapper(); - } - break; - case ACTIVITY: - dataPointMapper = new FitbitPhysicalActivityDataPointMapper(); - break; - case WEIGHT: - dataPointMapper = new FitbitBodyWeightDataPointMapper(); - break; - case SLEEP: - dataPointMapper = new FitbitSleepDurationDataPointMapper(); - break; - case BODY_MASS_INDEX: - dataPointMapper = new FitbitBodyMassIndexDataPointMapper(); - break; - default: - throw new UnsupportedOperationException(); - } - - return ShimDataResponse - .result(FitbitShim.SHIM_KEY, dataPointMapper.asDataPoints(singletonList(jsonNode))); - } - else { - /** - * The fitbit API's system works by retrieving each day's - * data. The date captured is not returned in the data from fitbit because - * it's implicit so we create a JSON wrapper that includes it. - */ - String jsonContent = "{\"result\": {\"date\": \"" + dateString + "\" " + - ",\"content\": " + writer.toString() + "}}"; + String endpointUrl = uriComponentsBuilder.buildAndExpand(fitbitDataType.getEndPoint(), dateString, + (fitbitDataType == STEPS ? "/1d/1min" : "")).encode().toString(); - return ShimDataResponse.result(FitbitShim.SHIM_KEY, - objectMapper.readTree(jsonContent)); - } - } - catch (IOException e) { - throw new ShimException("Could not fetch data", e); - } - finally { - dataRequest.releaseConnection(); - } + return executeRequest(endpointUrl, accessToken, tokenSecret, normalize, fitbitDataType, dateString); } } diff --git a/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitBodyMassIndexDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitBodyMassIndexDataPointMapper.java index 96e90802..e7081cc2 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitBodyMassIndexDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitBodyMassIndexDataPointMapper.java @@ -25,48 +25,41 @@ import java.time.OffsetDateTime; import java.util.Optional; +import static org.openmhealth.schema.domain.omh.BodyMassIndexUnit.KILOGRAMS_PER_SQUARE_METER; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asOptionalLong; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredDouble; /** - * A mapper from Fitbit Resource API body/log/weight responses to {@link BodyMassIndex} objects + * A mapper that translates responses from the Fitbit Resource API body/log/weight endpoint into {@link + * BodyMassIndex} data points. * * @author Chris Schaefbauer + * @see API documentation */ public class FitbitBodyMassIndexDataPointMapper extends FitbitDataPointMapper { - /** - * Maps a JSON response node from the Fitbit API into a {@link BodyMassIndex} measure - * - * @param node a JSON node for an individual object in the "weight" array retrieved from the body/log/weight Fitbit - * API call - * @return a {@link DataPoint} object containing a {@link BodyMassIndex} measure with the appropriate values from - * the JSON node parameter, wrapped as an {@link Optional} - */ + @Override + protected String getListNodeName() { + return "weight"; + } + @Override protected Optional> asDataPoint(JsonNode node) { TypedUnitValue bmiValue = - new TypedUnitValue(BodyMassIndexUnit.KILOGRAMS_PER_SQUARE_METER, - asRequiredDouble(node, "bmi")); + new TypedUnitValue<>(KILOGRAMS_PER_SQUARE_METER, asRequiredDouble(node, "bmi")); + BodyMassIndex.Builder builder = new BodyMassIndex.Builder(bmiValue); Optional dateTime = combineDateTimeAndTimezone(node); - if ( dateTime.isPresent()) { + if (dateTime.isPresent()) { builder.setEffectiveTimeFrame(dateTime.get()); } Optional externalId = asOptionalLong(node, "logId"); - return Optional.of(newDataPoint(builder.build(), externalId.orElse(null))); - } - /** - * @return the name of the list node returned from Fitbit Resource API body/log/weight response - */ - @Override - protected String getListNodeName() { - return "weight"; + return Optional.of(newDataPoint(builder.build(), externalId.orElse(null))); } } diff --git a/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitBodyWeightDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitBodyWeightDataPointMapper.java index c02ca42d..3ae8594b 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitBodyWeightDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitBodyWeightDataPointMapper.java @@ -19,35 +19,34 @@ 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.time.OffsetDateTime; import java.util.Optional; +import static org.openmhealth.schema.domain.omh.MassUnit.KILOGRAM; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asOptionalLong; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.asRequiredDouble; /** - * A mapper from Fitbit Resource API body/log/weight responses to {@link BodyWeight} objects + * A mapper that translates responses from the Fitbit Resource API body/log/weight endpoint into {@link + * BodyWeight} data points. * * @author Chris Schaefbauer + * @see API documentation */ public class FitbitBodyWeightDataPointMapper extends FitbitDataPointMapper { - /** - * Maps a JSON response node from the Fitbit API into a {@link BodyWeight} measure - * - * @param node a JSON node for an individual object in the "weight" array retrieved from the body/log/weight Fitbit - * API call - * @return a {@link DataPoint} object containing a {@link BodyWeight} measure with the appropriate values from the - * JSON node parameter, wrapped as an {@link Optional} - */ + @Override + protected String getListNodeName() { + return "weight"; + } + @Override protected Optional> asDataPoint(JsonNode node) { - MassUnitValue bodyWeight = new MassUnitValue(MassUnit.KILOGRAM, asRequiredDouble(node, "weight")); + MassUnitValue bodyWeight = new MassUnitValue(KILOGRAM, asRequiredDouble(node, "weight")); BodyWeight.Builder builder = new BodyWeight.Builder(bodyWeight); Optional dateTime = combineDateTimeAndTimezone(node); @@ -57,17 +56,7 @@ protected Optional> asDataPoint(JsonNode node) { } Optional externalId = asOptionalLong(node, "logId"); - BodyWeight measure = builder.build(); - - return Optional.of(newDataPoint(measure, externalId.orElse(null))); - } - - /** - * @return the name of the list node returned from Fitbit Resource API body/log/weight response - */ - @Override - protected String getListNodeName() { - return "weight"; + return Optional.of(newDataPoint(builder.build(), externalId.orElse(null))); } } 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 75380e74..62098d5d 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 @@ -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.JsonNodeDataPointMapper; +import javax.annotation.Nullable; import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.time.ZoneOffset; @@ -39,57 +37,50 @@ /** - * The base class for mappers that translate Fitbit API responses to {@link Measure} objects. + * The base class for mappers that translate Fitbit API responses to data points. + *

+ * Fitbit does not include time zone information in its responses. These mappers therefore set the time zone of + * effective time frame of the measures to UTC. There is currently no way to determine the correct time zone for a data + * point returned by the the Fitbit API. * * @author Chris Schaefbauer * @author Emerson Farrugia */ -public abstract class FitbitDataPointMapper implements JsonNodeDataPointMapper { +public abstract class FitbitDataPointMapper implements JsonNodeDataPointMapper { public static final String RESOURCE_API_SOURCE_NAME = "Fitbit Resource API"; - /** - * Maps JSON response nodes from the Fitbit API into a list of {@link DataPoint} objects with the appropriate type. - *

- *

Data points from the Fitbit API do not have any time zone information, so these mappers use UTC as the - * timezone. There is currently no way to determine the correct time zone for a datapoint given the Fitbit API.

- * - * @param responseNodes the list of two json nodes - the first being the get-user-info response (from - * user//profile) and the second being the specific data point of interest for the mapper - * @return a list of DataPoint objects of type T with the appropriate values mapped from the input JSON; if JSON - * objects are contained within an array in the input response, each item in that array will map into an item in - * the list - */ @Override public List> asDataPoints(List responseNodes) { checkNotNull(responseNodes); checkArgument(responseNodes.size() == 1, "A single response node is allowed per call."); - JsonNode targetTypeNodeList = asRequiredNode(responseNodes.get(0), getListNodeName()); + JsonNode listNode = asRequiredNode(responseNodes.get(0), getListNodeName()); List> dataPoints = Lists.newArrayList(); - for (JsonNode targetTypeNode : targetTypeNodeList) { - asDataPoint(targetTypeNode).ifPresent(dataPoints::add); + for (JsonNode listEntryNode : listNode) { + asDataPoint(listEntryNode).ifPresent(dataPoints::add); } return dataPoints; - - } /** - * Adds a {@link DataPointHeader} to a {@link Measure} object and wraps it as a {@link DataPoint} + * @return the name of the list node used by this mapper + */ + protected abstract String getListNodeName(); + + /** + * Creates a data point. * - * @param measure the body of the data point - * @param externalId the identifier of the measure as recorded by the data provider in the JSON data-point - * (optional) - * @param the measure type (e.g., StepCount, BodyMassIndex) - * @return a {@link DataPoint} object containing the body and header that map values from Fitbit API response nodes - * to schema objects + * @param measure the measure to set as the body of the data point + * @param externalId the identifier of the measure as recorded by the data provider + * @param the measure type + * @return a data point */ - protected DataPoint newDataPoint(T measure, Long externalId) { + protected DataPoint newDataPoint(T measure, @Nullable Long externalId) { DataPointAcquisitionProvenance acquisitionProvenance = new DataPointAcquisitionProvenance.Builder(RESOURCE_API_SOURCE_NAME).build(); @@ -105,11 +96,9 @@ protected DataPoint newDataPoint(T measure, Long external } /** - * 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} + * TODO rewrite this, the names don't make sense + * @param node a JSON node containing date and time properties + * @return the equivalent OffsetDateTime */ protected Optional combineDateTimeAndTimezone(JsonNode node) { @@ -118,8 +107,7 @@ protected Optional combineDateTimeAndTimezone(JsonNode node) { if (dateTime.isPresent()) { // FIXME fix the time zone offset to use the correct offset for the data point once it is fixed by Fitbit - offsetDateTime = Optional.ofNullable(OffsetDateTime.of(dateTime.get(), ZoneOffset.UTC)); - + offsetDateTime = Optional.of(OffsetDateTime.of(dateTime.get(), ZoneOffset.UTC)); } return offsetDateTime; @@ -138,16 +126,9 @@ protected OffsetDateTime combineDateTimeAndTimezone(LocalDateTime dateTime) { } /** - * Implemented by subclasses to map a JSON response node from the Fitbit API into a {@link Measure} object of the - * appropriate type + * Maps a JSON response node from the Fitbit API into a data point. * - * @return a {@link DataPoint} object containing the target measure with the appropriate values from the JSON node - * parameter, wrapped as an {@link Optional} + * @return the data point */ protected abstract Optional> asDataPoint(JsonNode node); - - /** - * @return the name of the list node used by this mapper - */ - protected abstract String 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 3c0c7a8a..3e6a10ac 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 @@ -19,6 +19,7 @@ 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.SchemaSupport; import java.time.LocalDate; import java.util.List; @@ -31,10 +32,13 @@ /** + * TODO add Javadoc + * * @author Chris Schaefbauer */ -public abstract class FitbitIntradayDataPointMapper extends FitbitDataPointMapper { +public abstract class FitbitIntradayDataPointMapper extends FitbitDataPointMapper { + // FIXME this shared state is a critical section if the mapper is reused private JsonNode responseNode; @Override @@ -58,6 +62,7 @@ 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 */ + // TODO discuss naming public Optional getDateFromSummaryForDay() { JsonNode summaryForDayNode = asRequiredNode(responseNode, getSummaryForDayNodeName()).get(0); @@ -67,5 +72,6 @@ public Optional getDateFromSummaryForDay() { /** * @return the name of the summary list node which contains a data point with the dateTime field */ + // TODO discuss naming 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 df470b00..1ced8a3f 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,9 +17,7 @@ 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.DurationUnitValue; -import org.openmhealth.schema.domain.omh.StepCount; +import org.openmhealth.schema.domain.omh.*; import java.math.BigDecimal; import java.time.LocalDate; @@ -34,10 +32,25 @@ /** + * A mapper that translates responses from the Fitbit Resource API activities/steps endpoint into {@link + * StepCount} data points. This mapper assumes one minute granularity, i.e. that the request specified a + * detail-level of 1min. + * * @author Chris Schaefbauer + * @see API documentation */ public class FitbitIntradayStepCountDataPointMapper extends FitbitIntradayDataPointMapper { + @Override + protected String getListNodeName() { + return "activities-steps-intraday.dataset"; + } + + @Override + public String getSummaryForDayNodeName() { + return "activities-steps"; + } + @Override protected Optional> asDataPoint(JsonNode listEntryNode) { @@ -63,14 +76,4 @@ protected Optional> asDataPoint(JsonNode listEntryNode) { return Optional.of(newDataPoint(stepCountBuilder.build(), null)); } - - @Override - protected String getListNodeName() { - return "activities-steps-intraday.dataset"; - } - - @Override - public String getSummaryForDayNodeName() { - return "activities-steps"; - } } diff --git a/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitPhysicalActivityDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitPhysicalActivityDataPointMapper.java index 1af35562..e54e9484 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitPhysicalActivityDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitPhysicalActivityDataPointMapper.java @@ -24,24 +24,26 @@ import java.time.OffsetDateTime; import java.util.Optional; +import static org.openmhealth.schema.domain.omh.DurationUnit.DAY; +import static org.openmhealth.schema.domain.omh.DurationUnit.MILLISECOND; +import static org.openmhealth.schema.domain.omh.LengthUnit.KILOMETER; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*; /** - * A mapper from Fitbit Resource API activities/date responses to {@link PhysicalActivity} objects. + * A mapper that translates responses from the Fitbit Resource API activities/date endpoint into {@link + * PhysicalActivity} data points. * * @author Chris Schaefbauer + * @see API documentation */ public class FitbitPhysicalActivityDataPointMapper extends FitbitDataPointMapper { - /** - * Maps a JSON response node from the Fitbit API into a {@link PhysicalActivity} measure. - * - * @param node a JSON node for an individual object in the "activities" array retrieved from the activities/date/ - * Fitbit API endpoint - * @return a {@link DataPoint} object containing a {@link PhysicalActivity} measure with the appropriate values from - * the node parameter, wrapped as an {@link Optional} - */ + @Override + protected String getListNodeName() { + return "activities"; + } + @Override protected Optional> asDataPoint(JsonNode node) { @@ -50,10 +52,11 @@ protected Optional> asDataPoint(JsonNode node) { Boolean hasStartTime = asRequiredBoolean(node, "hasStartTime"); - //hasStartTime is true if the startTime value has been set, which is required of entries through the user GUI - // and from sensed data, - // however some of their data import workflows may set dummy values for these (00:00:00), in which case - // hasStartTime is false and the time shouldn't be used + /* + * hasStartTime is true if the startTime value has been set, which is required of entries through the user + * GUI and from sensed data, however some of their data import workflows may set dummy values for these + * (00:00:00), in which case hasStartTime is false and the time shouldn't be used + */ if (hasStartTime) { Optional localStartDateTime = asOptionalLocalDateTime(node, "startDate", "startTime"); @@ -64,46 +67,37 @@ protected Optional> asDataPoint(JsonNode node) { OffsetDateTime offsetStartDateTime = combineDateTimeAndTimezone(localStartDateTime.get()); if (duration.isPresent()) { activityBuilder.setEffectiveTimeFrame(TimeInterval.ofStartDateTimeAndDuration(offsetStartDateTime, - new DurationUnitValue(DurationUnit.MILLISECOND, duration.get()))); + new DurationUnitValue(MILLISECOND, duration.get()))); } else { activityBuilder.setEffectiveTimeFrame(offsetStartDateTime); } - } } else { - Optional localStartDate = asOptionalLocalDate(node, "startDate"); if (localStartDate.isPresent()) { - //In this case we have a date, but no time, so we set the startTime to beginning of day on the + // in this case we have a date, but no time, so we set the startTime to beginning of day on the // startDate, add the offset, then set the duration as the entire day LocalDateTime localStartDateTime = localStartDate.get().atStartOfDay(); OffsetDateTime offsetStartDateTime = combineDateTimeAndTimezone(localStartDateTime); activityBuilder.setEffectiveTimeFrame(TimeInterval - .ofStartDateTimeAndDuration(offsetStartDateTime, new DurationUnitValue(DurationUnit.DAY, 1))); + .ofStartDateTimeAndDuration(offsetStartDateTime, new DurationUnitValue(DAY, 1))); } } Optional distance = asOptionalDouble(node, "distance"); if (distance.isPresent()) { - //by default fitbit returns metric unit values (https://wiki.fitbit.com/display/API/API+Unit+System), so + // by default fitbit returns metric unit values (https://wiki.fitbit.com/display/API/API+Unit+System), so // this assumes that the response is using the default for distance (KM) - activityBuilder.setDistance(new LengthUnitValue(LengthUnit.KILOMETER, distance.get())); + activityBuilder.setDistance(new LengthUnitValue(KILOMETER, distance.get())); } PhysicalActivity measure = activityBuilder.build(); Optional externalId = asOptionalLong(node, "logId"); - return Optional.of(newDataPoint(measure, externalId.orElse(null))); - } - /** - * @return the name of the list node returned from the activities/date Fitbit endpoint - */ - @Override - protected String getListNodeName() { - return "activities"; + return Optional.of(newDataPoint(measure, externalId.orElse(null))); } } diff --git a/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitSleepDurationDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitSleepDurationDataPointMapper.java index f5981a93..1383dbed 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitSleepDurationDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitSleepDurationDataPointMapper.java @@ -17,38 +17,39 @@ 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.SleepDuration; +import org.openmhealth.schema.domain.omh.TimeInterval; import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.util.Optional; +import static org.openmhealth.schema.domain.omh.DurationUnit.MINUTE; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*; /** - * A mapper from Fitbit Resource API sleep/date responses to {@link SleepDuration} objects + * A mapper that translates responses from the Fitbit Resource API sleep/date endpoint into {@link + * SleepDuration} data points. * * @author Chris Schaefbauer + * @see API documentation */ public class FitbitSleepDurationDataPointMapper extends FitbitDataPointMapper { - /** - * Maps a JSON response node from the Fitbit API into a {@link SleepDuration} measure - * - * @param node a JSON node for an individual object in the "sleep" array retrieved from the sleep/date/ - * Fitbit API endpoint - * @return a {@link DataPoint} object containing a {@link SleepDuration} measure with the appropriate values from - * the node parameter, wrapped as an {@link Optional} - */ + @Override + protected String getListNodeName() { + return "sleep"; + } + @Override protected Optional> asDataPoint(JsonNode node) { - DurationUnitValue unitValue = - new DurationUnitValue(DurationUnit.MINUTE, asRequiredDouble(node, "minutesAsleep")); + DurationUnitValue unitValue = new DurationUnitValue(MINUTE, asRequiredDouble(node, "minutesAsleep")); SleepDuration.Builder sleepDurationBuilder = new SleepDuration.Builder(unitValue); - Optional localStartTime = asOptionalLocalDateTime(node, "startTime"); if (localStartTime.isPresent()) { @@ -58,11 +59,11 @@ protected Optional> asDataPoint(JsonNode node) { if (timeInBed.isPresent()) { sleepDurationBuilder.setEffectiveTimeFrame(TimeInterval.ofStartDateTimeAndDuration(offsetStartDateTime, - new DurationUnitValue(DurationUnit.MINUTE, timeInBed.get()))); + new DurationUnitValue(MINUTE, timeInBed.get()))); } else { - //in this case, there is no "time in bed" value, however we still have a start time, so we can set - // the datapoint to a single datetime point + // in this case, there is no "time in bed" value, however we still have a start time, so we can set + // the data point to a single date time point sleepDurationBuilder.setEffectiveTimeFrame(offsetStartDateTime); } } @@ -70,14 +71,7 @@ protected Optional> asDataPoint(JsonNode node) { SleepDuration measure = sleepDurationBuilder.build(); Optional externalId = asOptionalLong(node, "logId"); - return Optional.of(newDataPoint(measure, externalId.orElse(null))); - } - /** - * @return the name of the list node returned from the sleep/date Fitbit endpoint - */ - @Override - protected String getListNodeName() { - return "sleep"; + return Optional.of(newDataPoint(measure, externalId.orElse(null))); } } diff --git a/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitStepCountDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitStepCountDataPointMapper.java index 397ac508..e24f4731 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitStepCountDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/fitbit/mapper/FitbitStepCountDataPointMapper.java @@ -17,31 +17,26 @@ 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 org.openmhealth.schema.domain.omh.TimeInterval; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Optional; +import static org.openmhealth.schema.domain.omh.DurationUnit.DAY; import static org.openmhealth.shim.common.mapper.JsonNodeMappingSupport.*; /** - * A mapper from Fitbit Resource API activities/date responses to {@link StepCount} objects + * A mapper from Fitbit Resource API activities/date responses to {@link StepCount} objects. * * @author Chris Schaefbauer */ public class FitbitStepCountDataPointMapper extends FitbitDataPointMapper { - /** - * Maps a JSON response node from the Fitbit API into a {@link StepCount} measure - * - * @param node a JSON node for an individual object in the "activities-steps" array retrieved from the - * activities/steps - * Fitbit API endpoint - * @return a {@link DataPoint} object containing a {@link StepCount} measure with the appropriate values from - * the node parameter, wrapped as an {@link Optional} - */ @Override protected Optional> asDataPoint(JsonNode node) { @@ -58,14 +53,14 @@ protected Optional> asDataPoint(JsonNode node) { if (stepDate.isPresent()) { LocalDateTime startDateTime = stepDate.get().atTime(0, 0, 0, 0); - builder.setEffectiveTimeFrame(TimeInterval.ofStartDateTimeAndDuration( - combineDateTimeAndTimezone(startDateTime), - new DurationUnitValue(DurationUnit.DAY, 1))); - + builder.setEffectiveTimeFrame( + TimeInterval.ofStartDateTimeAndDuration(combineDateTimeAndTimezone(startDateTime), + new DurationUnitValue(DAY, 1))); } StepCount measure = builder.build(); Optional externalId = asOptionalLong(node, "logId"); + return Optional.of(newDataPoint(measure, externalId.orElse(null))); } diff --git a/shim-server/src/main/java/org/openmhealth/shim/googlefit/mapper/GoogleFitCaloriesBurnedDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/googlefit/mapper/GoogleFitCaloriesBurnedDataPointMapper.java index d0cc1b54..2f3dc36a 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/googlefit/mapper/GoogleFitCaloriesBurnedDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/googlefit/mapper/GoogleFitCaloriesBurnedDataPointMapper.java @@ -59,6 +59,7 @@ protected Optional> asDataPoint(JsonNode listNode) { return Optional.empty(); } } + return Optional.of(newDataPoint(caloriesBurned, originDataSourceId.orElse(null))); } } 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 559f38b8..08e4c686 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 @@ -43,7 +43,7 @@ * @author Chris Schaefbauer * @author Emerson Farrugia */ -public abstract class IHealthDataPointMapper implements DataPointMapper { +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"; diff --git a/shim-server/src/main/java/org/openmhealth/shim/jawbone/JawboneShim.java b/shim-server/src/main/java/org/openmhealth/shim/jawbone/JawboneShim.java index 64473f09..26333a37 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/jawbone/JawboneShim.java +++ b/shim-server/src/main/java/org/openmhealth/shim/jawbone/JawboneShim.java @@ -150,11 +150,11 @@ protected ResponseEntity getData(OAuth2RestOperations restTemp + " in shimDataRequest, cannot retrieve data."); } - // FIXME this needs to get changed or documented - long numToReturn = 100; - if (shimDataRequest.getNumToReturn() != null) { - numToReturn = shimDataRequest.getNumToReturn(); - } + /* + Jawbone defaults to returning a maximum of 10 entries per request (limit = 10 by default), so + we override the default by specifying an arbitrarily large number as the limit. + */ + long numToReturn = 100_000; OffsetDateTime today = OffsetDateTime.now(); @@ -224,7 +224,7 @@ protected ResponseEntity getData(OAuth2RestOperations restTemp protected String getAuthorizationUrl(UserRedirectRequiredException exception) { final OAuth2ProtectedResourceDetails resource = getResource(); - + UriComponentsBuilder uriBuilder = UriComponentsBuilder .fromUriString(exception.getRedirectUri()) .queryParam("state", exception.getStateKey()) diff --git a/shim-server/src/main/java/org/openmhealth/shim/jawbone/mapper/JawboneBodyEventsDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/jawbone/mapper/JawboneBodyEventsDataPointMapper.java index ffbfa92d..bb530b26 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/jawbone/mapper/JawboneBodyEventsDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/jawbone/mapper/JawboneBodyEventsDataPointMapper.java @@ -29,7 +29,7 @@ /** - * Base class for Jawbone mappers that translate different individual body events (e.g., weight, bmi) into {@link + * Base class for Jawbone mappers that translate different individual body events (e.g., weight, BMI) into {@link * Measure} objects. * * @author Chris Schaefbauer diff --git a/shim-server/src/main/java/org/openmhealth/shim/jawbone/mapper/JawboneDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/jawbone/mapper/JawboneDataPointMapper.java index 55a73047..8d0893a2 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/jawbone/mapper/JawboneDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/jawbone/mapper/JawboneDataPointMapper.java @@ -36,7 +36,7 @@ /** - * The base class for mappers that translate Jawbone API responses with datapoints contained in an array to {@link + * The base class for mappers that translate Jawbone API responses with data points contained in an array to {@link * Measure} objects. * * @author Chris Schaefbauer diff --git a/shim-server/src/main/java/org/openmhealth/shim/jawbone/mapper/JawbonePhysicalActivityDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/jawbone/mapper/JawbonePhysicalActivityDataPointMapper.java index 35e38d9f..8157d8d1 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/jawbone/mapper/JawbonePhysicalActivityDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/jawbone/mapper/JawbonePhysicalActivityDataPointMapper.java @@ -99,7 +99,6 @@ protected Optional getMeasure(JsonNode workoutNode) { PhysicalActivity.Builder builder = new PhysicalActivity.Builder(activityName); - asOptionalBigDecimal(workoutNode, "details.meters") .ifPresent(distance -> builder.setDistance(new LengthUnitValue(METER, distance))); diff --git a/shim-server/src/main/java/org/openmhealth/shim/misfit/mapper/MisfitDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/misfit/mapper/MisfitDataPointMapper.java index 3e1c2e4e..bbe29771 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/misfit/mapper/MisfitDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/misfit/mapper/MisfitDataPointMapper.java @@ -17,10 +17,7 @@ package org.openmhealth.shim.misfit.mapper; import com.fasterxml.jackson.databind.JsonNode; -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.JsonNodeDataPointMapper; import java.util.ArrayList; @@ -36,7 +33,7 @@ /** * @author Emerson Farrugia */ -public abstract class MisfitDataPointMapper implements JsonNodeDataPointMapper { +public abstract class MisfitDataPointMapper implements JsonNodeDataPointMapper { public static final String RESOURCE_API_SOURCE_NAME = "Misfit Resource API"; diff --git a/shim-server/src/main/java/org/openmhealth/shim/runkeeper/RunkeeperShim.java b/shim-server/src/main/java/org/openmhealth/shim/runkeeper/RunkeeperShim.java index 225b989d..3e70e092 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/runkeeper/RunkeeperShim.java +++ b/shim-server/src/main/java/org/openmhealth/shim/runkeeper/RunkeeperShim.java @@ -165,9 +165,11 @@ protected ResponseEntity getData(OAuth2RestOperations restTemp OffsetDateTime endDateTime = shimDataRequest.getEndDateTime() == null ? now.plusDays(1) : shimDataRequest.getEndDateTime(); - long numToReturn = shimDataRequest.getNumToReturn() == null || - shimDataRequest.getNumToReturn() <= 0 ? 100 : - shimDataRequest.getNumToReturn(); + /* + Runkeeper defaults to returning a maximum of 25 entries per request (pageSize = 25 by default), so + we override the default by specifying an arbitrarily large number as the pageSize. + */ + long numToReturn = 100_000; UriComponentsBuilder uriBuilder = UriComponentsBuilder .fromUriString(DATA_URL) @@ -194,7 +196,7 @@ protected ResponseEntity getData(OAuth2RestOperations restTemp if (shimDataRequest.getNormalize()) { RunkeeperDataPointMapper dataPointMapper; - switch(runkeeperDataType){ + switch ( runkeeperDataType ) { case ACTIVITY: dataPointMapper = new RunkeeperPhysicalActivityDataPointMapper(); break; diff --git a/shim-server/src/main/java/org/openmhealth/shim/runkeeper/mapper/RunkeeperDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/runkeeper/mapper/RunkeeperDataPointMapper.java index 982910d9..1ee638dd 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/runkeeper/mapper/RunkeeperDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/runkeeper/mapper/RunkeeperDataPointMapper.java @@ -40,7 +40,7 @@ * * @author Emerson Farrugia */ -public abstract class RunkeeperDataPointMapper implements JsonNodeDataPointMapper { +public abstract class RunkeeperDataPointMapper implements JsonNodeDataPointMapper { public static final String RESOURCE_API_SOURCE_NAME = "Runkeeper HealthGraph API"; public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("EEE, d MMM yyyy HH:mm:ss"); 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 0d4eaa85..9d51c72d 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 @@ -201,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(); diff --git a/shim-server/src/main/java/org/openmhealth/shim/withings/mapper/WithingsDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/withings/mapper/WithingsDataPointMapper.java index ce6752c3..8bcf1851 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/withings/mapper/WithingsDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/withings/mapper/WithingsDataPointMapper.java @@ -16,10 +16,7 @@ package org.openmhealth.shim.withings.mapper; -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.JsonNodeDataPointMapper; import static java.util.UUID.randomUUID; @@ -30,7 +27,7 @@ /** * @author Chris Schaefbauer */ -public abstract class WithingsDataPointMapper implements JsonNodeDataPointMapper { +public abstract class WithingsDataPointMapper implements JsonNodeDataPointMapper { public final static String RESOURCE_API_SOURCE_NAME = "Withings Resource API"; protected static final String BODY_NODE_PROPERTY = "body"; diff --git a/shim-server/src/main/java/org/openmhealth/shim/withings/mapper/WithingsListDataPointMapper.java b/shim-server/src/main/java/org/openmhealth/shim/withings/mapper/WithingsListDataPointMapper.java index f4afcaa2..dcb0c65f 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/withings/mapper/WithingsListDataPointMapper.java +++ b/shim-server/src/main/java/org/openmhealth/shim/withings/mapper/WithingsListDataPointMapper.java @@ -20,6 +20,7 @@ import com.google.common.collect.Lists; import org.openmhealth.schema.domain.omh.DataPoint; import org.openmhealth.schema.domain.omh.Measure; +import org.openmhealth.schema.domain.omh.SchemaSupport; import java.util.List; import java.util.Optional; @@ -35,7 +36,7 @@ * @author Chris Schaefbauer * @author Emerson Farrugia */ -public abstract class WithingsListDataPointMapper extends WithingsDataPointMapper { +public abstract class WithingsListDataPointMapper extends WithingsDataPointMapper { /** * Maps a JSON response with individual data points contained in a JSON array to a list of {@link DataPoint} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/Application.java b/shim-server/src/main/java/org/openmhealth/shimmer/Application.java new file mode 100644 index 00000000..1c6b144e --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/Application.java @@ -0,0 +1,55 @@ +/* + * 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.shimmer; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.context.web.SpringBootServletInitializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; + +import static org.openmhealth.schema.configuration.JacksonConfiguration.newObjectMapper; + + +/** + * @author Emerson Farrugia + */ +@SpringBootApplication +@EnableConfigurationProperties +@EnableMongoRepositories("org.openmhealth.shim") +@ComponentScan(basePackages = {"org.openmhealth.shim", "org.openmhealth.shimmer"}) +public class Application extends SpringBootServletInitializer { + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(Application.class); + } + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + // TODO look into Jackson2ObjectMapperBuilder to support Spring Boot configuration, e.g. for indentation + @Bean + public ObjectMapper objectMapper() { + return newObjectMapper(); + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/configuration/DateTimeQuerySettings.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/configuration/DateTimeQuerySettings.java new file mode 100644 index 00000000..d2841e3d --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/configuration/DateTimeQuerySettings.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.shimmer.common.configuration; + +import org.openmhealth.shimmer.common.domain.DateTimeQueryReferenceFrame; +import org.openmhealth.shimmer.common.domain.DateTimeQueryTimeZoneRestriction; + +import java.time.Duration; +import java.time.ZoneId; +import java.util.Optional; + + +/** + * @author Emerson Farrugia + */ +public interface DateTimeQuerySettings { + + /** + * @return the frame of reference that the bounds are expressed in + */ + DateTimeQueryReferenceFrame getReferenceFrame(); + + /** + * @return the time zone restriction placed on the query bounds + */ + Optional getTimeZoneRestriction(); + + /** + * @return the time zone that the query bounds are fixed to + */ + Optional getFixedTimeZone(); + + /** + * @return the minimum amount of time between the query bounds + */ + Optional getMinimumDuration(); + + /** + * @return the maximum amount of time between the query bounds + */ + Optional getMaximumDuration(); +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/configuration/DefaultDateTimeQuerySettings.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/configuration/DefaultDateTimeQuerySettings.java new file mode 100644 index 00000000..d3979511 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/configuration/DefaultDateTimeQuerySettings.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.shimmer.common.configuration; + +import org.openmhealth.shimmer.common.domain.DateTimeQueryReferenceFrame; +import org.openmhealth.shimmer.common.domain.DateTimeQueryTimeZoneRestriction; + +import java.time.Duration; +import java.time.ZoneId; +import java.util.Optional; + +import static org.openmhealth.shimmer.common.domain.DateTimeQueryReferenceFrame.TIME_ZONES; +import static org.openmhealth.shimmer.common.domain.DateTimeQueryTimeZoneRestriction.ANY_TIME_ZONE; + + +/** + * @author Emerson Farrugia + */ +public class DefaultDateTimeQuerySettings implements DateTimeQuerySettings { + + private DateTimeQueryReferenceFrame referenceFrame = TIME_ZONES; + private DateTimeQueryTimeZoneRestriction timeZoneRestriction = ANY_TIME_ZONE; + private ZoneId fixedTimeZone; + private Duration minimumDuration; + private Duration maximumDuration; + + + @Override + public DateTimeQueryReferenceFrame getReferenceFrame() { + return referenceFrame; + } + + public void setReferenceFrame(DateTimeQueryReferenceFrame referenceFrame) { + this.referenceFrame = referenceFrame; + } + + @Override + public Optional getTimeZoneRestriction() { + return Optional.ofNullable(timeZoneRestriction); + } + + public void setTimeZoneRestriction(DateTimeQueryTimeZoneRestriction timeZoneRestriction) { + this.timeZoneRestriction = timeZoneRestriction; + } + + @Override + public Optional getFixedTimeZone() { + return Optional.ofNullable(fixedTimeZone); + } + + public void setFixedTimeZone(ZoneId expectedTimeZone) { + this.fixedTimeZone = expectedTimeZone; + } + + @Override + public Optional getMinimumDuration() { + return Optional.ofNullable(minimumDuration); + } + + public void setMinimumDuration(Duration minimumDuration) { + this.minimumDuration = minimumDuration; + } + + @Override + public Optional getMaximumDuration() { + return Optional.ofNullable(maximumDuration); + } + + public void setMaximumDuration(Duration maximumDuration) { + this.maximumDuration = maximumDuration; + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/configuration/DefaultEndpointSettings.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/configuration/DefaultEndpointSettings.java new file mode 100644 index 00000000..cec63106 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/configuration/DefaultEndpointSettings.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.shimmer.common.configuration; + +import org.springframework.web.util.UriTemplate; + +import java.util.Optional; + + +/** + * @author Emerson Farrugia + */ +public class DefaultEndpointSettings implements EndpointSettings { + + private String id; + private UriTemplate uriTemplate; + private DateTimeQuerySettings effectiveDateTimeQuerySettings; + private DateTimeQuerySettings creationDateTimeQuerySettings; + private DateTimeQuerySettings modificationDateTimeQuerySettings; + + @Override + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public UriTemplate getUriTemplate() { + return uriTemplate; + } + + public void setUriTemplate(UriTemplate uriTemplate) { + this.uriTemplate = uriTemplate; + } + + @Override + public Optional getEffectiveDateTimeQuerySettings() { + return Optional.ofNullable(effectiveDateTimeQuerySettings); + } + + public void setEffectiveDateTimeQuerySettings(DateTimeQuerySettings settings) { + this.effectiveDateTimeQuerySettings = settings; + } + + @Override + public Optional getCreationDateTimeQuerySettings() { + return Optional.ofNullable(creationDateTimeQuerySettings); + } + + public void setCreationDateTimeQuerySettings(DateTimeQuerySettings settings) { + this.creationDateTimeQuerySettings = settings; + } + + @Override + public Optional getModificationDateTimeQuerySettings() { + return Optional.ofNullable(modificationDateTimeQuerySettings); + } + + public void setModificationDateTimeQuerySettings(DateTimeQuerySettings settings) { + this.modificationDateTimeQuerySettings = settings; + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/configuration/EndpointSettings.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/configuration/EndpointSettings.java new file mode 100644 index 00000000..7fdbadc3 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/configuration/EndpointSettings.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.shimmer.common.configuration; + +import org.springframework.web.util.UriTemplate; + +import java.util.Optional; + + +/** + * @author Emerson Farrugia + */ +public interface EndpointSettings { + + /** + * @return the identifier of this endpoint + */ + String getId(); + + /** + * @return the URI template used to send HTTP requests to this endpoint + */ + UriTemplate getUriTemplate(); + + /** + * @return the settings used by this endpoint to handle creation date time range queries + */ + Optional getCreationDateTimeQuerySettings(); + + /** + * @return true if this endpoint supports creation data time range queries + */ + default boolean supportsCreationDateTimeQueries() { + return getCreationDateTimeQuerySettings().isPresent(); + } + + /** + * @return the settings used by this endpoint to handle effective date time range queries + */ + Optional getEffectiveDateTimeQuerySettings(); + + /** + * @return true if this endpoint supports effective data time range queries + */ + default boolean supportsEffectiveDateTimeQueries() { + return getEffectiveDateTimeQuerySettings().isPresent(); + } + + /** + * @return the settings used by this endpoint to handle modification date time range queries + */ + Optional getModificationDateTimeQuerySettings(); + + /** + * @return true if this endpoint supports modification data time range queries + */ + default boolean supportsModificationDateTimeQueries() { + return getModificationDateTimeQuerySettings().isPresent(); + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/configuration/SecurityConfiguration.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/configuration/SecurityConfiguration.java new file mode 100644 index 00000000..6b4d515e --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/configuration/SecurityConfiguration.java @@ -0,0 +1,24 @@ +package org.openmhealth.shimmer.common.configuration; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + + +/** + * TODO this is just a refactoring of existing code, needs to be revised + * + * @author Emerson Farrugia + */ +@Configuration +public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + + /** + * Allow full anonymous authentication. + */ + http.csrf().disable().authorizeRequests().anyRequest().permitAll(); + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/configuration/ValidationConfiguration.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/configuration/ValidationConfiguration.java new file mode 100644 index 00000000..66ddc17b --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/configuration/ValidationConfiguration.java @@ -0,0 +1,34 @@ +/* + * 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.shimmer.common.configuration; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; + + +/** + * @author Emerson Farrugia + */ +@Configuration +public class ValidationConfiguration { + + @Bean + public LocalValidatorFactoryBean localValidatorFactoryBean() { + return new LocalValidatorFactoryBean(); + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/controller/DataPointSearchController.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/controller/DataPointSearchController.java new file mode 100644 index 00000000..9dabe5a1 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/controller/DataPointSearchController.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.shimmer.common.controller; + +import com.google.common.collect.Range; +import org.openmhealth.shimmer.common.domain.DataPointSearchCriteria; +import org.openmhealth.shimmer.common.domain.DataPointSearchResult; +import org.openmhealth.shimmer.common.service.DataPointSearchService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Validator; +import java.time.OffsetDateTime; + +import static org.springframework.format.annotation.DateTimeFormat.ISO.DATE_TIME; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.http.ResponseEntity.badRequest; +import static org.springframework.http.ResponseEntity.ok; +import static org.springframework.web.bind.annotation.RequestMethod.GET; + + +/** + * A controller that finds and retrieves data points. + * + * @author Emerson Farrugia + */ +@RestController +public class DataPointSearchController { + + private static final Logger logger = LoggerFactory.getLogger(DataPointSearchController.class); + + /* + * These filtering parameters are temporary. They will likely change when a more generic filtering approach is + * implemented. + */ + public static final String CREATED_ON_OR_AFTER_PARAMETER = "created_on_or_after"; + public static final String CREATED_BEFORE_PARAMETER = "created_before"; + public static final String EFFECTIVE_ON_OR_AFTER_PARAMETER = "effective_on_or_after"; + public static final String EFFECTIVE_BEFORE_PARAMETER = "effective_before"; + public static final String SCHEMA_NAMESPACE_PARAMETER = "schema_namespace"; + public static final String SCHEMA_NAME_PARAMETER = "schema_name"; + // TODO searching by schema version should support wildcards, which requires more thought + // public static final String SCHEMA_VERSION_PARAMETER = "schema_version"; + public static final String ACQUISITION_SOURCE_ID_PARAMETER = "acquisition_source_id"; // TODO confirm name + public static final String END_USER_ID_PARAMETER = "end_user_id"; // TODO confirm name and implementation + + @Autowired + private Validator validator; + + @Autowired + private DataPointSearchService dataPointSearchService; + + + /** + * Finds and retrieves data points. + * + * @param schemaNamespace the namespace of the schema the data points conform to + * @param schemaName the name of the schema the data points conform to + * @param createdOnOrAfter the earliest creation timestamp of the data points to return, inclusive + * @param createdBefore the latest creation timestamp of the data points to return, exclusive + * @return a list of matching data points + */ + @RequestMapping(value = "/dataPoints", method = GET, produces = APPLICATION_JSON_VALUE) + public ResponseEntity findDataPoints( + @RequestParam(value = SCHEMA_NAMESPACE_PARAMETER) final String schemaNamespace, + @RequestParam(value = SCHEMA_NAME_PARAMETER) final String schemaName, + @RequestParam(value = CREATED_ON_OR_AFTER_PARAMETER, required = false) + @DateTimeFormat(iso = DATE_TIME) + final OffsetDateTime createdOnOrAfter, + @RequestParam(value = CREATED_BEFORE_PARAMETER, required = false) + @DateTimeFormat(iso = DATE_TIME) + final OffsetDateTime createdBefore, + @RequestParam(value = EFFECTIVE_ON_OR_AFTER_PARAMETER, required = false) + @DateTimeFormat(iso = DATE_TIME) + final OffsetDateTime effectiveOnOrAfter, + @RequestParam(value = EFFECTIVE_BEFORE_PARAMETER, required = false) + @DateTimeFormat(iso = DATE_TIME) + final OffsetDateTime effectiveBefore, + @RequestParam(value = ACQUISITION_SOURCE_ID_PARAMETER, required = false) final String acquisitionSourceId, + @RequestParam(value = END_USER_ID_PARAMETER, required = false) final String specifiedEndUserId, + Authentication authentication) { // FIXME revise authentication + + // TODO this provides feature parity, but no security + // FIXME revise authentication + String endUserId = specifiedEndUserId; + if (specifiedEndUserId == null || specifiedEndUserId.isEmpty()) { + // determine the user associated with the access token to restrict the search accordingly + endUserId = getEndUserId(authentication); + } + + DataPointSearchCriteria searchCriteria = new DataPointSearchCriteria(); + + searchCriteria.setUserId(specifiedEndUserId); + searchCriteria.setSchemaNamespace(schemaNamespace); + searchCriteria.setSchemaName(schemaName); + searchCriteria.setCreatedOnOrAfter(createdOnOrAfter); + searchCriteria.setCreatedBefore(createdBefore); + searchCriteria.setEffectiveOnOrAfter(effectiveOnOrAfter); + searchCriteria.setEffectiveBefore(effectiveBefore); + searchCriteria.setAcquisitionSourceId(acquisitionSourceId); + + if (!validator.validate(searchCriteria).isEmpty()) { + // TODO add feedback + return badRequest().body(null); + } + + DataPointSearchResult searchResult = dataPointSearchService.findDataPoints(searchCriteria); + + return ok().body(searchResult); + } + + // FIXME revise authentication + public String getEndUserId(Authentication authentication) { + return "foo"; + } + + public Range asRange(OffsetDateTime onOrAfterDateTime, OffsetDateTime beforeDateTime) { + + if (onOrAfterDateTime != null && beforeDateTime != null) { + return Range.closedOpen(onOrAfterDateTime, beforeDateTime); + } + + if (onOrAfterDateTime != null) { + return Range.atLeast(onOrAfterDateTime); + } + + else if (beforeDateTime != null) { + return Range.lessThan(beforeDateTime); + } + + return Range.all(); + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shim/Application.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/controller/LegacyAuthorizationController.java similarity index 57% rename from shim-server/src/main/java/org/openmhealth/shim/Application.java rename to shim-server/src/main/java/org/openmhealth/shimmer/common/controller/LegacyAuthorizationController.java index 36e3b0db..2f9b184b 100644 --- a/shim-server/src/main/java/org/openmhealth/shim/Application.java +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/controller/LegacyAuthorizationController.java @@ -14,19 +14,14 @@ * limitations under the License. */ -package org.openmhealth.shim; +package org.openmhealth.shimmer.common.controller; -import com.fasterxml.jackson.databind.ObjectMapper; +import org.openmhealth.shim.*; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; -import org.springframework.data.domain.Sort; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -36,12 +31,9 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.time.LocalDate; import java.util.*; -import static java.time.ZoneOffset.UTC; import static java.util.Collections.singletonList; -import static org.openmhealth.schema.configuration.JacksonConfiguration.newObjectMapper; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.web.bind.annotation.RequestMethod.*; @@ -54,99 +46,24 @@ @ComponentScan(basePackages = "org.openmhealth") @EnableWebSecurity @RestController -public class Application extends WebSecurityConfigurerAdapter { +public class LegacyAuthorizationController { + // TODO these should get passed in from the console private static final String AUTH_SUCCESS_URL = "/#authorizationComplete/success"; private static final String AUTH_FAILURE_URL = "/#authorizationComplete/failure"; + @Autowired private AccessParametersRepo accessParametersRepo; @Autowired private AuthorizationRequestParametersRepo authParametersRepo; - @Autowired - private ApplicationAccessParametersRepo applicationAccessParametersRepo; - @Autowired private ShimRegistry shimRegistry; @Autowired private ShimServerConfig shimServerProperties; - // TODO clarify what this is for - private static final String REDIRECT_OOB = "oob"; - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - - @Override - protected void configure(HttpSecurity http) throws Exception { - /** - * Allow full anonymous authentication. - */ - http.csrf().disable() - .authorizeRequests().anyRequest().permitAll(); - } - - /** - * Return shims available in the registry and all endpoints. - * - * @return list of shims + endpoints in a map. - */ - @RequestMapping(value = "registry", produces = APPLICATION_JSON_VALUE) - public List> shimList(@RequestParam(value = "available", defaultValue = "") String available) - throws ShimException { - - List> results = new ArrayList<>(); - List shims = "".equals(available) ? shimRegistry.getShims() : shimRegistry.getAvailableShims(); - - for (Shim shim : shims) { - List endpoints = new ArrayList<>(); - for (ShimDataType dataType : shim.getShimDataTypes()) { - endpoints.add(dataType.name()); - } - Map row = new HashMap<>(); - row.put("shimKey", shim.getShimKey()); - row.put("label", shim.getLabel()); - row.put("endpoints", endpoints); - ApplicationAccessParameters parameters = shim.findApplicationAccessParameters(); - if (parameters.getClientId() != null) { - row.put("clientId", parameters.getClientId()); - } - if (parameters.getClientSecret() != null) { - row.put("clientSecret", parameters.getClientSecret()); - } - results.add(row); - } - return results; - } - - /** - * Update shim configuration - * - * @return list of shims + endpoints in a map. - */ - @RequestMapping(value = "shim/{shim}/config", method = {GET, PUT, POST}, produces = APPLICATION_JSON_VALUE) - public List updateShimConfig( - @PathVariable("shim") String shimKey, - @RequestParam("clientId") String clientId, - @RequestParam("clientSecret") String clientSecret) - throws ShimException { - - ApplicationAccessParameters parameters = applicationAccessParametersRepo.findByShimKey(shimKey); - - if (parameters == null) { - parameters = new ApplicationAccessParameters(); - parameters.setShimKey(shimKey); - } - parameters.setClientId(clientId); - parameters.setClientSecret(clientSecret); - applicationAccessParametersRepo.save(parameters); - shimRegistry.init(); - - return singletonList("success"); - } /** * Retrieve access parameters for the given username/fragment. @@ -189,19 +106,21 @@ public List> authorizations(@RequestParam(value = "username" @RequestMapping(value = "/authorize/{shim}", produces = APPLICATION_JSON_VALUE) public AuthorizationRequestParameters authorize( @RequestParam(value = "username") String username, - @RequestParam(value = "client_redirect_url", defaultValue = REDIRECT_OOB) String clientRedirectUrl, + @RequestParam(value = "client_redirect_url", required = false) String clientRedirectUrl, @PathVariable("shim") String shim) throws ShimException { setPassThroughAuthentication(username, shim); AuthorizationRequestParameters authParams = shimRegistry.getShim(shim) - .getAuthorizationRequestParameters(username, Collections.emptyMap()); + .getAuthorizationRequestParameters(username, Collections.emptyMap()); /** * Save authorization parameters to local repo. They will be * re-fetched via stateKey upon approval. */ authParams.setUsername(username); authParams.setClientRedirectUrl(clientRedirectUrl); + authParametersRepo.save(authParams); + return authParams; } @@ -257,11 +176,9 @@ public AuthorizationResponse approve( /** * At this point the authorization is complete, if the authorization request - * required a client redirect we do it now, else just return - * the authorization response. + * required a client redirect we do it now */ - if (authParams.getClientRedirectUrl() != null && - !REDIRECT_OOB.equals(authParams.getClientRedirectUrl())) { + if (authParams.getClientRedirectUrl() != null) { try { servletResponse.sendRedirect(authParams.getClientRedirectUrl()); } @@ -273,7 +190,7 @@ public AuthorizationResponse approve( } String authorizationStatusURL = AUTH_FAILURE_URL; - if(response.getType().equals(AuthorizationResponse.Type.AUTHORIZED)){ + if (response.getType().equals(AuthorizationResponse.Type.AUTHORIZED)) { authorizationStatusURL = AUTH_SUCCESS_URL; } @@ -290,61 +207,10 @@ public AuthorizationResponse approve( } } - /** - * Endpoint for retrieving data from shims. - * - * @param username User ID record for which to retrieve data, if not approved this will throw ShimException. - * todo: finish javadoc! - * @return The shim data response wrapper with data from the shim. - */ - @RequestMapping(value = "/data/{shim}/{dataType}", produces = APPLICATION_JSON_VALUE) - public ShimDataResponse data( - @RequestParam(value = "username") String username, - @PathVariable("shim") String shim, - @PathVariable("dataType") String dataTypeKey, - @RequestParam(value = "normalize", defaultValue = "") String normalize, - @RequestParam(value = "dateStart", defaultValue = "") String dateStart, - @RequestParam(value = "dateEnd", defaultValue = "") String dateEnd, - @RequestParam(value = "numToReturn", defaultValue = "50") Long numToReturn) - throws ShimException { - - setPassThroughAuthentication(username, shim); - - ShimDataRequest shimDataRequest = new ShimDataRequest(); - - shimDataRequest.setDataTypeKey(dataTypeKey); - - if (!normalize.equals("")) { - shimDataRequest.setNormalize(Boolean.parseBoolean(normalize)); - } - - if (!"".equals(dateStart)) { - shimDataRequest.setStartDateTime(LocalDate.parse(dateStart).atStartOfDay().atOffset(UTC)); - } - if (!"".equals(dateEnd)) { - shimDataRequest.setEndDateTime(LocalDate.parse(dateEnd).atStartOfDay().atOffset(UTC)); - } - shimDataRequest.setNumToReturn(numToReturn); - - AccessParameters accessParameters = accessParametersRepo.findByUsernameAndShimKey( - username, shim, new Sort(Sort.Direction.DESC, "dateCreated")); - - if (accessParameters == null) { - throw new ShimException("User '" + username + "' has not authorized shim: '" + shim + "'"); - } - shimDataRequest.setAccessParameters(accessParameters); - return shimRegistry.getShim(shim).getData(shimDataRequest); - } - /** * Sets pass through authentication required by spring. */ private void setPassThroughAuthentication(String username, String shim) { SecurityContextHolder.getContext().setAuthentication(new ShimAuthentication(username, shim)); } - - @Bean - public ObjectMapper objectMapper() { - return newObjectMapper(); - } } diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/controller/LegacyConfigurationController.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/controller/LegacyConfigurationController.java new file mode 100644 index 00000000..67b33f6a --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/controller/LegacyConfigurationController.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.shimmer.common.controller; + +import org.openmhealth.shim.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static java.util.Collections.singletonList; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.web.bind.annotation.RequestMethod.*; + + +/** + * @author Danilo Bonilla + */ +@Configuration +@RestController +public class LegacyConfigurationController { + + @Autowired + private ApplicationAccessParametersRepo applicationAccessParametersRepo; + + @Autowired + private ShimRegistry shimRegistry; + + + /** + * Return shims available in the registry and all endpoints. + * + * @return list of shims + endpoints in a map. + */ + @RequestMapping(value = "registry", produces = APPLICATION_JSON_VALUE) + public List> shimList(@RequestParam(value = "available", defaultValue = "") String available) + throws ShimException { + + List> results = new ArrayList<>(); + List shims = "".equals(available) ? shimRegistry.getShims() : shimRegistry.getAvailableShims(); + + for (Shim shim : shims) { + List endpoints = new ArrayList<>(); + for (ShimDataType dataType : shim.getShimDataTypes()) { + endpoints.add(dataType.name()); + } + Map row = new HashMap<>(); + row.put("shimKey", shim.getShimKey()); + row.put("label", shim.getLabel()); + row.put("endpoints", endpoints); + ApplicationAccessParameters parameters = shim.findApplicationAccessParameters(); + if (parameters.getClientId() != null) { + row.put("clientId", parameters.getClientId()); + } + if (parameters.getClientSecret() != null) { + row.put("clientSecret", parameters.getClientSecret()); + } + results.add(row); + } + return results; + } + + /** + * Update shim configuration + * + * @return list of shims + endpoints in a map. + */ + @RequestMapping(value = "shim/{shim}/config", method = {GET, PUT, POST}, produces = APPLICATION_JSON_VALUE) + public List updateShimConfig( + @PathVariable("shim") String shimKey, + @RequestParam("clientId") String clientId, + @RequestParam("clientSecret") String clientSecret) + throws ShimException { + + ApplicationAccessParameters parameters = applicationAccessParametersRepo.findByShimKey(shimKey); + + if (parameters == null) { + parameters = new ApplicationAccessParameters(); + parameters.setShimKey(shimKey); + } + parameters.setClientId(clientId); + parameters.setClientSecret(clientSecret); + applicationAccessParametersRepo.save(parameters); + shimRegistry.init(); + + return singletonList("success"); + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/controller/LegacyDataPointSearchController.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/controller/LegacyDataPointSearchController.java new file mode 100644 index 00000000..2b10dda5 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/controller/LegacyDataPointSearchController.java @@ -0,0 +1,98 @@ +/* + * 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.shimmer.common.controller; + +import org.openmhealth.shim.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.domain.Sort; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDate; + +import static java.time.ZoneOffset.UTC; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + + +/** + * @author Danilo Bonilla + */ +@Configuration +@RestController +public class LegacyDataPointSearchController { + + @Autowired + private AccessParametersRepo accessParametersRepo; + + @Autowired + private ShimRegistry shimRegistry; + + + /** + * Endpoint for retrieving data from shims. + * + * @param username User ID record for which to retrieve data, if not approved this will throw ShimException. + *

+ * TODO: finish javadoc! + * @return The shim data response wrapper with data from the shim. + */ + @RequestMapping(value = "/data/{shim}/{dataType}", produces = APPLICATION_JSON_VALUE) + public ShimDataResponse data( + @RequestParam(value = "username") String username, + @PathVariable("shim") String shim, + @PathVariable("dataType") String dataTypeKey, + @RequestParam(value = "normalize", defaultValue = "true") boolean normalize, + @RequestParam(value = "dateStart", defaultValue = "") String dateStart, + @RequestParam(value = "dateEnd", defaultValue = "") String dateEnd) + throws ShimException { + + setPassThroughAuthentication(username, shim); + + ShimDataRequest shimDataRequest = new ShimDataRequest(); + + shimDataRequest.setDataTypeKey(dataTypeKey); + shimDataRequest.setNormalize(normalize); + + if (!dateStart.isEmpty()) { + shimDataRequest.setStartDateTime(LocalDate.parse(dateStart).atStartOfDay().atOffset(UTC)); + } + if (!dateEnd.isEmpty()) { + shimDataRequest.setEndDateTime(LocalDate.parse(dateEnd).atStartOfDay().atOffset(UTC)); + } + + AccessParameters accessParameters = accessParametersRepo.findByUsernameAndShimKey( + username, shim, new Sort(Sort.Direction.DESC, "dateCreated")); + + if (accessParameters == null) { + throw new ShimException("User '" + username + "' has not authorized shim: '" + shim + "'"); + } + shimDataRequest.setAccessParameters(accessParameters); + + return shimRegistry.getShim(shim).getData(shimDataRequest); + } + + /** + * Sets pass through authentication required by spring. + */ + private void setPassThroughAuthentication(String username, String shim) { + SecurityContextHolder.getContext().setAuthentication(new ShimAuthentication(username, shim)); + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/DataPointRequest.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/DataPointRequest.java new file mode 100644 index 00000000..a7360924 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/DataPointRequest.java @@ -0,0 +1,26 @@ +/* + * 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.shimmer.common.domain; + +/** + * FIXME + * + * @author Emerson Farrugia + */ +public class DataPointRequest { + +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/DataPointSearchCriteria.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/DataPointSearchCriteria.java new file mode 100644 index 00000000..10ae38ae --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/DataPointSearchCriteria.java @@ -0,0 +1,174 @@ +/* + * 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.shimmer.common.domain; + +import com.google.common.collect.Range; +import org.openmhealth.shimmer.common.validation.ValidDataPointSearchCriteria; +import org.openmhealth.shimmer.common.validation.ValidSchemaName; +import org.openmhealth.shimmer.common.validation.ValidSchemaNamespace; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.time.OffsetDateTime; +import java.util.Optional; + + +/** + * A bean that represents a search for data points. + * + * @author Emerson Farrugia + */ +@ValidDataPointSearchCriteria +public class DataPointSearchCriteria { + + private String userId; + private String schemaNamespace; + private String schemaName; + private OffsetDateTime createdOnOrAfter; + private OffsetDateTime createdBefore; + private OffsetDateTime effectiveOnOrAfter; + private OffsetDateTime effectiveBefore; + private String acquisitionSourceId; // TODO confirm if we want to run with this name + + + /** + * @return the user the data points belong to + */ + @NotNull + @Size(min = 1) + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + /** + * @return the schema namespace of the body of the data points + */ + @NotNull + @ValidSchemaNamespace + public String getSchemaNamespace() { + return schemaNamespace; + } + + public void setSchemaNamespace(String schemaNamespace) { + this.schemaNamespace = schemaNamespace; + } + + /** + * @return the schema name of the body of the data points + */ + @NotNull + @ValidSchemaName + public String getSchemaName() { + return schemaName; + } + + public void setSchemaName(String schemaName) { + this.schemaName = schemaName; + } + + /** + * @return the oldest creation timestamp of the data points + */ + public Optional getCreatedOnOrAfter() { + return Optional.ofNullable(createdOnOrAfter); + } + + public void setCreatedOnOrAfter(OffsetDateTime createdOnOrAfter) { + this.createdOnOrAfter = createdOnOrAfter; + } + + /** + * @return the newest creation timestamp of the data points + */ + public Optional getCreatedBefore() { + return Optional.ofNullable(createdBefore); + } + + public void setCreatedBefore(OffsetDateTime createdBefore) { + this.createdBefore = createdBefore; + } + + /** + * @return the oldest effective timestamp of the data points + */ + public Optional getEffectiveOnOrAfter() { + return Optional.ofNullable(effectiveOnOrAfter); + } + + public void setEffectiveOnOrAfter(OffsetDateTime effectiveOnOrAfter) { + this.effectiveOnOrAfter = effectiveOnOrAfter; + } + + /** + * @return the newest effective timestamp of the data points + */ + public Optional getEffectiveBefore() { + return Optional.ofNullable(effectiveBefore); + } + + public void setEffectiveBefore(OffsetDateTime effectiveBefore) { + this.effectiveBefore = effectiveBefore; + } + + /** + * @return the creation timestamp range of the data points + */ + public Range getCreationTimestampRange() { + return asRange(createdOnOrAfter, createdBefore); + } + + /** + * @return the effective timestamp range of the data points + */ + public Range getEffectiveTimestampRange() { + return asRange(effectiveOnOrAfter, effectiveBefore); + } + + /** + * @return the identifier of the acquisition source + */ + @Size(min = 1) + public Optional getAcquisitionSourceId() { + return Optional.ofNullable(acquisitionSourceId); + } + + public void setAcquisitionSourceId(String acquisitionSourceId) { + this.acquisitionSourceId = acquisitionSourceId; + } + + + protected Range asRange(OffsetDateTime onOrAfterDateTime, OffsetDateTime beforeDateTime) { + + if (onOrAfterDateTime != null && beforeDateTime != null) { + return Range.closedOpen(onOrAfterDateTime, beforeDateTime); + } + + if (onOrAfterDateTime != null) { + return Range.atLeast(onOrAfterDateTime); + } + + else if (beforeDateTime != null) { + return Range.lessThan(beforeDateTime); + } + + return Range.all(); + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/DataPointSearchResult.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/DataPointSearchResult.java new file mode 100644 index 00000000..b3796b46 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/DataPointSearchResult.java @@ -0,0 +1,68 @@ +/* + * 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.shimmer.common.domain; + +import org.openmhealth.schema.domain.omh.DataPoint; + +import javax.validation.constraints.NotNull; +import java.util.ArrayList; +import java.util.List; + + +/** + * A bean that represents the result of a search for data points. + * + * @author Emerson Farrugia + */ +public class DataPointSearchResult { + + private List> dataPoints = new ArrayList<>(); + private List> requestResponsePairs = new ArrayList<>(); + + + /** + * @return the list of data points matching the search criteria + */ + @NotNull + public List> getDataPoints() { + return dataPoints; + } + + public void setDataPoints(List> dataPoints) { + this.dataPoints = dataPoints; + } + + public void addDataPoint(DataPoint dataPoint) { + dataPoints.add(dataPoint); + } + + /** + * @return the list of request-response pairs executed to effect the search + */ + @NotNull + public List> getRequestResponsePairs() { + return requestResponsePairs; + } + + public void setRequestResponsePairs(List> requestResponsePairs) { + this.requestResponsePairs = requestResponsePairs; + } + + public void addRequestResponsePair(RequestResponsePair requestResponsePair) { + requestResponsePairs.add(requestResponsePair); + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/DateTimeQueryReferenceFrame.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/DateTimeQueryReferenceFrame.java new file mode 100644 index 00000000..2b75f64d --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/DateTimeQueryReferenceFrame.java @@ -0,0 +1,29 @@ +/* + * 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.shimmer.common.domain; + +/** + * A frame of reference that the bounds of a date time query are expressed in. + * + * @author Emerson Farrugia + */ +public enum DateTimeQueryReferenceFrame { + + TIME_ZONES, + USER_LOCAL_TIME, + CUSTOM +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/DateTimeQueryTimeZoneRestriction.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/DateTimeQueryTimeZoneRestriction.java new file mode 100644 index 00000000..8444bc02 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/DateTimeQueryTimeZoneRestriction.java @@ -0,0 +1,31 @@ +/* + * 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.shimmer.common.domain; + + +/** + * A time zone restriction placed on the bounds of a date time range query. + * + * @author Emerson Farrugia + */ +public enum DateTimeQueryTimeZoneRestriction { + + ANY_TIME_ZONE, + MATCHING_TIME_ZONE, + FIXED_TIME_ZONE, + CUSTOM +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/RequestEntityBuilder.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/RequestEntityBuilder.java new file mode 100644 index 00000000..e5b813be --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/RequestEntityBuilder.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.shimmer.common.domain; + +import org.springframework.http.HttpMethod; +import org.springframework.http.RequestEntity; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.util.UriTemplate; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + + +/** + * A builder that constructs a {@link RequestEntity}. + * + * @author Emerson Farrugia + * @author Chris Schaefbauer + */ +public class RequestEntityBuilder { + + private UriTemplate uriTemplate; + private HttpMethod httpMethod = HttpMethod.GET; + private MultiValueMap headers = new LinkedMultiValueMap<>(); + private MultiValueMap queryParameters = new LinkedMultiValueMap<>(); + + + /** + * @param uriTemplate the request entity URI as a template + */ + public RequestEntityBuilder(UriTemplate uriTemplate) { + + checkNotNull(uriTemplate); + + this.uriTemplate = uriTemplate; + } + + /** + * @return the request entity URI as a template + */ + public UriTemplate getUriTemplate() { + return uriTemplate; + } + + /** + * @return the method to use to exchange the request entity + */ + public HttpMethod getHttpMethod() { + return httpMethod; + } + + public void setHttpMethod(HttpMethod httpMethod) { + + checkNotNull(httpMethod); + + this.httpMethod = httpMethod; + } + + /** + * @param name the name of the header to add + * @param value the value of the header + */ + public void addHeader(String name, String value) { + + addToMultiValueMap(headers, name, value); + } + + /** + * @param name the name of the query parameter to add + * @param value the value of the parameter + */ + public void addQueryParameter(String name, String value) { + + addToMultiValueMap(queryParameters, name, value); + } + + private void addToMultiValueMap(MultiValueMap map, String key, String value) { + + checkNotNull(key); + checkArgument(!key.isEmpty()); + checkNotNull(value); + checkArgument(!value.isEmpty()); + + map.add(key, value); + } + + /** + * @return the request entity + */ + public RequestEntity build() { + + // FIXME implement me + return null; + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/RequestResponsePair.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/RequestResponsePair.java new file mode 100644 index 00000000..ef21e958 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/RequestResponsePair.java @@ -0,0 +1,143 @@ +/* + * 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.shimmer.common.domain; + +import org.springframework.http.RequestEntity; +import org.springframework.http.ResponseEntity; + +import java.time.OffsetDateTime; + + +/** + * An HTTP request-response pair. + * + * @param the request body + * @param the response boxy + * @author Emerson Farrugia + */ +public class RequestResponsePair implements Comparable> { + + private RequestEntity requestEntity; + private ResponseEntity responseEntity; + private OffsetDateTime requestDateTime; + private OffsetDateTime responseDateTime; + + /** + * @return the request that was sent + */ + public RequestEntity getRequestEntity() { + return requestEntity; + } + + public void setRequestEntity(RequestEntity requestEntity) { + this.requestEntity = requestEntity; + } + + /** + * @return the response that was received + */ + public ResponseEntity getResponseEntity() { + return responseEntity; + } + + public void setResponseEntity(ResponseEntity responseEntity) { + this.responseEntity = responseEntity; + } + + /** + * @return the timestamp at which the request was sent + */ + public OffsetDateTime getRequestDateTime() { + return requestDateTime; + } + + public void setRequestDateTime(OffsetDateTime requestDateTime) { + this.requestDateTime = requestDateTime; + } + + /** + * @return the timestamp at which the response was received + */ + public OffsetDateTime getResponseDateTime() { + return responseDateTime; + } + + public void setResponseDateTime(OffsetDateTime responseDateTime) { + this.responseDateTime = responseDateTime; + } + + @SuppressWarnings("SimplifiableIfStatement") + @Override + public boolean equals(Object object) { + + if (this == object) { + return true; + } + + if (object == null || getClass() != object.getClass()) { + return false; + } + + RequestResponsePair that = (RequestResponsePair) object; + + if (!requestEntity.equals(that.requestEntity)) { + return false; + } + if (!responseEntity.equals(that.responseEntity)) { + return false; + } + if (!requestDateTime.equals(that.requestDateTime)) { + return false; + } + return responseDateTime.equals(that.responseDateTime); + } + + @Override + public int hashCode() { + int result = requestEntity.hashCode(); + result = 31 * result + responseEntity.hashCode(); + result = 31 * result + requestDateTime.hashCode(); + result = 31 * result + responseDateTime.hashCode(); + return result; + } + + @Override + public int compareTo(RequestResponsePair that) { + + if (this.equals(that)) { + return 0; + } + + if (this.getRequestDateTime().isBefore(that.getRequestDateTime())) { + return -1; + } + + if (this.getRequestDateTime().isAfter(that.getRequestDateTime())) { + return 1; + } + + if (this.getResponseDateTime().isBefore(that.getResponseDateTime())) { + return -1; + } + + if (this.getResponseDateTime().isAfter(that.getResponseDateTime())) { + return 1; + } + + return 0; + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/parameters/DateTimeRequestParameter.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/parameters/DateTimeRequestParameter.java new file mode 100644 index 00000000..e6727ef8 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/parameters/DateTimeRequestParameter.java @@ -0,0 +1,39 @@ +/* + * 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.shimmer.common.domain.parameters; + +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; + + +/** + * @author Chris Schaefbauer + */ +public class DateTimeRequestParameter extends RequestParameter { + + // TODO unix time? + // TODO how do we parse? + private DateTimeFormatter dateTimeFormat; + + public DateTimeFormatter getDateTimeFormat() { + return dateTimeFormat; + } + + public void setDateTimeFormat(DateTimeFormatter dateTimeFormat) { + this.dateTimeFormat = dateTimeFormat; + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/parameters/ListRequestParameter.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/parameters/ListRequestParameter.java new file mode 100644 index 00000000..17ebcb5e --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/parameters/ListRequestParameter.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.shimmer.common.domain.parameters; + +import java.util.List; +import java.util.Optional; + + +/** + * @author Chris Schaefbauer + */ +public class ListRequestParameter extends RequestParameter> { + + List defaultValues; + String listDelineator; + List allowableValues; + + public Optional> getDefaultValues() { + return Optional.ofNullable(defaultValues); + } + + public void setDefaultValues(List defaultValues) { + this.defaultValues = defaultValues; + } + + public String getListDelineator() { + return listDelineator; + } + + public void setListDelineator(String listDelineator) { + this.listDelineator = listDelineator; + } + + public Optional> getAllowableValues() { + return Optional.ofNullable(allowableValues); + } + + public void setAllowableValues(List allowableValues) { + this.allowableValues = allowableValues; + } + +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/parameters/NumberRequestParameter.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/parameters/NumberRequestParameter.java new file mode 100644 index 00000000..947b833c --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/parameters/NumberRequestParameter.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.shimmer.common.domain.parameters; + +import java.util.List; +import java.util.Optional; + + +/** + * @author Chris Schaefbauer + */ +public class NumberRequestParameter extends RequestParameter { + + private Double maximumValue; + private Double minimumValue; + private List allowableValues; + + public Optional getMaximumValue() { + return Optional.ofNullable(maximumValue); + } + + public void setMaximumValue(Double maximumValue) { + this.maximumValue = maximumValue; + } + + public Optional getMinimumValue() { + return Optional.ofNullable(minimumValue); + } + + public void setMinimumValue(Double minimumValue) { + this.minimumValue = minimumValue; + } + + + public Optional> getAllowableValues() { + return Optional.ofNullable(allowableValues); + } + + public void setAllowableValues(List allowableValues) { + this.allowableValues = allowableValues; + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/parameters/PaginationOffsetNumberRequestParameter.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/parameters/PaginationOffsetNumberRequestParameter.java new file mode 100644 index 00000000..498c7b5f --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/parameters/PaginationOffsetNumberRequestParameter.java @@ -0,0 +1,39 @@ +/* + * 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.shimmer.common.domain.parameters; + +/** + * @author Chris Schaefbauer + */ +public class PaginationOffsetNumberRequestParameter extends NumberRequestParameter { + + public PaginationOffsetType getPaginationOffsetType() { + return paginationOffsetType; + } + + public void setPaginationOffsetType(PaginationOffsetType paginationOffsetType) { + this.paginationOffsetType = paginationOffsetType; + } + + PaginationOffsetType paginationOffsetType; + + + public enum PaginationOffsetType { + PAGE, + RAW + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/parameters/RequestParameter.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/parameters/RequestParameter.java new file mode 100644 index 00000000..c6920d69 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/parameters/RequestParameter.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.shimmer.common.domain.parameters; + +import java.util.Optional; + + +/** + * @author Chris Schaefbauer + */ +public abstract class RequestParameter { + + private T defaultValue; + private RequestParameterLocation requestParameterLocation; + private boolean required; + private String parameterName; + + public RequestParameterLocation getRequestParameterLocation() { + return requestParameterLocation; + } + + public void setRequestParameterLocation( + RequestParameterLocation requestParameterLocation) { + this.requestParameterLocation = requestParameterLocation; + } + + public Optional getDefaultValue() { + return Optional.ofNullable(defaultValue); + } + + public void setDefaultValue(T defaultValue) { + this.defaultValue = defaultValue; + } + + public boolean isRequired() { + return required; + } + + public void setRequired(boolean required) { + this.required = required; + } + + public String getParameterName() { + return parameterName; + } + + public void setParameterName(String parameterName) { + this.parameterName = parameterName; + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/parameters/RequestParameterLocation.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/parameters/RequestParameterLocation.java new file mode 100644 index 00000000..bd24e194 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/parameters/RequestParameterLocation.java @@ -0,0 +1,30 @@ +/* + * 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.shimmer.common.domain.parameters; + +/** + * @author Chris Schaefbauer + */ +public enum RequestParameterLocation { + + BODY, + QUERY, + HEADER, + PATH, + FORM, + CUSTOM +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/parameters/StringRequestParameter.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/parameters/StringRequestParameter.java new file mode 100644 index 00000000..617e0801 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/domain/parameters/StringRequestParameter.java @@ -0,0 +1,37 @@ +/* + * 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.shimmer.common.domain.parameters; + +import java.util.List; +import java.util.Optional; + + +/** + * @author Chris Schaefbauer + */ +public class StringRequestParameter extends RequestParameter { + + private List allowableValues; + + public Optional> getAllowableValues() { + return Optional.ofNullable(allowableValues); + } + + public void setAllowableValues(List allowableValues) { + this.allowableValues = allowableValues; + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/service/DataPointSearchService.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/service/DataPointSearchService.java new file mode 100644 index 00000000..ea68c96c --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/service/DataPointSearchService.java @@ -0,0 +1,35 @@ +/* + * 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.shimmer.common.service; + +import org.openmhealth.shimmer.common.domain.DataPointSearchCriteria; +import org.openmhealth.shimmer.common.domain.DataPointSearchResult; + + +/** + * A service used to search for data points. + * + * @author Emerson Farrugia + */ +public interface DataPointSearchService { + + /** + * @param criteria the criteria to use to filter the search + * @return the result of the search + */ + DataPointSearchResult findDataPoints(DataPointSearchCriteria criteria); +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/service/DataPointSearchServiceImpl.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/service/DataPointSearchServiceImpl.java new file mode 100644 index 00000000..93f2eb07 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/service/DataPointSearchServiceImpl.java @@ -0,0 +1,39 @@ +/* + * 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.shimmer.common.service; + +import org.openmhealth.shimmer.common.domain.DataPointSearchCriteria; +import org.openmhealth.shimmer.common.domain.DataPointSearchResult; +import org.springframework.stereotype.Service; + + +/** + * An implementation that assumes data points can be retrieved from third-parties by making requests to specific API + * endpoints. + * + * @author Emerson Farrugia + */ +@Service +public class DataPointSearchServiceImpl implements DataPointSearchService { + + @Override + public DataPointSearchResult findDataPoints(DataPointSearchCriteria criteria) { + + // TODO implement me + return new DataPointSearchResult(); + } +} \ No newline at end of file diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/service/RequestEntityAssembler.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/service/RequestEntityAssembler.java new file mode 100644 index 00000000..58c53fcf --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/service/RequestEntityAssembler.java @@ -0,0 +1,37 @@ +/* + * 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.shimmer.common.service; + +import org.openmhealth.shimmer.common.domain.DataPointRequest; +import org.openmhealth.shimmer.common.domain.RequestEntityBuilder; + + +/** + * FIXME + * + * @author Emerson Farrugia + */ +public interface RequestEntityAssembler { + + /** + * FIXME Assembles a request entity builder. + * + * @param builder the request entity builder to assemble + * @return the builder + */ + RequestEntityBuilder assemble(RequestEntityBuilder builder, DataPointRequest request); +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/transformer/DateTimeRangeTransformer.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/transformer/DateTimeRangeTransformer.java new file mode 100644 index 00000000..88dfcd87 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/transformer/DateTimeRangeTransformer.java @@ -0,0 +1,34 @@ +/* + * 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.shimmer.common.transformer; + +import com.google.common.collect.Range; + +import java.time.OffsetDateTime; + + +/** + * @author Emerson Farrugia + */ +public interface DateTimeRangeTransformer { + + /** + * @param inputRange the date time range to transform, assumed to be left-closed, right-open + * @return the transformed date time range + */ + Range transformRange(Range inputRange); +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/transformer/FixedTimeZoneDateTimeRangeTransformer.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/transformer/FixedTimeZoneDateTimeRangeTransformer.java new file mode 100644 index 00000000..ae9e00e3 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/transformer/FixedTimeZoneDateTimeRangeTransformer.java @@ -0,0 +1,75 @@ +/* + * 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.shimmer.common.transformer; + +import com.google.common.collect.Range; + +import java.time.OffsetDateTime; +import java.time.ZoneId; + +import static com.google.common.base.Preconditions.checkNotNull; + + +/** + * Sets the time zone of the date time range bounds to a fixed time zone. + * + * @author Emerson Farrugia + */ +public class FixedTimeZoneDateTimeRangeTransformer implements DateTimeRangeTransformer { + + private ZoneId timeZone; + + + /** + * @param timeZone the time zone to set the bounds to + */ + public FixedTimeZoneDateTimeRangeTransformer(ZoneId timeZone) { + + checkNotNull(timeZone); + + this.timeZone = timeZone; + } + + @Override + public Range transformRange(Range inputRange) { + + if (inputRange.hasLowerBound() && inputRange.hasUpperBound()) { + return Range.closedOpen( + toFixedTimeZone(inputRange.lowerEndpoint()), + toFixedTimeZone(inputRange.upperEndpoint())); + } + + if (inputRange.hasLowerBound()) { + return Range.atLeast(toFixedTimeZone(inputRange.lowerEndpoint())); + } + + if (inputRange.hasUpperBound()) { + return Range.lessThan(toFixedTimeZone(inputRange.upperEndpoint())); + } + + return Range.all(); + + } + + /** + * @return a date time with the same instant as the specified date time, but in the fixed time zone + */ + private OffsetDateTime toFixedTimeZone(OffsetDateTime dateTime) { + + return dateTime.atZoneSameInstant(timeZone).toOffsetDateTime(); + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/validation/DataPointSearchCriteriaValidator.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/validation/DataPointSearchCriteriaValidator.java new file mode 100644 index 00000000..373aa6e8 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/validation/DataPointSearchCriteriaValidator.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.shimmer.common.validation; + +import org.openmhealth.shimmer.common.domain.DataPointSearchCriteria; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + + +/** + * @author Emerson Farrugia + */ +public class DataPointSearchCriteriaValidator + implements ConstraintValidator { + + @Override + public void initialize(ValidDataPointSearchCriteria constraintAnnotation) { + } + + // TODO add constraint violations if valuable + @SuppressWarnings("RedundantIfStatement") + @Override + public boolean isValid(DataPointSearchCriteria value, ConstraintValidatorContext context) { + + if (value == null) { + return true; + } + + // TODO implement checks for reversed bounds + + if (!value.getCreationTimestampRange().hasLowerBound() + && !value.getCreationTimestampRange().hasUpperBound() + && !value.getEffectiveTimestampRange().hasLowerBound() + && !value.getEffectiveTimestampRange().hasUpperBound()) { + + return false; + } + + return true; + } +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/validation/ValidDataPointSearchCriteria.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/validation/ValidDataPointSearchCriteria.java new file mode 100644 index 00000000..44d0d1b2 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/validation/ValidDataPointSearchCriteria.java @@ -0,0 +1,45 @@ +/* + * 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.shimmer.common.validation; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + + +/** + * A constraint annotation used to trigger validation on data point search criteria. + * + * @author Emerson Farrugia + */ +@Target({TYPE}) +@Retention(RUNTIME) +@Documented +@Constraint(validatedBy = {DataPointSearchCriteriaValidator.class}) +public @interface ValidDataPointSearchCriteria { + + String message() default "The data point search criteria aren't valid."; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/validation/ValidSchemaName.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/validation/ValidSchemaName.java new file mode 100644 index 00000000..66ba8d2a --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/validation/ValidSchemaName.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.shimmer.common.validation; + +import javax.validation.Constraint; +import javax.validation.Payload; +import javax.validation.constraints.Pattern; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + + +/** + * A constraint annotation used to mark a schema name. + * + * @author Emerson Farrugia + */ +@Target({METHOD, FIELD, ANNOTATION_TYPE}) +@Retention(RUNTIME) +@Documented +@Constraint(validatedBy = {}) +@Pattern(regexp = "^[a-zA-Z0-9-]+$") // TODO reference the pattern in SchemaId +public @interface ValidSchemaName { + + String message() default "A schema name isn't valid."; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/shim-server/src/main/java/org/openmhealth/shimmer/common/validation/ValidSchemaNamespace.java b/shim-server/src/main/java/org/openmhealth/shimmer/common/validation/ValidSchemaNamespace.java new file mode 100644 index 00000000..47e23db4 --- /dev/null +++ b/shim-server/src/main/java/org/openmhealth/shimmer/common/validation/ValidSchemaNamespace.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.shimmer.common.validation; + +import javax.validation.Constraint; +import javax.validation.Payload; +import javax.validation.constraints.Pattern; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + + +/** + * A constraint annotation used to mark a schema namespace. + * + * @author Emerson Farrugia + */ +@Target({METHOD, FIELD, ANNOTATION_TYPE}) +@Retention(RUNTIME) +@Documented +@Constraint(validatedBy = {}) +@Pattern(regexp = "^[a-zA-Z0-9.-]+$") // TODO reference the pattern in SchemaId +public @interface ValidSchemaNamespace { + + String message() default "A schema name isn't valid."; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/shim-server/src/main/resources/application.yaml b/shim-server/src/main/resources/application.yaml index 70d56ee8..0a4d670c 100644 --- a/shim-server/src/main/resources/application.yaml +++ b/shim-server/src/main/resources/application.yaml @@ -1,10 +1,6 @@ spring: application: - name: Open mHealth shim server - # this is necessary to prevent proxy errors due https://github.com/spring-projects/spring-boot/issues/1929 - dao: - exceptiontranslation: - enabled: false + name: Shimmer data: mongodb: uri: mongodb://mongo:27017/omh_dsu @@ -17,16 +13,16 @@ security: basic: enabled: false logging: - file: omh-shims.log + file: shimmer.log openmhealth: shim: server: callbackUrlBase: http://localhost:8083 - #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 - #NOTE: Un-comment and fill in your serialValues for iHealth, otherwise the iHealth shim will not work correctly + #NOTE: Uncomment and fill in the clientId/clientSecret with your credentials if you're not using the UI + #NOTE: Uncomment and set partnerAccess to true if your credentials for a given API have partner access + #NOTE: Uncomment and fill in your serialValues for iHealth, otherwise the iHealth shim will not work correctly #ihealth: # serialValues: # SC: [YOUR_SC_VALUE] diff --git a/shim-server/src/main/resources/hv-application.properties b/shim-server/src/main/resources/hv-application.properties deleted file mode 100644 index a65d9019..00000000 --- a/shim-server/src/main/resources/hv-application.properties +++ /dev/null @@ -1,14 +0,0 @@ -app.id=bafb1313-d4e0-421c-b3b5-4e3a55639c19 - -shell.url=https://account.healthvault-ppe.com -healthvault.url=https://platform.healthvault-ppe.com/platform/wildcat.ashx - -transport.timeout.connect=0 -transport.timeout.read=0 - -keystore.filename=/keystore -keystore.keyname=java-wildcat -keystore.password=password - -target.auth.redirect=/auth -target.auth.actionqs=/ diff --git a/shim-server/src/main/resources/logback.xml b/shim-server/src/main/resources/logback.xml index 8a30f8c7..a2587ff8 100644 --- a/shim-server/src/main/resources/logback.xml +++ b/shim-server/src/main/resources/logback.xml @@ -1,24 +1,5 @@ + - - - - - - - - ./omh-shims.log - - %-4relative [%thread] %-5level %class - %msg%n - - - - - - - - - - - - \ No newline at end of file + + 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 171cf8c8..3fd9f58e 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 @@ -37,5 +37,4 @@ protected JsonNode asJsonNode(String classPathResourceName) { 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/fitbit/mapper/FitbitBodyMassIndexDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitBodyMassIndexDataPointMapperUnitTests.java index 3d3db83c..1eb420e1 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitBodyMassIndexDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitBodyMassIndexDataPointMapperUnitTests.java @@ -6,7 +6,6 @@ import org.openmhealth.schema.domain.omh.DataPoint; import org.openmhealth.schema.domain.omh.TypedUnitValue; import org.openmhealth.shim.common.mapper.DataPointMapperUnitTests; -import org.springframework.core.io.ClassPathResource; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; @@ -14,52 +13,60 @@ import java.time.OffsetDateTime; import java.util.List; -import static java.util.Collections.singletonList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; +import static org.openmhealth.schema.domain.omh.BodyMassIndexUnit.KILOGRAMS_PER_SQUARE_METER; + /** * @author Chris Schaefbauer + * @author Emerson Farrugia */ -public class FitbitBodyMassIndexDataPointMapperUnitTests extends DataPointMapperUnitTests{ - - protected JsonNode responseNodeWeight; +public class FitbitBodyMassIndexDataPointMapperUnitTests extends DataPointMapperUnitTests { private final FitbitBodyMassIndexDataPointMapper mapper = new FitbitBodyMassIndexDataPointMapper(); + private JsonNode responseNode; + @BeforeTest public void initializeResponseNode() throws IOException { - ClassPathResource resource = new ClassPathResource("org/openmhealth/shim/fitbit/mapper/fitbit-get-body-weight.json"); - responseNodeWeight = objectMapper.readTree(resource.getInputStream()); + responseNode = asJsonNode("org/openmhealth/shim/fitbit/mapper/fitbit-body-log-weight.json"); } @Test - public void asDataPointsShouldReturnCorrectNumberOfDataPoints(){ + public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNodeWeight)); - assertThat(dataPoints.size(),equalTo(4)); + assertThat(mapper.asDataPoints(responseNode).size(), equalTo(4)); } @Test public void asDataPointsShouldReturnCorrectDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNodeWeight)); - testFitbitBodyMassIndexDataPoint(dataPoints.get(0), 21.48, "2015-05-13T18:28:59Z", 1431541739000L); - testFitbitBodyMassIndexDataPoint(dataPoints.get(1), 21.17, "2015-05-14T11:51:57Z", 1431604317000L); - testFitbitBodyMassIndexDataPoint(dataPoints.get(2), 21.99, "2015-05-22T18:12:06Z", 1432318326000L); - testFitbitBodyMassIndexDataPoint(dataPoints.get(3), 21.65, "2015-05-24T15:15:25Z", 1432480525000L); + List> dataPoints = mapper.asDataPoints(responseNode); + + assertThatDataPointMatches(dataPoints.get(0), 21.48, "2015-05-13T18:28:59Z", 1431541739000L); + assertThatDataPointMatches(dataPoints.get(1), 21.17, "2015-05-14T11:51:57Z", 1431604317000L); + assertThatDataPointMatches(dataPoints.get(2), 21.99, "2015-05-22T18:12:06Z", 1432318326000L); + assertThatDataPointMatches(dataPoints.get(3), 21.65, "2015-05-24T15:15:25Z", 1432480525000L); } - public void testFitbitBodyMassIndexDataPoint(DataPoint dataPoint, double bodyMassIndexValue, String timeString, long logId){ + public void assertThatDataPointMatches(DataPoint dataPoint, double expectedBmiValue, + String expectedEffectiveDateTime, long expectedExternalId) { + + TypedUnitValue bmiUnitValue = + new TypedUnitValue<>(KILOGRAMS_PER_SQUARE_METER, expectedBmiValue); + + BodyMassIndex expectedBodyMassIndex = new BodyMassIndex.Builder(bmiUnitValue) + .setEffectiveTimeFrame(OffsetDateTime.parse(expectedEffectiveDateTime)) + .build(); - TypedUnitValue bmiValue = new TypedUnitValue(BodyMassIndexUnit.KILOGRAMS_PER_SQUARE_METER,bodyMassIndexValue); - BodyMassIndex expectedBodyMassIndex = new BodyMassIndex.Builder(bmiValue).setEffectiveTimeFrame(OffsetDateTime.parse(timeString)).build(); assertThat(dataPoint.getBody(), equalTo(expectedBodyMassIndex)); assertThat(dataPoint.getHeader().getBodySchemaId(), equalTo(BodyMassIndex.SCHEMA_ID)); - assertThat(dataPoint.getHeader().getAcquisitionProvenance().getAdditionalProperties().get("external_id"), equalTo(logId)); - assertThat(dataPoint.getHeader().getAcquisitionProvenance().getSourceName(), equalTo(FitbitDataPointMapper.RESOURCE_API_SOURCE_NAME)); - + assertThat(dataPoint.getHeader().getAcquisitionProvenance().getAdditionalProperties().get("external_id"), + equalTo(expectedExternalId)); + assertThat(dataPoint.getHeader().getAcquisitionProvenance().getSourceName(), + equalTo(FitbitDataPointMapper.RESOURCE_API_SOURCE_NAME)); } } diff --git a/shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitBodyWeightDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitBodyWeightDataPointMapperUnitTests.java index bf820d35..25f1a447 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitBodyWeightDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitBodyWeightDataPointMapperUnitTests.java @@ -3,10 +3,8 @@ 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.shim.common.mapper.DataPointMapperUnitTests; -import org.springframework.core.io.ClassPathResource; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; @@ -14,64 +12,65 @@ 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.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.openmhealth.schema.domain.omh.MassUnit.KILOGRAM; + /** * @author Chris Schaefbauer */ public class FitbitBodyWeightDataPointMapperUnitTests extends DataPointMapperUnitTests { - protected JsonNode responseNodeWeight; - private final FitbitBodyWeightDataPointMapper mapper = new FitbitBodyWeightDataPointMapper(); + private JsonNode responseNode; + + @BeforeTest public void initializeResponseNode() throws IOException { - ClassPathResource resource = new ClassPathResource("org/openmhealth/shim/fitbit/mapper/fitbit-get-body-weight.json"); - responseNodeWeight = objectMapper.readTree(resource.getInputStream()); - + responseNode = asJsonNode("org/openmhealth/shim/fitbit/mapper/fitbit-body-log-weight.json"); } @Test - public void asDataPointsShouldReturnCorrectNumberOfDataPoints(){ - - List> bodyWeightDataPoints = mapper.asDataPoints(singletonList(responseNodeWeight)); - assertThat(bodyWeightDataPoints.size(),equalTo(4)); + public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { + assertThat(mapper.asDataPoints(responseNode).size(), equalTo(4)); } @Test - public void asDataPointsShouldReturnCorrectDataPoints(){ - - List> bodyWeightDataPoints = mapper.asDataPoints(singletonList(responseNodeWeight)); + public void asDataPointsShouldReturnCorrectDataPoints() { - testFitbitBodyWeightDataPoint(bodyWeightDataPoints.get(0),56.7,"2015-05-13T18:28:59Z",1431541739000L); - testFitbitBodyWeightDataPoint(bodyWeightDataPoints.get(1),55.9,"2015-05-14T11:51:57Z",1431604317000L); - testFitbitBodyWeightDataPoint(bodyWeightDataPoints.get(2),58.1,"2015-05-22T18:12:06Z",1432318326000L); - testFitbitBodyWeightDataPoint(bodyWeightDataPoints.get(3),57.2,"2015-05-24T15:15:25Z",1432480525000L); + List> dataPoints = mapper.asDataPoints(responseNode); + assertThatDataPointMatches(dataPoints.get(0), 56.7, "2015-05-13T18:28:59Z", 1431541739000L); + assertThatDataPointMatches(dataPoints.get(1), 55.9, "2015-05-14T11:51:57Z", 1431604317000L); + assertThatDataPointMatches(dataPoints.get(2), 58.1, "2015-05-22T18:12:06Z", 1432318326000L); + assertThatDataPointMatches(dataPoints.get(3), 57.2, "2015-05-24T15:15:25Z", 1432480525000L); } @Test - public void asDataPointsShouldReturnEmptyListForEmptyArray() throws IOException { + public void asDataPointsShouldReturnEmptyListWhenResponseIsEmpty() throws IOException { - JsonNode emptyWeightNode = objectMapper.readTree("{\n" + - " \"weight\": []\n" + - "}"); - List> emptyDataPoints = mapper.asDataPoints(singletonList(emptyWeightNode)); - assertThat(emptyDataPoints.isEmpty(),equalTo(true)); + JsonNode emptyWeightNode = objectMapper.readTree("{ \"weight\": [] }"); + + assertThat(mapper.asDataPoints(emptyWeightNode), is(empty())); } - public void testFitbitBodyWeightDataPoint(DataPoint dataPoint,double massValue,String timeString,long logId){ + public void assertThatDataPointMatches(DataPoint dataPoint, double expectedMassValue, + String expectedEffectiveDateTime, long expectedExternalId) { - BodyWeight expectedBodyWeight = new BodyWeight.Builder(new MassUnitValue(MassUnit.KILOGRAM,massValue)).setEffectiveTimeFrame(OffsetDateTime.parse(timeString)).build(); - assertThat(dataPoint.getBody(),equalTo(expectedBodyWeight)); - assertThat(dataPoint.getHeader().getBodySchemaId(),equalTo(BodyWeight.SCHEMA_ID)); - assertThat(dataPoint.getHeader().getAcquisitionProvenance().getAdditionalProperties().get("external_id"),equalTo(logId)); - assertThat(dataPoint.getHeader().getAcquisitionProvenance().getSourceName(),equalTo(FitbitDataPointMapper.RESOURCE_API_SOURCE_NAME)); + BodyWeight expectedBodyWeight = new BodyWeight.Builder(new MassUnitValue(KILOGRAM, expectedMassValue)) + .setEffectiveTimeFrame(OffsetDateTime.parse(expectedEffectiveDateTime)) + .build(); + assertThat(dataPoint.getBody(), equalTo(expectedBodyWeight)); + assertThat(dataPoint.getHeader().getBodySchemaId(), equalTo(BodyWeight.SCHEMA_ID)); + assertThat(dataPoint.getHeader().getAcquisitionProvenance().getAdditionalProperties().get("external_id"), + equalTo(expectedExternalId)); + assertThat(dataPoint.getHeader().getAcquisitionProvenance().getSourceName(), + equalTo(FitbitDataPointMapper.RESOURCE_API_SOURCE_NAME)); } - } 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 index 95136d04..66adfc9f 100644 --- 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 @@ -22,7 +22,6 @@ 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; @@ -50,9 +49,7 @@ public class FitbitIntradayStepCountDataPointMapperUnitTests extends DataPointMa @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()); + responseNode = asJsonNode("/org/openmhealth/shim/fitbit/mapper/fitbit-activities-steps-1d-1m-intraday.json"); } @Test 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 d346b66e..8d761986 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 @@ -3,94 +3,132 @@ 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.LocalDate; +import java.time.LocalTime; import java.time.OffsetDateTime; -import java.time.format.DateTimeFormatter; import java.util.List; -import static java.util.Collections.singletonList; +import static java.time.ZoneOffset.UTC; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; +import static org.openmhealth.schema.domain.omh.DurationUnit.DAY; +import static org.openmhealth.schema.domain.omh.LengthUnit.KILOMETER; + /** * @author Chris Schaefbauer + * @author Emerson Farrugia */ public class FitbitPhysicalActivityDataPointMapperUnitTests extends DataPointMapperUnitTests { - JsonNode responseNodeSingleActivity,responseNodeMultipleActivities,responseNodeEmptyActivities; - FitbitPhysicalActivityDataPointMapper mapper = new FitbitPhysicalActivityDataPointMapper(); + private final FitbitPhysicalActivityDataPointMapper mapper = new FitbitPhysicalActivityDataPointMapper(); + private JsonNode singleActivityResponseNode; + private JsonNode multipleActivityResponseNode; @BeforeTest public void initializeResponseNodes() throws IOException { - ClassPathResource resource = new ClassPathResource("org/openmhealth/shim/fitbit/mapper/fitbit-get-activities-single.json"); - responseNodeSingleActivity = objectMapper.readTree(resource.getInputStream()); - resource = new ClassPathResource("org/openmhealth/shim/fitbit/mapper/fitbit-get-activities-multiple.json"); - responseNodeMultipleActivities = objectMapper.readTree(resource.getInputStream()); - resource = new ClassPathResource("org/openmhealth/shim/fitbit/mapper/fitbit-get-activities-empty.json"); - responseNodeEmptyActivities = objectMapper.readTree(resource.getInputStream()); - + singleActivityResponseNode = + asJsonNode("org/openmhealth/shim/fitbit/mapper/fitbit-activities-date-single-in-activities-list.json"); + multipleActivityResponseNode = + asJsonNode( + "org/openmhealth/shim/fitbit/mapper/fitbit-activities-date-multiple-in-activities-list.json"); } @Test - public void asDataPointsShouldReturnCorrectNumberOfDataPoints(){ - List> dataPoints = mapper.asDataPoints(singletonList(responseNodeSingleActivity)); - assertThat(dataPoints.size(),equalTo(1)); + public void asDataPointsShouldReturnEmptyListWhenResponseIsEmpty() throws IOException { - dataPoints = mapper.asDataPoints(singletonList(responseNodeMultipleActivities)); - assertThat(dataPoints.size(),equalTo(3)); + JsonNode emptyNode = + asJsonNode("org/openmhealth/shim/fitbit/mapper/fitbit-activities-date-empty-activities-list.json"); - dataPoints = mapper.asDataPoints(singletonList(responseNodeEmptyActivities)); - assertThat(dataPoints.size(),equalTo(0)); + assertThat(mapper.asDataPoints(emptyNode), is(empty())); } @Test - public void asDataPointsShouldReturnCorrectDataPointForSingleActivty(){ - List> dataPoints = mapper.asDataPoints(singletonList(responseNodeSingleActivity)); - testFitbitPhysicalActivityDataPoint(dataPoints.get(0),"Walk","2014-06-19","09:00",3.36,3600000L,79441095L); + public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { + assertThat(mapper.asDataPoints(singleActivityResponseNode).size(), equalTo(1)); + assertThat(mapper.asDataPoints(multipleActivityResponseNode).size(), equalTo(3)); } @Test - public void asDataPointsShouldReturnCorrectDataPointsForMultipleActivities(){ - List> dataPoints = mapper.asDataPoints(singletonList(responseNodeMultipleActivities)); - testFitbitPhysicalActivityDataPoint(dataPoints.get(0),"Run","2015-06-23","11:55",6.43738,1440000L,253202765L); - testFitbitPhysicalActivityDataPoint(dataPoints.get(1),"Swimming","2015-06-23","10:00",null,null,253246706L); - testFitbitPhysicalActivityDataPoint(dataPoints.get(2),"Walk","2015-06-23",null,6.43738,null,253202766L); + public void asDataPointsShouldReturnCorrectDataPointForSingleActivity() { + List> dataPoints = mapper.asDataPoints(singleActivityResponseNode); + + assertThatDataPointMatches(dataPoints.get(0), "Walk", "2014-06-19", "09:00", 3.36, 3600000L, 79441095L); } - public void testFitbitPhysicalActivityDataPoint(DataPoint dataPoint,String activityName,String dateString,String startTimeString, Double distance,Long durationInMillis,Long logId){ + @Test + public void asDataPointsShouldReturnCorrectDataPointsForMultipleActivities() { - PhysicalActivity.Builder dataPointBuilderForExpected = new PhysicalActivity.Builder(activityName); + List> dataPoints = mapper.asDataPoints(multipleActivityResponseNode); - if(startTimeString!=null && durationInMillis!=null){ - OffsetDateTime offsetStartDateTime = OffsetDateTime.parse(dateString + "T" + startTimeString + "Z", DateTimeFormatter.ISO_OFFSET_DATE_TIME); - dataPointBuilderForExpected.setEffectiveTimeFrame(TimeInterval.ofStartDateTimeAndDuration(offsetStartDateTime,new DurationUnitValue(DurationUnit.MILLISECOND,durationInMillis))); - } - else if(startTimeString !=null){ - OffsetDateTime offsetStartDateTime = OffsetDateTime.parse(dateString + "T" + startTimeString + "Z",DateTimeFormatter.ISO_OFFSET_DATE_TIME); - dataPointBuilderForExpected.setEffectiveTimeFrame(offsetStartDateTime); - } - else{ - OffsetDateTime offsetStartDateTime = OffsetDateTime.parse(dateString + "T" + "00:00:00Z",DateTimeFormatter.ISO_OFFSET_DATE_TIME); - dataPointBuilderForExpected.setEffectiveTimeFrame(TimeInterval.ofStartDateTimeAndDuration(offsetStartDateTime, new DurationUnitValue(DurationUnit.DAY,1))); - } + assertThatDataPointMatches(dataPoints.get(0), "Run", "2015-06-23", "11:55", 6.43738, 1440000L, 253202765L); + assertThatDataPointMatches(dataPoints.get(1), "Swimming", "2015-06-23", "10:00", null, null, 253246706L); + assertThatDataPointMatches(dataPoints.get(2), "Walk", "2015-06-23", 6.43738, 253202766L); + } + + public void assertThatDataPointMatches(DataPoint dataPoint, String expectedActivityName, + String expectedEffectiveDate, Double expectedDistance, Long expectedExternalId) { + + OffsetDateTime expectedEffectiveStartDateTime = + OffsetDateTime.of(LocalDate.parse(expectedEffectiveDate).atStartOfDay(), UTC); + + TimeFrame expectedTimeFrame = new TimeFrame( + TimeInterval.ofStartDateTimeAndDuration(expectedEffectiveStartDateTime, new DurationUnitValue(DAY, 1))); + + assertThatDataPointMatches(dataPoint, expectedActivityName, expectedTimeFrame, expectedDistance, + expectedExternalId); + } - if(distance!=null){ - dataPointBuilderForExpected.setDistance(new LengthUnitValue(LengthUnit.KILOMETER,distance)); + public void assertThatDataPointMatches(DataPoint dataPoint, String expectedActivityName, + TimeFrame expectedTimeFrame, Double expectedDistance, Long expectedExternalId) { + + PhysicalActivity.Builder expectedMeasureBuilder = new PhysicalActivity.Builder(expectedActivityName); + + expectedMeasureBuilder.setEffectiveTimeFrame(expectedTimeFrame); + + if (expectedDistance != null) { + expectedMeasureBuilder.setDistance(new LengthUnitValue(KILOMETER, expectedDistance)); } - PhysicalActivity expectedPhysicalActivity = dataPointBuilderForExpected.build(); + + PhysicalActivity expectedPhysicalActivity = expectedMeasureBuilder.build(); 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(), equalTo(FitbitDataPointMapper.RESOURCE_API_SOURCE_NAME)); - + assertThat(dataPoint.getHeader().getAcquisitionProvenance().getAdditionalProperties().get("external_id"), + equalTo(expectedExternalId)); + assertThat(dataPoint.getHeader().getAcquisitionProvenance().getSourceName(), + equalTo(FitbitDataPointMapper.RESOURCE_API_SOURCE_NAME)); } + public void assertThatDataPointMatches(DataPoint dataPoint, String expectedActivityName, + String expectedEffectiveDate, String expectedEffectiveStartTime, Double expectedDistance, + Long expectedDurationInMs, Long expectedExternalId) { + + OffsetDateTime expectedEffectiveStartDateTime = OffsetDateTime + .of(LocalDate.parse(expectedEffectiveDate), LocalTime.parse(expectedEffectiveStartTime), UTC); + + TimeFrame expectedTimeFrame; + + if (expectedDurationInMs != null) { + DurationUnitValue expectedDuration = new DurationUnitValue(DurationUnit.MILLISECOND, expectedDurationInMs); + + expectedTimeFrame = new TimeFrame( + TimeInterval.ofStartDateTimeAndDuration(expectedEffectiveStartDateTime, expectedDuration)); + } + else { + expectedTimeFrame = new TimeFrame(expectedEffectiveStartDateTime); + } + + assertThatDataPointMatches(dataPoint, expectedActivityName, expectedTimeFrame, expectedDistance, + expectedExternalId); + } } diff --git a/shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitSleepDurationDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitSleepDurationDataPointMapperUnitTests.java index 6e24efe4..1287e98c 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitSleepDurationDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitSleepDurationDataPointMapperUnitTests.java @@ -1,9 +1,10 @@ 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.SleepDuration; import org.openmhealth.shim.common.mapper.DataPointMapperUnitTests; -import org.springframework.core.io.ClassPathResource; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; @@ -11,71 +12,63 @@ 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.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.openmhealth.schema.domain.omh.DurationUnit.MINUTE; +import static org.openmhealth.schema.domain.omh.TimeInterval.ofStartDateTimeAndDuration; + /** * @author Chris Schaefbauer */ public class FitbitSleepDurationDataPointMapperUnitTests extends DataPointMapperUnitTests { - JsonNode responseNodeUserInfo,responseNodeSleep,responseNodeMultipleSleep; - protected FitbitSleepDurationDataPointMapper mapper = new FitbitSleepDurationDataPointMapper(); + private final FitbitSleepDurationDataPointMapper mapper = new FitbitSleepDurationDataPointMapper(); + private JsonNode singleSleepResponseNode; + private JsonNode multipleSleepResponseNode; + @BeforeTest public void initializeResponseNode() throws IOException { - ClassPathResource resource = new ClassPathResource("org/openmhealth/shim/fitbit/mapper/fitbit-get-sleep.json"); - responseNodeSleep = objectMapper.readTree(resource.getInputStream()); - - resource = new ClassPathResource("org/openmhealth/shim/fitbit/mapper/fitbit-get-user-info.json"); - responseNodeUserInfo = objectMapper.readTree(resource.getInputStream()); - - resource = new ClassPathResource("org/openmhealth/shim/fitbit/mapper/fitbit-get-sleep-multiple.json"); - responseNodeMultipleSleep = objectMapper.readTree(resource.getInputStream()); + singleSleepResponseNode = asJsonNode("org/openmhealth/shim/fitbit/mapper/fitbit-sleep-date.json"); + multipleSleepResponseNode = + asJsonNode("org/openmhealth/shim/fitbit/mapper/fitbit-sleep-date-multiple-in-sleep-list.json"); } @Test - public void asDataPointsShouldReturnCorrectNumberOfDataPoints(){ - - List> dataPoints = mapper.asDataPoints(singletonList(responseNodeSleep)); - assertThat(dataPoints.size(),equalTo(1)); - - dataPoints = mapper.asDataPoints(singletonList(responseNodeMultipleSleep)); - assertThat(dataPoints.size(),equalTo(2)); + public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { + assertThat(mapper.asDataPoints(singleSleepResponseNode).size(), equalTo(1)); + assertThat(mapper.asDataPoints(multipleSleepResponseNode).size(), equalTo(2)); } @Test - public void asDataPointsShouldReturnCorrectDataPoints(){ + public void asDataPointsShouldReturnCorrectDataPoints() { - List> dataPoints = mapper.asDataPoints(singletonList(responseNodeSleep)); + SleepDuration expectedSleepDuration = new SleepDuration.Builder(new DurationUnitValue(MINUTE, 831)) + .setEffectiveTimeFrame(ofStartDateTimeAndDuration( + OffsetDateTime.parse("2014-07-19T11:58:00Z"), new DurationUnitValue(MINUTE, 961))) + .build(); - SleepDuration.Builder expectedSleepDurationBuilder = new SleepDuration.Builder(new DurationUnitValue(DurationUnit.MINUTE,831)); - OffsetDateTime offsetStartDateTime = OffsetDateTime.parse("2014-07-19T11:58:00Z"); - expectedSleepDurationBuilder.setEffectiveTimeFrame(TimeInterval - .ofStartDateTimeAndDuration(offsetStartDateTime, new DurationUnitValue(DurationUnit.MINUTE, 961))); - - SleepDuration expectedSleepDuration = expectedSleepDurationBuilder.build(); - - SleepDuration body = dataPoints.get(0).getBody(); - assertThat(body,equalTo(expectedSleepDuration)); + List> dataPoints = mapper.asDataPoints(singleSleepResponseNode); + DataPoint dataPoint = dataPoints.get(0); + assertThat(dataPoint.getBody(), equalTo(expectedSleepDuration)); + assertThat(dataPoint.getHeader().getBodySchemaId(), equalTo(SleepDuration.SCHEMA_ID)); + assertThat(dataPoint.getHeader().getAcquisitionProvenance().getSourceName(), + equalTo(FitbitDataPointMapper.RESOURCE_API_SOURCE_NAME)); } @Test public void asDataPointsShouldReturnEmptyListWhenResponseIsEmpty() throws IOException { - ClassPathResource resource = new ClassPathResource("org/openmhealth/shim/fitbit/mapper/fitbit-get-sleep-empty.json"); - JsonNode responseNodeEmptySleep = objectMapper.readTree(resource.getInputStream()); - - List> dataPoints = mapper.asDataPoints(singletonList(responseNodeEmptySleep)); - - assertThat(dataPoints.size(),equalTo(0)); - + JsonNode responseNode = + asJsonNode("org/openmhealth/shim/fitbit/mapper/fitbit-sleep-date-empty-sleep-list.json"); + assertThat(mapper.asDataPoints(responseNode), is(empty())); } - } diff --git a/shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitStepCountDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitStepCountDataPointMapperUnitTests.java index 4c761c0d..37ba4225 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitStepCountDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/fitbit/mapper/FitbitStepCountDataPointMapperUnitTests.java @@ -1,21 +1,27 @@ 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.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.LocalDate; import java.time.OffsetDateTime; -import java.time.format.DateTimeFormatter; import java.util.List; +import static java.time.ZoneOffset.UTC; import static java.util.Collections.singletonList; +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.hamcrest.Matchers.equalTo; +import static org.openmhealth.schema.domain.omh.TimeInterval.ofStartDateTimeAndDuration; /** @@ -23,71 +29,59 @@ */ public class FitbitStepCountDataPointMapperUnitTests extends DataPointMapperUnitTests { - JsonNode responseNodeStepTimeSeries; - protected final FitbitStepCountDataPointMapper mapper = new FitbitStepCountDataPointMapper(); + private final FitbitStepCountDataPointMapper mapper = new FitbitStepCountDataPointMapper(); + private JsonNode responseNode; @BeforeTest public void initializeResponseNodes() throws IOException { - ClassPathResource resource = - new ClassPathResource("org/openmhealth/shim/fitbit/mapper/fitbit-time-series-steps.json"); - responseNodeStepTimeSeries = objectMapper.readTree(resource.getInputStream()); - + responseNode = asJsonNode("org/openmhealth/shim/fitbit/mapper/fitbit-activities-steps-timeseries.json"); } @Test public void asDataPointsShouldReturnCorrectNumberOfDataPoints() { - List> stepDataPoints = mapper.asDataPoints(singletonList(responseNodeStepTimeSeries)); - assertThat(stepDataPoints.size(), equalTo(2)); - } - - @Test - public void asDataPointsShouldReturnCorrectDataPoints() { - - List> stepDataPoints = mapper.asDataPoints(singletonList(responseNodeStepTimeSeries)); - - testFitbitStepCountDataPoint(stepDataPoints.get(0), 2170, "2015-05-26"); - testFitbitStepCountDataPoint(stepDataPoints.get(1), 3248, "2015-05-27"); + assertThat(mapper.asDataPoints(responseNode).size(), equalTo(2)); } @Test - public void asDataPointsShouldReturnNoDataPointWhenStepCountEqualsZero() throws IOException { + public void asDataPointsShouldReturnCorrectDataPointsWhenMultipleInResponse() { - JsonNode zeroStepsNode = objectMapper.readTree( - "{\n" + - "\"activities-steps\": [ \n" + - "{\n" + - "\"dateTime\": \"2015-05-24\"\n," + - "\"value\": \"0\"\n" + - "}\n" + - "]\n" + - "}"); - - List> dataPoints = mapper.asDataPoints(singletonList(zeroStepsNode)); - assertThat(dataPoints.size(),equalTo(0)); + List> dataPoints = mapper.asDataPoints(singletonList(responseNode)); + assertThatDataPointMatches(dataPoints.get(0), 175, "2015-08-23"); + assertThatDataPointMatches(dataPoints.get(1), 2937, "2015-08-24"); } - public void testFitbitStepCountDataPoint(DataPoint dataPoint, long stepCount, String dateString) { - - StepCount.Builder dataPointBuilderForExpected = new StepCount.Builder(stepCount); - - OffsetDateTime startDateTime = - OffsetDateTime.parse(dateString + "T" + "00:00:00Z", DateTimeFormatter.ISO_OFFSET_DATE_TIME); - startDateTime = startDateTime.withNano(000000000); + public void assertThatDataPointMatches(DataPoint dataPoint, long expectedStepCountValue, + String expectedEffectiveDate) { - dataPointBuilderForExpected.setEffectiveTimeFrame( - TimeInterval.ofStartDateTimeAndDuration(startDateTime, new DurationUnitValue(DurationUnit.DAY, 1))); - StepCount expectedStepCount = dataPointBuilderForExpected.build(); + StepCount expectedStepCount = new StepCount.Builder(expectedStepCountValue) + .setEffectiveTimeFrame(ofStartDateTimeAndDuration( + OffsetDateTime.of(LocalDate.parse(expectedEffectiveDate).atStartOfDay(), UTC), + new DurationUnitValue(DurationUnit.DAY, 1))) + .build(); - assertThat(dataPoint.getBody(), CoreMatchers.equalTo(expectedStepCount)); - assertThat(dataPoint.getHeader().getBodySchemaId(), CoreMatchers.equalTo(StepCount.SCHEMA_ID)); + assertThat(dataPoint.getBody(), equalTo(expectedStepCount)); + assertThat(dataPoint.getHeader().getBodySchemaId(), equalTo(StepCount.SCHEMA_ID)); assertThat(dataPoint.getHeader().getAcquisitionProvenance().getAdditionalProperties().get("external_id"), - CoreMatchers.nullValue()); + nullValue()); assertThat(dataPoint.getHeader().getAcquisitionProvenance().getSourceName(), - CoreMatchers.equalTo(FitbitDataPointMapper.RESOURCE_API_SOURCE_NAME)); - + equalTo(FitbitDataPointMapper.RESOURCE_API_SOURCE_NAME)); } + @Test + public void asDataPointsShouldReturnEmptyListWhenStepCountEqualsZero() throws IOException { + + JsonNode zeroStepsNode = objectMapper.readTree("{\n" + + " \"activities-steps\": [\n" + + " {\n" + + " \"dateTime\": \"2015-05-24\",\n" + + " \"value\": \"0\"\n" + + " }\n" + + " ]\n" + + "}\n"); + + assertThat(mapper.asDataPoints(zeroStepsNode), is(empty())); + } } diff --git a/shim-server/src/test/java/org/openmhealth/shim/googlefit/mapper/GoogleFitBodyHeightDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/googlefit/mapper/GoogleFitBodyHeightDataPointMapperUnitTests.java index 99a8e1b1..2741e20e 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/googlefit/mapper/GoogleFitBodyHeightDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/googlefit/mapper/GoogleFitBodyHeightDataPointMapperUnitTests.java @@ -1,7 +1,9 @@ package org.openmhealth.shim.googlefit.mapper; -import org.openmhealth.schema.domain.omh.*; -import org.springframework.core.io.ClassPathResource; +import org.openmhealth.schema.domain.omh.BodyHeight; +import org.openmhealth.schema.domain.omh.DataPoint; +import org.openmhealth.schema.domain.omh.LengthUnitValue; +import org.openmhealth.schema.domain.omh.TimeInterval; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; @@ -29,9 +31,7 @@ public class GoogleFitBodyHeightDataPointMapperUnitTests extends GoogleFitDataPo @Override public void initializeResponseNode() throws IOException { - ClassPathResource resource = - new ClassPathResource("org/openmhealth/shim/googlefit/mapper/googlefit-body-height.json"); - responseNode = objectMapper.readTree(resource.getInputStream()); + responseNode = asJsonNode("org/openmhealth/shim/googlefit/mapper/googlefit-merge-height.json"); } @Test diff --git a/shim-server/src/test/java/org/openmhealth/shim/googlefit/mapper/GoogleFitBodyWeightDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/googlefit/mapper/GoogleFitBodyWeightDataPointMapperUnitTests.java index 744288e2..93512e86 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/googlefit/mapper/GoogleFitBodyWeightDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/googlefit/mapper/GoogleFitBodyWeightDataPointMapperUnitTests.java @@ -1,7 +1,9 @@ package org.openmhealth.shim.googlefit.mapper; -import org.openmhealth.schema.domain.omh.*; -import org.springframework.core.io.ClassPathResource; +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.testng.annotations.BeforeTest; import org.testng.annotations.Test; @@ -27,9 +29,8 @@ public class GoogleFitBodyWeightDataPointMapperUnitTests extends GoogleFitDataPo @BeforeTest @Override public void initializeResponseNode() throws IOException { - ClassPathResource resource = - new ClassPathResource("org/openmhealth/shim/googlefit/mapper/googlefit-body-weight.json"); - responseNode = objectMapper.readTree(resource.getInputStream()); + + responseNode = asJsonNode("org/openmhealth/shim/googlefit/mapper/googlefit-merge-weight.json"); } @Test diff --git a/shim-server/src/test/java/org/openmhealth/shim/googlefit/mapper/GoogleFitCaloriesBurnedDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/googlefit/mapper/GoogleFitCaloriesBurnedDataPointMapperUnitTests.java index c9d07f2a..60a24b8b 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/googlefit/mapper/GoogleFitCaloriesBurnedDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/googlefit/mapper/GoogleFitCaloriesBurnedDataPointMapperUnitTests.java @@ -1,7 +1,6 @@ package org.openmhealth.shim.googlefit.mapper; import org.openmhealth.schema.domain.omh.*; -import org.springframework.core.io.ClassPathResource; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; @@ -26,9 +25,7 @@ public class GoogleFitCaloriesBurnedDataPointMapperUnitTests extends GoogleFitDa @Override public void initializeResponseNode() throws IOException { - ClassPathResource resource = - new ClassPathResource("org/openmhealth/shim/googlefit/mapper/googlefit-calories-burned.json"); - responseNode = objectMapper.readTree(resource.getInputStream()); + responseNode = asJsonNode("org/openmhealth/shim/googlefit/mapper/googlefit-merge-calories-expended.json"); } @Test diff --git a/shim-server/src/test/java/org/openmhealth/shim/googlefit/mapper/GoogleFitHeartRateDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/googlefit/mapper/GoogleFitHeartRateDataPointMapperUnitTests.java index 422fe419..0f009fb9 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/googlefit/mapper/GoogleFitHeartRateDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/googlefit/mapper/GoogleFitHeartRateDataPointMapperUnitTests.java @@ -2,7 +2,6 @@ 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; @@ -27,9 +26,7 @@ public class GoogleFitHeartRateDataPointMapperUnitTests extends GoogleFitDataPoi @Override public void initializeResponseNode() throws IOException { - ClassPathResource resource = - new ClassPathResource("org/openmhealth/shim/googlefit/mapper/googlefit-heart-rate.json"); - responseNode = objectMapper.readTree(resource.getInputStream()); + responseNode = asJsonNode("org/openmhealth/shim/googlefit/mapper/googlefit-merge-heart-rate.json"); } @Test diff --git a/shim-server/src/test/java/org/openmhealth/shim/googlefit/mapper/GoogleFitPhysicalActivityDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/googlefit/mapper/GoogleFitPhysicalActivityDataPointMapperUnitTests.java index 09d9dc21..42732a2f 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/googlefit/mapper/GoogleFitPhysicalActivityDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/googlefit/mapper/GoogleFitPhysicalActivityDataPointMapperUnitTests.java @@ -4,7 +4,6 @@ import org.openmhealth.schema.domain.omh.DataPoint; import org.openmhealth.schema.domain.omh.DataPointModality; import org.openmhealth.schema.domain.omh.PhysicalActivity; -import org.springframework.core.io.ClassPathResource; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; @@ -31,13 +30,9 @@ public class GoogleFitPhysicalActivityDataPointMapperUnitTests @Override public void initializeResponseNode() throws IOException { - ClassPathResource resource = - new ClassPathResource("org/openmhealth/shim/googlefit/mapper/googlefit-physical-activity.json"); - responseNode = objectMapper.readTree(resource.getInputStream()); - resource = new ClassPathResource("org/openmhealth/shim/googlefit/mapper/googlefit-sleep-activity.json"); - sleepActivityNode = objectMapper.readTree(resource.getInputStream()); - - + responseNode = asJsonNode("org/openmhealth/shim/googlefit/mapper/googlefit-merge-activity-segments.json"); + sleepActivityNode = + asJsonNode("org/openmhealth/shim/googlefit/mapper/googlefit-merge-activity-segments-only-sleep.json"); } @Test @@ -82,12 +77,12 @@ public void asDataPointsShouldNotReturnDataPointsForSleepActivityType() { @Test public void asDataPointsShouldNotReturnDataPointsForStationaryActivityTypes() throws IOException { - ClassPathResource resource = - new ClassPathResource("org/openmhealth/shim/googlefit/mapper/googlefit-stationary-activity.json"); - JsonNode stationaryActivityNode = objectMapper.readTree(resource.getInputStream()); + JsonNode stationaryActivityNode = asJsonNode( + "org/openmhealth/shim/googlefit/mapper/googlefit-merge-activity-segments-only-stationary-activity" + + ".json"); List> dataPoints = mapper.asDataPoints(singletonList(stationaryActivityNode)); - assertThat(dataPoints.size(),equalTo(0)); + assertThat(dataPoints.size(), equalTo(0)); } /* Helper methods */ diff --git a/shim-server/src/test/java/org/openmhealth/shim/googlefit/mapper/GoogleFitStepCountDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/googlefit/mapper/GoogleFitStepCountDataPointMapperUnitTests.java index ecb8c04c..d3001bdc 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/googlefit/mapper/GoogleFitStepCountDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/googlefit/mapper/GoogleFitStepCountDataPointMapperUnitTests.java @@ -4,7 +4,6 @@ import org.openmhealth.schema.domain.omh.DataPoint; import org.openmhealth.schema.domain.omh.DataPointModality; import org.openmhealth.schema.domain.omh.StepCount; -import org.springframework.core.io.ClassPathResource; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; @@ -30,12 +29,8 @@ public class GoogleFitStepCountDataPointMapperUnitTests extends GoogleFitDataPoi @Override public void initializeResponseNode() throws IOException { - ClassPathResource resource = - new ClassPathResource("org/openmhealth/shim/googlefit/mapper/googlefit-step-count.json"); - responseNode = objectMapper.readTree(resource.getInputStream()); - resource = new ClassPathResource("org/openmhealth/shim/googlefit/mapper/googlefit-empty.json"); - emptyNode = objectMapper.readTree(resource.getInputStream()); - + responseNode = asJsonNode("org/openmhealth/shim/googlefit/mapper/googlefit-merge-step-deltas.json"); + emptyNode = asJsonNode("org/openmhealth/shim/googlefit/mapper/googlefit-empty.json"); } @Test 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 ecaf346a..d85ae1c1 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 @@ -17,8 +17,9 @@ 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.BloodGlucose; +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; @@ -33,10 +34,12 @@ 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.BloodGlucoseUnit.MILLIGRAMS_PER_DECILITER; +import static org.openmhealth.schema.domain.omh.BloodGlucoseUnit.MILLIMOLES_PER_LITER; 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.*; +import static org.openmhealth.schema.domain.omh.TemporalRelationshipToMeal.AFTER_BREAKFAST; +import static org.openmhealth.schema.domain.omh.TemporalRelationshipToMeal.BEFORE_BREAKFAST; /** @@ -51,7 +54,7 @@ public class IHealthBloodGlucoseDataPointMapperUnitTests extends IHealthDataPoin @BeforeTest public void initializeResponseNode() throws IOException { - responseNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose.json"); + responseNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-glucose.json"); } @BeforeMethod @@ -103,9 +106,7 @@ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { @Test public void asDataPointsShouldReturnNoDataPointsWhenBloodGlucoseListIsEmpty() throws IOException { - ClassPathResource resource = - new ClassPathResource("/org/openmhealth/shim/ihealth/mapper/ihealth-empty-glucose.json"); - JsonNode emptyListResponseNode = objectMapper.readTree(resource.getInputStream()); + JsonNode emptyListResponseNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-glucose-empty.json"); assertThat(mapper.asDataPoints(singletonList(emptyListResponseNode)), is(empty())); } 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 b1a168ee..b7624aa3 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 @@ -51,7 +51,7 @@ public class IHealthBloodOxygenEndpointHeartRateDataPointMapperUnitTests extends @BeforeTest public void initializeResponseNode() throws IOException { - responseNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-blood-oxygen.json"); + responseNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-spo2.json"); } @BeforeMethod @@ -101,7 +101,7 @@ public void asDataPointsShouldReturnCorrectUserNotesWithDataPoints() { public void asDataPointsShouldReturnNoDataPointWhenHeartRateDataIsNotPresent() throws IOException { JsonNode noHeartRateBloodOxygenNode = asJsonNode( - "org/openmhealth/shim/ihealth/mapper/ihealth-blood-oxygen-missing-heart-rate.json"); + "org/openmhealth/shim/ihealth/mapper/ihealth-spo2-no-heart-rate.json"); assertThat(mapper.asDataPoints(singletonList(noHeartRateBloodOxygenNode)), is(empty())); } @@ -109,7 +109,7 @@ public void asDataPointsShouldReturnNoDataPointWhenHeartRateDataIsNotPresent() t @Test public void asDataPointsShouldReturnEmptyListWhenEmptyIHealthResponse() { - JsonNode emptyNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-empty-blood-oxygen.json"); + JsonNode emptyNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-spo2-empty.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 9a06c4b2..28cc30e2 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 @@ -48,7 +48,7 @@ public class IHealthBloodPressureDataPointMapperUnitTests extends IHealthDataPoi @BeforeTest public void initializeResponseNode() throws IOException { - responseNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json"); + responseNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-bp.json"); } @BeforeMethod @@ -109,7 +109,7 @@ public void asDataPointsShouldReturnCorrectUserNotesWithDataPoints() { @Test public void asDataPointsShouldReturnEmptyListWhenEmptyIHealthResponse() { - JsonNode emptyNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-empty-blood-pressure.json"); + JsonNode emptyNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-bp-empty.json"); assertThat(mapper.asDataPoints(singletonList(emptyNode)), is(empty())); } 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 1ca5931d..a009468d 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 @@ -52,7 +52,7 @@ public class IHealthBloodPressureEndpointHeartRateDataPointMapperUnitTests exten @BeforeTest public void initializeResponseNodes() throws IOException { - responseNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json"); + responseNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-bp.json"); } @BeforeMethod @@ -103,7 +103,7 @@ public void asDataPointsShouldReturnCorrectUserNotesWithDataPoints() { public void asDataPointsShouldReturnNoDataPointWhenHeartRateDataIsNotPresent() throws IOException { JsonNode noHeartRateBloodPressureNode = asJsonNode( - "org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure-missing-heart-rate.json"); + "org/openmhealth/shim/ihealth/mapper/ihealth-bp-no-heart-rate.json"); 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 43d38d6c..9ce62583 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 @@ -51,7 +51,7 @@ public class IHealthBodyMassIndexDataPointMapperUnitTests extends IHealthDataPoi @BeforeTest public void initializeResponse() throws IOException { - responseNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-body-weight.json"); + responseNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-weight.json"); } @BeforeMethod @@ -97,7 +97,7 @@ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { public void asDataPointsShouldReturnNoDataPointsWhenBodyMassIndexValueIsZero() throws IOException { JsonNode zeroValueNode = - asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-missing-body-weight-value.json"); + asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-weight-no-weight-value.json"); assertThat(mapper.asDataPoints(singletonList(zeroValueNode)), is(empty())); } @@ -105,7 +105,7 @@ public void asDataPointsShouldReturnNoDataPointsWhenBodyMassIndexValueIsZero() t @Test public void asDataPointsShouldReturnNoDataPointsWhenWeightListIsEmpty() throws IOException { - JsonNode emptyListNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight.json"); + JsonNode emptyListNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-weight-empty.json"); 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 94b29f79..4a38513c 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 @@ -48,7 +48,7 @@ public class IHealthBodyWeightDataPointMapperUnitTests extends IHealthDataPointM @BeforeTest public void initializeResponseNode() throws IOException { - responseNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-body-weight.json"); + responseNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-weight.json"); } @BeforeMethod @@ -104,7 +104,7 @@ public void asDataPointsShouldReturnCorrectUserNotes() { public void asDataPointsShouldReturnNoDataPointsWhenWeightValueEqualsZero() throws IOException { JsonNode zeroValueNode = - asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-missing-body-weight-value.json"); + asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-weight-no-weight-value.json"); assertThat(mapper.asDataPoints(singletonList(zeroValueNode)), is(empty())); } @@ -112,7 +112,7 @@ public void asDataPointsShouldReturnNoDataPointsWhenWeightValueEqualsZero() thro @Test public void asDataPointsShouldReturnNoDataPointsWhenWeightListIsEmpty() throws IOException { - JsonNode emptyListNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight.json"); + JsonNode emptyListNode = asJsonNode("org/openmhealth/shim/ihealth/mapper/ihealth-weight-empty.json"); assertThat(mapper.asDataPoints(singletonList(emptyListNode)), is(empty())); } 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 f221b101..fe4684b8 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 @@ -51,7 +51,7 @@ public class IHealthPhysicalActivityDataPointMapperUnitTests extends IHealthData @BeforeTest public void initializeResponseNode() throws IOException { - responseNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity.json"); + responseNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-sport.json"); } @BeforeMethod @@ -99,7 +99,7 @@ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { public void asDataPointsReturnsNoDataPointsForAnEmptyList() throws IOException { JsonNode emptyListResponseNode = - asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-empty-sports-activity.json"); + asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-sport-empty.json"); 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 0cdd4cf8..4bdc94eb 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 @@ -121,7 +121,7 @@ public void asDataPointsShouldReturnCorrectDataPointsWhenManuallyEntered() { @Test public void asDataPointsShouldReturnEmptyListWhenEmptyIHealthResponse() { - JsonNode emptyNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-empty-sleep.json"); + JsonNode emptyNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-sleep-empty.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 69a35e97..46319930 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 @@ -113,7 +113,7 @@ public void asDataPointsShouldReturnSensedDataPointWhenManuallyEntered() { @Test public void asDataPointsShouldReturnEmptyListWhenEmptyIHealthResponse() { - JsonNode emptyNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-empty-activity-response.json"); + JsonNode emptyNode = asJsonNode("/org/openmhealth/shim/ihealth/mapper/ihealth-activity-empty.json"); assertThat(mapper.asDataPoints(singletonList(emptyNode)), is(empty())); } diff --git a/shim-server/src/test/java/org/openmhealth/shim/runkeeper/mapper/RunkeeperPhysicalActivityDataPointMapperUnitTests.java b/shim-server/src/test/java/org/openmhealth/shim/runkeeper/mapper/RunkeeperPhysicalActivityDataPointMapperUnitTests.java index f11b1a01..6b424097 100644 --- a/shim-server/src/test/java/org/openmhealth/shim/runkeeper/mapper/RunkeeperPhysicalActivityDataPointMapperUnitTests.java +++ b/shim-server/src/test/java/org/openmhealth/shim/runkeeper/mapper/RunkeeperPhysicalActivityDataPointMapperUnitTests.java @@ -14,8 +14,10 @@ import static java.util.Collections.singletonList; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.greaterThan; import static org.openmhealth.schema.domain.omh.DataPointModality.SELF_REPORTED; import static org.openmhealth.schema.domain.omh.DataPointModality.SENSED; @@ -102,13 +104,10 @@ public void asDataPointsShouldReturnCorrectSelfReportedDataPoints() { @Test public void asDataPointsShouldNotCreateDataPointWhenOffsetMissing() throws IOException { - ClassPathResource resource = - new ClassPathResource( - "org/openmhealth/shim/runkeeper/mapper/runkeeper-fitness-activities-no-offset.json"); - JsonNode responseNodeWithoutUtcOffset = objectMapper.readTree(resource.getInputStream()); - - List> dataPoints = mapper.asDataPoints(singletonList(responseNodeWithoutUtcOffset)); - - assertThat(dataPoints.size(), equalTo(0)); + assertThat(mapper.asDataPoints( + asJsonNode( + "org/openmhealth/shim/runkeeper/mapper/runkeeper-fitness-activities-missing-offset" + + ".json")), + is(empty())); } } \ No newline at end of file diff --git a/shim-server/src/test/java/org/openmhealth/shimmer/common/transformer/DateTimeRangeTransformerUnitTests.java b/shim-server/src/test/java/org/openmhealth/shimmer/common/transformer/DateTimeRangeTransformerUnitTests.java new file mode 100644 index 00000000..2db7e527 --- /dev/null +++ b/shim-server/src/test/java/org/openmhealth/shimmer/common/transformer/DateTimeRangeTransformerUnitTests.java @@ -0,0 +1,71 @@ +/* + * 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.shimmer.common.transformer; + +import com.google.common.collect.Lists; +import com.google.common.collect.Range; +import org.testng.annotations.DataProvider; + +import java.time.LocalDate; +import java.time.Month; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.util.Iterator; + +import static com.google.common.collect.Range.*; +import static java.time.ZoneOffset.UTC; + + +/** + * @author Emerson Farrugia + */ +public abstract class DateTimeRangeTransformerUnitTests { + + private static final OffsetDateTime JANUARY_FIRST_MIDNIGHT_UTC + = LocalDate.of(2015, Month.JANUARY.getValue(), 1).atStartOfDay(UTC).toOffsetDateTime(); + + private static final OffsetDateTime FEBRUARY_FIRST_MIDNIGHT_UTC = JANUARY_FIRST_MIDNIGHT_UTC.plusMonths(1); + + public static final Range ALL_TIME = all(); + public static final Range AFTER_DECEMBER = atLeast(JANUARY_FIRST_MIDNIGHT_UTC); + public static final Range BEFORE_JANUARY = lessThan(JANUARY_FIRST_MIDNIGHT_UTC); + public static final Range JANUARY = + closedOpen(JANUARY_FIRST_MIDNIGHT_UTC, FEBRUARY_FIRST_MIDNIGHT_UTC); + + public static final ZoneId EST = ZoneId.of("-05:00"); + public static final ZoneId CEST = ZoneId.of("+02:00"); + public static final ZoneId AUSTRALIA_ADELAIDE_TZ = ZoneId.of("Australia/Adelaide"); + + + @DataProvider(name = "allZoneIdAndDateTimeRangeCombinations") + protected Iterator allZoneIdAndDateTimeRangeCombinationProvider() { + + return Lists.newArrayList( + new Object[] {EST, ALL_TIME}, + new Object[] {EST, AFTER_DECEMBER}, + new Object[] {EST, BEFORE_JANUARY}, + new Object[] {EST, JANUARY}, + new Object[] {CEST, ALL_TIME}, + new Object[] {CEST, AFTER_DECEMBER}, + new Object[] {CEST, BEFORE_JANUARY}, + new Object[] {CEST, JANUARY}, + new Object[] {AUSTRALIA_ADELAIDE_TZ, ALL_TIME}, + new Object[] {AUSTRALIA_ADELAIDE_TZ, AFTER_DECEMBER}, + new Object[] {AUSTRALIA_ADELAIDE_TZ, BEFORE_JANUARY}, + new Object[] {AUSTRALIA_ADELAIDE_TZ, JANUARY}).iterator(); + } +} diff --git a/shim-server/src/test/java/org/openmhealth/shimmer/common/transformer/FixedTimeZoneDateTimeRangeTransformerUnitTests.java b/shim-server/src/test/java/org/openmhealth/shimmer/common/transformer/FixedTimeZoneDateTimeRangeTransformerUnitTests.java new file mode 100644 index 00000000..1ca63cdb --- /dev/null +++ b/shim-server/src/test/java/org/openmhealth/shimmer/common/transformer/FixedTimeZoneDateTimeRangeTransformerUnitTests.java @@ -0,0 +1,57 @@ +/* + * 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.shimmer.common.transformer; + +import com.google.common.collect.Range; +import org.testng.annotations.Test; + +import java.time.OffsetDateTime; +import java.time.ZoneId; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; + + +/** + * @author Emerson Farrugia + */ +public class FixedTimeZoneDateTimeRangeTransformerUnitTests extends DateTimeRangeTransformerUnitTests { + + @Test(dataProvider = "allZoneIdAndDateTimeRangeCombinations") + public void transformRangeShouldReturnCorrectRange(ZoneId zoneId, Range inputRange) + throws Exception { + + FixedTimeZoneDateTimeRangeTransformer transformer = new FixedTimeZoneDateTimeRangeTransformer(zoneId); + Range transformedRange = transformer.transformRange(inputRange); + + assertThat(transformedRange.hasLowerBound(), equalTo(inputRange.hasLowerBound())); + + if (transformedRange.hasLowerBound()) { + // FIXME convert zone ID to offset + // assertThat(transformedRange.lowerEndpoint().getOffset(), equalTo(zoneId)); + assertThat(transformedRange.lowerEndpoint().toInstant(), equalTo(inputRange.lowerEndpoint().toInstant())); + } + + assertThat(transformedRange.hasUpperBound(), equalTo(inputRange.hasUpperBound())); + + if (transformedRange.hasUpperBound()) { + // FIXME convert zone ID to offset + // assertThat(transformedRange.upperEndpoint().getOffset(), equalTo(zoneId)); + assertThat(transformedRange.upperEndpoint().toInstant(), equalTo(inputRange.upperEndpoint().toInstant())); + } + } +} \ No newline at end of file diff --git a/shim-server/src/test/java/org/openmhealth/shimmer/common/validation/DataPointSearchCriteriaValidationUnitTests.java b/shim-server/src/test/java/org/openmhealth/shimmer/common/validation/DataPointSearchCriteriaValidationUnitTests.java new file mode 100644 index 00000000..d3056e5c --- /dev/null +++ b/shim-server/src/test/java/org/openmhealth/shimmer/common/validation/DataPointSearchCriteriaValidationUnitTests.java @@ -0,0 +1,167 @@ +/* + * 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.shimmer.common.validation; + +import org.openmhealth.shimmer.common.domain.DataPointSearchCriteria; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static java.time.OffsetDateTime.now; + + +/** + * @author Emerson Farrugia + */ +public class DataPointSearchCriteriaValidationUnitTests extends ValidationUnitTests { + + private DataPointSearchCriteria searchCriteria; + + + @BeforeMethod + public void initializeFixture() { + + searchCriteria = new DataPointSearchCriteria(); + + searchCriteria.setUserId("someUserId"); // Tesla + searchCriteria.setSchemaNamespace("someNamespace"); + searchCriteria.setSchemaName("someName"); + searchCriteria.setAcquisitionSourceId("someSourceId"); + searchCriteria.setEffectiveOnOrAfter(now().minusDays(1)); + searchCriteria.setEffectiveOnOrAfter(now().minusHours(23)); + } + + @Test + public void validateShouldPassOnValidCriteria() { + + assertThatBeanIsValid(searchCriteria); + } + + @Test + public void validateShouldPassOnUndefinedAcquisitionSourceId() { + + searchCriteria.setAcquisitionSourceId(null); + + assertThatBeanIsValid(searchCriteria); + } + + @Test + public void validateShouldFailOnUndefinedUserId() { + + searchCriteria.setUserId(null); + + assertThatBeanIsNotValid(searchCriteria); + } + + @Test + public void validateShouldFailOnEmptyUserId() { + + searchCriteria.setUserId(""); + + assertThatBeanIsNotValid(searchCriteria); + } + + @Test + public void validateShouldFailOnUndefinedSchemaNamespace() { + + searchCriteria.setSchemaNamespace(null); + + assertThatBeanIsNotValid(searchCriteria); + } + + @Test + public void validateShouldFailOnEmptySchemaNamespace() { + + searchCriteria.setSchemaNamespace(""); + + assertThatBeanIsNotValid(searchCriteria); + } + + @Test + public void validateShouldFailOnInvalidSchemaNamespace() { + + searchCriteria.setSchemaNamespace("foo*bar"); + + assertThatBeanIsNotValid(searchCriteria); + } + + @Test + public void validateShouldFailOnUndefinedSchemaName() { + + searchCriteria.setSchemaName(null); + + assertThatBeanIsNotValid(searchCriteria); + } + + @Test + public void validateShouldFailOnEmptySchemaName() { + + searchCriteria.setSchemaName(""); + + assertThatBeanIsNotValid(searchCriteria); + } + + @Test + public void validateShouldFailOnInvalidSchemaName() { + + searchCriteria.setSchemaName("foo.bar"); + + assertThatBeanIsNotValid(searchCriteria); + } + + @Test + public void validateShouldFailOnEmptyAcquisitionSourceId() { + + searchCriteria.setAcquisitionSourceId(""); + + assertThatBeanIsNotValid(searchCriteria); + } + + // TODO add tests for reversed time range bounds + + @Test + public void validateShouldPassOnUnrestrictedEffectiveRangeAndRestrictedCreationRange() { + + searchCriteria.setEffectiveOnOrAfter(null); + searchCriteria.setEffectiveBefore(null); + searchCriteria.setCreatedOnOrAfter(now().minusDays(1)); + searchCriteria.setCreatedBefore(now().minusHours(23)); + + assertThatBeanIsValid(searchCriteria); + } + + @Test + public void validateShouldPassOnRestrictedEffectiveRangeAndUnrestrictedCreationRange() { + + searchCriteria.setEffectiveOnOrAfter(now().minusDays(1)); + searchCriteria.setEffectiveBefore(now().minusHours(23)); + searchCriteria.setCreatedOnOrAfter(null); + searchCriteria.setCreatedBefore(null); + + assertThatBeanIsValid(searchCriteria); + } + + @Test + public void validateShouldFailOnUnrestrictedTimestampRange() { + + searchCriteria.setCreatedOnOrAfter(null); + searchCriteria.setCreatedBefore(null); + searchCriteria.setEffectiveOnOrAfter(null); + searchCriteria.setEffectiveBefore(null); + + assertThatBeanIsNotValid(searchCriteria); + } +} diff --git a/shim-server/src/test/java/org/openmhealth/shimmer/common/validation/SchemaNameValidationUnitTests.java b/shim-server/src/test/java/org/openmhealth/shimmer/common/validation/SchemaNameValidationUnitTests.java new file mode 100644 index 00000000..e2c02f97 --- /dev/null +++ b/shim-server/src/test/java/org/openmhealth/shimmer/common/validation/SchemaNameValidationUnitTests.java @@ -0,0 +1,59 @@ +/* + * 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.shimmer.common.validation; + +import org.testng.annotations.Test; + + +/** + * @author Emerson Farrugia + */ +public class SchemaNameValidationUnitTests extends ValidationUnitTests { + + class Wrapper { + + private String name; + + public Wrapper(String name) { + this.name = name; + } + + @ValidSchemaName + public String getName() { + return name; + } + } + + + @Test + public void validateShouldFailOnEmptyString() { + + assertThatBeanIsNotValid(new Wrapper("")); + } + + @Test + public void validateShouldFailOnIllegalCharacter() { + + assertThatBeanIsNotValid(new Wrapper("abc.")); + } + + @Test + public void validateShouldPassOnValidString() { + + assertThatBeanIsValid(new Wrapper("abc")); + } +} diff --git a/shim-server/src/test/java/org/openmhealth/shimmer/common/validation/SchemaNamespaceValidationUnitTests.java b/shim-server/src/test/java/org/openmhealth/shimmer/common/validation/SchemaNamespaceValidationUnitTests.java new file mode 100644 index 00000000..13f13f54 --- /dev/null +++ b/shim-server/src/test/java/org/openmhealth/shimmer/common/validation/SchemaNamespaceValidationUnitTests.java @@ -0,0 +1,59 @@ +/* + * 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.shimmer.common.validation; + +import org.testng.annotations.Test; + + +/** + * @author Emerson Farrugia + */ +public class SchemaNamespaceValidationUnitTests extends ValidationUnitTests { + + class Wrapper { + + private String namespace; + + public Wrapper(String namespace) { + this.namespace = namespace; + } + + @ValidSchemaNamespace + public String getNamespace() { + return namespace; + } + } + + + @Test + public void validateShouldFailOnEmptyString() { + + assertThatBeanIsNotValid(new Wrapper("")); + } + + @Test + public void validateShouldFailOnIllegalCharacter() { + + assertThatBeanIsNotValid(new Wrapper("abc%")); + } + + @Test + public void validateShouldPassOnValidString() { + + assertThatBeanIsValid(new Wrapper("a.bc")); + } +} diff --git a/shim-server/src/test/java/org/openmhealth/shimmer/common/validation/ValidationUnitTests.java b/shim-server/src/test/java/org/openmhealth/shimmer/common/validation/ValidationUnitTests.java new file mode 100644 index 00000000..caf6a405 --- /dev/null +++ b/shim-server/src/test/java/org/openmhealth/shimmer/common/validation/ValidationUnitTests.java @@ -0,0 +1,71 @@ +/* + * 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.shimmer.common.validation; + + +import org.hibernate.validator.HibernateValidator; +import org.hibernate.validator.HibernateValidatorConfiguration; +import org.testng.annotations.BeforeClass; + +import javax.validation.Validation; +import javax.validation.Validator; +import javax.validation.ValidatorFactory; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; + + +/** + * @author Emerson Farrugia + */ +public abstract class ValidationUnitTests { + + private static Validator validator; + + + /** + * @return the validator + */ + protected static Validator getValidator() { + return validator; + } + + + @BeforeClass + public static void prepareValidator() { + + HibernateValidatorConfiguration configuration = Validation.byProvider(HibernateValidator.class).configure(); + + // enable fail-fast mode for unit tests to simplify debugging + ValidatorFactory factory = configuration.failFast(true).buildValidatorFactory(); + + validator = factory.getValidator(); + } + + protected void assertThatBeanIsValid(Object bean) { + + assertThat(getValidator().validate(bean), is(empty())); + + } + + protected void assertThatBeanIsNotValid(Object bean) { + + assertThat(getValidator().validate(bean), is(not(empty()))); + } +} \ No newline at end of file diff --git a/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-activities-empty.json b/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-activities-date-empty-activities-list.json similarity index 99% rename from shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-activities-empty.json rename to shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-activities-date-empty-activities-list.json index 40cf9bd3..505a0f68 100644 --- a/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-activities-empty.json +++ b/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-activities-date-empty-activities-list.json @@ -48,4 +48,4 @@ "steps": 12517, "veryActiveMinutes": 42 } -} \ No newline at end of file +} diff --git a/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-activities-multiple.json b/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-activities-date-multiple-in-activities-list.json similarity index 99% rename from shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-activities-multiple.json rename to shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-activities-date-multiple-in-activities-list.json index 3c7a680a..eee8223e 100644 --- a/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-activities-multiple.json +++ b/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-activities-date-multiple-in-activities-list.json @@ -110,4 +110,4 @@ "steps": 15242, "veryActiveMinutes": 24 } -} \ No newline at end of file +} diff --git a/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-activities-single.json b/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-activities-date-single-in-activities-list.json similarity index 99% rename from shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-activities-single.json rename to shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-activities-date-single-in-activities-list.json index 4429eac3..2929779e 100644 --- a/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-activities-single.json +++ b/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-activities-date-single-in-activities-list.json @@ -64,4 +64,4 @@ "steps": 10007, "veryActiveMinutes": 3 } -} \ No newline at end of file +} 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-activities-steps-1d-1m-intraday.json similarity index 99% rename from shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-intraday-steps.json rename to shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-activities-steps-1d-1m-intraday.json index f7076fe5..7e9fc9f9 100644 --- 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-activities-steps-1d-1m-intraday.json @@ -23,4 +23,4 @@ "datasetInterval": 1, "datasetType": "minute" } -} \ No newline at end of file +} diff --git a/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-activities-steps-timeseries.json b/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-activities-steps-timeseries.json new file mode 100644 index 00000000..29e6ff08 --- /dev/null +++ b/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-activities-steps-timeseries.json @@ -0,0 +1,16 @@ +{ + "activities-steps": [ + { + "dateTime": "2015-08-23", + "value": "175" + }, + { + "dateTime": "2015-08-24", + "value": "2937" + }, + { + "dateTime": "2015-08-25", + "value": "0" + } + ] +} \ No newline at end of file diff --git a/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-body-weight.json b/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-body-log-weight.json similarity index 99% rename from shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-body-weight.json rename to shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-body-log-weight.json index 24b6cf7d..a89d230e 100644 --- a/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-body-weight.json +++ b/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-body-log-weight.json @@ -29,4 +29,4 @@ "weight": 57.2 } ] -} \ No newline at end of file +} diff --git a/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-user-info.json b/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-user-info.json deleted file mode 100644 index a0cc36b9..00000000 --- a/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-user-info.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "user": { - "age": 26, - "avatar": "http://static0.fitbit.com/images/profile/defaultProfile_100_female.gif", - "avatar150": "http://static0.fitbit.com/images/profile/defaultProfile_150_female.gif", - "country": "US", - "dateOfBirth": "1989-05-17", - "displayName": "Sarah", - "distanceUnit": "en_US", - "encodedId": "25X23T", - "foodsLocale": "en_US", - "fullName": "Sarah Francis", - "gender": "FEMALE", - "glucoseUnit": "en_US", - "height": 162.60000000000002, - "heightUnit": "en_US", - "locale": "en_US", - "memberSince": "2013-05-11", - "offsetFromUTCMillis": -14400000, - "startDayOfWeek": "MONDAY", - "strideLengthRunning": 86.60000000000001, - "strideLengthWalking": 67.2, - "timezone": "America/New_York", - "topBadges": [ - { - "badgeGradientEndColor": "FFDB01", - "badgeGradientStartColor": "D99123", - "badgeType": "DAILY_STEPS", - "category": "Daily Steps", - "cheers": [], - "dateTime": "2013-07-27", - "description": "25,000 steps in a day", - "earnedMessage": "Congrats on earning your first Classics badge!", - "encodedId": "228TLX", - "image100px": "http://static0.fitbit.com/images/badges_new/100px/badge_daily_steps25k.png", - "image125px": "http://static0.fitbit.com/images/badges_new/125px/badge_daily_steps25k.png", - "image300px": "http://static0.fitbit.com/images/badges_new/300px/badge_daily_steps25k.png", - "image50px": "http://static0.fitbit.com/images/badges_new/badge_daily_steps25k.png", - "image75px": "http://static0.fitbit.com/images/badges_new/75px/badge_daily_steps25k.png", - "marketingDescription": "You've walked 25,000 steps And earned the Classics badge!", - "mobileDescription": "With this impressive fitness feat, you've added a badge to your growing collection. Nice job, you stepping all-star!", - "name": "Classics (25,000 steps in a day)", - "shareImage640px": "http://static0.fitbit.com/images/badges_new/386px/shareLocalized/en_US/badge_daily_steps25k.png", - "shareText": "I took 25,000 steps and earned the Classics badge! #Fitbit", - "shortDescription": "25,000 steps", - "shortName": "Classics", - "timesAchieved": 1, - "value": 25000 - }, - { - "badgeGradientEndColor": "42C401", - "badgeGradientStartColor": "007D3C", - "badgeType": "LIFETIME_DISTANCE", - "category": "Lifetime Distance", - "cheers": [], - "dateTime": "2015-01-15", - "description": "736 lifetime miles", - "earnedMessage": "Whoa! You've earned the Italy badge!", - "encodedId": "22B8MC", - "image100px": "http://static0.fitbit.com/images/badges_new/100px/badge_lifetime_miles736.png", - "image125px": "http://static0.fitbit.com/images/badges_new/125px/badge_lifetime_miles736.png", - "image300px": "http://static0.fitbit.com/images/badges_new/300px/badge_lifetime_miles736.png", - "image50px": "http://static0.fitbit.com/images/badges_new/badge_lifetime_miles736.png", - "image75px": "http://static0.fitbit.com/images/badges_new/75px/badge_lifetime_miles736.png", - "marketingDescription": "By reaching 736 lifetime miles, you've earned the Italy badge!", - "mobileDescription": "By walking the entire length of Italy, you've stepped your way to another collosal achievement!", - "name": "Italy (736 lifetime miles)", - "shareImage640px": "http://static0.fitbit.com/images/badges_new/386px/shareLocalized/en_US/badge_lifetime_miles736.png", - "shareText": "I covered 736 miles with my #Fitbit and earned the Italy badge.", - "shortDescription": "736 miles", - "shortName": "Italy", - "timesAchieved": 1, - "unit": "MILES", - "value": 736 - } - ], - "waterUnit": "en_US", - "waterUnitName": "fl oz", - "weight": 55.4, - "weightUnit": "en_US" - } -} \ No newline at end of file diff --git a/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-sleep-empty.json b/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-sleep-date-empty-sleep-list.json similarity index 98% rename from shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-sleep-empty.json rename to shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-sleep-date-empty-sleep-list.json index a02ae4a2..d494c304 100644 --- a/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-sleep-empty.json +++ b/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-sleep-date-empty-sleep-list.json @@ -5,4 +5,4 @@ "totalSleepRecords": 0, "totalTimeInBed": 0 } -} \ No newline at end of file +} diff --git a/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-sleep-multiple.json b/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-sleep-date-multiple-in-sleep-list.json similarity index 99% rename from shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-sleep-multiple.json rename to shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-sleep-date-multiple-in-sleep-list.json index d7ed9f73..7cfbeee9 100644 --- a/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-sleep-multiple.json +++ b/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-sleep-date-multiple-in-sleep-list.json @@ -2326,4 +2326,4 @@ "totalSleepRecords": 2, "totalTimeInBed": 570 } -} \ No newline at end of file +} diff --git a/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-sleep.json b/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-sleep-date.json similarity index 99% rename from shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-sleep.json rename to shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-sleep-date.json index 2153fa41..c3a6a42f 100644 --- a/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-get-sleep.json +++ b/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-sleep-date.json @@ -3870,4 +3870,4 @@ "totalSleepRecords": 1, "totalTimeInBed": 961 } -} \ No newline at end of file +} diff --git a/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-time-series-steps.json b/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-time-series-steps.json deleted file mode 100644 index 2b02b1b3..00000000 --- a/shim-server/src/test/resources/org/openmhealth/shim/fitbit/mapper/fitbit-time-series-steps.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "activities-steps": [ - { - "dateTime": "2015-05-24", - "value": "0" - }, - { - "dateTime": "2015-05-26", - "value": "2170" - }, - { - "dateTime": "2015-05-27", - "value": "3248" - } - ] -} \ No newline at end of file diff --git a/shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-sleep-activity.json b/shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-merge-activity-segments-only-sleep.json similarity index 100% rename from shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-sleep-activity.json rename to shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-merge-activity-segments-only-sleep.json diff --git a/shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-stationary-activity.json b/shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-merge-activity-segments-only-stationary-activity.json similarity index 100% rename from shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-stationary-activity.json rename to shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-merge-activity-segments-only-stationary-activity.json diff --git a/shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-physical-activity.json b/shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-merge-activity-segments.json similarity index 100% rename from shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-physical-activity.json rename to shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-merge-activity-segments.json diff --git a/shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-calories-burned.json b/shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-merge-calories-expended.json similarity index 100% rename from shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-calories-burned.json rename to shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-merge-calories-expended.json diff --git a/shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-heart-rate.json b/shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-merge-heart-rate.json similarity index 100% rename from shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-heart-rate.json rename to shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-merge-heart-rate.json diff --git a/shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-body-height.json b/shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-merge-height.json similarity index 97% rename from shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-body-height.json rename to shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-merge-height.json index f81bd85d..6f37119f 100644 --- a/shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-body-height.json +++ b/shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-merge-height.json @@ -28,4 +28,4 @@ "modifiedTimeMillis": "1436370178573" } ] -} \ No newline at end of file +} diff --git a/shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-step-count.json b/shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-merge-step-deltas.json similarity index 100% rename from shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-step-count.json rename to shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-merge-step-deltas.json diff --git a/shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-body-weight.json b/shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-merge-weight.json similarity index 100% rename from shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-body-weight.json rename to shim-server/src/test/resources/org/openmhealth/shim/googlefit/mapper/googlefit-merge-weight.json 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-activity-empty.json similarity index 98% rename from shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-activity-response.json rename to shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-activity-empty.json index eabb67c7..d345e805 100644 --- 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-activity-empty.json @@ -7,4 +7,4 @@ "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-bp-empty.json similarity index 98% rename from shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-blood-pressure.json rename to shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-bp-empty.json index 138bb791..ff70ea26 100644 --- 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-bp-empty.json @@ -7,4 +7,4 @@ "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-pressure-missing-heart-rate.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-bp-no-heart-rate.json similarity index 99% rename from shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure-missing-heart-rate.json rename to shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-bp-no-heart-rate.json index c22dd10c..d9b4c524 100644 --- 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-bp-no-heart-rate.json @@ -23,4 +23,4 @@ "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-bp.json similarity index 99% rename from shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-pressure.json rename to shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-bp.json index 20fa5f72..6d359100 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-bp.json @@ -38,4 +38,4 @@ "PageNumber": 1, "PrevPageUrl": "", "RecordCount": 5 -} \ No newline at end of file +} diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-glucose.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-glucose-empty.json similarity index 98% rename from shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-glucose.json rename to shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-glucose-empty.json index 1f97ef02..09a9c3ca 100644 --- a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-glucose.json +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-glucose-empty.json @@ -7,4 +7,4 @@ "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-glucose.json similarity index 99% rename from shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-glucose.json rename to shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-glucose.json index 60ba0a06..172e1416 100644 --- 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-glucose.json @@ -34,4 +34,4 @@ "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-sleep.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sleep-empty.json similarity index 98% rename from shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-sleep.json rename to shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sleep-empty.json index 9b372102..a19bf7e6 100644 --- 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-sleep-empty.json @@ -6,4 +6,4 @@ "PrevPageUrl": "", "RecordCount": 0, "SRDataList": [] -} \ 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-spo2-empty.json similarity index 98% rename from shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-blood-oxygen.json rename to shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-spo2-empty.json index 895db9cb..90205a92 100644 --- 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-spo2-empty.json @@ -6,4 +6,4 @@ "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-oxygen-missing-heart-rate.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-spo2-no-heart-rate.json similarity index 99% rename from shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-oxygen-missing-heart-rate.json rename to shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-spo2-no-heart-rate.json index a5cf36fa..bd8ca518 100644 --- 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-spo2-no-heart-rate.json @@ -19,4 +19,4 @@ "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-oxygen.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-spo2.json similarity index 99% rename from shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-oxygen.json rename to shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-spo2.json index e35aef3f..45b76364 100644 --- a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-blood-oxygen.json +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-spo2.json @@ -31,4 +31,4 @@ "PageNumber": 1, "PrevPageUrl": "", "RecordCount": 2 -} \ No newline at end of file +} diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-sports-activity.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sport-empty.json similarity index 98% rename from shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-sports-activity.json rename to shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sport-empty.json index 33b065b7..56093dd3 100644 --- a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-sports-activity.json +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sport-empty.json @@ -6,4 +6,4 @@ "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-sport.json similarity index 99% rename from shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sports-activity.json rename to shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-sport.json index 2744bc8a..013bdcac 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-sport.json @@ -31,4 +31,4 @@ "TimeZone": 1 } ] -} \ No newline at end of file +} diff --git a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight.json b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-weight-empty.json similarity index 98% rename from shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight.json rename to shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-weight-empty.json index 765b8503..17f9d6a6 100644 --- a/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-empty-body-weight.json +++ b/shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-weight-empty.json @@ -7,4 +7,4 @@ "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-weight-no-weight-value.json similarity index 99% rename from shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-missing-body-weight-value.json rename to shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-weight-no-weight-value.json index 89955bb3..8e1f79a6 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-weight-no-weight-value.json @@ -23,4 +23,4 @@ } ], "WeightUnit": 0 -} \ 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-weight.json similarity index 99% rename from shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-body-weight.json rename to shim-server/src/test/resources/org/openmhealth/shim/ihealth/mapper/ihealth-weight.json index 452fbf9f..b649ee22 100644 --- 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-weight.json @@ -38,4 +38,4 @@ } ], "WeightUnit": 0 -} \ No newline at end of file +} diff --git a/shim-server/src/test/resources/org/openmhealth/shim/runkeeper/mapper/runkeeper-fitness-activities-no-offset.json b/shim-server/src/test/resources/org/openmhealth/shim/runkeeper/mapper/runkeeper-fitness-activities-missing-offset.json similarity index 100% rename from shim-server/src/test/resources/org/openmhealth/shim/runkeeper/mapper/runkeeper-fitness-activities-no-offset.json rename to shim-server/src/test/resources/org/openmhealth/shim/runkeeper/mapper/runkeeper-fitness-activities-missing-offset.json