Skip to content

Commit

Permalink
MORE2-5 enable relative events on all places
Browse files Browse the repository at this point in the history
  • Loading branch information
Thomas Kurz committed Nov 17, 2023
1 parent 81d0963 commit 4fb2288
Show file tree
Hide file tree
Showing 12 changed files with 199 additions and 64 deletions.
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<!-- Cache -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

<!-- Persistence -->
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.redlink.more.data.configuration;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

@Configuration
@EnableCaching
@EnableScheduling
public class CachingConfiguration {

public static final String OBSERVATION_ENDINGS = "observationEndings";
private static final Logger LOGGER = LoggerFactory.getLogger(CachingConfiguration.class);
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager(OBSERVATION_ENDINGS);
}

@CacheEvict(allEntries = true, value = {OBSERVATION_ENDINGS})
@Scheduled(fixedDelay = 60 * 60 * 1000 , initialDelay = 5000)
public void reportCacheEvict() {
LOGGER.info("Flush Cache");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import io.redlink.more.data.api.app.v1.model.ExternalDataDTO;
import io.redlink.more.data.api.app.v1.webservices.ExternalDataApi;
import io.redlink.more.data.controller.transformer.DataTransformer;
import io.redlink.more.data.exception.BadRequestException;
import io.redlink.more.data.model.ApiRoutingInfo;
import io.redlink.more.data.model.RoutingInfo;
import io.redlink.more.data.model.scheduler.Interval;
import io.redlink.more.data.service.ElasticService;
import io.redlink.more.data.service.ExternalService;
import io.redlink.more.data.util.LoggingUtils;
Expand Down Expand Up @@ -55,10 +57,14 @@ public ResponseEntity<Void> storeExternalBulk(String moreApiToken, EndpointDataB
throw new AccessDeniedException("Invalid token");
}

externalService.validateTimeFrame(studyId, observationId,
endpointDataBulkDTO.getDataPoints().stream().map(datapoint ->
datapoint.getTimestamp().toInstant()
).toList());
Interval interval = externalService.getIntervalForObservation(studyId, observationId, participantId);

endpointDataBulkDTO.getDataPoints().stream()
.map(datapoint -> datapoint.getTimestamp().toInstant())
.map(timestamp -> timestamp.isBefore(interval.getStart()) || timestamp.isAfter(interval.getEnd()))

This comment has been minimized.

Copy link
@alireza-dhp

alireza-dhp Apr 23, 2024

Member

Dear @tkurz, @ja-fra

I think this condition does not satisfy the true requirement for saving bulk data endpoint.
because when the "timestamp" >= "participant.starton" data-points do not save!
I think the opposite of this condition should be fulfilled...

.filter(v -> v)
.findFirst()
.orElseThrow(BadRequestException::TimeFrame);

final RoutingInfo routingInfo = new RoutingInfo(
externalService.validateRoutingInfo(apiRoutingInfo.get(), participantId),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import io.redlink.more.data.model.Observation;
import io.redlink.more.data.model.SimpleParticipant;
import io.redlink.more.data.model.Study;
import io.redlink.more.data.schedule.ICalendarParser;
import io.redlink.more.data.schedule.SchedulerUtils;
import org.apache.commons.lang3.tuple.Pair;

import java.time.Instant;
Expand All @@ -31,7 +31,7 @@ public static StudyDTO toDTO(Study study) {
.contact(toDTO(study.contact()))
.start(study.startDate())
.end(study.endDate())
.observations(toDTO(study.observations(), study.participant().start()))
.observations(toDTO(study.observations(), study.participant().start(), study.participant().end()))
.version(BaseTransformers.toVersionTag(study.modified()))
;
}
Expand All @@ -54,11 +54,11 @@ public static ContactInfoDTO toDTO(Contact contact) {
;
}

public static List<ObservationDTO> toDTO(List<Observation> observations, LocalDateTime start) {
return observations.stream().map(o -> StudyTransformer.toDTO(o, start)).toList();
public static List<ObservationDTO> toDTO(List<Observation> observations, Instant start, Instant end) {
return observations.stream().map(o -> StudyTransformer.toDTO(o, start, end)).toList();
}

public static ObservationDTO toDTO(Observation observation, LocalDateTime start) {
public static ObservationDTO toDTO(Observation observation, Instant start, Instant end) {
ObservationDTO dto = new ObservationDTO()
.observationId(String.valueOf(observation.observationId()))
.observationType(observation.type())
Expand All @@ -70,8 +70,8 @@ public static ObservationDTO toDTO(Observation observation, LocalDateTime start)
.noSchedule(observation.noSchedule())
;
if(observation.observationSchedule() != null && start != null) {
dto.schedule(ICalendarParser
.parseToObservationSchedules(observation.observationSchedule(), start)
dto.schedule(SchedulerUtils
.parseToObservationSchedules(observation.observationSchedule(), start, end)
.stream()
.map(StudyTransformer::toObservationScheduleDTO)
.toList());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package io.redlink.more.data.model;

import java.time.LocalDateTime;
import java.time.Instant;

public record SimpleParticipant(
int id,
String alias,
LocalDateTime start
Instant start,
Instant end
) {
}
16 changes: 16 additions & 0 deletions src/main/java/io/redlink/more/data/model/scheduler/Duration.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,22 @@

import com.fasterxml.jackson.annotation.JsonCreator;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;

public class Duration {

private Integer value;

public Instant getEnd(Instant start) {
if(start == null) {
return null;
}
return start.plus(value, unit.toTemporalUnit());
}

/**
* unit of time to offset
*/
Expand All @@ -31,6 +43,10 @@ public String toString() {
return String.valueOf(value);
}

public TemporalUnit toTemporalUnit() {
return ChronoUnit.valueOf(value);
}

@JsonCreator
public static Unit fromValue(String value) {
for (Unit b : Unit.values()) {
Expand Down
25 changes: 25 additions & 0 deletions src/main/java/io/redlink/more/data/model/scheduler/Interval.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.redlink.more.data.model.scheduler;

import java.time.Instant;

public class Interval {
private Instant start;
private Instant end;

public Interval(Instant start, Instant end) {
this.start = start;
this.end = end;
}

public static Interval from(Event event) {
return new Interval(event.getDateStart(), event.getDateEnd());
}

public Instant getStart() {
return start;
}

public Instant getEnd() {
return end;
}
}
11 changes: 11 additions & 0 deletions src/main/java/io/redlink/more/data/repository/DbUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import io.redlink.more.data.model.scheduler.Duration;
import io.redlink.more.data.model.scheduler.ScheduleEvent;

import java.sql.*;
Expand Down Expand Up @@ -60,6 +61,16 @@ public static ScheduleEvent readEvent(ResultSet row, String columnLabel) throws
}
}

public static Duration readDuration(ResultSet row, String columnLabel) throws SQLException {
var rawValue = row.getString(columnLabel);
if(rawValue == null) return null;
try {
return MAPPER.readValue(rawValue, Duration.class);
} catch (JsonProcessingException e) {
throw new SQLDataException("Could not read Duration from column '" + columnLabel + "'", e);
}
}

public static Object readObject(ResultSet row, String columnLabel) throws SQLException {
var rawValue = row.getString(columnLabel);
if(rawValue == null) return null;
Expand Down
52 changes: 43 additions & 9 deletions src/main/java/io/redlink/more/data/repository/StudyRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.function.Supplier;

import io.redlink.more.data.model.scheduler.Duration;
import io.redlink.more.data.model.scheduler.Interval;
import io.redlink.more.data.model.scheduler.RelativeEvent;
import io.redlink.more.data.model.scheduler.ScheduleEvent;
import io.redlink.more.data.schedule.SchedulerUtils;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
Expand All @@ -33,9 +38,6 @@ public class StudyRepository {
private static final String SQL_FIND_STUDY_BY_ID =
"SELECT * FROM studies WHERE study_id = ?";

private static final String SQL_FIND_PARTICIPANT_BY_STUDY_AND_ID =
"SELECT * FROM participants WHERE study_id = ? AND participant_id = ?";

private static final String SQL_LIST_OBSERVATIONS_BY_STUDY =
"SELECT * FROM observations WHERE study_id = ? AND ( study_group_id IS NULL OR study_group_id = ? )";

Expand Down Expand Up @@ -93,6 +95,12 @@ public class StudyRepository {

private static final String GET_OBSERVATION_SCHEDULE = "SELECT schedule FROM observations WHERE study_id = ? AND observation_id = ?";

private static final String GET_PARTICIPANT_INFO_AND_START_DURATION_END_FOR_STUDY_AND_PARTICIPANT =
"SELECT start, participant_id, alias, COALESCE(sg.duration, s.duration) AS duration, s.planned_end_date FROM participants p " +
"LEFT OUTER JOIN study_groups sg on p.study_id = sg.study_id and p.study_group_id = sg.study_group_id " +
"JOIN studies s on p.study_id = s.study_id " +
"WHERE p.study_id = ? AND participant_id = ?";

private final JdbcTemplate jdbcTemplate;
private final NamedParameterJdbcTemplate namedTemplate;

Expand Down Expand Up @@ -155,12 +163,20 @@ public Optional<Study> findStudy(RoutingInfo routingInfo) {
}

public Optional<SimpleParticipant> findParticipant(RoutingInfo routingInfo) {
try (var stream = jdbcTemplate.queryForStream(SQL_FIND_PARTICIPANT_BY_STUDY_AND_ID,
(rs, rowNum) -> new SimpleParticipant(
rs.getInt("participant_id"),
rs.getString("alias"),
Optional.ofNullable(rs.getTimestamp("start")).map(Timestamp::toLocalDateTime).orElse(null)
)
try (var stream = jdbcTemplate.queryForStream(GET_PARTICIPANT_INFO_AND_START_DURATION_END_FOR_STUDY_AND_PARTICIPANT,
(rs, rowNum) -> {
Instant start = Optional.ofNullable(rs.getTimestamp("start"))
.map(Timestamp::toInstant).orElse(null);
Instant end = Optional.ofNullable(DbUtils.readDuration(rs, "duration"))
.map(d -> d.getEnd(start))
.orElse(Instant.ofEpochMilli(rs.getDate("endDate").getTime()));
return new SimpleParticipant(
rs.getInt("participant_id"),
rs.getString("alias"),
start,
end
);
}
, routingInfo.studyId(), routingInfo.participantId())) {
return stream.findFirst();
}
Expand Down Expand Up @@ -346,4 +362,22 @@ private static MapSqlParameterSource toParameterSource(long studyId, int partici
.addValue("observation_id", consent.observationId())
;
}

public Interval getInterval(Long studyId, Integer participantId, RelativeEvent event) {
try(var stream = jdbcTemplate.queryForStream(
GET_PARTICIPANT_INFO_AND_START_DURATION_END_FOR_STUDY_AND_PARTICIPANT,
((rs, rowNum) -> {
Instant start = rs.getTimestamp("start").toInstant();
// TODO correct sql.Date to Instant with Time 0 ?!
Instant end = Optional.ofNullable(DbUtils.readDuration(rs, "duration"))
.map(d -> d.getEnd(start))
.orElse(Instant.ofEpochMilli(rs.getDate("endDate").getTime()));
return new Interval(start, SchedulerUtils.getEnd(event, start, end));

}),
studyId, participantId
)) {
return stream.findFirst().orElse(null);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,31 @@
import biweekly.util.Frequency;
import biweekly.util.Recurrence;
import biweekly.util.com.google.ical.compat.javautil.DateIterator;
import io.redlink.more.data.model.scheduler.Event;
import io.redlink.more.data.model.scheduler.RecurrenceRule;
import io.redlink.more.data.model.scheduler.ScheduleEvent;
import io.redlink.more.data.model.scheduler.*;
import org.apache.commons.lang3.tuple.Pair;

import java.sql.Date;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.TimeZone;

public class ICalendarParser {
public class SchedulerUtils {

public static List<Pair<Instant, Instant>> parseToObservationSchedules(ScheduleEvent scheduleEvent, LocalDateTime start) {
public static Instant getEnd(RelativeEvent event, Instant start, Instant end) {
return parseToObservationSchedulesForRelativeEvent(event, start, end)
.stream().map(Pair::getRight).max(Instant::compareTo).orElse(null);
}

public static List<Pair<Instant, Instant>> parseToObservationSchedulesForRelativeEvent(
RelativeEvent event, Instant start, Instant end) {
//TODO implement
Event event = (Event) scheduleEvent;
return List.of();
}
public static List<Pair<Instant, Instant>> parseToObservationSchedulesForEvent(Event event) {
List<Pair<Instant, Instant>> observationSchedules = new ArrayList<>();
if(event.getDateStart() != null && event.getDateEnd() != null) {
VEvent iCalEvent = parseToICalEvent(event);
Expand All @@ -40,6 +45,14 @@ public static List<Pair<Instant, Instant>> parseToObservationSchedules(ScheduleE
return observationSchedules;
}

public static List<Pair<Instant, Instant>> parseToObservationSchedules(ScheduleEvent scheduleEvent, Instant start, Instant end) {
if(Event.class.isAssignableFrom(scheduleEvent.getClass())) {
return parseToObservationSchedulesForEvent((Event) scheduleEvent);
} else {
return parseToObservationSchedulesForRelativeEvent((RelativeEvent) scheduleEvent, start, end);
}
}

private static long getEventTime(Event event) {
return Duration.between(event.getDateStart(), event.getDateEnd()).getSeconds();
}
Expand Down
Loading

0 comments on commit 4fb2288

Please sign in to comment.