diff --git a/README.md b/README.md index 85e29b54..fcd7fdbe 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ To run the application do: - `/usr/share/conf/radar/rest-api` - `/usr/local/conf/radar/rest-api` - run `./gradlew build` -- Run the `radar-restapi-0.2.1.jar` located at `build/libs/` +- Run the `radar-restapi-0.3.jar` located at `build/libs/` By default, log messages are redirected to the `STDOUT`. diff --git a/build.gradle b/build.gradle index e434f1f5..84169215 100644 --- a/build.gradle +++ b/build.gradle @@ -4,13 +4,13 @@ plugins { } group 'org.radarcns' -version '0.2.1' +version '0.3' targetCompatibility = '1.8' sourceCompatibility = '1.8' ext.grizzly = '2.4.3' -ext.jersey = '2.26' +ext.jersey = '2.27' ext.junitVersion = '4.12' ext.hamcrestVersion = '1.3' ext.logback = '1.2.2' @@ -18,8 +18,7 @@ ext.mongodb = '3.6.0' ext.radarCommons = '0.7' ext.swagger = '2.0.0-rc4' ext.tomcat = '8.0.47' -ext.radarAuthVersion = '0.3.3-SNAPSHOT' -ext.managmentPortalClientVersion = '0.3.4' +ext.radarAuthVersion = '0.5.0' ext.wireMockVersion = '2.5.1' ext.jacksonVersion = '2.9.3' ext.mockitoVersion = '2.2.29' @@ -76,3 +75,8 @@ if (!hasProperty('profile')) { } apply from: "gradle/profile.${profile}.gradle" + + +wrapper { + gradleVersion '4.9' +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 933b6473..949819d2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip diff --git a/src/endToEndTest/java/org/radarcns/EndToEndTest.java b/src/endToEndTest/java/org/radarcns/EndToEndTest.java index d09e40f4..68cf7775 100644 --- a/src/endToEndTest/java/org/radarcns/EndToEndTest.java +++ b/src/endToEndTest/java/org/radarcns/EndToEndTest.java @@ -53,8 +53,8 @@ import org.radarcns.domain.restapi.dataset.Dataset; import org.radarcns.domain.restapi.format.Acceleration; import org.radarcns.domain.restapi.format.Quartiles; +import org.radarcns.domain.restapi.header.DataSetHeader; import org.radarcns.domain.restapi.header.DescriptiveStatistic; -import org.radarcns.domain.restapi.header.Header; import org.radarcns.integration.util.ApiClient; import org.radarcns.integration.util.ExpectedDataSetFactory; import org.radarcns.integration.util.Utility; @@ -258,10 +258,10 @@ private static Map getReceivedMessage( Dataset dataset = Utility.cloneDataset(expectedCount.get(config)); - Header updatedHeader = dataset.getHeader(); - updatedHeader.setDescriptiveStatistic(DescriptiveStatistic.RECEIVED_MESSAGES); - updatedHeader.setUnit("PERCENTAGE"); - dataset.setHeader(updatedHeader); + DataSetHeader updatedHeader = (DataSetHeader) dataset.getHeader() + .descriptiveStatistic(DescriptiveStatistic.RECEIVED_MESSAGES) + .unit("PERCENTAGE"); + dataset.header(updatedHeader); for (DataItem item : dataset.getDataset()) { if (item.getValue() instanceof Double) { diff --git a/src/integrationTest/docker-compose.yml b/src/integrationTest/docker-compose.yml index 6b446425..7b4ae13c 100644 --- a/src/integrationTest/docker-compose.yml +++ b/src/integrationTest/docker-compose.yml @@ -32,6 +32,8 @@ services: - simplestack ports: - "8080:8080" + environment: + RADAR_IS_CONFIG_LOCATION: /volumes/radar/radar-is.yml volumes: - ./volumes/radar:/usr/local/conf/radar/rest-api - ../test/resources/org/radarcns/status/hdfs/bins.csv:/var/lib/hdfs/output/bins.csv @@ -40,7 +42,7 @@ services: # Management Portal # #---------------------------------------------------------------------------# managementportal-app: - image: radarcns/management-portal:0.3.3 + image: radarbase/management-portal:0.5.0 networks: - default - simplestack diff --git a/src/integrationTest/java/org/radarcns/integration/util/ExpectedDataSetFactory.java b/src/integrationTest/java/org/radarcns/integration/util/ExpectedDataSetFactory.java index 5b3fb96e..860c2dd5 100644 --- a/src/integrationTest/java/org/radarcns/integration/util/ExpectedDataSetFactory.java +++ b/src/integrationTest/java/org/radarcns/integration/util/ExpectedDataSetFactory.java @@ -27,13 +27,14 @@ import org.radarcns.domain.restapi.dataset.Dataset; import org.radarcns.domain.restapi.format.Acceleration; import org.radarcns.domain.restapi.format.Quartiles; +import org.radarcns.domain.restapi.header.DataSetHeader; import org.radarcns.domain.restapi.header.DescriptiveStatistic; import org.radarcns.domain.restapi.header.Header; import org.radarcns.domain.restapi.header.TimeFrame; import org.radarcns.mock.model.ExpectedValue; import org.radarcns.stream.collector.DoubleArrayCollector; import org.radarcns.stream.collector.DoubleValueCollector; -import org.radarcns.util.RadarConverter; +import org.radarcns.util.TimeScale; /** * Produces {@link Dataset} and {@link org.bson.Document} for {@link ExpectedValue}. @@ -57,8 +58,8 @@ public Dataset getDataset(ExpectedValue expectedValue, String projectName, Strin String sourceId, String sourceType, String sensorType, DescriptiveStatistic statistic, TimeWindow timeWindow) { - Header header = getHeader(expectedValue, projectName, subjectId, sourceId, sourceType, - sensorType, statistic, timeWindow); + DataSetHeader header = getHeader(expectedValue, projectName, subjectId, sourceId, + sourceType, sensorType, statistic, timeWindow); return new Dataset(header, getItem(expectedValue, header)); } @@ -75,11 +76,11 @@ public Dataset getDataset(ExpectedValue expectedValue, String projectName, Strin * @param timeWindow time interval between two consecutive samples * @return {@link Header} for a {@link Dataset} */ - public Header getHeader(ExpectedValue expectedValue, String projectName, String subjectId, - String sourceId, String sourceType, String sensorType, DescriptiveStatistic statistic, - TimeWindow timeWindow) { - return new Header(projectName, subjectId, sourceId, sourceType, sensorType, statistic, - null, timeWindow, null, + public DataSetHeader getHeader(ExpectedValue expectedValue, String projectName, + String subjectId, String sourceId, String sourceType, String sensorType, + DescriptiveStatistic statistic, TimeWindow timeWindow) { + return new DataSetHeader(projectName, subjectId, sourceId, sourceType, sensorType, + statistic, null, timeWindow, null, getEffectiveTimeFrame(expectedValue, timeWindow)); } @@ -97,7 +98,7 @@ public TimeFrame getEffectiveTimeFrame(ExpectedValue expectedValue, TimeWindo return new TimeFrame( Instant.ofEpochMilli(windows.get(0)), Instant.ofEpochMilli(windows.get(windows.size() - 1)) - .plus(RadarConverter.getDuration(timeWindow))); + .plus(TimeScale.getDuration(timeWindow))); } @@ -108,7 +109,7 @@ public TimeFrame getEffectiveTimeFrame(ExpectedValue expectedValue, TimeWindo * @return {@code List} for a {@link Dataset} * @see DataItem **/ - public List getItem(ExpectedValue expectedValue, Header header) { + public List getItem(ExpectedValue expectedValue, DataSetHeader header) { if (expectedValue.getSeries().isEmpty()) { return Collections.emptyList(); diff --git a/src/integrationTest/java/org/radarcns/integration/util/ExpectedDocumentFactory.java b/src/integrationTest/java/org/radarcns/integration/util/ExpectedDocumentFactory.java index 4dfd523d..4c43e0d9 100644 --- a/src/integrationTest/java/org/radarcns/integration/util/ExpectedDocumentFactory.java +++ b/src/integrationTest/java/org/radarcns/integration/util/ExpectedDocumentFactory.java @@ -47,7 +47,7 @@ import org.radarcns.mongo.util.MongoHelper.Stat; import org.radarcns.stream.collector.DoubleArrayCollector; import org.radarcns.stream.collector.DoubleValueCollector; -import org.radarcns.util.RadarConverter; +import org.radarcns.util.TimeScale; /** * It computes the expected Documents for a test case i.e. {@link ExpectedValue}. @@ -120,7 +120,7 @@ private List getDocumentsBySingle(ExpectedValue expectedValue, DoubleValueCollector doubleValueCollector = (DoubleValueCollector) expectedValue .getSeries().get(timestamp); Instant start = Instant.ofEpochMilli(timestamp); - Instant end = start.plus(RadarConverter.getDuration(timeWindow)); + Instant end = start.plus(TimeScale.getDuration(timeWindow)); list.add(buildDocument(expectedValue.getLastKey().getProjectId(), expectedValue.getLastKey().getUserId(), expectedValue.getLastKey().getSourceId(), start, end, diff --git a/src/integrationTest/java/org/radarcns/integration/util/RandomInput.java b/src/integrationTest/java/org/radarcns/integration/util/RandomInput.java index c0c5a6be..c9ab9612 100644 --- a/src/integrationTest/java/org/radarcns/integration/util/RandomInput.java +++ b/src/integrationTest/java/org/radarcns/integration/util/RandomInput.java @@ -16,9 +16,10 @@ package org.radarcns.integration.util; -import static org.radarcns.mongo.data.monitor.ApplicationStatusRecordCounter.RECORD_COLLECTION; -import static org.radarcns.mongo.data.monitor.ApplicationStatusServerStatus.STATUS_COLLECTION; -import static org.radarcns.mongo.data.monitor.ApplicationStatusUpTime.UPTIME_COLLECTION; +import static java.time.temporal.ChronoUnit.SECONDS; +import static org.radarcns.mongo.data.monitor.application.ApplicationStatusRecordCounter.RECORD_COLLECTION; +import static org.radarcns.mongo.data.monitor.application.ApplicationStatusServerStatus.STATUS_COLLECTION; +import static org.radarcns.mongo.data.monitor.application.ApplicationStatusUpTime.UPTIME_COLLECTION; import static org.radarcns.mongo.util.MongoHelper.ID; import static org.radarcns.mongo.util.MongoHelper.KEY; import static org.radarcns.mongo.util.MongoHelper.PROJECT_ID; @@ -42,7 +43,7 @@ import org.radarcns.monitor.application.ServerStatus; import org.radarcns.stream.collector.DoubleArrayCollector; import org.radarcns.stream.collector.DoubleValueCollector; -import org.radarcns.util.RadarConverter; +import org.radarcns.util.TimeScale; /** * All supported sources specifications. @@ -59,29 +60,31 @@ public class RandomInput { new ExpectedDataSetFactory(); private static ExpectedValue randomDoubleValue( - ObservationKey key, TimeWindow timeWindow, int numberOfRecords, Instant startTime) { + ObservationKey key, TimeWindow timeWindow, int numberOfRecords, Instant endTime) { ExpectedDoubleValue instance = new ExpectedDoubleValue(); - Instant timeStamp = startTime; + Instant timeStamp = endTime.minus( + TimeScale.getSeconds(timeWindow) * numberOfRecords, SECONDS); ThreadLocalRandom random = ThreadLocalRandom.current(); for (int i = 0; i < numberOfRecords; i++) { instance.add(key, timeStamp.toEpochMilli(), random.nextDouble()); - timeStamp = timeStamp.plus(RadarConverter.getDuration(timeWindow)); + timeStamp = timeStamp.plus(TimeScale.getDuration(timeWindow)); } return instance; } private static ExpectedValue randomArrayValue(ObservationKey key, - TimeWindow timeWindow, int numberOfRecords, Instant startTime) { + TimeWindow timeWindow, int numberOfRecords, Instant endTime) { ExpectedArrayValue instance = new ExpectedArrayValue(); ThreadLocalRandom random = ThreadLocalRandom.current(); - Instant timeStamp = startTime; + Instant timeStamp = endTime.minus( + TimeScale.getSeconds(timeWindow) * numberOfRecords, SECONDS); for (int i = 0; i < numberOfRecords; i++) { instance.add(key, timeStamp.toEpochMilli(), random.nextDouble(), random.nextDouble(), random.nextDouble()); - timeStamp = timeStamp.plus(RadarConverter.getDuration(timeWindow)); + timeStamp = timeStamp.plus(TimeScale.getDuration(timeWindow)); } return instance; @@ -94,7 +97,7 @@ private static ExpectedValue randomArrayValue(ObservationK @SuppressWarnings("PMD.ExcessiveParameterList") public static Map getDatasetAndDocumentsRandom(String project, String user, String source, String sourceType, String sourceDataName, DescriptiveStatistic stat, - TimeWindow timeWindow, int samples, boolean singleWindow, Instant startTime) { + TimeWindow timeWindow, int samples, boolean singleWindow, Instant endTime) { ObservationKey key = new ObservationKey(project, user, source); int numberOfRecords = samples; @@ -104,7 +107,7 @@ public static Map getDatasetAndDocumentsRandom(String project, S if (SUPPORTED_SOURCE_TYPE.equals(sourceType)) { return getBoth(key, sourceType, sourceDataName, stat, - timeWindow, numberOfRecords, startTime); + timeWindow, numberOfRecords, endTime); } throw new UnsupportedOperationException(sourceType + " is not" @@ -114,14 +117,14 @@ public static Map getDatasetAndDocumentsRandom(String project, S @SuppressWarnings("PMD.ExcessiveParameterList") private static Map getBoth(ObservationKey key, String sourceType, String sourceDataName, DescriptiveStatistic stat, - TimeWindow timeWindow, int numberOfRecords, Instant startTime) { + TimeWindow timeWindow, int numberOfRecords, Instant endTime) { ExpectedValue expectedValue; switch (sourceDataName) { case "EMPATICA_E4_v1_ACCELEROMETER": - expectedValue = randomArrayValue(key, timeWindow, numberOfRecords, startTime); + expectedValue = randomArrayValue(key, timeWindow, numberOfRecords, endTime); break; default: - expectedValue = randomDoubleValue(key, timeWindow, numberOfRecords, startTime); + expectedValue = randomDoubleValue(key, timeWindow, numberOfRecords, endTime); break; } @@ -136,6 +139,28 @@ private static Map getBoth(ObservationKey key, return map; } + /** + * Generates and returns a randomly generated + * {@link org.radarcns.domain.restapi.monitor.QuestionnaireCompletionStatus} mock data + * sent by RADAR-CNS aRMT. + **/ + public static Document getRandomQuestionnaireCompletionLog(String project, + String user, String source) { + + ThreadLocalRandom random = ThreadLocalRandom.current(); + + double timestamp = random.nextDouble(); + double completionPercentage = random.nextDouble(0d,100d); + String name = "PHQ8"; + Document completionDoc = new Document() + .append("time", timestamp) + .append("name", name) + .append("completionPercentage", completionPercentage); + + return buildDocumentWithObservationKey(project, user, source, completionDoc); + + } + /** * Generates and returns a randomly generated {@code ApplicationStatus} mocking data sent by * RADAR-CNS pRMT. @@ -181,9 +206,12 @@ public static Map getRandomApplicationStatus(String project, S .append("recordsUnsent", recordsUnsent); Map documents = new HashMap<>(); - documents.put(STATUS_COLLECTION, buildAppStatusDocument(project, user, source, statusDoc)); - documents.put(RECORD_COLLECTION, buildAppStatusDocument(project, user, source, recordsDoc)); - documents.put(UPTIME_COLLECTION, buildAppStatusDocument(project, user, source, uptimeDoc)); + documents.put(STATUS_COLLECTION, + buildDocumentWithObservationKey(project, user, source, statusDoc)); + documents.put(RECORD_COLLECTION, + buildDocumentWithObservationKey(project, user, source, recordsDoc)); + documents.put(UPTIME_COLLECTION, + buildDocumentWithObservationKey(project, user, source, uptimeDoc)); return documents; } @@ -194,7 +222,7 @@ private static Document buildKeyDocument(String projectName, String subjectId, .append(SOURCE_ID, sourceId); } - private static Document buildAppStatusDocument(String projectName, String subjectId, + private static Document buildDocumentWithObservationKey(String projectName, String subjectId, String sourceId, Document value) { return new Document().append(ID, "{" diff --git a/src/integrationTest/java/org/radarcns/integration/util/Utility.java b/src/integrationTest/java/org/radarcns/integration/util/Utility.java index a5d96fdb..58a3cadb 100644 --- a/src/integrationTest/java/org/radarcns/integration/util/Utility.java +++ b/src/integrationTest/java/org/radarcns/integration/util/Utility.java @@ -16,34 +16,51 @@ package org.radarcns.integration.util; -import static org.radarcns.mongo.data.monitor.ApplicationStatusRecordCounter.RECORD_COLLECTION; -import static org.radarcns.mongo.data.monitor.ApplicationStatusServerStatus.STATUS_COLLECTION; -import static org.radarcns.mongo.data.monitor.ApplicationStatusUpTime.UPTIME_COLLECTION; +import static org.radarcns.mongo.data.monitor.application.ApplicationStatusRecordCounter.RECORD_COLLECTION; +import static org.radarcns.mongo.data.monitor.application.ApplicationStatusServerStatus.STATUS_COLLECTION; +import static org.radarcns.mongo.data.monitor.application.ApplicationStatusUpTime.UPTIME_COLLECTION; import static org.radarcns.mongo.util.MongoHelper.VALUE; import java.util.ArrayList; import java.util.List; import java.util.Map; + import org.bson.Document; -import org.radarcns.domain.restapi.Application; import org.radarcns.domain.restapi.dataset.DataItem; import org.radarcns.domain.restapi.dataset.Dataset; import org.radarcns.domain.restapi.format.Acceleration; -import org.radarcns.domain.restapi.header.Header; +import org.radarcns.domain.restapi.header.DataSetHeader; import org.radarcns.domain.restapi.header.TimeFrame; +import org.radarcns.domain.restapi.monitor.ApplicationStatus; +import org.radarcns.domain.restapi.monitor.QuestionnaireCompletionStatus; import org.radarcns.util.RadarConverter; public class Utility { /** - * Converts Bson Document into an ApplicationConfig. + * Converts Bson Document into an QuestionnaireCompletionStatus. * - * @param documents map containing variables to create the ApplicationConfig class + * @param document Document data to create the QuestionnaireCompletionStatus class * @return an ApplicationConfig class - * @see Application + * @see ApplicationStatus */ //TODO take field names from RADAR Mongo Connector - public static Application convertDocToApplication(Map documents) { - return new Application( + public static QuestionnaireCompletionStatus convertDocToQuestionnaireCompletionStatus(Document + document) { + return new QuestionnaireCompletionStatus( + ((Document)document.get(VALUE)).getDouble("time"), + ((Document)document.get(VALUE)).getString("name"), + ((Document)document.get(VALUE)).getDouble("completionPercentage")); + } + + /** + * Converts Bson Document into an ApplicationStatus. + * + * @param documents map containing variables to create the ApplicationStatus class + * @return an ApplicationStatus class + * @see ApplicationStatus + */ + public static ApplicationStatus convertDocToApplicationStatus(Map documents) { + return new ApplicationStatus( ((Document) documents.get(STATUS_COLLECTION).get(VALUE)).getString("clientIP"), ((Document) documents.get(UPTIME_COLLECTION).get(VALUE)).getDouble("uptime"), RadarConverter.getServerStatus( @@ -52,8 +69,8 @@ public static Application convertDocToApplication(Map document ((Document) documents.get(RECORD_COLLECTION).get(VALUE)) .getInteger("recordsCached"), ((Document) documents.get(RECORD_COLLECTION).get(VALUE)).getInteger("recordsSent"), - ((Document) documents.get(RECORD_COLLECTION).get(VALUE)).getInteger("recordsUnsent") - ); + ((Document) documents.get(RECORD_COLLECTION).get(VALUE)) + .getInteger("recordsUnsent")); } /** @@ -63,18 +80,18 @@ public static Application convertDocToApplication(Map document * @return {@link Dataset} cloned from {@code input} */ public static Dataset cloneDataset(Dataset input) { - Header inputHeader = input.getHeader(); - TimeFrame cloneEffectiveTimeFrame = new TimeFrame( - inputHeader.getEffectiveTimeFrame().getStartDateTime(), - inputHeader.getEffectiveTimeFrame().getEndDateTime()); - TimeFrame cloneTimeFrame = new TimeFrame( - inputHeader.getTimeFrame().getStartDateTime(), + DataSetHeader inputHeader = input.getHeader(); + TimeFrame cloneEffectiveTimeFrame = + new TimeFrame(inputHeader.getEffectiveTimeFrame().getStartDateTime(), + inputHeader.getEffectiveTimeFrame().getEndDateTime()); + TimeFrame cloneTimeFrame = new TimeFrame(inputHeader.getTimeFrame().getStartDateTime(), inputHeader.getTimeFrame().getEndDateTime()); - Header cloneHeader = new Header(inputHeader.getProjectId(), inputHeader.getSubjectId(), + DataSetHeader cloneHeader = new DataSetHeader(inputHeader.getProjectId(), + inputHeader.getSubjectId(), inputHeader.getSourceId(), inputHeader.getSourceType(), - inputHeader.getSourceDataType(), - inputHeader.getDescriptiveStatistic(), inputHeader.getUnit(), - inputHeader.getTimeWindow(), cloneTimeFrame, cloneEffectiveTimeFrame); + inputHeader.getSourceDataType(), inputHeader.getDescriptiveStatistic(), + inputHeader.getUnit(), inputHeader.getTimeWindow(), cloneTimeFrame, + cloneEffectiveTimeFrame); List cloneItem = new ArrayList<>(); Object value; @@ -86,8 +103,8 @@ public static Dataset cloneDataset(Dataset input) { Acceleration temp = (Acceleration) item.getValue(); value = new Acceleration(temp.getX(), temp.getY(), temp.getZ()); } else { - throw new IllegalArgumentException(item.getValue().getClass().getCanonicalName() - + " is not supported yet"); + throw new IllegalArgumentException( + item.getValue().getClass().getCanonicalName() + " is not supported yet"); } cloneItem.add(new DataItem(value, item.getStartDateTime())); diff --git a/src/integrationTest/java/org/radarcns/webapp/AggregatedDataPointsEndPointTest.java b/src/integrationTest/java/org/radarcns/webapp/AggregatedDataPointsEndPointTest.java index 35467abb..26293904 100644 --- a/src/integrationTest/java/org/radarcns/webapp/AggregatedDataPointsEndPointTest.java +++ b/src/integrationTest/java/org/radarcns/webapp/AggregatedDataPointsEndPointTest.java @@ -1,9 +1,9 @@ package org.radarcns.webapp; +import static java.time.temporal.ChronoUnit.SECONDS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import static org.radarcns.domain.restapi.TimeWindow.ONE_HOUR; import static org.radarcns.domain.restapi.TimeWindow.TEN_MIN; import static org.radarcns.domain.restapi.header.DescriptiveStatistic.AVERAGE; import static org.radarcns.domain.restapi.header.DescriptiveStatistic.QUARTILES; @@ -39,7 +39,7 @@ import org.radarcns.integration.util.ApiClient; import org.radarcns.integration.util.RandomInput; import org.radarcns.integration.util.RestApiDetails; -import org.radarcns.util.RadarConverter; +import org.radarcns.util.TimeScale; import org.radarcns.webapp.param.DataAggregateParam; import org.radarcns.webapp.resource.Parameter; @@ -57,7 +57,7 @@ public void getAllRecordsWithAggregatedDataPointsInTimeRange() throws IOExceptio Instant now = Instant.now(); TimeWindow window = TEN_MIN; - // injects 10 records for 10 min + // injects 10 records for last 100 min MongoCollection collection = mongoRule.getCollection( BATTERY_LEVEL_COLLECTION_FOR_TEN_MINUTES); @@ -69,10 +69,10 @@ public void getAllRecordsWithAggregatedDataPointsInTimeRange() throws IOExceptio MongoCollection accelerationCollection = mongoRule.getCollection( ACCELERATION_COLLECTION_FOR_TEN_MINITES); - // injects 5 records for acceleration + // injects 5 records for acceleration, for last 50 minutes Map accDocs = RandomInput.getDatasetAndDocumentsRandom( PROJECT, SUBJECT, SOURCE, SOURCE_TYPE, ACCELEROMETER_SOURCE_DATA_NAME, AVERAGE, - TEN_MIN,5, false, Instant.now()); + TEN_MIN,5, false, now); accelerationCollection.insertMany((List) accDocs.get(DOCUMENTS)); @@ -83,8 +83,10 @@ public void getAllRecordsWithAggregatedDataPointsInTimeRange() throws IOExceptio DataAggregateParam aggregateParam = new DataAggregateParam( Collections.singletonList(aggregateDataSource)); - Instant start = now.plus(RadarConverter.getDuration(TEN_MIN)); - Instant end = start.plus(RadarConverter.getDuration(ONE_HOUR)); + // reduces 70 min + Instant start = now + .minus(TimeScale.getSeconds(TEN_MIN) * 7, SECONDS); + Instant end = now.minus(TimeScale.getDuration(TEN_MIN)); String requestPath = PROJECT + '/' + SUBJECT + '/' + DISTINCT + '?' + Parameter.TIME_WINDOW + '=' + window + '&' @@ -95,7 +97,7 @@ public void getAllRecordsWithAggregatedDataPointsInTimeRange() throws IOExceptio assertNotNull(dataset); assertTrue(dataset.getDataset().size() <= 6); List dataItems = dataset.getDataset(); - assertEquals(2, dataItems.get(0).getValue()); - assertEquals(1, dataItems.get(4).getValue()); + assertEquals(1, dataItems.get(0).getValue()); + assertEquals(2, dataItems.get(4).getValue()); } } diff --git a/src/integrationTest/java/org/radarcns/webapp/AppStatusEndPointTest.java b/src/integrationTest/java/org/radarcns/webapp/AppStatusEndPointTest.java index 10f4b04f..3223c9c5 100644 --- a/src/integrationTest/java/org/radarcns/webapp/AppStatusEndPointTest.java +++ b/src/integrationTest/java/org/radarcns/webapp/AppStatusEndPointTest.java @@ -17,20 +17,25 @@ package org.radarcns.webapp; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.radarcns.domain.restapi.header.MonitorHeader.MonitorCategory.PASSIVE; +import static org.radarcns.domain.restapi.header.MonitorHeader.MonitorCategory.QUESTIONNAIRE; +import static org.radarcns.mongo.data.monitor.questionnaire.QuestionnaireCompletionLogWrapper.QUESTIONNAIRE_COMPLETION_LOG_COLLECTION; import static org.radarcns.webapp.SampleDataHandler.PROJECT; -import static org.radarcns.webapp.SampleDataHandler.SOURCE; -import static org.radarcns.webapp.SampleDataHandler.SUBJECT; import static org.radarcns.webapp.resource.BasePath.APPLICATION_STATUS; import java.io.IOException; import java.util.Map; import javax.ws.rs.core.Response.Status; + import org.bson.Document; import org.junit.Rule; import org.junit.Test; -import org.radarcns.domain.restapi.Application; -import org.radarcns.domain.restapi.ServerStatus; +import org.radarcns.domain.restapi.header.MonitorHeader; +import org.radarcns.domain.restapi.monitor.ApplicationStatus; +import org.radarcns.domain.restapi.monitor.MonitorData; +import org.radarcns.domain.restapi.monitor.QuestionnaireCompletionStatus; import org.radarcns.integration.MongoRule; import org.radarcns.integration.util.ApiClient; import org.radarcns.integration.util.RandomInput; @@ -40,8 +45,6 @@ public class AppStatusEndPointTest { - private static final String SOURCE_PATH = - APPLICATION_STATUS + '/' + PROJECT + '/' + SUBJECT + '/' + SOURCE; @Rule public final ApiClient apiClient = new ApiClient( @@ -52,21 +55,67 @@ public class AppStatusEndPointTest { @Test public void getStatusTest200Unknown() throws IOException { - Application actual = apiClient.getJson(SOURCE_PATH, Application.class, Status.OK); - assertSame(ServerStatus.UNKNOWN, actual.getServerStatus()); + String sourceId = "c4064d12-b963-47ae-b560-067131e321ea"; + String subjectId = "sub-2"; + + MonitorHeader monitorHeader = new MonitorHeader(PROJECT, subjectId, sourceId, null); + assertRequestsMatch(subjectId, sourceId, monitorHeader); } @Test public void getStatusTest200() throws IOException { + String sourceId = "c4064d12-b963-47ae-b560-067131e321ea"; + String subjectId = "sub-2"; + Map map = RandomInput.getRandomApplicationStatus( - PROJECT, SUBJECT, SOURCE); + PROJECT, subjectId, sourceId); map.forEach((k, v) -> mongoRule.getCollection(k).insertOne(v)); - Application expected = Utility.convertDocToApplication(map); - Application actual = apiClient.getJson(SOURCE_PATH, Application.class, Status.OK); + ApplicationStatus expected = Utility.convertDocToApplicationStatus(map); + MonitorHeader monitorHeader = new MonitorHeader(PROJECT, subjectId, sourceId, PASSIVE); + MonitorData actual = assertRequestsMatch(subjectId, sourceId, monitorHeader); + + assertTrue(actual.getData() instanceof Map); + Map status = (Map) actual.getData(); + + assertEquals(expected.getServerStatus().toString(), status.get("serverStatus")); + } + + @Test + public void getQuestionnaireCompletionStatusTest200() throws IOException { + String sourceId = "0d29b9eb-289a-4dc6-b969-534dca72a187"; + String subjectId = "sub-3"; + + Document document = RandomInput.getRandomQuestionnaireCompletionLog( + PROJECT, subjectId, sourceId); + + mongoRule.getCollection(QUESTIONNAIRE_COMPLETION_LOG_COLLECTION).insertOne(document); + + QuestionnaireCompletionStatus expected = Utility + .convertDocToQuestionnaireCompletionStatus(document); + MonitorHeader monitorHeader = new MonitorHeader(PROJECT, subjectId, sourceId, + QUESTIONNAIRE); + MonitorData actual = assertRequestsMatch(subjectId, sourceId, monitorHeader); + + assertTrue(actual.getData() instanceof Map); + Map status = (Map) actual.getData(); + + assertEquals(expected.getCompletionPercentage(), status.get("completionPercentage")); + } + + private MonitorData assertRequestsMatch(String subjectId, String sourceId, MonitorHeader + expectedHeader) + throws IOException { + String relativeUrl = + APPLICATION_STATUS + '/' + PROJECT + '/' + subjectId + '/' + sourceId; - assertEquals(expected.getServerStatus(), actual.getServerStatus()); - assertEquals(expected.getIpAddress(), actual.getIpAddress()); + MonitorData actual = apiClient.getJson(relativeUrl, MonitorData.class, Status.OK); + assertNotNull(actual); + MonitorHeader actualHeader = actual.getHeader(); + assertEquals(expectedHeader.getProjectId(), actualHeader.getProjectId()); + assertEquals(expectedHeader.getSubjectId(), actualHeader.getSubjectId()); + assertEquals(expectedHeader.getSourceId(), actualHeader.getSourceId()); + return actual; } } diff --git a/src/integrationTest/java/org/radarcns/webapp/DataSetEndPointTest.java b/src/integrationTest/java/org/radarcns/webapp/DataSetEndPointTest.java index 6a99f20b..1401b346 100644 --- a/src/integrationTest/java/org/radarcns/webapp/DataSetEndPointTest.java +++ b/src/integrationTest/java/org/radarcns/webapp/DataSetEndPointTest.java @@ -61,7 +61,7 @@ import org.radarcns.integration.util.ApiClient; import org.radarcns.integration.util.RandomInput; import org.radarcns.integration.util.RestApiDetails; -import org.radarcns.util.RadarConverter; +import org.radarcns.util.TimeScale; import org.radarcns.webapp.resource.Parameter; public class DataSetEndPointTest { @@ -78,7 +78,7 @@ public class DataSetEndPointTest { public final MongoRule mongoRule = new MongoRule(); @Test - public void getRecords() throws IOException { + public void getRecordsInTenSecondsWindow() throws IOException { MongoCollection collection = mongoRule.getCollection( BATTERY_LEVEL_COLLECTION_NAME); @@ -148,8 +148,8 @@ public void getAllRecordsWithQuartiles() throws IOException { @Test public void getAllRecordsWithQuartilesInTimeRange() throws IOException { Instant now = Instant.now(); - Instant start = now.plus(RadarConverter.getSecond(TEN_SECOND), SECONDS); - Instant end = now.plus(7 * RadarConverter.getSecond(TEN_SECOND), SECONDS); + Instant end = now.minus(TimeScale.getSeconds(TEN_SECOND), SECONDS); + Instant start = now.minus(7 * TimeScale.getSeconds(TEN_SECOND), SECONDS); MongoCollection collection = mongoRule.getCollection( BATTERY_LEVEL_COLLECTION_NAME); Map docs = RandomInput @@ -179,8 +179,8 @@ public void getAllRecordsWithQuartilesInTimeRangeWithTenMinutes() throws IOExcep Instant now = Instant.now(); TimeWindow window = TimeWindow.TEN_MIN; - Instant start = now.plus(RadarConverter.getSecond(window), SECONDS); - Instant end = now.plus(7 * RadarConverter.getSecond(window), SECONDS); + Instant end = now.minus(TimeScale.getSeconds(window), SECONDS); + Instant start = now.minus(7 * TimeScale.getSeconds(window), SECONDS); MongoCollection collection = mongoRule.getCollection( BATTERY_LEVEL_COLLECTION_FOR_TEN_MINUTES); Map docs = RandomInput diff --git a/src/integrationTest/java/org/radarcns/webapp/SourceTypeEndPointTest.java b/src/integrationTest/java/org/radarcns/webapp/SourceTypeEndPointTest.java index cdec449d..15eeaf15 100644 --- a/src/integrationTest/java/org/radarcns/webapp/SourceTypeEndPointTest.java +++ b/src/integrationTest/java/org/radarcns/webapp/SourceTypeEndPointTest.java @@ -43,6 +43,7 @@ public void getSourceTypeByIdentifierStatusTest200() throws IOException { assertEquals(PRODUCER, project.getProducer()); assertEquals(MODEL, project.getModel()); assertEquals(CATALOGUE_VERSION, project.getCatalogVersion()); + assertTrue(project.getId() > 0); } diff --git a/src/integrationTest/java/org/radarcns/webapp/SubjectEndPointTest.java b/src/integrationTest/java/org/radarcns/webapp/SubjectEndPointTest.java index 947bee3e..be8bf03e 100644 --- a/src/integrationTest/java/org/radarcns/webapp/SubjectEndPointTest.java +++ b/src/integrationTest/java/org/radarcns/webapp/SubjectEndPointTest.java @@ -65,6 +65,7 @@ public void getSubjectsByProjectName200() throws IOException { assertNotNull(subjects); assertTrue(subjects.size() > 0); assertEquals(PROJECT, subjects.get(0).getProject()); + assertTrue(subjects.get(2).getSources().get(0).getSourceTypeId() > 0); } @@ -89,6 +90,7 @@ public void getSubjectsBySubjectIdAndProjectName200() throws IOException { .getEndDateTime()); assertEquals(CONNECTED, subject.getSources().get(0).getStatus()); assertNotNull(subject.getLastSeen()); + assertTrue(subject.getSources().get(0).getSourceTypeId() > 0); } diff --git a/src/integrationTest/resources/config/keystore.jks b/src/integrationTest/resources/config/keystore.jks index f273cf80..236dd366 100644 Binary files a/src/integrationTest/resources/config/keystore.jks and b/src/integrationTest/resources/config/keystore.jks differ diff --git a/src/integrationTest/resources/config/liquibase/source_type.csv b/src/integrationTest/resources/config/liquibase/source_type.csv new file mode 100644 index 00000000..11717887 --- /dev/null +++ b/src/integrationTest/resources/config/liquibase/source_type.csv @@ -0,0 +1,5 @@ +"ID";"PRODUCER";"MODEL";"catalog_version";"SOURCE_TYPE_SCOPE";"dynamic_registration";"created_by" +"1";"Empatica";"E4";"v1";"PASSIVE";"FALSE";"system" +"2";"THINC-IT App";"App";"v1";"ACTIVE";"true";"system" +"3";"RADAR";"pRMT";"1.0.0";"MONITOR";"false";"system" +"4";"RADAR";"aRMT-App";"1.2.0";"ACTIVE";"true";"system" diff --git a/src/integrationTest/resources/config/liquibase/sources.csv b/src/integrationTest/resources/config/liquibase/sources.csv new file mode 100644 index 00000000..0957ab67 --- /dev/null +++ b/src/integrationTest/resources/config/liquibase/sources.csv @@ -0,0 +1,4 @@ +"ID";"source_id";"source_name";"assigned";"created_by";"source_type_id";"project_id" +"1";"03d28e5c-e005-46d4-a9b3-279c27fbbc83";"source-1";"TRUE";"system";"1";"1" +"2";"c4064d12-b963-47ae-b560-067131e321ea";"source-2";"TRUE";"system";"3";"1" +"3";"0d29b9eb-289a-4dc6-b969-534dca72a187";"source-3";"TRUE";"system";"4";"1" \ No newline at end of file diff --git a/src/integrationTest/resources/config/liquibase/subject_sources.csv b/src/integrationTest/resources/config/liquibase/subject_sources.csv new file mode 100644 index 00000000..ad40944d --- /dev/null +++ b/src/integrationTest/resources/config/liquibase/subject_sources.csv @@ -0,0 +1,5 @@ +"SUBJECTS_ID";"SOURCES_ID" +"1";"1" +"2";"2" +"3";"3" + diff --git a/src/integrationTest/resources/openapi-configuration.yaml b/src/integrationTest/resources/openapi-configuration.yaml index f0511d7c..89eae158 100644 --- a/src/integrationTest/resources/openapi-configuration.yaml +++ b/src/integrationTest/resources/openapi-configuration.yaml @@ -2,7 +2,7 @@ prettyPrint: true openAPI: info: - version: 0.2.1 + version: 0.3 title: RADAR-CNS Downstream REST APIs license: name: Apache 2.0 diff --git a/src/integrationTest/resources/radar-is.yml b/src/integrationTest/resources/radar-is.yml new file mode 100644 index 00000000..1b51b99d --- /dev/null +++ b/src/integrationTest/resources/radar-is.yml @@ -0,0 +1,22 @@ +resourceName: res_RestApi +publicKeys: + - |- + -----BEGIN EC PUBLIC KEY----- + MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMs5Dr4DNVKRtXcgjoYdwvvNi8wj3 + YO/REE0WhOLlcWSY9B94ouRF+01d8zaksqOfThT4kb6r2U7Kk4XtV6Alww== + -----END EC PUBLIC KEY----- + - |- + -----BEGIN PUBLIC KEY----- + MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsz4umuU7hrXF6cFcBLdI + KzoVSbU22N3AbBPCtx4sBNKOgb0cI7dLAf+lCtiuezatbk7KsbUoNJ8nNaAjBrld + RjUZkFC1sMpiTsK8SG4C9A6OMz/4AHPziY4iZWWAel7ZjFoCbXG/gnS1i7ZYZ83C + QabJ5jr0hZzqz9aOO5X2592lhtf6mLHl6QFNhsPwmVxnkrjQVy1hIqPrGxYqDILW + QBM9RRrgXr0tFzMMIxLZvPiGCLxUpFD8ma/YxCoKfvWGSJ1oiePF1T9+K9RTzdwG + iPqY5CGMIo0vjpFf1tyCBWxTWy6HNFQWkEpLoya7SumAFOK0sPOd4wfQA8Uvp5JU + D8LyPnZQlQ/KUozp0bUmotw65tj0NEdUOjJ0NGFaHJ8oi7aff07OsV/phzBhZ0iB + CWjJdtCf1Y7jqqOyFRAHMz8F+EqBZ40htPx7ejQyjfqLtCetxULlp2WSaXWWCYen + 36e+lO7+eDKEBrAc5Npd9EjYjnyYFzhhT+f3JDy3+fEbzkItm02OnKENnxq3IU2q + sGFqG2tBnRjnViWmzA271Yi9FqTdM/AVzsqGsw85oPebmAzzVbgIYEipClHS2JjF + J55li+nztyqGU1VIg6LstDabuBax2M7hX6CCisguS+RlDZ3e3caVF9x1KcXeR5no + XERYv/iwTBI1F1Gp5waemi8CAwEAAQ== + -----END PUBLIC KEY----- diff --git a/src/main/java/org/radarcns/domain/managementportal/ProjectDTO.java b/src/main/java/org/radarcns/domain/managementportal/ProjectDTO.java index 2cdd6104..45bab609 100644 --- a/src/main/java/org/radarcns/domain/managementportal/ProjectDTO.java +++ b/src/main/java/org/radarcns/domain/managementportal/ProjectDTO.java @@ -1,9 +1,9 @@ package org.radarcns.domain.managementportal; - import java.io.Serializable; import java.time.ZonedDateTime; import java.util.HashSet; +import java.util.Map; import java.util.Objects; import java.util.Set; import javax.validation.constraints.NotNull; @@ -20,6 +20,7 @@ public class ProjectDTO implements Serializable { @NotNull private String projectName; + private String humanReadableProjectName; @NotNull private String description; @@ -38,7 +39,7 @@ public class ProjectDTO implements Serializable { private Set sourceTypes = new HashSet<>(); - private Set attributes = new HashSet<>(); + private Map attributes; public Long getId() { return id; @@ -120,14 +121,22 @@ public void setSourceTypes(Set sourceTypes) { this.sourceTypes = sourceTypes; } - public Set getAttributes() { + public Map getAttributes() { return attributes; } - public void setAttributes(Set attributes) { + public void setAttributes(Map attributes) { this.attributes = attributes; } + public String getHumanReadableProjectName() { + return humanReadableProjectName; + } + + public void setHumanReadableProjectName(String humanReadableProjectName) { + this.humanReadableProjectName = humanReadableProjectName; + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/src/main/java/org/radarcns/domain/managementportal/SourceDTO.java b/src/main/java/org/radarcns/domain/managementportal/SourceDTO.java index cad8f8bf..5e83d6ad 100644 --- a/src/main/java/org/radarcns/domain/managementportal/SourceDTO.java +++ b/src/main/java/org/radarcns/domain/managementportal/SourceDTO.java @@ -1,6 +1,5 @@ package org.radarcns.domain.managementportal; - import com.fasterxml.jackson.annotation.JsonIgnore; import java.io.Serializable; import java.util.Map; @@ -14,6 +13,9 @@ public class SourceDTO implements Serializable { private static final long serialVersionUID = 1L; + @NotNull + private Long id; + private String sourceId; @NotNull @@ -29,6 +31,15 @@ public class SourceDTO implements Serializable { private Map attributes; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + public String getSourceId() { return sourceId; } @@ -89,7 +100,8 @@ public boolean equals(Object o) { SourceDTO sourceDto = (SourceDTO) o; return Objects.equals(sourceId, sourceDto.sourceId) - && Objects.equals(sourceName, sourceDto.sourceName); + && Objects.equals(sourceName, sourceDto.sourceName) + && Objects.equals(id, sourceDto.id); } @Override @@ -100,6 +112,7 @@ public int hashCode() { @Override public String toString() { return "SourceDTO{" + + ", id='" + id + '\'' + ", sourceId='" + sourceId + '\'' + ", sourceName='" + sourceName + '\'' + ", assigned=" + assigned diff --git a/src/main/java/org/radarcns/domain/managementportal/SourceDataDTO.java b/src/main/java/org/radarcns/domain/managementportal/SourceDataDTO.java index ca04e6b6..93126040 100644 --- a/src/main/java/org/radarcns/domain/managementportal/SourceDataDTO.java +++ b/src/main/java/org/radarcns/domain/managementportal/SourceDataDTO.java @@ -1,6 +1,5 @@ package org.radarcns.domain.managementportal; - import java.io.Serializable; import java.util.Objects; import javax.validation.constraints.NotNull; @@ -12,6 +11,8 @@ public class SourceDataDTO implements Serializable { private static final long serialVersionUID = 1L; + @NotNull + private Long id; //Source data type. @NotNull private String sourceDataType; @@ -41,6 +42,14 @@ public class SourceDataDTO implements Serializable { private boolean enabled = true; + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + public String getFrequency() { return frequency; } @@ -141,7 +150,8 @@ public boolean equals(Object o) { if (sourceDataDto.sourceDataName == null || sourceDataName == null) { return false; } - return Objects.equals(sourceDataName, sourceDataDto.sourceDataName); + return Objects.equals(sourceDataName, sourceDataDto.sourceDataName) + && Objects.equals(id, sourceDataDto.id); } @Override @@ -152,6 +162,7 @@ public int hashCode() { @Override public String toString() { return "SourceDataDTO{" + + ", id='" + id + '\'' + ", sourceDataType='" + sourceDataType + '\'' + ", sourceDataName='" + sourceDataName + '\'' + ", frequency='" + frequency + '\'' diff --git a/src/main/java/org/radarcns/domain/managementportal/SourceTypeDTO.java b/src/main/java/org/radarcns/domain/managementportal/SourceTypeDTO.java index dc38f051..0f87a25e 100644 --- a/src/main/java/org/radarcns/domain/managementportal/SourceTypeDTO.java +++ b/src/main/java/org/radarcns/domain/managementportal/SourceTypeDTO.java @@ -1,6 +1,5 @@ package org.radarcns.domain.managementportal; - import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import java.io.Serializable; @@ -20,6 +19,9 @@ public class SourceTypeDTO implements Serializable { @JsonIgnore private static final String SOURCE_STATISTICS_MONITOR = "source_statistics"; + @NotNull + private Long id; + @NotNull private String producer; @@ -49,6 +51,14 @@ public class SourceTypeDTO implements Serializable { private Set sourceData = new HashSet<>(); + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + public String getProducer() { return producer; } @@ -142,7 +152,8 @@ public boolean equals(Object o) { return Objects.equals(producer, sourceTypeDto.producer) && Objects.equals(model, sourceTypeDto.model) - && Objects.equals(catalogVersion, sourceTypeDto.catalogVersion); + && Objects.equals(catalogVersion, sourceTypeDto.catalogVersion) + && Objects.equals(id, sourceTypeDto.id); } @Override @@ -153,6 +164,7 @@ public int hashCode() { @Override public String toString() { return "SourceTypeDTO{" + + " id='" + id + "'" + " producer='" + producer + "'" + ", model='" + model + "'" + ", catalogVersion='" + catalogVersion + "'" diff --git a/src/main/java/org/radarcns/domain/restapi/Source.java b/src/main/java/org/radarcns/domain/restapi/Source.java index 4561a063..b5b4f1fa 100644 --- a/src/main/java/org/radarcns/domain/restapi/Source.java +++ b/src/main/java/org/radarcns/domain/restapi/Source.java @@ -11,6 +11,9 @@ public class Source { @JsonProperty private String sourceName; + @JsonProperty + private Long sourceTypeId; + @JsonProperty private String sourceTypeProducer; @@ -66,6 +69,14 @@ public String getSourceName() { return sourceName; } + public Long getSourceTypeId() { + return sourceTypeId; + } + + public Source sourceTypeId(Long sourceTypeId) { + this.sourceTypeId = sourceTypeId; + return this; + } public Source sourceTypeProducer(String sourceTypeProducer) { this.sourceTypeProducer = sourceTypeProducer; diff --git a/src/main/java/org/radarcns/domain/restapi/dataset/AggregatedDataPoints.java b/src/main/java/org/radarcns/domain/restapi/dataset/AggregatedDataPoints.java index dcbe310a..7dca9b6c 100644 --- a/src/main/java/org/radarcns/domain/restapi/dataset/AggregatedDataPoints.java +++ b/src/main/java/org/radarcns/domain/restapi/dataset/AggregatedDataPoints.java @@ -5,9 +5,8 @@ import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; import org.radarcns.domain.restapi.AggregateDataSource; -import org.radarcns.domain.restapi.TimeWindow; import org.radarcns.domain.restapi.header.AggregatedDataPointsHeader; -import org.radarcns.domain.restapi.header.TimeFrame; +import org.radarcns.util.TimeScale; public class AggregatedDataPoints { @@ -27,18 +26,17 @@ public AggregatedDataPoints() { * @param projectName of project * @param subjectId of subject * @param maximumCount of records - * @param timeFrame start to end - * @param timeWindow interval window + * @param timeScale time scale * @param sources to request availability * @param dataset computed data. */ public AggregatedDataPoints(String projectName, String subjectId, Integer maximumCount, - TimeFrame timeFrame, TimeWindow timeWindow, List sources, + TimeScale timeScale, List sources, List dataset) { this.dataset = dataset; this.header = new AggregatedDataPointsHeader(projectName, subjectId, maximumCount, - timeFrame, timeWindow, DISTINCT, sources); + timeScale.getTimeFrame(), timeScale.getTimeWindow(), DISTINCT, sources); } diff --git a/src/main/java/org/radarcns/domain/restapi/dataset/Dataset.java b/src/main/java/org/radarcns/domain/restapi/dataset/Dataset.java index 8ef6b238..5493452d 100644 --- a/src/main/java/org/radarcns/domain/restapi/dataset/Dataset.java +++ b/src/main/java/org/radarcns/domain/restapi/dataset/Dataset.java @@ -3,12 +3,13 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; -import org.radarcns.domain.restapi.header.Header; + +import org.radarcns.domain.restapi.header.DataSetHeader; public class Dataset { @JsonProperty - public Header header; + public DataSetHeader header; @JsonProperty public List dataset; @@ -21,25 +22,27 @@ public class Dataset { */ @JsonCreator public Dataset( - @JsonProperty("header") Header header, + @JsonProperty("header") DataSetHeader header, @JsonProperty("dataset") List dataset) { this.header = header; this.dataset = dataset; } - public Header getHeader() { + public DataSetHeader getHeader() { return header; } - public void setHeader(Header header) { + public Dataset header(DataSetHeader header) { this.header = header; + return this; } public List getDataset() { return dataset; } - public void setDataset(List dataset) { + public Dataset setDataset(List dataset) { this.dataset = dataset; + return this; } } diff --git a/src/main/java/org/radarcns/domain/restapi/format/SourceData.java b/src/main/java/org/radarcns/domain/restapi/format/SourceData.java index af15b0c2..094adf6f 100644 --- a/src/main/java/org/radarcns/domain/restapi/format/SourceData.java +++ b/src/main/java/org/radarcns/domain/restapi/format/SourceData.java @@ -19,7 +19,7 @@ public SourceData(String name) { public SourceData(String name, String type) { this.name = name; - this.name = type; + this.type = type; } public String getName() { diff --git a/src/main/java/org/radarcns/domain/restapi/header/DataSetHeader.java b/src/main/java/org/radarcns/domain/restapi/header/DataSetHeader.java new file mode 100644 index 00000000..48133396 --- /dev/null +++ b/src/main/java/org/radarcns/domain/restapi/header/DataSetHeader.java @@ -0,0 +1,92 @@ +package org.radarcns.domain.restapi.header; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Objects; + +import org.radarcns.domain.restapi.TimeWindow; + +public class DataSetHeader extends Header { + + /** + * Report the source data name or specific type of assessment. + */ + @JsonProperty + public String sourceDataType; + + /** + * Statical value expressed by samples. + */ + @JsonProperty + public DescriptiveStatistic descriptiveStatistic; + + /** + * Default constructor. + */ + public DataSetHeader() { + // default constructor + } + + /** + * All-args constructor. + * + * @param subjectId Subject identifier. + * @param sourceId Source identifier. + * @param sourceType Sourcetype information, it can be a device or assessment name. + * @param sourceDataType Source data information, it can be a device or assessment name. + * @param descriptiveStatistic Statical value expressed by samples. + * @param unit Unit used by the sourceType. + * @param timeWindow Time interval between two consecutive samples. + * @param timeFrame Timestamps of request. + * @param effectiveTimeFrame Timestamps of the first and the last samples in the data-set. + */ + @SuppressWarnings("PMD.ExcessiveParameterList") + public DataSetHeader(String projectId, String subjectId, String sourceId, String sourceType, + String sourceDataType, DescriptiveStatistic descriptiveStatistic, String unit, + TimeWindow timeWindow, TimeFrame timeFrame, TimeFrame effectiveTimeFrame) { + super(projectId, subjectId, sourceId, sourceType, unit, timeWindow, timeFrame, + effectiveTimeFrame); + this.sourceDataType = sourceDataType; + this.descriptiveStatistic = descriptiveStatistic; + } + + public String getSourceDataType() { + return sourceDataType; + } + + public DataSetHeader sourceDataType(String sourceDataType) { + this.sourceDataType = sourceDataType; + return this; + } + + public DescriptiveStatistic getDescriptiveStatistic() { + return descriptiveStatistic; + } + + public DataSetHeader descriptiveStatistic(DescriptiveStatistic descriptiveStatistic) { + this.descriptiveStatistic = descriptiveStatistic; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + DataSetHeader that = (DataSetHeader) o; + return Objects.equals(sourceDataType, that.sourceDataType) + && descriptiveStatistic == that.descriptiveStatistic; + } + + @Override + public int hashCode() { + + return Objects.hash(super.hashCode(), sourceDataType, descriptiveStatistic); + } +} diff --git a/src/main/java/org/radarcns/domain/restapi/header/Header.java b/src/main/java/org/radarcns/domain/restapi/header/Header.java index d609d658..74c2bd94 100644 --- a/src/main/java/org/radarcns/domain/restapi/header/Header.java +++ b/src/main/java/org/radarcns/domain/restapi/header/Header.java @@ -26,18 +26,6 @@ public class Header { */ @JsonProperty public String sourceType; - - /** - * Report the source data name or specific type of assessment. - */ - @JsonProperty - public String sourceDataType; - - /** - * Statical value expressed by samples. - */ - @JsonProperty - public DescriptiveStatistic descriptiveStatistic; /** * Unit used by the sourceType. */ @@ -69,110 +57,96 @@ public Header() { * @param subjectId Subject identifier. * @param sourceId Source identifier. * @param sourceType Sourcetype information, it can be a device or assessment name. - * @param sourceDataType Source data information, it can be a device or assessment name. - * @param descriptiveStatistic Statical value expressed by samples. * @param unit Unit used by the sourceType. * @param timeWindow Time interval between two consecutive samples. * @param timeFrame Timestamps of request. * @param effectiveTimeFrame Timestamps of the first and the last samples in the data-set. */ @SuppressWarnings("PMD.ExcessiveParameterList") - public Header(String projectId, String subjectId, String sourceId, String sourceType, String - sourceDataType, - DescriptiveStatistic descriptiveStatistic, String unit, TimeWindow timeWindow, - TimeFrame timeFrame, TimeFrame effectiveTimeFrame) { + public Header(String projectId, String subjectId, String sourceId, String sourceType, + String unit, TimeWindow timeWindow, TimeFrame timeFrame, + TimeFrame effectiveTimeFrame) { this.projectId = projectId; this.subjectId = subjectId; this.sourceId = sourceId; this.sourceType = sourceType; - this.sourceDataType = sourceDataType; - this.descriptiveStatistic = descriptiveStatistic; this.unit = unit; this.timeWindow = timeWindow; this.timeFrame = timeFrame; this.effectiveTimeFrame = effectiveTimeFrame; } + public String getProjectId() { + return projectId; + } + + public Header projectId(String projectId) { + this.projectId = projectId; + return this; + } + public String getSubjectId() { return subjectId; } - public void setSubjectId(String subjectId) { + public Header subjectId(String subjectId) { this.subjectId = subjectId; + return this; } public String getSourceId() { return sourceId; } - public void setSourceId(String sourceId) { + public Header sourceId(String sourceId) { this.sourceId = sourceId; - } - - public String getProjectId() { - return projectId; - } - - public void setProjectId(String projectId) { - this.projectId = projectId; + return this; } public String getSourceType() { return sourceType; } - public void setSourceType(String sourceType) { + public Header sourceType(String sourceType) { this.sourceType = sourceType; - } - - public String getSourceDataType() { - return sourceDataType; - } - - public void setSourceDataType(String sourceDataType) { - this.sourceDataType = sourceDataType; - } - - public DescriptiveStatistic getDescriptiveStatistic() { - return descriptiveStatistic; - } - - public void setDescriptiveStatistic( - DescriptiveStatistic descriptiveStatistic) { - this.descriptiveStatistic = descriptiveStatistic; + return this; } public String getUnit() { return unit; } - public void setUnit(String unit) { + public Header unit(String unit) { this.unit = unit; + return this; } public TimeWindow getTimeWindow() { return timeWindow; } - public void setTimeWindow(TimeWindow timeWindow) { + public Header timeWindow(TimeWindow timeWindow) { this.timeWindow = timeWindow; + return this; } public TimeFrame getEffectiveTimeFrame() { return effectiveTimeFrame; } - public void setEffectiveTimeFrame( + public Header effectiveTimeFrame( TimeFrame effectiveTimeFrame) { this.effectiveTimeFrame = effectiveTimeFrame; + return this; } public TimeFrame getTimeFrame() { return timeFrame; } - public void setTimeFrame(TimeFrame timeFrame) { + public Header timeFrame(TimeFrame timeFrame) { this.timeFrame = timeFrame; + return this; } @@ -184,23 +158,21 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - Header effectiveTimeFrame = (Header) o; + Header that = (Header) o; - return Objects.equals(subjectId, effectiveTimeFrame.subjectId) - && Objects.equals(projectId, effectiveTimeFrame.projectId) - && Objects.equals(sourceId, effectiveTimeFrame.sourceId) - && Objects.equals(sourceType, effectiveTimeFrame.sourceType) - && Objects.equals(sourceDataType, effectiveTimeFrame.sourceDataType) - && Objects.equals(descriptiveStatistic, effectiveTimeFrame.descriptiveStatistic) - && Objects.equals(unit, effectiveTimeFrame.unit) - && Objects.equals(timeWindow, effectiveTimeFrame.timeWindow) - && Objects.equals(timeFrame, effectiveTimeFrame.timeFrame) - && Objects.equals(effectiveTimeFrame, effectiveTimeFrame.effectiveTimeFrame); + return Objects.equals(subjectId, that.subjectId) + && Objects.equals(projectId, that.projectId) + && Objects.equals(sourceId, that.sourceId) + && Objects.equals(sourceType, that.sourceType) + && Objects.equals(unit, that.unit) + && Objects.equals(timeWindow, that.timeWindow) + && Objects.equals(timeFrame, that.timeFrame) + && Objects.equals(effectiveTimeFrame, that.effectiveTimeFrame); } @Override public int hashCode() { - return Objects.hash(subjectId, projectId, sourceId, sourceType, sourceDataType, - descriptiveStatistic, unit, timeWindow, timeFrame, effectiveTimeFrame); + return Objects.hash(subjectId, projectId, sourceId, sourceType, unit, timeWindow, timeFrame, + effectiveTimeFrame); } } diff --git a/src/main/java/org/radarcns/domain/restapi/header/MonitorHeader.java b/src/main/java/org/radarcns/domain/restapi/header/MonitorHeader.java new file mode 100644 index 00000000..9e549335 --- /dev/null +++ b/src/main/java/org/radarcns/domain/restapi/header/MonitorHeader.java @@ -0,0 +1,96 @@ +package org.radarcns.domain.restapi.header; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Objects; + +import org.radarcns.domain.restapi.TimeWindow; + +public class MonitorHeader extends Header { + + public enum MonitorCategory { + PASSIVE, QUESTIONNAIRE + } + + @JsonProperty + private MonitorCategory monitorCategory; + + /** + * Default constructor. + */ + public MonitorHeader() { + + } + + /** + * Contains meta-data of the monitored source. + * @param projectId project name + * @param subjectId subject identifier. + * @param sourceId source identifier. + * @param monitorCategory monitor category. + */ + public MonitorHeader(String projectId, String subjectId, String sourceId, + MonitorCategory monitorCategory) { + super(projectId, subjectId, sourceId, null, null, null, null, null); + this.monitorCategory = monitorCategory; + } + + + /** + * Contains meta-data of the monitored source. + * @param projectId project name + * @param subjectId subject identifier. + * @param sourceId source identifier. + * @param sourceType source-type of source. + * @param unit unit of measurement + * @param timeWindow timewindow between records. + * @param timeFrame requested time frame. + * @param effectiveTimeFrame effective time frame. + * @param monitorCategory monitor category. + */ + public MonitorHeader(String projectId, String subjectId, String sourceId, String sourceType, + String unit, TimeWindow timeWindow, TimeFrame timeFrame, TimeFrame effectiveTimeFrame, + MonitorCategory monitorCategory) { + super(projectId, subjectId, sourceId, sourceType, unit, timeWindow, timeFrame, + effectiveTimeFrame); + this.monitorCategory = monitorCategory; + } + + + + public MonitorCategory getMonitorCategory() { + return monitorCategory; + } + + public MonitorHeader monitorCategory(MonitorCategory monitorCategory) { + this.monitorCategory = monitorCategory; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MonitorHeader that = (MonitorHeader) o; + return Objects.equals(projectId, that.projectId) && Objects + .equals(subjectId, that.subjectId) && Objects.equals(sourceId, that.sourceId) + && monitorCategory == that.monitorCategory; + } + + @Override + public int hashCode() { + + return Objects.hash(projectId, subjectId, sourceId, monitorCategory); + } + + @Override + public String toString() { + return "MonitorHeader{" + "projectId='" + projectId + '\'' + ", subjectId='" + subjectId + + '\'' + ", sourceId='" + sourceId + '\'' + ", monitorCategory=" + monitorCategory + + '}'; + } +} diff --git a/src/main/java/org/radarcns/domain/restapi/Application.java b/src/main/java/org/radarcns/domain/restapi/monitor/ApplicationStatus.java similarity index 79% rename from src/main/java/org/radarcns/domain/restapi/Application.java rename to src/main/java/org/radarcns/domain/restapi/monitor/ApplicationStatus.java index d3aa1b13..15112aed 100644 --- a/src/main/java/org/radarcns/domain/restapi/Application.java +++ b/src/main/java/org/radarcns/domain/restapi/monitor/ApplicationStatus.java @@ -1,9 +1,10 @@ -package org.radarcns.domain.restapi; +package org.radarcns.domain.restapi.monitor; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Objects; +import org.radarcns.domain.restapi.ServerStatus; -public class Application { +public class ApplicationStatus { /** * Hardware identifier of client application. @@ -39,7 +40,7 @@ public class Application { /** * Default constructor. */ - public Application() { + public ApplicationStatus() { } /** @@ -52,7 +53,7 @@ public Application() { * @param recordsSent Number of records sent since application start. * @param recordsUnsent Number of unsent records. */ - public Application(java.lang.String ipAddress, java.lang.Double uptime, + public ApplicationStatus(java.lang.String ipAddress, java.lang.Double uptime, ServerStatus serverStatus, java.lang.Integer recordsCached, java.lang.Integer recordsSent, java.lang.Integer recordsUnsent) { @@ -120,14 +121,14 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - Application application = (Application) o; - - return Objects.equals(ipAddress, application.ipAddress) - && Objects.equals(uptime, application.uptime) - && Objects.equals(serverStatus, application.serverStatus) - && Objects.equals(recordsCached, application.recordsCached) - && Objects.equals(recordsSent, application.recordsSent) - && Objects.equals(recordsUnsent, application.recordsUnsent); + ApplicationStatus applicationStatus = (ApplicationStatus) o; + + return Objects.equals(ipAddress, applicationStatus.ipAddress) + && Objects.equals(uptime, applicationStatus.uptime) + && Objects.equals(serverStatus, applicationStatus.serverStatus) + && Objects.equals(recordsCached, applicationStatus.recordsCached) + && Objects.equals(recordsSent, applicationStatus.recordsSent) + && Objects.equals(recordsUnsent, applicationStatus.recordsUnsent); } @Override diff --git a/src/main/java/org/radarcns/domain/restapi/monitor/MonitorData.java b/src/main/java/org/radarcns/domain/restapi/monitor/MonitorData.java new file mode 100644 index 00000000..885df827 --- /dev/null +++ b/src/main/java/org/radarcns/domain/restapi/monitor/MonitorData.java @@ -0,0 +1,72 @@ +package org.radarcns.domain.restapi.monitor; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Objects; +import org.radarcns.domain.restapi.header.MonitorHeader; + +public class MonitorData { + + @JsonProperty + private MonitorHeader header; + + @JsonProperty + private Object data; + + public MonitorData() { + + } + + public MonitorData(MonitorHeader monitorHeader, Object data) { + this.header = monitorHeader; + this.data = data; + } + + public MonitorHeader getHeader() { + return header; + } + + public void setHeader(MonitorHeader header) { + this.header = header; + } + + public Object getData() { + return data; + } + + public MonitorData header(MonitorHeader monitorHeader) { + this.header = monitorHeader; + return this; + } + + public void setData(Object data) { + this.data = data; + } + + public MonitorData data(Object data) { + this.data = data; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MonitorData that = (MonitorData) o; + return Objects.equals(header, that.header) && Objects.equals(data, that.data); + } + + @Override + public int hashCode() { + + return Objects.hash(header, data); + } + + @Override + public String toString() { + return "MonitorData{" + "header=" + header + ", data=" + data + '}'; + } +} diff --git a/src/main/java/org/radarcns/domain/restapi/monitor/QuestionnaireCompletionStatus.java b/src/main/java/org/radarcns/domain/restapi/monitor/QuestionnaireCompletionStatus.java new file mode 100644 index 00000000..923761cf --- /dev/null +++ b/src/main/java/org/radarcns/domain/restapi/monitor/QuestionnaireCompletionStatus.java @@ -0,0 +1,88 @@ +package org.radarcns.domain.restapi.monitor; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Objects; + + +public class QuestionnaireCompletionStatus { + + @JsonProperty + private Double timeRecorded; + + @JsonProperty + private String questionnaireName; + + @JsonProperty + private Double completionPercentage = 0d; + + /** + * Default constructor. + */ + public QuestionnaireCompletionStatus() { + // default constructor + } + + /** + * Constructor. + * @param timeRecorded recorded time of the status. + * @param questionnaireName name of the questionnaire. + * @param completionPercentage percentage of completion. + */ + public QuestionnaireCompletionStatus(Double timeRecorded, + String questionnaireName, Double completionPercentage) { + this.timeRecorded = timeRecorded; + this.questionnaireName = questionnaireName; + this.completionPercentage = completionPercentage; + } + + public Double getTimeRecorded() { + return timeRecorded; + } + + public void setTimeRecorded(Double timeRecorded) { + this.timeRecorded = timeRecorded; + } + + public String getQuestionnaireName() { + return questionnaireName; + } + + public void setQuestionnaireName(String questionnaireName) { + this.questionnaireName = questionnaireName; + } + + public Double getCompletionPercentage() { + return completionPercentage; + } + + public void setCompletionPercentage(Double completionPercentage) { + this.completionPercentage = completionPercentage; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + QuestionnaireCompletionStatus that = (QuestionnaireCompletionStatus) o; + return Objects.equals(timeRecorded, that.timeRecorded) && Objects + .equals(questionnaireName, that.questionnaireName) && Objects + .equals(completionPercentage, that.completionPercentage); + } + + @Override + public int hashCode() { + + return Objects.hash(timeRecorded, questionnaireName, completionPercentage); + } + + @Override + public String toString() { + return "QuestionnaireCompletionStatus{" + ", timeRecorded=" + + timeRecorded + ", questionnaireName='" + questionnaireName + '\'' + + ", completionPercentage=" + completionPercentage + '}'; + } +} diff --git a/src/main/java/org/radarcns/listener/managementportal/ManagementPortalClient.java b/src/main/java/org/radarcns/listener/managementportal/ManagementPortalClient.java index 927bbc88..d9e69c11 100644 --- a/src/main/java/org/radarcns/listener/managementportal/ManagementPortalClient.java +++ b/src/main/java/org/radarcns/listener/managementportal/ManagementPortalClient.java @@ -325,7 +325,8 @@ public List retrieveSourceData() throws IOException { } List allSourceData = SOURCE_DATA_LIST_READER.readValue(responseBody); logger.info("Retrieved {} SourceData from MP", allSourceData.size()); - return allSourceData.stream().map(DataFormat::getMongoSensor) + return allSourceData.stream() + .map(DataFormat::getMongoSensor) .collect(Collectors.toList()); } } diff --git a/src/main/java/org/radarcns/mongo/data/monitor/ApplicationStatusRecordCounter.java b/src/main/java/org/radarcns/mongo/data/monitor/application/ApplicationStatusRecordCounter.java similarity index 85% rename from src/main/java/org/radarcns/mongo/data/monitor/ApplicationStatusRecordCounter.java rename to src/main/java/org/radarcns/mongo/data/monitor/application/ApplicationStatusRecordCounter.java index f31420bb..dd71574c 100644 --- a/src/main/java/org/radarcns/mongo/data/monitor/ApplicationStatusRecordCounter.java +++ b/src/main/java/org/radarcns/mongo/data/monitor/application/ApplicationStatusRecordCounter.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package org.radarcns.mongo.data.monitor; +package org.radarcns.mongo.data.monitor.application; import org.bson.Document; -import org.radarcns.domain.restapi.Application; +import org.radarcns.domain.restapi.monitor.ApplicationStatus; public class ApplicationStatusRecordCounter extends MongoApplicationStatusWrapper { @@ -25,7 +25,7 @@ public class ApplicationStatusRecordCounter extends MongoApplicationStatusWrappe //TODO take field names from RADAR MongoDb Connector @Override - protected Application getApplication(Document doc, Application app) { + protected ApplicationStatus getApplication(Document doc, ApplicationStatus app) { app.setRecordsCached(doc.getInteger("recordsCached")); app.setRecordsSent(doc.getInteger("recordsSent")); app.setRecordsUnsent(doc.getInteger("recordsUnsent")); diff --git a/src/main/java/org/radarcns/mongo/data/monitor/ApplicationStatusServerStatus.java b/src/main/java/org/radarcns/mongo/data/monitor/application/ApplicationStatusServerStatus.java similarity index 85% rename from src/main/java/org/radarcns/mongo/data/monitor/ApplicationStatusServerStatus.java rename to src/main/java/org/radarcns/mongo/data/monitor/application/ApplicationStatusServerStatus.java index a162a41b..29a036e4 100644 --- a/src/main/java/org/radarcns/mongo/data/monitor/ApplicationStatusServerStatus.java +++ b/src/main/java/org/radarcns/mongo/data/monitor/application/ApplicationStatusServerStatus.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package org.radarcns.mongo.data.monitor; +package org.radarcns.mongo.data.monitor.application; import org.bson.Document; -import org.radarcns.domain.restapi.Application; +import org.radarcns.domain.restapi.monitor.ApplicationStatus; import org.radarcns.util.RadarConverter; public class ApplicationStatusServerStatus extends MongoApplicationStatusWrapper { @@ -26,7 +26,7 @@ public class ApplicationStatusServerStatus extends MongoApplicationStatusWrapper //TODO take field names from RADAR MongoDb Connector @Override - protected Application getApplication(Document doc, Application app) { + protected ApplicationStatus getApplication(Document doc, ApplicationStatus app) { app.setIpAddress(doc.getString("clientIP")); app.setServerStatus(RadarConverter.getServerStatus(doc.getString("serverStatus"))); diff --git a/src/main/java/org/radarcns/mongo/data/monitor/ApplicationStatusUpTime.java b/src/main/java/org/radarcns/mongo/data/monitor/application/ApplicationStatusUpTime.java similarity index 83% rename from src/main/java/org/radarcns/mongo/data/monitor/ApplicationStatusUpTime.java rename to src/main/java/org/radarcns/mongo/data/monitor/application/ApplicationStatusUpTime.java index 8ae57c10..006d96e0 100644 --- a/src/main/java/org/radarcns/mongo/data/monitor/ApplicationStatusUpTime.java +++ b/src/main/java/org/radarcns/mongo/data/monitor/application/ApplicationStatusUpTime.java @@ -14,10 +14,10 @@ * limitations under the License. */ -package org.radarcns.mongo.data.monitor; +package org.radarcns.mongo.data.monitor.application; import org.bson.Document; -import org.radarcns.domain.restapi.Application; +import org.radarcns.domain.restapi.monitor.ApplicationStatus; public class ApplicationStatusUpTime extends MongoApplicationStatusWrapper { @@ -25,7 +25,7 @@ public class ApplicationStatusUpTime extends MongoApplicationStatusWrapper { //TODO take field names from RADAR MongoDb Connector @Override - protected Application getApplication(Document doc, Application app) { + protected ApplicationStatus getApplication(Document doc, ApplicationStatus app) { app.setUptime(doc.getDouble("uptime")); return app; diff --git a/src/main/java/org/radarcns/mongo/data/monitor/MongoApplicationStatusWrapper.java b/src/main/java/org/radarcns/mongo/data/monitor/application/MongoApplicationStatusWrapper.java similarity index 78% rename from src/main/java/org/radarcns/mongo/data/monitor/MongoApplicationStatusWrapper.java rename to src/main/java/org/radarcns/mongo/data/monitor/application/MongoApplicationStatusWrapper.java index b973605e..00953571 100644 --- a/src/main/java/org/radarcns/mongo/data/monitor/MongoApplicationStatusWrapper.java +++ b/src/main/java/org/radarcns/mongo/data/monitor/application/MongoApplicationStatusWrapper.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.radarcns.mongo.data.monitor; +package org.radarcns.mongo.data.monitor.application; import static org.radarcns.mongo.util.MongoHelper.ASCENDING; import static org.radarcns.mongo.util.MongoHelper.VALUE; @@ -22,7 +22,7 @@ import com.mongodb.MongoClient; import com.mongodb.client.MongoCursor; import org.bson.Document; -import org.radarcns.domain.restapi.Application; +import org.radarcns.domain.restapi.monitor.ApplicationStatus; import org.radarcns.mongo.util.MongoHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,20 +36,19 @@ public abstract class MongoApplicationStatusWrapper { .getLogger(MongoApplicationStatusWrapper.class); /** - * Returns an {@code Application} initialised with the extracted value. + * Returns an {@code ApplicationStatus} initialised with the extracted value. * * @param subject is the subjectID * @param source is the sourceID * @param client is the mongoDb client instance * @return the last seen status update for the given subject and sourceType, otherwise null */ - public Application valueByProjectSubjectSource(String project, String subject, String source, - Application app, - MongoClient client) { + public ApplicationStatus valueByProjectSubjectSource(String project, String subject, + String source, ApplicationStatus app, MongoClient client) { MongoCursor cursor = MongoHelper .findDocumentBySource(MongoHelper.getCollection(client, getCollectionName()), - project, subject, source, null, ASCENDING, + project, subject, source, VALUE + ".time", ASCENDING, 1); if (!cursor.hasNext()) { @@ -62,14 +61,14 @@ public Application valueByProjectSubjectSource(String project, String subject, S cursor.close(); if (app == null) { - return getApplication((Document) doc.get(VALUE), new Application()); + return getApplication((Document) doc.get(VALUE), new ApplicationStatus()); } return getApplication((Document) doc.get(VALUE), app); } - protected abstract Application getApplication(Document doc, Application app); + protected abstract ApplicationStatus getApplication(Document doc, ApplicationStatus app); public abstract String getCollectionName(); diff --git a/src/main/java/org/radarcns/mongo/data/monitor/questionnaire/QuestionnaireCompletionLogWrapper.java b/src/main/java/org/radarcns/mongo/data/monitor/questionnaire/QuestionnaireCompletionLogWrapper.java new file mode 100644 index 00000000..4db24b3b --- /dev/null +++ b/src/main/java/org/radarcns/mongo/data/monitor/questionnaire/QuestionnaireCompletionLogWrapper.java @@ -0,0 +1,56 @@ +package org.radarcns.mongo.data.monitor.questionnaire; + +import static org.radarcns.mongo.util.MongoHelper.ASCENDING; +import static org.radarcns.mongo.util.MongoHelper.VALUE; + +import com.mongodb.MongoClient; +import com.mongodb.client.MongoCursor; +import org.bson.Document; +import org.radarcns.domain.restapi.monitor.QuestionnaireCompletionStatus; +import org.radarcns.mongo.data.monitor.application.MongoApplicationStatusWrapper; +import org.radarcns.mongo.util.MongoHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class QuestionnaireCompletionLogWrapper { + private static final Logger LOGGER = + LoggerFactory.getLogger(MongoApplicationStatusWrapper.class); + + public static final String QUESTIONNAIRE_COMPLETION_LOG_COLLECTION = + "questionnaire-completion-log"; + + + /** + * Returns an {@code QuestionnaireCompletionStatus} initialised with the extracted value. + * + * @param project is the projectName + * @param subject is the subjectID + * @param source is the sourceID + * @param client is the mongoDb client instance + * @return the last recorded value of questionnaire-completion-log, otherwise null + */ + public QuestionnaireCompletionStatus valueByProjectSubjectSource(String project, String subject, + String source, MongoClient client) { + + MongoCursor cursor = MongoHelper.findDocumentBySource( + MongoHelper.getCollection(client, QUESTIONNAIRE_COMPLETION_LOG_COLLECTION), project, + subject, source, VALUE + ".time", ASCENDING, 1); + + if (!cursor.hasNext()) { + LOGGER.debug("Empty cursor"); + cursor.close(); + return null; + } + + Document doc = cursor.next(); + cursor.close(); + Document value = (Document) doc.get(VALUE); + + QuestionnaireCompletionStatus data = new QuestionnaireCompletionStatus(); + data.setTimeRecorded(value.getDouble("time")); + data.setQuestionnaireName(value.getString("name")); + data.setCompletionPercentage(value.getDouble("completionPercentage")); + + return data; + } +} diff --git a/src/main/java/org/radarcns/mongo/data/passive/SourceDataMongoWrapper.java b/src/main/java/org/radarcns/mongo/data/passive/SourceDataMongoWrapper.java index 3d61d625..4f3610d0 100644 --- a/src/main/java/org/radarcns/mongo/data/passive/SourceDataMongoWrapper.java +++ b/src/main/java/org/radarcns/mongo/data/passive/SourceDataMongoWrapper.java @@ -32,12 +32,14 @@ import org.radarcns.domain.restapi.TimeWindow; import org.radarcns.domain.restapi.dataset.DataItem; import org.radarcns.domain.restapi.dataset.Dataset; +import org.radarcns.domain.restapi.header.DataSetHeader; import org.radarcns.domain.restapi.header.DescriptiveStatistic; import org.radarcns.domain.restapi.header.Header; import org.radarcns.domain.restapi.header.TimeFrame; import org.radarcns.mongo.util.MongoHelper; import org.radarcns.mongo.util.MongoHelper.Stat; import org.radarcns.util.RadarConverter; +import org.radarcns.util.TimeScale; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -92,10 +94,10 @@ public String getSourceDataName() { * @see Dataset */ @SuppressWarnings("checkstyle:AbbreviationAsWordInName") - public Dataset getLatestRecord(String projectName, String subject, String source, Header + public Dataset getLatestRecord(String projectName, String subject, String source, DataSetHeader header, Stat stat, MongoCollection collection) { try (MongoCursor cursor = MongoHelper.findDocumentBySource( - collection, projectName, subject, source, END, DESCENDING, 1)) { + collection, projectName, subject, source, KEY + "." + END, DESCENDING, 1)) { return getDataSet(stat.getParam(), RadarConverter.getDescriptiveStatistic(stat), header, cursor); } @@ -114,9 +116,9 @@ public Dataset getLatestRecord(String projectName, String subject, String source * @see Dataset */ public Dataset getAllRecords(MongoCollection collection, String projectName, - String subject, String source, Header header, Stat stat) { + String subject, String source, DataSetHeader header, Stat stat) { try (MongoCursor cursor = MongoHelper.findDocumentBySource( - collection, projectName, subject, source, START, ASCENDING, null)) { + collection, projectName, subject, source, KEY + "." + START, ASCENDING, null)) { return getDataSet(stat.getParam(), RadarConverter.getDescriptiveStatistic(stat), header, cursor); } @@ -136,7 +138,7 @@ public Dataset getAllRecords(MongoCollection collection, String projec * @see Dataset */ public Dataset getAllRecordsInWindow(MongoCollection collection, String projectName, - String subject, String source, Header header, Stat stat, TimeFrame timeFrame) { + String subject, String source, DataSetHeader header, Stat stat, TimeFrame timeFrame) { try (MongoCursor cursor = MongoHelper.findDocumentsBySource( collection, projectName, subject, source, timeFrame)) { return getDataSet(stat.getParam(), RadarConverter.getDescriptiveStatistic(stat), header, @@ -155,7 +157,7 @@ public Dataset getAllRecordsInWindow(MongoCollection collection, Strin * @return data dataset for the given input, otherwise empty dataset * @see Dataset */ - private Dataset getDataSet(String field, DescriptiveStatistic stat, Header header, + private Dataset getDataSet(String field, DescriptiveStatistic stat, DataSetHeader header, MongoCursor cursor) { TimeFrame timeFrame = null; @@ -179,7 +181,7 @@ private Dataset getDataSet(String field, DescriptiveStatistic stat, Header heade currentFrame.getStartDateTime())); } - header.setEffectiveTimeFrame(timeFrame); + header.effectiveTimeFrame(timeFrame); LOGGER.debug("Found {} value(s)", list.size()); @@ -218,6 +220,16 @@ public String getCollectionName(TimeWindow interval) { + "is not yest supported."); } + /** + * Returns the required mongoDB collection name for the given timeWindow of this source-data. + * + * @param timeScale of data-set query. + * @return the MongoDB Collection name for given time window. + */ + public String getCollectionName(TimeScale timeScale) { + return getCollectionName(timeScale.getTimeWindow()); + } + /** * Convert a {@link Document} to the corresponding SpecificRecord. This function must be * override by the subclass @@ -242,10 +254,9 @@ protected Object documentToDataFormat(Document doc, String field, DescriptiveSta */ protected abstract int extractCount(Document doc); - public Double getExpectedRecordCount(TimeWindow timeWindow) { - return RadarConverter.getSecond(timeWindow) * Double - .valueOf(this.sourceData.getFrequency()); + return RadarConverter.getExpectedMessages(timeWindow, + Double.valueOf(this.sourceData.getFrequency())); } /** diff --git a/src/main/java/org/radarcns/mongo/util/MongoHelper.java b/src/main/java/org/radarcns/mongo/util/MongoHelper.java index 3354d331..922f58d1 100644 --- a/src/main/java/org/radarcns/mongo/util/MongoHelper.java +++ b/src/main/java/org/radarcns/mongo/util/MongoHelper.java @@ -34,17 +34,20 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.bson.BsonDocument; import org.bson.Document; import org.bson.conversions.Bson; import org.radarcns.config.Properties; import org.radarcns.domain.restapi.header.TimeFrame; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Generic MongoDB helper. */ public class MongoHelper { - //private static final Logger logger = LoggerFactory.getLogger(MongoHelper.class); + private static final Logger logger = LoggerFactory.getLogger(MongoHelper.class); public static final String ID = "_id"; public static final String KEY = "key"; @@ -112,9 +115,18 @@ public static MongoCursor findDocumentsBySource( MongoCollection collection, String projectName, String subjectId, String sourceId, TimeFrame timeFrame) { createIndexIfNotAvailable(collection, indexProjectSubjectSourceTimestart); + Bson querySource = filterSource(projectName, subjectId, sourceId, timeFrame); + BasicDBObject sortStartTime = new BasicDBObject(KEY + "." + START, ASCENDING); + + if (logger.isDebugEnabled()) { + BsonDocument findQueryDocument = querySource.toBsonDocument( + Document.class, collection.getCodecRegistry()); + logger.debug("Filtering query {} and sorting by {}", findQueryDocument, sortStartTime); + } + return collection - .find(filterSource(projectName, subjectId, sourceId, timeFrame)) - .sort(new BasicDBObject(KEY + "." + START, ASCENDING)) + .find(querySource) + .sort(sortStartTime) .iterator(); } @@ -122,13 +134,18 @@ public static MongoCursor findDocumentsBySource( * Finds all documents belonging to the given subject, source and project. * Close the returned iterator after use, for example with a try-with-resources construct. * - * @param collection is the MongoDB that will be queried - * @param project is the projectName - * @param subject is the subjectID - * @param source is the sourceID - * @param sortBy It is optional. {@code 1} means ascending while {@code -1} means descending - * @param limit is the number of document that will be retrieved + * @param collection MongoDB collection name that will be queried + * @param project project name + * @param subject subject ID + * @param source source ID + * @param sortBy Field to sort by. If sortBy is {@code null}, the data will not be sorted. + * The field should be prefixed with {@link MongoHelper#KEY} or + * {@link MongoHelper#VALUE}. + * @param order {@code 1} means ascending while {@code -1} means descending + * @param limit is the number of document that will be retrieved. If the limit is {@code null}, + * no limit is used. * @return a MongoDB cursor containing all documents from query. + * @throws IllegalArgumentException if sortBy does not start with a key or value object. */ public static MongoCursor findDocumentBySource( MongoCollection collection, String project, String subject, String source, @@ -139,6 +156,10 @@ public static MongoCursor findDocumentBySource( FindIterable result = collection.find(filterSource(project, subject, source)); if (sortBy != null) { + if (!sortBy.startsWith(KEY + ".") && !sortBy.startsWith(VALUE + ".")) { + throw new IllegalArgumentException( + "Should sort by a MongoHelper.KEY or MongoHelper.VALUE property."); + } result = result.sort(new BasicDBObject(sortBy, order)); } if (limit != null) { diff --git a/src/main/java/org/radarcns/service/ApplicationStatusMonitorService.java b/src/main/java/org/radarcns/service/ApplicationStatusMonitorService.java deleted file mode 100644 index 467ae516..00000000 --- a/src/main/java/org/radarcns/service/ApplicationStatusMonitorService.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2016 King's College London and The Hyve - * - * 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.radarcns.service; - -import com.mongodb.MongoClient; -import java.util.LinkedList; -import java.util.List; -import javax.inject.Inject; -import org.radarcns.domain.restapi.Application; -import org.radarcns.mongo.data.monitor.ApplicationStatusRecordCounter; -import org.radarcns.mongo.data.monitor.ApplicationStatusServerStatus; -import org.radarcns.mongo.data.monitor.ApplicationStatusUpTime; -import org.radarcns.mongo.data.monitor.MongoApplicationStatusWrapper; - -/** - * Data Access Object for Android App Status values. - */ -public class ApplicationStatusMonitorService { - - - private final List dataAccessObjects; - - /** - * Default constructor. Initiates all the delegate classes to compute Application Status. - */ - @Inject - public ApplicationStatusMonitorService() { - //TODO simplify processing application status - dataAccessObjects = new LinkedList<>(); - dataAccessObjects.add(new ApplicationStatusUpTime()); - dataAccessObjects.add(new ApplicationStatusRecordCounter()); - dataAccessObjects.add(new ApplicationStatusServerStatus()); - } - - /** - * Computes the Android App Status realign on different collection. - * - * @param project of the subject - * @param subject identifier - * @param source identifier - * @param client is the MongoDb client - * @return {@code Application} representing the status of the related Android App - */ - public Application getStatus(String project, String subject, String source, MongoClient - client) { - Application app = null; - - for (MongoApplicationStatusWrapper dataAccessObject : dataAccessObjects) { - app = dataAccessObject - .valueByProjectSubjectSource(project, subject, source, app, client); - } - - return app; - } -} \ No newline at end of file diff --git a/src/main/java/org/radarcns/service/DataSetService.java b/src/main/java/org/radarcns/service/DataSetService.java index ee13e696..123b208f 100644 --- a/src/main/java/org/radarcns/service/DataSetService.java +++ b/src/main/java/org/radarcns/service/DataSetService.java @@ -16,28 +16,16 @@ package org.radarcns.service; -import static org.radarcns.domain.restapi.TimeWindow.ONE_DAY; -import static org.radarcns.domain.restapi.TimeWindow.ONE_HOUR; -import static org.radarcns.domain.restapi.TimeWindow.ONE_MIN; -import static org.radarcns.domain.restapi.TimeWindow.ONE_WEEK; -import static org.radarcns.domain.restapi.TimeWindow.TEN_MIN; -import static org.radarcns.domain.restapi.TimeWindow.TEN_SECOND; +import static org.radarcns.util.ThrowingFunction.tryOrRethrow; import com.mongodb.MongoClient; import com.mongodb.client.MongoCollection; import java.io.IOException; -import java.time.Duration; import java.time.Instant; -import java.time.temporal.TemporalAmount; -import java.util.AbstractMap.SimpleImmutableEntry; import java.util.Collections; -import java.util.Date; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; -import java.util.stream.Stream; import javax.inject.Inject; -import javax.ws.rs.BadRequestException; import org.bson.Document; import org.radarcns.catalog.SourceCatalog; import org.radarcns.domain.managementportal.SourceDTO; @@ -48,6 +36,7 @@ import org.radarcns.domain.restapi.dataset.DataItem; import org.radarcns.domain.restapi.dataset.Dataset; import org.radarcns.domain.restapi.format.SourceData; +import org.radarcns.domain.restapi.header.DataSetHeader; import org.radarcns.domain.restapi.header.DescriptiveStatistic; import org.radarcns.domain.restapi.header.Header; import org.radarcns.domain.restapi.header.TimeFrame; @@ -55,6 +44,7 @@ import org.radarcns.mongo.data.passive.SourceDataMongoWrapper; import org.radarcns.mongo.util.MongoHelper; import org.radarcns.util.RadarConverter; +import org.radarcns.util.TimeScale; import org.radarcns.webapp.exception.BadGatewayException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,9 +53,6 @@ * Generic Data Access Object database independent. */ public class DataSetService { - - private static final int MAXIMUM_NUMBER_OF_WINDOWS = 1000; - private static final Logger LOGGER = LoggerFactory.getLogger(DataSetService.class); private final ManagementPortalClient managementPortalClient; @@ -74,18 +61,12 @@ public class DataSetService { private final MongoClient mongoClient; - private static final List> TIME_WINDOW_LOG = Stream - .of(TEN_SECOND, ONE_MIN, TEN_MIN, ONE_HOUR, ONE_DAY, ONE_WEEK) - .map(w -> pair(w, Math.log(RadarConverter.getSecond(w)))) - .collect(Collectors.toList()); - /** * Constructor. **/ @Inject public DataSetService(SourceCatalog sourceCatalog, - ManagementPortalClient managementPortalClient, MongoClient mongoClient) - throws IOException { + ManagementPortalClient managementPortalClient, MongoClient mongoClient) { this.managementPortalClient = managementPortalClient; this.sourceCatalog = sourceCatalog; this.mongoClient = mongoClient; @@ -110,8 +91,8 @@ public static Dataset emptyDataset(String projectName, String subjectId, String TimeFrame timeFrame) { return new Dataset( - new Header(projectName, subjectId, sourceId, "UNKNOWN", sourceDataName, stat, - null, interval, timeFrame, null), + new DataSetHeader(projectName, subjectId, sourceId, "UNKNOWN", + sourceDataName, stat, null, interval, timeFrame, null), Collections.emptyList()); } @@ -120,16 +101,15 @@ public static Dataset emptyDataset(String projectName, String subjectId, String * * @param projectName of project * @param subjectId of subject - * @param interval timeWindow - * @param timeFrame startToEnd + * @param timeScale time scale * @param sources requested * @return an instance of AggregatedDataPoints */ public static AggregatedDataPoints emptyAggregatedData(String projectName, String subjectId, - TimeWindow interval, TimeFrame timeFrame, List sources) { + TimeScale timeScale, List sources) { - return new AggregatedDataPoints(projectName, subjectId, 0, timeFrame, interval, sources, - Collections.emptyList()); + return new AggregatedDataPoints(projectName, subjectId, 0, + timeScale, sources, Collections.emptyList()); } /** @@ -148,90 +128,59 @@ public Dataset getLastReceivedSample(String projectName, String subjectId, Strin String sourceDataName, DescriptiveStatistic stat, TimeWindow timeWindow) throws IOException { Instant now = Instant.now(); - TimeFrame timeFrame = new TimeFrame(now.minus(RadarConverter.getDuration(timeWindow)), now); + TimeScale timeScale = new TimeScale( + new TimeFrame(now.minus(TimeScale.getDuration(timeWindow)), now), + timeWindow); - Header header = getHeader(projectName, subjectId, sourceId, - sourceDataName, stat, timeWindow, timeFrame); - - SourceDataMongoWrapper sourceDataWrapper = this.sourceCatalog - .getSourceDataWrapper(sourceDataName); - - return sourceDataWrapper.getLatestRecord(projectName, subjectId, sourceId, header, - RadarConverter.getMongoStat(stat), MongoHelper.getCollection(mongoClient, - sourceDataWrapper.getCollectionName(timeWindow))); - } - - /** - * Returns a {@code Dataset} containing all available values for the couple subject sourceType. - * - * @param projectName of the subject - * @param subjectId of the subject - * @param sourceId of the source - * @param sourceDataName of data - * @param stat is the required statistical value - * @param timeWindow time frame resolution - * @return dataset for the given subject and sourceType, otherwise empty dataset - * @see Dataset - */ - public Dataset getAllDataItems(String projectName, String subjectId, String sourceId, - String sourceDataName, DescriptiveStatistic stat, TimeWindow timeWindow) - throws IOException { - Header header = getHeader(projectName, subjectId, sourceId, - sourceDataName, stat, timeWindow, null); + DataSetHeader header = getHeader(projectName, subjectId, sourceId, + sourceDataName, stat, timeScale); - SourceDataMongoWrapper sourceDataWrapper = this.sourceCatalog + SourceDataMongoWrapper sourceData = this.sourceCatalog .getSourceDataWrapper(sourceDataName); - return sourceDataWrapper.getAllRecords(MongoHelper.getCollection(mongoClient, - sourceDataWrapper.getCollectionName(timeWindow)), projectName, subjectId, sourceId, - header, RadarConverter.getMongoStat(stat)); + return sourceData.getLatestRecord(projectName, subjectId, sourceId, header, + RadarConverter.getMongoStat(stat), + MongoHelper.getCollection(mongoClient, sourceData.getCollectionName(timeWindow))); } /** - * Returns a {@link Dataset} containing all available values for the couple subject surce. + * Returns a {@link Dataset} containing all available values for the couple subject source. * * @param projectName of the subject * @param subjectId of the subject * @param sourceId is the sourceID * @param sourceDataName is the required sensor type * @param stat is the required statistical value - * @param timeWindow time frame resolution - * @param start is time window start point in millisecond - * @param end is time window end point in millisecond + * @param timeScale time frame resolution * @return dataset for the given subject and source for given query. * @see Dataset */ public Dataset getAllRecordsInWindow(String projectName, String subjectId, - String sourceId, String sourceDataName, DescriptiveStatistic stat, - TimeWindow timeWindow, - Date start, Date end) throws IOException { + String sourceId, String sourceDataName, DescriptiveStatistic stat, TimeScale timeScale) + throws IOException { SourceDTO source = managementPortalClient.getSource(sourceId); - TimeFrame timeFrame = new TimeFrame(start, end); + SourceDataMongoWrapper sourceData = this.sourceCatalog.getSourceDataWrapper(sourceDataName); - SourceDataMongoWrapper sourceDataWrapper = this.sourceCatalog - .getSourceDataWrapper(sourceDataName); + DataSetHeader header = getHeader(projectName, subjectId, sourceId, + sourceData.getSourceData(), stat, timeScale, + source.getSourceTypeIdentifier().toString()); - Header header = getHeader(projectName, subjectId, sourceId, - sourceDataWrapper.getSourceData(), stat, timeWindow, - source.getSourceTypeIdentifier().toString(), timeFrame); - - return sourceDataWrapper.getAllRecordsInWindow(MongoHelper.getCollection(mongoClient, - sourceDataWrapper.getCollectionName(timeWindow)), projectName, subjectId, sourceId, - header, RadarConverter.getMongoStat(stat), timeFrame - ); + return sourceData.getAllRecordsInWindow( + MongoHelper.getCollection(mongoClient, sourceData.getCollectionName(timeScale)), + projectName, subjectId, sourceId, header, RadarConverter.getMongoStat(stat), + timeScale.getTimeFrame()); } - private Header getHeader(String projectName, String subjectId, String sourceId, - String sourceDataName, DescriptiveStatistic stat, TimeWindow timeWindow, - TimeFrame timeFrame) + private DataSetHeader getHeader(String projectName, String subjectId, String sourceId, + String sourceDataName, DescriptiveStatistic stat, TimeScale timeScale) throws IOException { SourceDTO source = managementPortalClient.getSource(sourceId); return getHeader(projectName, subjectId, sourceId, this.sourceCatalog.getSourceDataWrapper(sourceDataName).getSourceData(), stat, - timeWindow, source.getSourceTypeIdentifier().toString(), timeFrame); + timeScale, source.getSourceTypeIdentifier().toString()); } /** @@ -241,15 +190,16 @@ private Header getHeader(String projectName, String subjectId, String sourceId, * @param source is the sourceID * @param sourceData is sourceData involved in the operation * @param stat {@link DescriptiveStatistic} stating the required statistical value - * @param timeWindow time window is the time interval between two consecutive samples * @return {@link Header} related to the given inputs * @see Dataset */ - private Header getHeader(String project, String subject, String source, - SourceDataDTO sourceData, DescriptiveStatistic stat, TimeWindow timeWindow, - String sourceType, TimeFrame timeFrame) { - return new Header(project, subject, source, sourceType, sourceData.getSourceDataType(), - stat, sourceData.getUnit(), timeWindow, timeFrame, null); + private DataSetHeader getHeader(String project, String subject, String source, + SourceDataDTO sourceData, DescriptiveStatistic stat, TimeScale timeScale, + String sourceType) { + return new DataSetHeader(project, subject, source, sourceType, + sourceData.getSourceDataType(), stat, sourceData.getUnit(), + timeScale.getTimeWindow(), timeScale.getTimeFrame(), + null); } /** @@ -259,14 +209,11 @@ private Header getHeader(String project, String subject, String source, * @param projectName of project * @param subjectId of subject * @param sources requested - * @param timeWindow interval size - * @param timeFrame time frame to look withing + * @param timeScale time scale * @return calculated data. */ public AggregatedDataPoints getDistinctData(String projectName, String subjectId, - List sources, TimeWindow timeWindow, TimeFrame timeFrame) { - checkTimeFrameSize(timeFrame, timeWindow, MAXIMUM_NUMBER_OF_WINDOWS); - + List sources, TimeScale timeScale) { // fill up sourceData.type field try { for (AggregateDataSource source : sources) { @@ -280,32 +227,27 @@ public AggregatedDataPoints getDistinctData(String projectName, String subjectId throw new BadGatewayException(exe); } - List dataItems = calculateIntervals(timeFrame, timeWindow) - .map(t -> collectDistinctSources(projectName, subjectId, sources, t, timeWindow)) + List dataItems = timeScale.streamIntervals() + .map(t -> collectDistinctSources(projectName, subjectId, sources, t, + timeScale.getTimeWindow())) .collect(Collectors.toList()); - Integer maximumValue = dataItems.stream() - .map(p -> ((Number) p.getValue()).intValue()) - .reduce(Integer::max) - .orElse(null); + Integer maximumValue = dataItems.isEmpty() ? null : dataItems.stream() + .mapToInt(p -> ((Number)p.getValue()).intValue()) + .max() + .orElseThrow(() -> new IllegalStateException("Data items are empty")); return new AggregatedDataPoints(projectName, subjectId, - maximumValue, timeFrame, timeWindow, sources, dataItems); + maximumValue, timeScale, sources, dataItems); } private DataItem collectDistinctSources(String projectName, String subjectId, List aggregateDataSources, TimeFrame timeFrame, TimeWindow timeWindow) { int count = aggregateDataSources.stream() - .map(aggregate -> (int) aggregate.getSourceData().stream() - .map(sourceData -> { - try { - return this.sourceCatalog - .getSourceDataWrapper(sourceData.getName()); - } catch (IOException exe) { - throw new BadGatewayException(exe); - } - }) + .mapToInt(aggregate -> (int) aggregate.getSourceData().stream() + .map(tryOrRethrow(s -> this.sourceCatalog.getSourceDataWrapper(s.getName()), + BadGatewayException::new)) .filter(wrapper -> { MongoCollection collection = MongoHelper.getCollection( mongoClient, wrapper.getCollectionName(timeWindow)); @@ -314,68 +256,8 @@ private DataItem collectDistinctSources(String projectName, String subjectId, aggregate.getSourceId(), timeFrame); }) .count()) - .reduce(0, Integer::sum); + .sum(); return new DataItem(count, timeFrame.getStartDateTime()); } - - private Stream calculateIntervals(TimeFrame timeFrame, TimeWindow timeWindow) { - TemporalAmount window = RadarConverter.getDuration(timeWindow); - return Stream.iterate( - windowTimeFrame(timeFrame.getStartDateTime(), window), - t -> windowTimeFrame(t.getEndDateTime(), window)) - .limit(windowsInTimeFrame(timeFrame, timeWindow)); - } - - private static TimeFrame windowTimeFrame(Instant start, TemporalAmount duration) { - return new TimeFrame(start, start.plus(duration)); - } - - private static long windowsInTimeFrame(TimeFrame timeFrame, TimeWindow timeWindow) { - Duration duration = timeFrame.getDuration(); - if (duration == null) { - throw new IllegalStateException("Start or end time of time frame unknown."); - } - return (long) Math.floor(duration.getSeconds() - / (double) RadarConverter.getSecond(timeWindow)); - } - - /** - * Get the time window that closest matches given time frame. - * - * @param timeFrame time frame to compute time window for - * @param numberOfWindows number of time windows that should ideally be returned. - * @return closest match with given time frame. - */ - public TimeWindow getFittingTimeWindow(TimeFrame timeFrame, int numberOfWindows) { - double logSeconds = Math.log(timeFrame.getDuration().getSeconds() / numberOfWindows); - return TIME_WINDOW_LOG.stream() - .map(e -> pair(e.getKey(), Math.abs(logSeconds - e.getValue()))) - .reduce((e1, e2) -> e1.getValue() < e2.getValue() ? e1 : e2) - .orElseThrow(() -> new AssertionError("No close time window found")) - .getKey(); - } - - /** - * Checks that for a given time frame with given time window, the number of data points does not - * exceed a maximum. - * - * @param timeFrame time frame to request - * @param timeWindow time window granularity - * @param maximumSize maximum number of data points - * @throws BadRequestException if the number of data points would exceed maximum size. - */ - public void checkTimeFrameSize(TimeFrame timeFrame, TimeWindow timeWindow, int maximumSize) { - long requestedTimeFrames = windowsInTimeFrame(timeFrame, timeWindow); - if (requestedTimeFrames > maximumSize) { - throw new BadRequestException( - "Cannot request more than " + maximumSize + " time windows. Requested " - + requestedTimeFrames + " with time frame " + timeFrame - + " and time window " + timeWindow + '.'); - } - } - - private static Map.Entry pair(K key, V value) { - return new SimpleImmutableEntry<>(key, value); - } } diff --git a/src/main/java/org/radarcns/service/SourceMonitorService.java b/src/main/java/org/radarcns/service/SourceMonitorService.java index 1982d673..78c9a600 100644 --- a/src/main/java/org/radarcns/service/SourceMonitorService.java +++ b/src/main/java/org/radarcns/service/SourceMonitorService.java @@ -16,12 +16,13 @@ package org.radarcns.service; -import static org.radarcns.mongo.util.MongoHelper.DESCENDING; +import static org.radarcns.mongo.util.MongoHelper.ASCENDING; import static org.radarcns.mongo.util.MongoHelper.END; import static org.radarcns.mongo.util.MongoHelper.START; import static org.radarcns.mongo.util.MongoHelper.VALUE; import com.mongodb.MongoClient; +import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoCursor; import javax.inject.Inject; import org.bson.Document; @@ -62,11 +63,11 @@ public SourceMonitorService(MongoClient mongoClient) { public TimeFrame getEffectiveTimeFrame(String projectId, String subjectId, String sourceId, SourceTypeDTO sourceType) { - // get the last document sorted by timeEnd - try (MongoCursor cursor = MongoHelper.findDocumentBySource( - MongoHelper.getCollection(this.mongoClient, - sourceType.getSourceStatisticsMonitorTopic()), - projectId, subjectId, sourceId, VALUE + "." + END, DESCENDING, 1)) { + MongoCollection collection = MongoHelper.getCollection( + this.mongoClient, sourceType.getSourceStatisticsMonitorTopic()); + + try (MongoCursor cursor = MongoHelper.findDocumentBySource(collection, + projectId, subjectId, sourceId, null, ASCENDING, null)) { TimeFrame timeFrame = null; while (cursor.hasNext()) { diff --git a/src/main/java/org/radarcns/service/SourceService.java b/src/main/java/org/radarcns/service/SourceService.java index 4c9d3008..f82ebf19 100644 --- a/src/main/java/org/radarcns/service/SourceService.java +++ b/src/main/java/org/radarcns/service/SourceService.java @@ -57,8 +57,9 @@ public SourceService(SourceMonitorService sourceMonitorService, SourceCatalog so */ private List buildSourcesFromMinimal(String projectId, String subjectId, Collection sources) { - return sources.stream().map(p -> buildSource(projectId, subjectId, p)).collect(Collectors - .toList()); + return sources.stream() + .map(p -> buildSource(projectId, subjectId, p)) + .collect(Collectors.toList()); } /** @@ -92,6 +93,7 @@ private Source buildSource(String projectId, String subjectId, .sourceId(source.getSourceId().toString()) .assigned(source.isAssigned()) .sourceName(source.getSourceName()) + .sourceTypeId(source.getSourceTypeId()) .sourceTypeCatalogVersion(source.getSourceTypeCatalogVersion()) .sourceTypeProducer(source.getSourceTypeProducer()) .sourceTypeModel(source.getSourceTypeModel()) @@ -131,6 +133,7 @@ private Source buildSource(String projectId, String subjectId, .sourceId(source.getSourceId()) .assigned(source.getAssigned()) .sourceName(source.getSourceName()) + .sourceTypeId(sourceType.getId()) .sourceTypeCatalogVersion(sourceType.getCatalogVersion()) .sourceTypeProducer(sourceType.getProducer()) .sourceTypeModel(sourceType.getModel()) diff --git a/src/main/java/org/radarcns/service/SourceStatusMonitorService.java b/src/main/java/org/radarcns/service/SourceStatusMonitorService.java new file mode 100644 index 00000000..05d6c8f4 --- /dev/null +++ b/src/main/java/org/radarcns/service/SourceStatusMonitorService.java @@ -0,0 +1,104 @@ +/* + * Copyright 2016 King's College London and The Hyve + * + * 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.radarcns.service; + +import static org.radarcns.domain.restapi.header.MonitorHeader.MonitorCategory.PASSIVE; +import static org.radarcns.domain.restapi.header.MonitorHeader.MonitorCategory.QUESTIONNAIRE; + +import com.mongodb.MongoClient; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; +import javax.inject.Inject; +import org.radarcns.domain.managementportal.SourceDTO; +import org.radarcns.domain.restapi.header.MonitorHeader; +import org.radarcns.domain.restapi.monitor.ApplicationStatus; +import org.radarcns.domain.restapi.monitor.MonitorData; +import org.radarcns.listener.managementportal.ManagementPortalClient; +import org.radarcns.mongo.data.monitor.application.ApplicationStatusRecordCounter; +import org.radarcns.mongo.data.monitor.application.ApplicationStatusServerStatus; +import org.radarcns.mongo.data.monitor.application.ApplicationStatusUpTime; +import org.radarcns.mongo.data.monitor.application.MongoApplicationStatusWrapper; +import org.radarcns.mongo.data.monitor.questionnaire.QuestionnaireCompletionLogWrapper; + +/** + * Data Access Object for Source Status values. + */ +public class SourceStatusMonitorService { + + + private final List dataAccessObjects; + + private QuestionnaireCompletionLogWrapper questionnaireCompletionLogWrapper; + + private final ManagementPortalClient managementPortalClient; + + /** + * Default constructor. Initiates all the delegate classes to compute Source Status. + */ + @Inject + public SourceStatusMonitorService(ManagementPortalClient managementPortalClient) { + this.managementPortalClient = managementPortalClient; + dataAccessObjects = new LinkedList<>(); + dataAccessObjects.add(new ApplicationStatusUpTime()); + dataAccessObjects.add(new ApplicationStatusRecordCounter()); + dataAccessObjects.add(new ApplicationStatusServerStatus()); + + this.questionnaireCompletionLogWrapper = new QuestionnaireCompletionLogWrapper(); + } + + /** + * Computes the Source Status realign on different collection. + * + * @param projectName of the subject + * @param subjectId identifier + * @param sourceId identifier + * @param client is the MongoDb client + * @return {@code MonitorData} representing the status of the related source + */ + public MonitorData getStatus(String projectName, String subjectId, String sourceId, + MongoClient client) throws IOException { + + MonitorHeader header = (MonitorHeader) new MonitorHeader() + .projectId(projectName) + .subjectId(subjectId) + .sourceId(sourceId); + SourceDTO source = managementPortalClient.getSource(sourceId); + if (source.getSourceType() != null) { + header.sourceType(source.getSourceType().getSourceTypeIdentifier().toString()); + if (source.getSourceType().getModel().contains("pRMT") || source.getSourceType() + .getSourceTypeScope().equals("MONITOR")) { + ApplicationStatus app = new ApplicationStatus(); + for (MongoApplicationStatusWrapper dataAccessObject : dataAccessObjects) { + app = dataAccessObject + .valueByProjectSubjectSource(projectName, subjectId, sourceId, app, + client); + } + header.monitorCategory(PASSIVE); + return new MonitorData().header(header).data(app); + } + + if (source.getSourceType().getModel().contains("aRMT-App")) { + header.monitorCategory(QUESTIONNAIRE); + return new MonitorData().header(header).data(questionnaireCompletionLogWrapper + .valueByProjectSubjectSource(projectName, subjectId, sourceId, client)); + } + + } + return new MonitorData().header(header); + } +} \ No newline at end of file diff --git a/src/main/java/org/radarcns/service/SubjectService.java b/src/main/java/org/radarcns/service/SubjectService.java index eb16b58e..4b45bb14 100644 --- a/src/main/java/org/radarcns/service/SubjectService.java +++ b/src/main/java/org/radarcns/service/SubjectService.java @@ -1,16 +1,19 @@ package org.radarcns.service; +import static org.radarcns.util.ThrowingFunction.tryOrRethrow; + import java.io.IOException; import java.time.Instant; +import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; import javax.inject.Inject; -import javax.ws.rs.BadRequestException; import javax.ws.rs.NotFoundException; import org.radarcns.domain.managementportal.SubjectDTO; import org.radarcns.domain.restapi.Source; import org.radarcns.domain.restapi.Subject; +import org.radarcns.domain.restapi.header.TimeFrame; import org.radarcns.listener.managementportal.ManagementPortalClient; import org.radarcns.webapp.exception.BadGatewayException; import org.slf4j.Logger; @@ -42,20 +45,19 @@ public SubjectService(ManagementPortalClient managementPortalClient, * * @param subjectId of subject * @param sourceId of source - * @return {@code true} if available. * @throws IOException when unable to process the request. */ - public boolean checkSourceAssignedToSubject(String subjectId, String sourceId) + public void checkSourceAssignedToSubject(String subjectId, String sourceId) throws IOException { SubjectDTO subject = managementPortalClient.getSubject(subjectId); - if (subject.getSources().stream().filter(p -> p.getSourceId().toString().equals(sourceId)) - .collect(Collectors.toList()).isEmpty()) { + if (subject.getSources().stream() + .map(s -> s.getSourceId().toString()) + .noneMatch(sourceId::equals)) { LOGGER.error("Cannot find source-id " + sourceId + "for subject" + subject.getId()); - throw new BadRequestException( - "Source-id " + sourceId + " is not available for subject " + throw new NotFoundException( + "Source-id " + sourceId + " is not found for subject " + subject.getId()); } - return true; } private Subject buildSubject(SubjectDTO subject) @@ -73,10 +75,11 @@ private Subject buildSubject(SubjectDTO subject) private Instant getLastSeenForSubject(List sources) { return sources.stream() - .map(s -> s.getEffectiveTimeFrame() != null - ? s.getEffectiveTimeFrame().getEndDateTime() : null) + .map(Source::getEffectiveTimeFrame) + .filter(Objects::nonNull) + .map(TimeFrame::getEndDateTime) .filter(Objects::nonNull) - .reduce((i1, i2) -> i1.isAfter(i2) ? i1 : i2) + .max(Comparator.naturalOrder()) .orElse(null); } @@ -93,13 +96,8 @@ public List getAllSubjectsFromProject(String projectName) // returns NotFound if a project is not available this.managementPortalClient.getProject(projectName); return this.managementPortalClient.getAllSubjectsFromProject(projectName).stream() - .map(s -> { - try { - return buildSubject(s); - } catch (IOException exe) { - throw new BadGatewayException(exe); - } - }).collect(Collectors.toList()); + .map(tryOrRethrow(this::buildSubject, BadGatewayException::new)) + .collect(Collectors.toList()); } public Subject getSubjectBySubjectId(String projectName, String subjectId) diff --git a/src/main/java/org/radarcns/util/RadarConverter.java b/src/main/java/org/radarcns/util/RadarConverter.java index 2ee86cdc..b44d7c66 100644 --- a/src/main/java/org/radarcns/util/RadarConverter.java +++ b/src/main/java/org/radarcns/util/RadarConverter.java @@ -29,16 +29,13 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import java.math.BigDecimal; import java.math.RoundingMode; -import java.time.DateTimeException; import java.time.Duration; import java.time.Instant; -import java.time.format.DateTimeParseException; import java.time.temporal.Temporal; -import java.time.temporal.TemporalAmount; +import java.util.AbstractMap; import java.util.Collection; -import java.util.Date; import java.util.Locale; -import java.util.concurrent.TimeUnit; +import java.util.Map; import org.radarcns.domain.restapi.ServerStatus; import org.radarcns.domain.restapi.TimeWindow; import org.radarcns.domain.restapi.header.DescriptiveStatistic; @@ -90,39 +87,6 @@ private RadarConverter() { // utility class } - /** - * Converts {@code long} to ISO8601 {@code String}. - * - * @param value input {@code long} that has to be converted - * @return a {@code String} representing a date in ISO8601 format - */ - @SuppressWarnings("checkstyle:AbbreviationAsWordInName") - public static String getISO8601(long value) throws DateTimeException { - return Instant.ofEpochMilli(value).toString(); - } - - /** - * Converts {@link Date} to ISO8601 {@code String}. - * - * @param value input {@link Date} that has to be converted - * @return a {@code String} representing a date in ISO8601 format - */ - @SuppressWarnings("checkstyle:AbbreviationAsWordInName") - public static String getISO8601(Date value) { - return value.toInstant().toString(); - } - - /** - * Converts ISO8601 {@code String} to java {@link Date}. - * - * @param value input {@code String} formatted in ISO8601 - * @return {@link Date} object according to the given input - */ - @SuppressWarnings("checkstyle:AbbreviationAsWordInName") - public static Date getISO8601(String value) throws DateTimeParseException { - return Date.from(Instant.parse(value)); - } - /** * Converts a {@code MongoHelper.Stat} to {@code DescriptiveStatistic}. **/ @@ -241,57 +205,7 @@ public static String getSourceType(String value) { * @return the number of expected messages */ public static double getExpectedMessages(TimeWindow timeWindow, double frequency) { - return getSecond(timeWindow) * frequency; - } - - /** - * Converts a time window to seconds. - * - * @param timeWindow time window that has to be converted in seconds - * @return a {@link Long} representing the amount of seconds - */ - public static long getSecond(TimeWindow timeWindow) { - switch (timeWindow) { - case TEN_SECOND: - return TimeUnit.SECONDS.toSeconds(10); - case ONE_MIN: - return TimeUnit.MINUTES.toSeconds(1); - case TEN_MIN: - return TimeUnit.MINUTES.toSeconds(10); - case ONE_HOUR: - return TimeUnit.HOURS.toSeconds(1); - case ONE_DAY: - return TimeUnit.DAYS.toSeconds(1); - case ONE_WEEK: - return TimeUnit.DAYS.toSeconds(7); - default: - throw new IllegalArgumentException(timeWindow + " is not yet supported"); - } - } - - /** - * Converts a time window to seconds. - * - * @param timeWindow time window that has to be converted in seconds - * @return a {@link Long} representing the amount of seconds - */ - public static TemporalAmount getDuration(TimeWindow timeWindow) { - switch (timeWindow) { - case TEN_SECOND: - return Duration.ofSeconds(10); - case ONE_MIN: - return Duration.ofMinutes(1); - case TEN_MIN: - return Duration.ofMinutes(10); - case ONE_HOUR: - return Duration.ofHours(1); - case ONE_DAY: - return Duration.ofDays(1); - case ONE_WEEK: - return Duration.ofDays(7); - default: - throw new IllegalArgumentException(timeWindow + " is not yet supported"); - } + return TimeScale.getSeconds(timeWindow) * frequency; } /** @@ -328,4 +242,8 @@ public static ObjectReader readerForCollection(Class collC public static boolean isThresholdPassed(Temporal time, Duration duration) { return Duration.between(time, Instant.now()).compareTo(duration) > 0; } + + public static Map.Entry pair(K key, V value) { + return new AbstractMap.SimpleImmutableEntry<>(key, value); + } } diff --git a/src/main/java/org/radarcns/util/ThrowingFunction.java b/src/main/java/org/radarcns/util/ThrowingFunction.java new file mode 100644 index 00000000..bdac1e63 --- /dev/null +++ b/src/main/java/org/radarcns/util/ThrowingFunction.java @@ -0,0 +1,26 @@ +package org.radarcns.util; + +import java.util.function.Function; + +/** Function that may throw an unchecked exception. */ +@FunctionalInterface +public interface ThrowingFunction { + R apply(T value) throws Exception; + + /** + * Run a function. If it throws any exception, apply toException to map it to a runtime + * exception and throw that instead. RuntimeException objects will also be mapped by + * toException. + */ + static Function tryOrRethrow( + ThrowingFunction function, + Function toException) { + return t -> { + try { + return function.apply(t); + } catch (Exception e) { + throw toException.apply(e); + } + }; + } +} diff --git a/src/main/java/org/radarcns/util/TimeScale.java b/src/main/java/org/radarcns/util/TimeScale.java new file mode 100644 index 00000000..877588d0 --- /dev/null +++ b/src/main/java/org/radarcns/util/TimeScale.java @@ -0,0 +1,118 @@ +package org.radarcns.util; + +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.TemporalAmount; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; +import org.radarcns.domain.restapi.TimeWindow; +import org.radarcns.domain.restapi.header.TimeFrame; + +/** + * TimeScale containing temporal extent and resolution. + */ +public class TimeScale { + private final TimeFrame timeFrame; + private final TimeWindow timeWindow; + + public TimeScale(TimeFrame timeFrame, TimeWindow timeWindow) { + this.timeFrame = timeFrame; + this.timeWindow = timeWindow; + } + + public TimeFrame getTimeFrame() { + return timeFrame; + } + + public TimeWindow getTimeWindow() { + return timeWindow; + } + + public long getNumberOfWindows() { + return (long) Math.floor(timeFrame.getDuration().getSeconds() + / (double) getWindowSeconds()); + } + + /** + * All time intervals covered by this TimeScale as a stream. The time intervals are returned + * in order. + * @return stream of time frames. + */ + public Stream streamIntervals() { + TemporalAmount window = getWindowDuration(); + return Stream.iterate( + windowTimeFrame(timeFrame.getStartDateTime(), window), + t -> windowTimeFrame(t.getEndDateTime(), window)) + .limit(getNumberOfWindows()); + } + + private static TimeFrame windowTimeFrame(Instant start, TemporalAmount duration) { + return new TimeFrame(start, start.plus(duration)); + } + + public long getWindowSeconds() { + return getSeconds(timeWindow); + } + + public TemporalAmount getWindowDuration() { + return getDuration(timeWindow); + } + + @Override + public String toString() { + return "TimeScale{" + "timeFrame=" + timeFrame + + ", timeWindow=" + timeWindow + + ", numberOfWindows=" + getNumberOfWindows() + + '}'; + } + + /** + * Converts a time window to seconds. + * + * @param timeWindow time window that has to be converted in seconds + * @return a {@link Long} representing the amount of seconds + */ + public static long getSeconds(TimeWindow timeWindow) { + switch (timeWindow) { + case TEN_SECOND: + return TimeUnit.SECONDS.toSeconds(10); + case ONE_MIN: + return TimeUnit.MINUTES.toSeconds(1); + case TEN_MIN: + return TimeUnit.MINUTES.toSeconds(10); + case ONE_HOUR: + return TimeUnit.HOURS.toSeconds(1); + case ONE_DAY: + return TimeUnit.DAYS.toSeconds(1); + case ONE_WEEK: + return TimeUnit.DAYS.toSeconds(7); + default: + throw new IllegalArgumentException(timeWindow + " is not yet supported"); + } + } + + /** + * Converts a time window to seconds. + * + * @param timeWindow time window that has to be converted in seconds + * @return a {@link Long} representing the amount of seconds + */ + public static TemporalAmount getDuration(TimeWindow timeWindow) { + switch (timeWindow) { + case TEN_SECOND: + return Duration.ofSeconds(10); + case ONE_MIN: + return Duration.ofMinutes(1); + case TEN_MIN: + return Duration.ofMinutes(10); + case ONE_HOUR: + return Duration.ofHours(1); + case ONE_DAY: + return Duration.ofDays(1); + case ONE_WEEK: + return Duration.ofDays(7); + default: + throw new IllegalArgumentException(timeWindow + " is not yet supported"); + } + } +} diff --git a/src/main/java/org/radarcns/webapp/RadarApplication.java b/src/main/java/org/radarcns/webapp/RadarApplication.java index 7aa3ab57..4a85bfa1 100644 --- a/src/main/java/org/radarcns/webapp/RadarApplication.java +++ b/src/main/java/org/radarcns/webapp/RadarApplication.java @@ -9,13 +9,14 @@ import org.radarcns.listener.HttpClientFactory; import org.radarcns.listener.MongoFactory; import org.radarcns.listener.managementportal.ManagementPortalClient; -import org.radarcns.service.ApplicationStatusMonitorService; import org.radarcns.service.DataSetService; import org.radarcns.service.SourceMonitorService; import org.radarcns.service.SourceService; +import org.radarcns.service.SourceStatusMonitorService; import org.radarcns.service.SubjectService; import org.radarcns.webapp.filter.AuthenticationFilter; import org.radarcns.webapp.filter.AuthorizationFeature; +import org.radarcns.webapp.param.TimeScaleParser; /** * Radar application configuration. @@ -40,6 +41,9 @@ public RadarApplication() { @SuppressWarnings("RedundantToBinding") @Override protected void configure() { + bind(new TimeScaleParser()) + .to(TimeScaleParser.class); + bindFactory(HttpClientFactory.class) .to(OkHttpClient.class) .in(Singleton.class); @@ -72,8 +76,8 @@ protected void configure() { .to(DataSetService.class) .in(Singleton.class); - bind(ApplicationStatusMonitorService.class) - .to(ApplicationStatusMonitorService.class) + bind(SourceStatusMonitorService.class) + .to(SourceStatusMonitorService.class) .in(Singleton.class); } }); diff --git a/src/main/java/org/radarcns/webapp/filter/AuthenticationFilter.java b/src/main/java/org/radarcns/webapp/filter/AuthenticationFilter.java index b60626f5..e99e6fdc 100644 --- a/src/main/java/org/radarcns/webapp/filter/AuthenticationFilter.java +++ b/src/main/java/org/radarcns/webapp/filter/AuthenticationFilter.java @@ -2,6 +2,7 @@ import java.net.URI; import java.net.URISyntaxException; +import java.util.Collections; import java.util.Locale; import javax.annotation.Priority; import javax.inject.Singleton; @@ -12,9 +13,9 @@ import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.SecurityContext; import javax.ws.rs.ext.Provider; + import org.radarcns.auth.RadarSecurityContext; import org.radarcns.auth.authentication.TokenValidator; -import org.radarcns.auth.config.ServerConfig; import org.radarcns.auth.config.YamlServerConfig; import org.radarcns.auth.exception.TokenValidationException; import org.radarcns.auth.token.RadarToken; @@ -37,24 +38,30 @@ public class AuthenticationFilter implements ContainerRequestFilter { public static final String REALM_VALUE = "api"; public static final String ERROR = "error"; public static final String ERROR_DESCRIPTION = "error_description"; - private final TokenValidator validator; + private TokenValidator validator; /** Constructs a filter with a fixed validator. */ public AuthenticationFilter() { - ServerConfig config = null; - String mpUrlString = Properties.getApiConfig().getManagementPortalConfig() - .getManagementPortalUrl().toString(); - if (mpUrlString != null) { - try { - YamlServerConfig cfg = new YamlServerConfig(); - cfg.setResourceName("res_RestApi"); - cfg.setPublicKeyEndpoint(new URI(mpUrlString + "oauth/token_key")); - config = cfg; - } catch (URISyntaxException exc) { - logger.error("Failed to load Management Portal URL " + mpUrlString, exc); + + try { + validator = new TokenValidator(); + logger.debug("Failed to create default TokenValidator"); + } catch (RuntimeException ex) { + String mpUrlString = + Properties.getApiConfig().getManagementPortalConfig().getManagementPortalUrl() + .toString(); + if (mpUrlString != null) { + try { + YamlServerConfig cfg = new YamlServerConfig(); + cfg.setResourceName("res_RestApi"); + cfg.setPublicKeyEndpoints( + Collections.singletonList(new URI(mpUrlString + "oauth/token_key"))); + validator = new TokenValidator(cfg); + } catch (URISyntaxException exc) { + logger.error("Failed to load Management Portal URL " + mpUrlString, exc); + } } } - validator = config == null ? new TokenValidator() : new TokenValidator(config); } @Override diff --git a/src/main/java/org/radarcns/webapp/param/TimeScaleParser.java b/src/main/java/org/radarcns/webapp/param/TimeScaleParser.java new file mode 100644 index 00000000..85ec6422 --- /dev/null +++ b/src/main/java/org/radarcns/webapp/param/TimeScaleParser.java @@ -0,0 +1,121 @@ +package org.radarcns.webapp.param; + +import static org.radarcns.domain.restapi.TimeWindow.ONE_DAY; +import static org.radarcns.domain.restapi.TimeWindow.ONE_HOUR; +import static org.radarcns.domain.restapi.TimeWindow.ONE_MIN; +import static org.radarcns.domain.restapi.TimeWindow.ONE_WEEK; +import static org.radarcns.domain.restapi.TimeWindow.TEN_MIN; +import static org.radarcns.domain.restapi.TimeWindow.TEN_SECOND; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.ws.rs.BadRequestException; +import org.radarcns.domain.restapi.TimeWindow; +import org.radarcns.domain.restapi.header.TimeFrame; +import org.radarcns.util.RadarConverter; +import org.radarcns.util.TimeScale; + +/** + * Parser for temporal query parameters, calculating new values when necessary. + *

+ * If the startTime is not provided startTime will be calculated based on default number of windows + * and given timeWindow. + * If no timeWindow is provided, a best fitting timeWindow will be calculated. + * If none of the parameters are provided, API will return data for a period of 1 year with + * ONE_WEEK of timeWindow (~52 records) from current timestamp. + *

+ */ +public class TimeScaleParser { + public static final int DEFAULT_NUMBER_OF_WINDOWS = 100; + public static final int MAX_NUMBER_OF_WINDOWS = 1000; + + private static final List> TIME_WINDOW_LOG = Stream + .of(TEN_SECOND, ONE_MIN, TEN_MIN, ONE_HOUR, ONE_DAY, ONE_WEEK) + .map(w -> RadarConverter.pair(w, Math.log(TimeScale.getSeconds(w)))) + .collect(Collectors.toList()); + + private final int defaultNumberOfWindows; + private final int maxNumberOfWindows; + + public TimeScaleParser() { + this(DEFAULT_NUMBER_OF_WINDOWS, MAX_NUMBER_OF_WINDOWS); + } + + public TimeScaleParser(int defaultNumberOfWindows, int maxNumberOfWindows) { + this.defaultNumberOfWindows = defaultNumberOfWindows; + this.maxNumberOfWindows = maxNumberOfWindows; + } + + /** + * Parse a set of start and end time and a time window. Use default values where necessary. + * The end time defaults to the current moment. If not provided, the other parameters will be + * deduced to get a useful set of time windows back. If no values are provided, the default time + * frame is one year of one week intervals. + * + * @param startTimeParam parameter possibly containing startTime. + * @param endTimeParam parameter possibly containing endTime. + * @param timeWindow timeWindow of the query. + * @throws BadRequestException if the start time is after the end time or if the number of + * windows requested exceeds the maximum number of windows. + */ + public TimeScale parse(InstantParam startTimeParam, InstantParam endTimeParam, + final TimeWindow timeWindow) { + + Instant startTime = startTimeParam != null ? startTimeParam.getValue() : null; + Instant endTime = endTimeParam != null ? endTimeParam.getValue() : Instant.now(); + + if (startTime != null && startTime.isAfter(endTime)) { + throw new BadRequestException("startTime " + startTime + + " should not be after endTime " + endTime); + } + + TimeScale timeScale = parseWithDefaults(startTime, endTime, timeWindow); + + if (timeScale.getNumberOfWindows() > maxNumberOfWindows) { + throw new BadRequestException("Cannot request more than " + maxNumberOfWindows + + " time windows using " + timeScale + "."); + } + + return timeScale; + } + + private TimeScale parseWithDefaults(@Nullable Instant startTime, @Nonnull Instant endTime, + @Nullable TimeWindow timeWindow) { + + TimeFrame timeFrame; + if (startTime != null) { + timeFrame = new TimeFrame(startTime, endTime); + } else if (timeWindow != null) { + long totalSeconds = TimeScale.getSeconds(timeWindow) * defaultNumberOfWindows; + timeFrame = new TimeFrame(endTime.minus(totalSeconds, ChronoUnit.SECONDS), endTime); + } else { + timeFrame = new TimeFrame(endTime.minus(365, ChronoUnit.DAYS), endTime); + } + + return new TimeScale(timeFrame, + timeWindow != null ? timeWindow : getFittingTimeWindow(timeFrame)); + } + + /** + * Get the time window that closest matches given time frame. + * + * @param timeFrame time frame to compute time window for + * @return closest match with given time frame. + */ + private TimeWindow getFittingTimeWindow(TimeFrame timeFrame) { + double logSeconds = Math.log(timeFrame.getDuration().getSeconds() / defaultNumberOfWindows); + return TIME_WINDOW_LOG.stream() + .map(e -> RadarConverter.pair(e.getKey(), Math.abs(logSeconds - e.getValue()))) + .min(Comparator.comparing(Entry::getValue)) + .orElseThrow(() -> new AssertionError("No close time window found")) + .getKey(); + } +} diff --git a/src/main/java/org/radarcns/webapp/resource/AggregatedDataPointsEndPoint.java b/src/main/java/org/radarcns/webapp/resource/AggregatedDataPointsEndPoint.java index 50300f0f..723c5d24 100644 --- a/src/main/java/org/radarcns/webapp/resource/AggregatedDataPointsEndPoint.java +++ b/src/main/java/org/radarcns/webapp/resource/AggregatedDataPointsEndPoint.java @@ -3,8 +3,8 @@ import static javax.ws.rs.core.MediaType.APPLICATION_JSON; import static org.radarcns.auth.authorization.Permission.Entity.MEASUREMENT; import static org.radarcns.auth.authorization.Permission.Operation.READ; -import static org.radarcns.domain.restapi.TimeWindow.ONE_WEEK; import static org.radarcns.service.DataSetService.emptyAggregatedData; +import static org.radarcns.webapp.param.TimeScaleParser.MAX_NUMBER_OF_WINDOWS; import static org.radarcns.webapp.resource.BasePath.AGGREGATE; import static org.radarcns.webapp.resource.BasePath.DISTINCT; import static org.radarcns.webapp.resource.Parameter.END; @@ -16,9 +16,6 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import java.io.IOException; -import java.time.Instant; -import java.time.Period; -import java.time.temporal.ChronoUnit; import javax.inject.Inject; import javax.ws.rs.Consumes; import javax.ws.rs.POST; @@ -29,13 +26,13 @@ import org.radarcns.auth.NeedsPermissionOnSubject; import org.radarcns.domain.restapi.TimeWindow; import org.radarcns.domain.restapi.dataset.AggregatedDataPoints; -import org.radarcns.domain.restapi.header.TimeFrame; import org.radarcns.listener.managementportal.ManagementPortalClient; import org.radarcns.service.DataSetService; -import org.radarcns.util.RadarConverter; +import org.radarcns.util.TimeScale; import org.radarcns.webapp.filter.Authenticated; import org.radarcns.webapp.param.DataAggregateParam; import org.radarcns.webapp.param.InstantParam; +import org.radarcns.webapp.param.TimeScaleParser; import org.radarcns.webapp.validation.Alphanumeric; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,8 +42,6 @@ @Path("/" + AGGREGATE) public class AggregatedDataPointsEndPoint { - private static final int DEFAULT_NUMBER_OF_WINDOWS = 100; - private static final Logger LOGGER = LoggerFactory .getLogger(AggregatedDataPointsEndPoint.class); @@ -56,25 +51,36 @@ public class AggregatedDataPointsEndPoint { @Inject private DataSetService dataSetService; + @Inject + private TimeScaleParser timeScaleParser; + //--------------------------------------------------------------------------------------------// // Aggregated Data Points API functions // //--------------------------------------------------------------------------------------------// /** - * JSON function that returns aggregated volumes of source-data requested. + * Aggregated volumes of source-data as requested by the user. */ @POST @Produces(APPLICATION_JSON) @Consumes(APPLICATION_JSON) @Path("/{" + PROJECT_NAME + "}/{" + SUBJECT_ID + "}/" + DISTINCT) - @Operation(summary = "Returns a Dataset object formatted in JSON.", + @Operation(summary = "Performs data aggregation on request.", description = "Each collected sample is aggregated to provide near real-time " + "statistical results. This end-point returns the latest available record " + "for the stat for the given projectID, subjectID, sourceID and SourceDataName" - + " Data can be queried using different time-frame resolutions.") + + " Data can be queried using different time-frame resolutions. If the " + + "startTime is not provided startTime will be calculated based on default " + + "number of windows and given timeWindow. If no timeWindow is provided, a " + + "best fitting timeWindow will be calculated. If none of the parameters are " + + "provided, API will return data for a period of 1 year with ONE_WEEK of " + + "timeWindow (~52 records) from current timestamp.") @ApiResponse(responseCode = "500", description = "An error occurs while executing") @ApiResponse(responseCode = "200", description = "Returns a dataset object containing latest " + "available record for the given inputs") + @ApiResponse(responseCode = "400", description = "startTime should not be after endTime in " + + "query and the maximum number of time windows should not exceed " + + MAX_NUMBER_OF_WINDOWS + ".") @ApiResponse(responseCode = "401", description = "Access denied error occurred") @ApiResponse(responseCode = "403", description = "Not Authorised error occurred") @ApiResponse(responseCode = "404", description = "Subject not found.") @@ -88,39 +94,15 @@ public AggregatedDataPoints getDistinctDataPoints(DataAggregateParam aggregatePa mpClient.checkSubjectInProject(projectName, subjectId); - // Don't request future data - Instant endTime = end.getValue(); - if (endTime == null) { - endTime = Instant.now(); - } - - TimeWindow timeWindow = interval; - Instant startTime = start.getValue(); - TimeFrame timeFrame = new TimeFrame(startTime, endTime); + TimeScale timeScale = timeScaleParser.parse(start, end, interval); - if (startTime != null && startTime.isAfter(endTime)) { - // don't mix up time frame order - timeFrame.setStartDateTime(endTime); - } else if (startTime == null && timeWindow == null) { - // default settings, 1 year with 1 week intervals - timeFrame.setStartDateTime(endTime.minus(Period.ofYears(1))); - timeWindow = ONE_WEEK; - } else if (startTime == null) { - // use a fixed number of windows. - timeFrame.setStartDateTime(endTime.minus( - RadarConverter.getSecond(timeWindow) * DEFAULT_NUMBER_OF_WINDOWS, - ChronoUnit.SECONDS)); - } else if (timeWindow == null) { - // use the fixed time frame with a time frame close to the number of windows. - timeWindow = dataSetService.getFittingTimeWindow(timeFrame, DEFAULT_NUMBER_OF_WINDOWS); - } + AggregatedDataPoints dataSet = dataSetService.getDistinctData( + projectName, subjectId, aggregateParam.getSources(), timeScale); - AggregatedDataPoints dataSet = this.dataSetService.getDistinctData(projectName, subjectId, - aggregateParam.getSources(), timeWindow, timeFrame); - if (dataSet == null || dataSet.getDataset().isEmpty()) { + if (dataSet.getDataset().isEmpty()) { LOGGER.debug("No aggregated data available for the subject {} with source", subjectId); - return emptyAggregatedData(projectName, subjectId, timeWindow, - new TimeFrame(startTime, endTime), aggregateParam.getSources()); + return emptyAggregatedData(projectName, subjectId, timeScale, + aggregateParam.getSources()); } return dataSet; } diff --git a/src/main/java/org/radarcns/webapp/resource/AppStatusEndPoint.java b/src/main/java/org/radarcns/webapp/resource/AppStatusEndPoint.java index 886547d9..db422d0e 100644 --- a/src/main/java/org/radarcns/webapp/resource/AppStatusEndPoint.java +++ b/src/main/java/org/radarcns/webapp/resource/AppStatusEndPoint.java @@ -35,10 +35,9 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import org.radarcns.auth.NeedsPermissionOnSubject; -import org.radarcns.domain.restapi.Application; -import org.radarcns.domain.restapi.ServerStatus; +import org.radarcns.domain.restapi.monitor.MonitorData; import org.radarcns.listener.managementportal.ManagementPortalClient; -import org.radarcns.service.ApplicationStatusMonitorService; +import org.radarcns.service.SourceStatusMonitorService; import org.radarcns.service.SubjectService; import org.radarcns.webapp.filter.Authenticated; import org.radarcns.webapp.validation.Alphanumeric; @@ -60,7 +59,7 @@ public class AppStatusEndPoint { private SubjectService subjectService; @Inject - private ApplicationStatusMonitorService applicationStatusMonitorService; + private SourceStatusMonitorService sourceStatusMonitorService; //--------------------------------------------------------------------------------------------// // APPLICATION_STATUS FUNCTIONS // //--------------------------------------------------------------------------------------------// @@ -78,22 +77,15 @@ public class AppStatusEndPoint { "Return a application object containing last received status") @ApiResponse(responseCode = "401", description = "Access denied error occurred") @ApiResponse(responseCode = "403", description = "Not Authorised error occurred") - @ApiResponse(responseCode = "404", description = "Subject not found.") + @ApiResponse(responseCode = "404", description = "project, subject or source not found.") @NeedsPermissionOnSubject(entity = SOURCE, operation = READ) - public Application getLastReceivedAppStatusJson( + public MonitorData getLastReceivedAppStatusJson( @Alphanumeric @PathParam(PROJECT_NAME) String projectName, @Alphanumeric @PathParam(SUBJECT_ID) String subjectId, @Alphanumeric @PathParam(SOURCE_ID) String sourceId) throws IOException { mpClient.checkSubjectInProject(projectName, subjectId); - Application application = null; - if (subjectService.checkSourceAssignedToSubject(subjectId, sourceId)) { - application = applicationStatusMonitorService.getStatus(projectName, - subjectId, sourceId, mongoClient); - } - if (application == null) { - return new Application(null, 0d, ServerStatus.UNKNOWN, -1, -1, -1); + subjectService.checkSourceAssignedToSubject(subjectId, sourceId); - } - return application; + return sourceStatusMonitorService.getStatus(projectName, subjectId, sourceId, mongoClient); } } diff --git a/src/main/java/org/radarcns/webapp/resource/DataSetEndPoint.java b/src/main/java/org/radarcns/webapp/resource/DataSetEndPoint.java index 4c500c8b..02bbb13d 100644 --- a/src/main/java/org/radarcns/webapp/resource/DataSetEndPoint.java +++ b/src/main/java/org/radarcns/webapp/resource/DataSetEndPoint.java @@ -21,6 +21,7 @@ import static org.radarcns.auth.authorization.Permission.Operation.READ; import static org.radarcns.domain.restapi.TimeWindow.TEN_SECOND; import static org.radarcns.service.DataSetService.emptyDataset; +import static org.radarcns.webapp.param.TimeScaleParser.MAX_NUMBER_OF_WINDOWS; import static org.radarcns.webapp.resource.BasePath.AVRO_BINARY; import static org.radarcns.webapp.resource.BasePath.DATA; import static org.radarcns.webapp.resource.BasePath.LATEST; @@ -37,7 +38,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import java.io.IOException; import java.time.Instant; -import java.util.Date; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; @@ -51,9 +51,10 @@ import org.radarcns.domain.restapi.header.TimeFrame; import org.radarcns.listener.managementportal.ManagementPortalClient; import org.radarcns.service.DataSetService; -import org.radarcns.util.RadarConverter; +import org.radarcns.util.TimeScale; import org.radarcns.webapp.filter.Authenticated; import org.radarcns.webapp.param.InstantParam; +import org.radarcns.webapp.param.TimeScaleParser; import org.radarcns.webapp.validation.Alphanumeric; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -73,12 +74,11 @@ public class DataSetEndPoint { @Inject private DataSetService dataSetService; - //--------------------------------------------------------------------------------------------// - // LATEST RECORD FUNCTION // - //--------------------------------------------------------------------------------------------// + @Inject + private TimeScaleParser timeScaleParser; /** - * JSON function that returns the last seen data value if available. + * Last seen data value if available. */ @GET @Produces({APPLICATION_JSON, AVRO_BINARY}) @@ -117,18 +117,14 @@ public Dataset getLastReceivedSampleJson( LOGGER.debug("No data for the subject {} with source {}", subjectId, sourceId); Instant now = Instant.now(); return emptyDataset(projectName, subjectId, sourceId, sourceDataName, stat, interval, - new TimeFrame(now.minus(RadarConverter.getDuration(timeWindow)), now)); + new TimeFrame(now.minus(TimeScale.getDuration(timeWindow)), now)); } return dataset; } - //--------------------------------------------------------------------------------------------// - // ALL RECORDS FUNCTIONS // - //--------------------------------------------------------------------------------------------// - /** - * JSON function that returns all available records for the given data. + * All available records for the given data. */ @GET @Produces({APPLICATION_JSON, AVRO_BINARY}) @@ -138,10 +134,19 @@ public Dataset getLastReceivedSampleJson( description = "Each collected sample is aggregated to provide near real-time " + "statistical results. This end-point returns the all available record " + "for the stat for the given projectID, subjectID, sourceID and SourceDataName" - + " Data can be queried using different time-frame resolutions.") + + "Data can be queried using different time-frame resolutions. If endTime is" + + "not provided the data will be calculated from current timestamp. If the " + + "startTime is not provided startTime will be calculated based on default " + + "number of windows and given timeWindow. If no timeWindow is provided, a " + + "best fitting timeWindow will be calculated. If none of the parameters are " + + "provided, API will return data for a period of 1 year with ONE_WEEK of " + + "timeWindow (~52 records) from current timestamp.") @ApiResponse(responseCode = "500", description = "An error occurs while executing") @ApiResponse(responseCode = "200", description = "Returns a dataset object containing all " + "available record for the given inputs") + @ApiResponse(responseCode = "400", description = "startTime should not be after endTime in " + + "query and the maximum number of time windows should not exceed " + + MAX_NUMBER_OF_WINDOWS + ".") @ApiResponse(responseCode = "401", description = "Access denied error occurred") @ApiResponse(responseCode = "403", description = "Not Authorised error occurred") @ApiResponse(responseCode = "404", description = "Subject not found.") @@ -158,19 +163,13 @@ public Dataset getSamplesJson( // todo: 404 if given source does not exist. // Note that a source doesn't necessarily need to be linked anymore, as long as it exists // and historical data of it is linked to the given user. - mpClient.getSubject(subjectId); + mpClient.checkSubjectInProject(projectName, subjectId); Dataset dataset; - TimeWindow timeWindow = interval != null ? interval : TEN_SECOND; + TimeScale timeScale = timeScaleParser.parse(start, end, interval); - if (start != null && end != null) { - dataset = dataSetService.getAllRecordsInWindow(projectName, - subjectId, sourceId, sourceDataName, stat, timeWindow, - Date.from(start.getValue()), Date.from(end.getValue())); - } else { - dataset = dataSetService.getAllDataItems(projectName, subjectId, - sourceId, sourceDataName, stat, timeWindow); - } + dataset = dataSetService.getAllRecordsInWindow(projectName, subjectId, sourceId, + sourceDataName, stat, timeScale); if (dataset.getDataset().isEmpty()) { LOGGER.debug("No data for the subject {} with source {}", subjectId, sourceId); @@ -181,6 +180,4 @@ public Dataset getSamplesJson( return dataset; } - - } diff --git a/src/main/resources/openapi-configuration.yaml b/src/main/resources/openapi-configuration.yaml index c00056b2..d65ee3a8 100644 --- a/src/main/resources/openapi-configuration.yaml +++ b/src/main/resources/openapi-configuration.yaml @@ -2,7 +2,7 @@ prettyPrint: true openAPI: info: - version: 0.2.1 + version: 0.3 title: RADAR-CNS Downstream REST APIs license: name: Apache 2.0 diff --git a/src/test/java/org/radarcns/util/RadarConverterTest.java b/src/test/java/org/radarcns/util/RadarConverterTest.java index e1fb7835..c3d27e08 100644 --- a/src/test/java/org/radarcns/util/RadarConverterTest.java +++ b/src/test/java/org/radarcns/util/RadarConverterTest.java @@ -31,19 +31,12 @@ import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.ObjectWriter; import java.io.IOException; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.time.Duration; import java.time.Instant; import java.time.format.DateTimeParseException; import java.time.temporal.Temporal; -import java.util.Calendar; -import java.util.Date; -import java.util.TimeZone; import org.junit.Test; import org.radarcns.domain.restapi.ServerStatus; -import org.radarcns.domain.restapi.TimeWindow; import org.radarcns.mongo.util.MongoHelper.Stat; public class RadarConverterTest { @@ -52,49 +45,6 @@ public class RadarConverterTest { private static final String BIOVOTION = "BIOVOTION"; private static final String EMPATICA = "EMPATICA"; - @Test - @SuppressWarnings("checkstyle:AbbreviationAsWordInName") - public void getISO8601TestFromDate() throws ParseException { - Date date = new Date(); - Calendar calExpected = Calendar.getInstance(); - calExpected.setTime(date); - // we will get UTC time from RadarConverter - calExpected.setTimeZone(TimeZone.getTimeZone("UTC")); - - String dateString = RadarConverter.getISO8601(date); - - DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); - Date dateResult = format.parse(dateString); - Calendar calActual = Calendar.getInstance(); - calActual.setTime(dateResult); - - assertEquals(calExpected.get(Calendar.YEAR), calActual.get(Calendar.YEAR)); - assertEquals(calExpected.get(Calendar.MONTH), calActual.get(Calendar.MONTH)); - assertEquals(calExpected.get(Calendar.DAY_OF_MONTH), calActual.get(Calendar.DAY_OF_MONTH)); - assertEquals(calExpected.get(Calendar.HOUR), calActual.get(Calendar.HOUR)); - assertEquals(calExpected.get(Calendar.MINUTE), calActual.get(Calendar.MINUTE)); - assertEquals(calExpected.get(Calendar.SECOND), calActual.get(Calendar.SECOND)); - } - - @Test - @SuppressWarnings("checkstyle:AbbreviationAsWordInName") - public void getISO8601TestFromString() throws DateTimeParseException { - Date date = RadarConverter.getISO8601("2017-03-05T22:37:59Z"); - - Calendar cal = Calendar.getInstance(); - cal.setTime(date); - cal.setTimeZone(TimeZone.getTimeZone("UTC")); - - assertEquals(2017, cal.get(Calendar.YEAR)); - assertEquals(2, cal.get(Calendar.MONTH)); - assertEquals(5, cal.get(Calendar.DAY_OF_MONTH)); - - assertEquals(10, cal.get(Calendar.HOUR)); - assertEquals(37, cal.get(Calendar.MINUTE)); - assertEquals(59, cal.get(Calendar.SECOND)); - assertEquals(1, cal.get(Calendar.AM_PM)); - } - @Test public void getDescriptiveStatisticTest() throws DateTimeParseException { assertEquals(AVERAGE, RadarConverter.getDescriptiveStatistic(Stat.avg)); @@ -163,16 +113,6 @@ public void getSourceTypeTest() { RadarConverter.getSourceType("PEBBLE")); } - @Test - public void getSecondTest() { - assertEquals(10, RadarConverter.getSecond(TimeWindow.TEN_SECOND), 0); - assertEquals(60, RadarConverter.getSecond(TimeWindow.ONE_MIN), 0); - assertEquals(600, RadarConverter.getSecond(TimeWindow.TEN_MIN), 0); - assertEquals(3600, RadarConverter.getSecond(TimeWindow.ONE_HOUR), 0); - assertEquals(3600 * 24, RadarConverter.getSecond(TimeWindow.ONE_DAY), 0); - assertEquals(3600 * 24 * 7, RadarConverter.getSecond(TimeWindow.ONE_WEEK), 0); - } - @Test public void isThresholdPassed() { Temporal hourAgo = Instant.now().minus(Duration.ofHours(1)); diff --git a/src/test/java/org/radarcns/util/TimeScaleTest.java b/src/test/java/org/radarcns/util/TimeScaleTest.java new file mode 100644 index 00000000..ead8d566 --- /dev/null +++ b/src/test/java/org/radarcns/util/TimeScaleTest.java @@ -0,0 +1,18 @@ +package org.radarcns.util; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.radarcns.domain.restapi.TimeWindow; + +public class TimeScaleTest { + @Test + public void getSecond() { + assertEquals(10, TimeScale.getSeconds(TimeWindow.TEN_SECOND), 0); + assertEquals(60, TimeScale.getSeconds(TimeWindow.ONE_MIN), 0); + assertEquals(600, TimeScale.getSeconds(TimeWindow.TEN_MIN), 0); + assertEquals(3600, TimeScale.getSeconds(TimeWindow.ONE_HOUR), 0); + assertEquals(3600 * 24, TimeScale.getSeconds(TimeWindow.ONE_DAY), 0); + assertEquals(3600 * 24 * 7, TimeScale.getSeconds(TimeWindow.ONE_WEEK), 0); + } +} \ No newline at end of file diff --git a/src/test/java/org/radarcns/webapp/param/TimeScaleParserTest.java b/src/test/java/org/radarcns/webapp/param/TimeScaleParserTest.java new file mode 100644 index 00000000..2ef2abaa --- /dev/null +++ b/src/test/java/org/radarcns/webapp/param/TimeScaleParserTest.java @@ -0,0 +1,84 @@ +package org.radarcns.webapp.param; + +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.HOURS; +import static java.time.temporal.ChronoUnit.MINUTES; +import static org.junit.Assert.assertEquals; +import static org.radarcns.domain.restapi.TimeWindow.ONE_MIN; +import static org.radarcns.domain.restapi.TimeWindow.TEN_SECOND; +import static org.radarcns.webapp.param.TimeScaleParser.DEFAULT_NUMBER_OF_WINDOWS; + +import java.time.Duration; +import java.time.Instant; +import javax.ws.rs.BadRequestException; +import org.junit.Before; +import org.junit.Test; +import org.radarcns.domain.restapi.TimeWindow; +import org.radarcns.util.TimeScale; + +public class TimeScaleParserTest { + private TimeScaleParser parser; + + @Before + public void setUp() { + parser = new TimeScaleParser(); + } + + @Test + public void testDefaults() { + TimeScale scale = parser.parse(null, null, null); + assertEquals(Duration.ofDays(365), scale.getTimeFrame().getDuration()); + assertEquals(TimeWindow.ONE_WEEK, scale.getTimeWindow()); + assertEquals(52, scale.getNumberOfWindows()); + } + + @Test + public void testDefaultTimeFrame() { + TimeScale scale = parser.parse(null, null, ONE_MIN); + assertEquals(Duration.ofMinutes(DEFAULT_NUMBER_OF_WINDOWS), + scale.getTimeFrame().getDuration()); + assertEquals(ONE_MIN, scale.getTimeWindow()); + assertEquals(DEFAULT_NUMBER_OF_WINDOWS, scale.getNumberOfWindows()); + } + + @Test + public void testFixedTimeScale() { + Instant now = Instant.now(); + InstantParam start = new InstantParam(now.minus(1, HOURS).toString()); + InstantParam end = new InstantParam(now.toString()); + TimeScale scale = parser.parse(start, end, ONE_MIN); + assertEquals(scale.getTimeFrame().getDuration(), + Duration.ofHours(1)); + assertEquals(scale.getTimeWindow(), ONE_MIN); + assertEquals(scale.getNumberOfWindows(), 60); + } + + @Test + public void testDefaultTimeWindow() { + Instant now = Instant.now(); + InstantParam start = new InstantParam(now.minus(1, HOURS).toString()); + InstantParam end = new InstantParam(now.toString()); + + TimeScale scale = parser.parse(start, end, null); + assertEquals(scale.getTimeFrame().getDuration(), + Duration.ofHours(1)); + assertEquals(scale.getTimeWindow(), ONE_MIN); + assertEquals(scale.getNumberOfWindows(), 60); + } + + @Test(expected = BadRequestException.class) + public void testTooLarge() { + Instant now = Instant.now(); + InstantParam start = new InstantParam(now.minus(365, DAYS).toString()); + InstantParam end = new InstantParam(now.toString()); + parser.parse(start, end, TEN_SECOND); + } + + @Test(expected = BadRequestException.class) + public void testWrongStartTime() { + Instant now = Instant.now(); + InstantParam start = new InstantParam(now.plus(1, MINUTES).toString()); + InstantParam end = new InstantParam(now.toString()); + parser.parse(start, end, TEN_SECOND); + } +}